Clean up text decoration drawing code
[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 TextUnderlinePosition::Auto 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     auto resolvedUnderlinePosition = underlinePosition;
51     if (resolvedUnderlinePosition == TextUnderlinePosition::Auto) {
52         if (inlineTextBox)
53             resolvedUnderlinePosition = inlineTextBox->root().baselineType() == IdeographicBaseline ? TextUnderlinePosition::Under : TextUnderlinePosition::Auto;
54         else
55             resolvedUnderlinePosition = TextUnderlinePosition::Auto;
56     }
57     
58     switch (resolvedUnderlinePosition) {
59     case TextUnderlinePosition::Auto:
60     case TextUnderlinePosition::FromFont:
61         return fontMetrics.ascent() + gap;
62     case TextUnderlinePosition::Under: {
63         ASSERT(inlineTextBox);
64         // Position underline relative to the bottom edge of the lowest element's content box.
65         const RootInlineBox& rootBox = inlineTextBox->root();
66         const RenderElement* decorationRenderer = inlineTextBox->parent()->renderer().enclosingRendererWithTextDecoration(TextDecoration::Underline, inlineTextBox->isFirstLine());
67         
68         float offset;
69         if (inlineTextBox->renderer().style().isFlippedLinesWritingMode()) {
70             offset = inlineTextBox->logicalTop();
71             rootBox.minLogicalTopForTextDecorationLine(offset, decorationRenderer, TextDecoration::Underline);
72             offset = inlineTextBox->logicalTop() - offset;
73         } else {
74             offset = inlineTextBox->logicalBottom();
75             rootBox.maxLogicalBottomForTextDecorationLine(offset, decorationRenderer, TextDecoration::Underline);
76             offset -= inlineTextBox->logicalBottom();
77         }
78         return inlineTextBox->logicalHeight() + gap + std::max<float>(offset, 0);
79     }
80     }
81
82     ASSERT_NOT_REACHED();
83     return fontMetrics.ascent() + gap;
84 }
85     
86 WavyStrokeParameters getWavyStrokeParameters(float fontSize)
87 {
88     WavyStrokeParameters result;
89     // More information is in the WavyStrokeParameters definition.
90     result.controlPointDistance = fontSize * 1.5 / 16;
91     result.step = fontSize / 4.5;
92     return result;
93 }
94
95 static inline void extendIntToFloat(int& extendMe, float extendTo)
96 {
97     extendMe = std::max(extendMe, static_cast<int>(ceilf(extendTo)));
98 }
99
100 GlyphOverflow visualOverflowForDecorations(const RenderStyle& lineStyle, const InlineTextBox* inlineTextBox)
101 {
102     ASSERT(!inlineTextBox || inlineTextBox->lineStyle() == lineStyle);
103     
104     auto decoration = lineStyle.textDecorationsInEffect();
105     if (decoration.isEmpty())
106         return GlyphOverflow();
107
108     float strokeThickness = textDecorationStrokeThickness(lineStyle.computedFontPixelSize());
109     WavyStrokeParameters wavyStrokeParameters;
110     float wavyOffset = 0;
111         
112     TextDecorationStyle decorationStyle = lineStyle.textDecorationStyle();
113     float height = lineStyle.fontCascade().fontMetrics().floatHeight();
114     GlyphOverflow overflowResult;
115     
116     if (decorationStyle == TextDecorationStyle::Wavy) {
117         wavyStrokeParameters = getWavyStrokeParameters(lineStyle.computedFontPixelSize());
118         wavyOffset = wavyOffsetFromDecoration();
119         overflowResult.left = strokeThickness;
120         overflowResult.right = strokeThickness;
121     }
122
123     // These metrics must match where underlines get drawn.
124     if (decoration & TextDecoration::Underline) {
125         // Compensate for the integral ceiling in GraphicsContext::computeLineBoundsAndAntialiasingModeForText()
126         int underlineOffset = 1;
127         underlineOffset += computeUnderlineOffset(lineStyle.textUnderlinePosition(), lineStyle.fontMetrics(), inlineTextBox, strokeThickness);
128         if (decorationStyle == TextDecorationStyle::Wavy) {
129             extendIntToFloat(overflowResult.bottom, underlineOffset + wavyOffset + wavyStrokeParameters.controlPointDistance + strokeThickness - height);
130             extendIntToFloat(overflowResult.top, -(underlineOffset + wavyOffset - wavyStrokeParameters.controlPointDistance - strokeThickness));
131         } else {
132             extendIntToFloat(overflowResult.bottom, underlineOffset + strokeThickness - height);
133             extendIntToFloat(overflowResult.top, -underlineOffset);
134         }
135     }
136     if (decoration & TextDecoration::Overline) {
137         if (decorationStyle == TextDecorationStyle::Wavy) {
138             extendIntToFloat(overflowResult.bottom, -wavyOffset + wavyStrokeParameters.controlPointDistance + strokeThickness - height);
139             extendIntToFloat(overflowResult.top, wavyOffset + wavyStrokeParameters.controlPointDistance + strokeThickness);
140         } else {
141             extendIntToFloat(overflowResult.bottom, strokeThickness - height);
142             // top is untouched
143         }
144     }
145     if (decoration & TextDecoration::LineThrough) {
146         float baseline = lineStyle.fontMetrics().floatAscent();
147         if (decorationStyle == TextDecorationStyle::Wavy) {
148             extendIntToFloat(overflowResult.bottom, 2 * baseline / 3 + wavyStrokeParameters.controlPointDistance + strokeThickness - height);
149             extendIntToFloat(overflowResult.top, -(2 * baseline / 3 - wavyStrokeParameters.controlPointDistance - strokeThickness));
150         } else {
151             extendIntToFloat(overflowResult.bottom, 2 * baseline / 3 + strokeThickness - height);
152             extendIntToFloat(overflowResult.top, -(2 * baseline / 3));
153         }
154     }
155     return overflowResult;
156 }
157     
158 }