Support ::before/::after pseudo elements on elements with display:contents
[WebKit-https.git] / Source / WebCore / style / InlineTextBoxStyle.cpp
1 /*
2  * Copyright (C) 2014 Apple Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #include "config.h"
27 #include "InlineTextBoxStyle.h"
28
29 #include "FontCascade.h"
30 #include "InlineTextBox.h"
31 #include "RootInlineBox.h"
32
33 namespace WebCore {
34     
35 int computeUnderlineOffset(TextUnderlinePosition underlinePosition, const FontMetrics& fontMetrics, const InlineTextBox* inlineTextBox, int textDecorationThickness)
36 {
37     // This represents the gap between the baseline and the closest edge of the underline.
38     int gap = std::max<int>(1, ceilf(textDecorationThickness / 2.0));
39     
40     // FIXME: The code for visual overflow detection passes in a null inline text box. This means it is now
41     // broken for the case where auto needs to behave like "under".
42     
43     // According to the specification TextUnderlinePositionAuto should avoid drawing through glyphs in
44     // scripts where it would not be appropriate (e.g., ideographs).
45     // Strictly speaking this can occur whenever the line contains ideographs
46     // even if it is horizontal, but detecting this has performance implications. For now we only work with
47     // vertical text, since we already determined the baseline type to be ideographic in that
48     // case.
49     
50     TextUnderlinePosition resolvedUnderlinePosition = underlinePosition;
51     if (resolvedUnderlinePosition == TextUnderlinePositionAuto) {
52         if (inlineTextBox)
53             resolvedUnderlinePosition = inlineTextBox->root().baselineType() == IdeographicBaseline ? TextUnderlinePositionUnder : TextUnderlinePositionAlphabetic;
54         else
55             resolvedUnderlinePosition = TextUnderlinePositionAlphabetic;
56     }
57     
58     switch (resolvedUnderlinePosition) {
59     case TextUnderlinePositionAlphabetic:
60         return fontMetrics.ascent() + gap;
61     case TextUnderlinePositionUnder: {
62         ASSERT(inlineTextBox);
63         // Position underline relative to the bottom edge of the lowest element's content box.
64         const RootInlineBox& rootBox = inlineTextBox->root();
65         const RenderElement* decorationRenderer = inlineTextBox->parent()->renderer().enclosingRendererWithTextDecoration(TextDecorationUnderline, inlineTextBox->isFirstLine());
66         
67         float offset;
68         if (inlineTextBox->renderer().style().isFlippedLinesWritingMode()) {
69             offset = inlineTextBox->logicalTop();
70             rootBox.minLogicalTopForTextDecorationLine(offset, decorationRenderer, TextDecorationUnderline);
71             offset = inlineTextBox->logicalTop() - offset;
72         } else {
73             offset = inlineTextBox->logicalBottom();
74             rootBox.maxLogicalBottomForTextDecorationLine(offset, decorationRenderer, TextDecorationUnderline);
75             offset -= inlineTextBox->logicalBottom();
76         }
77         return inlineTextBox->logicalHeight() + gap + std::max<float>(offset, 0);
78     }
79     case TextUnderlinePositionAuto:
80         ASSERT_NOT_REACHED();
81     }
82
83     ASSERT_NOT_REACHED();
84     return fontMetrics.ascent() + gap;
85 }
86     
87 void getWavyStrokeParameters(float fontSize, float& controlPointDistance, float& step)
88 {
89     // Distance between decoration's axis and Bezier curve's control points.
90     // The height of the curve is based on this distance. Increases the curve's height
91     // as fontSize increases to make the curve look better.
92     controlPointDistance = 0.09375 * fontSize;
93
94     // Increment used to form the diamond shape between start point (p1), control
95     // points and end point (p2) along the axis of the decoration. The curve gets
96     // wider as font size increases.
97     step = fontSize / 4.5;
98 }
99
100 static inline void extendIntToFloat(int& extendMe, float extendTo)
101 {
102     extendMe = std::max(extendMe, static_cast<int>(ceilf(extendTo)));
103 }
104
105 GlyphOverflow visualOverflowForDecorations(const RenderStyle& lineStyle, const InlineTextBox* inlineTextBox)
106 {
107     ASSERT(!inlineTextBox || inlineTextBox->lineStyle() == lineStyle);
108     
109     TextDecoration decoration = lineStyle.textDecorationsInEffect();
110     if (decoration == TextDecorationNone)
111         return GlyphOverflow();
112     
113     float strokeThickness = textDecorationStrokeThickness(lineStyle.computedFontPixelSize());
114     float controlPointDistance = 0;
115     float step;
116     float wavyOffset = 0;
117         
118     TextDecorationStyle decorationStyle = lineStyle.textDecorationStyle();
119     float height = lineStyle.fontCascade().fontMetrics().floatHeight();
120     GlyphOverflow overflowResult;
121     
122     if (decorationStyle == TextDecorationStyleWavy) {
123         getWavyStrokeParameters(lineStyle.computedFontPixelSize(), controlPointDistance, step);
124         wavyOffset = wavyOffsetFromDecoration();
125         overflowResult.left = strokeThickness;
126         overflowResult.right = strokeThickness;
127     }
128
129     // These metrics must match where underlines get drawn.
130     if (decoration & TextDecorationUnderline) {
131         // Compensate for the integral ceiling in GraphicsContext::computeLineBoundsAndAntialiasingModeForText()
132         int underlineOffset = 1;
133         underlineOffset += computeUnderlineOffset(lineStyle.textUnderlinePosition(), lineStyle.fontMetrics(), inlineTextBox, strokeThickness);
134         if (decorationStyle == TextDecorationStyleWavy) {
135             extendIntToFloat(overflowResult.bottom, underlineOffset + wavyOffset + controlPointDistance + strokeThickness - height);
136             extendIntToFloat(overflowResult.top, -(underlineOffset + wavyOffset - controlPointDistance - strokeThickness));
137         } else {
138             extendIntToFloat(overflowResult.bottom, underlineOffset + strokeThickness - height);
139             extendIntToFloat(overflowResult.top, -underlineOffset);
140         }
141     }
142     if (decoration & TextDecorationOverline) {
143         if (decorationStyle == TextDecorationStyleWavy) {
144             extendIntToFloat(overflowResult.bottom, -wavyOffset + controlPointDistance + strokeThickness - height);
145             extendIntToFloat(overflowResult.top, wavyOffset + controlPointDistance + strokeThickness);
146         } else {
147             extendIntToFloat(overflowResult.bottom, strokeThickness - height);
148             // top is untouched
149         }
150     }
151     if (decoration & TextDecorationLineThrough) {
152         float baseline = lineStyle.fontMetrics().floatAscent();
153         if (decorationStyle == TextDecorationStyleWavy) {
154             extendIntToFloat(overflowResult.bottom, 2 * baseline / 3 + controlPointDistance + strokeThickness - height);
155             extendIntToFloat(overflowResult.top, -(2 * baseline / 3 - controlPointDistance - strokeThickness));
156         } else {
157             extendIntToFloat(overflowResult.bottom, 2 * baseline / 3 + strokeThickness - height);
158             extendIntToFloat(overflowResult.top, -(2 * baseline / 3));
159         }
160     }
161     return overflowResult;
162 }
163     
164 }