8453e789cf0081efda03a8c9a39e74c3acb94389
[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 strokeThickness, 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. Use a minimum of 6 pixels distance since
91     // the actual curve passes approximately at half of that distance, that is 3 pixels.
92     // The minimum height of the curve is also approximately 3 pixels. Increases the curve's height
93     // as strokeThickness increases to make the curve look better.
94     controlPointDistance = 3 * std::max<float>(2, strokeThickness);
95
96     // Increment used to form the diamond shape between start point (p1), control
97     // points and end point (p2) along the axis of the decoration. Makes the
98     // curve wider as strokeThickness increases to make the curve look better.
99     step = 2 * std::max<float>(2, strokeThickness);
100 }
101
102 static inline void extendIntToFloat(int& extendMe, float extendTo)
103 {
104     extendMe = std::max(extendMe, static_cast<int>(ceilf(extendTo)));
105 }
106
107 GlyphOverflow visualOverflowForDecorations(const RenderStyle& lineStyle, const InlineTextBox* inlineTextBox)
108 {
109     ASSERT(!inlineTextBox || inlineTextBox->lineStyle() == lineStyle);
110     
111     TextDecoration decoration = lineStyle.textDecorationsInEffect();
112     if (decoration == TextDecorationNone)
113         return GlyphOverflow();
114     
115     float strokeThickness = textDecorationStrokeThickness(lineStyle.fontSize());
116     float controlPointDistance;
117     float step;
118     float wavyOffset;
119         
120     TextDecorationStyle decorationStyle = lineStyle.textDecorationStyle();
121     float height = lineStyle.fontCascade().fontMetrics().floatHeight();
122     GlyphOverflow overflowResult;
123     
124     if (decorationStyle == TextDecorationStyleWavy) {
125         getWavyStrokeParameters(strokeThickness, controlPointDistance, step);
126         wavyOffset = wavyOffsetFromDecoration();
127         overflowResult.left = strokeThickness;
128         overflowResult.right = strokeThickness;
129     }
130
131     // These metrics must match where underlines get drawn.
132     if (decoration & TextDecorationUnderline) {
133         // Compensate for the integral ceiling in GraphicsContext::computeLineBoundsAndAntialiasingModeForText()
134         int underlineOffset = 1;
135         underlineOffset += computeUnderlineOffset(lineStyle.textUnderlinePosition(), lineStyle.fontMetrics(), inlineTextBox, strokeThickness);
136         if (decorationStyle == TextDecorationStyleWavy) {
137             extendIntToFloat(overflowResult.bottom, underlineOffset + wavyOffset + controlPointDistance + strokeThickness - height);
138             extendIntToFloat(overflowResult.top, -(underlineOffset + wavyOffset - controlPointDistance - strokeThickness));
139         } else {
140             extendIntToFloat(overflowResult.bottom, underlineOffset + strokeThickness - height);
141             extendIntToFloat(overflowResult.top, -underlineOffset);
142         }
143     }
144     if (decoration & TextDecorationOverline) {
145         if (decorationStyle == TextDecorationStyleWavy) {
146             extendIntToFloat(overflowResult.bottom, -wavyOffset + controlPointDistance + strokeThickness - height);
147             extendIntToFloat(overflowResult.top, wavyOffset + controlPointDistance + strokeThickness);
148         } else {
149             extendIntToFloat(overflowResult.bottom, strokeThickness - height);
150             // top is untouched
151         }
152     }
153     if (decoration & TextDecorationLineThrough) {
154         float baseline = lineStyle.fontMetrics().floatAscent();
155         if (decorationStyle == TextDecorationStyleWavy) {
156             extendIntToFloat(overflowResult.bottom, 2 * baseline / 3 + controlPointDistance + strokeThickness - height);
157             extendIntToFloat(overflowResult.top, -(2 * baseline / 3 - controlPointDistance - strokeThickness));
158         } else {
159             extendIntToFloat(overflowResult.bottom, 2 * baseline / 3 + strokeThickness - height);
160             extendIntToFloat(overflowResult.top, -(2 * baseline / 3));
161         }
162     }
163     return overflowResult;
164 }
165     
166 }