0dca8fe9b05714f728c9ad6023e94609ba11f7e2
[WebKit-https.git] / Source / WebCore / rendering / mathml / RenderMathMLMenclose.cpp
1 /*
2  * Copyright (C) 2014 Gurpreet Kaur (k.gurpreet@samsung.com). All rights reserved.
3  * Copyright (C) 2016 Igalia S.L.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
17  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
18  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
20  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26
27 #include "config.h"
28 #include "RenderMathMLMenclose.h"
29
30 #if ENABLE(MATHML)
31
32 #include "GraphicsContext.h"
33 #include "MathMLNames.h"
34 #include "PaintInfo.h"
35 #include <wtf/IsoMallocInlines.h>
36 #include <wtf/MathExtras.h>
37
38 namespace WebCore {
39
40 using namespace MathMLNames;
41
42 WTF_MAKE_ISO_ALLOCATED_IMPL(RenderMathMLMenclose);
43
44 // The MathML in HTML5 implementation note suggests drawing the left part of longdiv with a parenthesis.
45 // For now, we use a Bezier curve and this somewhat arbitrary value.
46 const unsigned short longDivLeftSpace = 10;
47
48 RenderMathMLMenclose::RenderMathMLMenclose(MathMLMencloseElement& element, RenderStyle&& style)
49     : RenderMathMLRow(element, WTFMove(style))
50 {
51 }
52
53 // This arbitrary thickness value is used for the parameter \xi_8 from the MathML in HTML5 implementation note.
54 // For now, we take:
55 // - OverbarVerticalGap = UnderbarVerticalGap = 3\xi_8
56 // - OverbarRuleThickness = UnderbarRuleThickness = \xi_8
57 // - OverbarExtraAscender = UnderbarExtraAscender = \xi_8
58 // FIXME: OverBar and UnderBar parameters should be read from the MATH tables.
59 // See https://bugs.webkit.org/show_bug.cgi?id=122297
60 LayoutUnit RenderMathMLMenclose::ruleThickness() const
61 {
62     return 0.05f * style().fontCascade().size();
63 }
64
65 RenderMathMLMenclose::SpaceAroundContent RenderMathMLMenclose::spaceAroundContent(LayoutUnit contentWidth, LayoutUnit contentHeight) const
66 {
67     SpaceAroundContent space;
68     space.right = 0;
69     space.top = 0;
70     space.bottom = 0;
71     space.left = 0;
72
73     LayoutUnit thickness = ruleThickness();
74     // In the MathML in HTML5 implementation note, the "left" notation is described as follows:
75     // - left side is 3\xi_8 padding + \xi_8 border + \xi_8 margin = 5\xi_8
76     // - top space is Overbar Vertical Gap + Overbar Rule Thickness = 3\xi_8 + \xi_8 = 4\xi_8
77     // - bottom space is Underbar Vertical Gap + Underbar Rule Thickness = 3\xi_8 + \xi_8 = 4\xi_8
78     // The "right" notation is symmetric.
79     if (hasNotation(MathMLMencloseElement::Left))
80         space.left = std::max(space.left, 5 * thickness);
81     if (hasNotation(MathMLMencloseElement::Right))
82         space.right = std::max(space.right, 5 * thickness);
83     if (hasNotation(MathMLMencloseElement::Left) || hasNotation(MathMLMencloseElement::Right)) {
84         LayoutUnit extraSpace = 4 * thickness;
85         space.top = std::max(space.top, extraSpace);
86         space.bottom = std::max(space.bottom, extraSpace);
87     }
88
89     // In the MathML in HTML5 implementation note, the "top" notation is described as follows:
90     // - left and right space are 4\xi_8
91     // - top side is Vertical Gap + Rule Thickness + Extra Ascender = 3\xi_8 + \xi_8 + \xi_8 = 5\xi_8
92     // The "bottom" notation is symmetric.
93     if (hasNotation(MathMLMencloseElement::Top))
94         space.top = std::max(space.top, 5 * thickness);
95     if (hasNotation(MathMLMencloseElement::Bottom))
96         space.bottom = std::max(space.bottom, 5 * thickness);
97     if (hasNotation(MathMLMencloseElement::Top) || hasNotation(MathMLMencloseElement::Bottom)) {
98         LayoutUnit extraSpace = 4 * thickness;
99         space.left = std::max(space.left, extraSpace);
100         space.right = std::max(space.right, extraSpace);
101     }
102
103     // For longdiv, we use our own rules for now:
104     // - top space is like "top" notation
105     // - bottom space is like "bottom" notation
106     // - right space is like "right" notation
107     // - left space is longDivLeftSpace * \xi_8
108     if (hasNotation(MathMLMencloseElement::LongDiv)) {
109         space.top = std::max(space.top, 5 * thickness);
110         space.bottom = std::max(space.bottom, 5 * thickness);
111         space.left = std::max(space.left, longDivLeftSpace * thickness);
112         space.right = std::max(space.right, 4 * thickness);
113     }
114
115     // In the MathML in HTML5 implementation note, the "rounded" notation is described as follows:
116     // - top/bottom/left/right side have 3\xi_8 padding + \xi_8 border + \xi_8 margin = 5\xi_8
117     if (hasNotation(MathMLMencloseElement::RoundedBox)) {
118         LayoutUnit extraSpace = 5 * thickness;
119         space.left = std::max(space.left, extraSpace);
120         space.right = std::max(space.right, extraSpace);
121         space.top = std::max(space.top, extraSpace);
122         space.bottom = std::max(space.bottom, extraSpace);
123     }
124
125     // In the MathML in HTML5 implementation note, the "rounded" notation is described as follows:
126     // - top/bottom/left/right spaces are \xi_8/2
127     if (hasNotation(MathMLMencloseElement::UpDiagonalStrike) || hasNotation(MathMLMencloseElement::DownDiagonalStrike)) {
128         LayoutUnit extraSpace = thickness / 2;
129         space.left = std::max(space.left, extraSpace);
130         space.right = std::max(space.right, extraSpace);
131         space.top = std::max(space.top, extraSpace);
132         space.bottom = std::max(space.bottom, extraSpace);
133     }
134
135     // In the MathML in HTML5 implementation note, the "circle" notation is described as follows:
136     // - We draw the ellipse of axes the axes of symmetry of this ink box
137     // - The radii of the ellipse are \sqrt{2}contentWidth/2 and \sqrt{2}contentHeight/2
138     // - The thickness of the ellipse is \xi_8
139     // - We add extra margin of \xi_8
140     // Then for example the top space is \sqrt{2}contentHeight/2 - contentHeight/2 + \xi_8/2 + \xi_8.
141     if (hasNotation(MathMLMencloseElement::Circle)) {
142         LayoutUnit extraSpace = (contentWidth * (sqrtOfTwoFloat - 1) + 3 * thickness) / 2;
143         space.left = std::max(space.left, extraSpace);
144         space.right = std::max(space.right, extraSpace);
145         extraSpace = (contentHeight * (sqrtOfTwoFloat - 1) + 3 * thickness) / 2;
146         space.top = std::max(space.top, extraSpace);
147         space.bottom = std::max(space.bottom, extraSpace);
148     }
149
150     // In the MathML in HTML5 implementation note, the "vertical" and "horizontal" notations do not add space around the content.
151
152     return space;
153 }
154
155 void RenderMathMLMenclose::computePreferredLogicalWidths()
156 {
157     ASSERT(preferredLogicalWidthsDirty());
158
159     RenderMathMLRow::computePreferredLogicalWidths();
160
161     LayoutUnit preferredWidth = m_maxPreferredLogicalWidth;
162     SpaceAroundContent space = spaceAroundContent(preferredWidth, 0);
163     m_maxPreferredLogicalWidth = space.left + preferredWidth + space.right;
164     m_maxPreferredLogicalWidth = m_minPreferredLogicalWidth;
165
166     setPreferredLogicalWidthsDirty(false);
167 }
168
169 void RenderMathMLMenclose::layoutBlock(bool relayoutChildren, LayoutUnit)
170 {
171     ASSERT(needsLayout());
172
173     if (!relayoutChildren && simplifiedLayout())
174         return;
175
176     LayoutUnit contentWidth, contentAscent, contentDescent;
177     stretchVerticalOperatorsAndLayoutChildren();
178     getContentBoundingBox(contentWidth, contentAscent, contentDescent);
179     layoutRowItems(contentWidth, contentAscent);
180
181     SpaceAroundContent space = spaceAroundContent(contentWidth, contentAscent + contentDescent);
182     setLogicalWidth(space.left + contentWidth + space.right);
183     setLogicalHeight(space.top + contentAscent + contentDescent + space.bottom);
184
185     LayoutPoint contentLocation(space.left, space.top);
186     for (auto* child = firstChildBox(); child; child = child->nextSiblingBox())
187         child->setLocation(child->location() + contentLocation);
188
189     m_contentRect = LayoutRect(space.left, space.top, contentWidth, contentAscent + contentDescent);
190
191     layoutPositionedObjects(relayoutChildren);
192
193     clearNeedsLayout();
194 }
195
196 // GraphicsContext::drawLine does not seem appropriate to draw menclose lines.
197 // To avoid unexpected behaviors and inconsistency with other notations, we just use strokePath.
198 static void drawLine(PaintInfo& info, const LayoutUnit& xStart, const LayoutUnit& yStart, const LayoutUnit& xEnd, const LayoutUnit& yEnd)
199 {
200     Path line;
201     line.moveTo(LayoutPoint(xStart, yStart));
202     line.addLineTo(LayoutPoint(xEnd, yEnd));
203     info.context().strokePath(line);
204 }
205
206 void RenderMathMLMenclose::paint(PaintInfo& info, const LayoutPoint& paintOffset)
207 {
208     RenderMathMLRow::paint(info, paintOffset);
209
210     if (info.context().paintingDisabled() || info.phase != PaintPhaseForeground || style().visibility() != Visibility::Visible)
211         return;
212
213     LayoutUnit thickness = ruleThickness();
214
215     // Make a copy of the PaintInfo because applyTransform will modify its rect.
216     PaintInfo paintInfo(info);
217     GraphicsContextStateSaver stateSaver(paintInfo.context());
218
219     paintInfo.context().setStrokeThickness(thickness);
220     paintInfo.context().setStrokeStyle(SolidStroke);
221     paintInfo.context().setStrokeColor(style().visitedDependentColorWithColorFilter(CSSPropertyColor));
222     paintInfo.context().setFillColor(Color::transparent);
223     paintInfo.applyTransform(AffineTransform().translate(paintOffset + location()));
224
225     // In the MathML in HTML5 implementation note, the "left" notation is described as follows:
226     // - center of the left vertical bar is at 3\xi_8 padding + \xi_8 border/2 = 7\xi_8/2
227     // - top space is Overbar Vertical Gap + Overbar Rule Thickness = 3\xi_8 + \xi_8 = 4\xi_8
228     // - bottom space is Underbar Vertical Gap + Underbar Rule Thickness = 3\xi_8 + \xi_8 = 4\xi_8
229     if (hasNotation(MathMLMencloseElement::Left)) {
230         LayoutUnit x = m_contentRect.x() - 7 * thickness / 2;
231         LayoutUnit yStart = m_contentRect.y() - 4 * thickness;
232         LayoutUnit yEnd = m_contentRect.maxY() + 4 * thickness;
233         drawLine(info, x, yStart, x, yEnd);
234     }
235
236     // In the MathML in HTML5 implementation note, the "right" notation is described as follows:
237     // - center of the right vertical bar is at 3\xi_8 padding + \xi_8 border/2 = 7\xi_8/2
238     // - top space is Overbar Vertical Gap + Overbar Rule Thickness = 3\xi_8 + \xi_8 = 4\xi_8
239     // - bottom space is Underbar Vertical Gap + Underbar Rule Thickness = 3\xi_8 + \xi_8 = 4\xi_8
240     if (hasNotation(MathMLMencloseElement::Right)) {
241         LayoutUnit x = m_contentRect.maxX() + 7 * thickness / 2;
242         LayoutUnit yStart = m_contentRect.y() - 4 * thickness;
243         LayoutUnit yEnd = m_contentRect.maxY() + 4 * thickness;
244         drawLine(info, x, yStart, x, yEnd);
245     }
246
247     // In the MathML in HTML5 implementation note, the "vertical" notation is horizontally centered.
248     if (hasNotation(MathMLMencloseElement::VerticalStrike)) {
249         LayoutUnit x = m_contentRect.x() + (m_contentRect.width() - thickness) / 2;
250         LayoutUnit yStart = m_contentRect.y();
251         LayoutUnit yEnd = m_contentRect.maxY();
252         drawLine(info, x, yStart, x, yEnd);
253     }
254
255     // In the MathML in HTML5 implementation note, the "top" notation is described as follows:
256     // - middle of the top horizontal bar is at Vertical Gap + Rule Thickness / 2 = 7\xi_8/2
257     // - left and right spaces have size 4\xi_8
258     if (hasNotation(MathMLMencloseElement::Top)) {
259         LayoutUnit y = m_contentRect.y() - 7 * thickness / 2;
260         LayoutUnit xStart = m_contentRect.x() - 4 * thickness;
261         LayoutUnit xEnd = m_contentRect.maxX() + 4 * thickness;
262         drawLine(info, xStart, y, xEnd, y);
263     }
264
265     // In the MathML in HTML5 implementation note, the "bottom" notation is described as follows:
266     // - middle of the bottom horizontal bar is at Vertical Gap + Rule Thickness / 2 = 7\xi_8/2
267     // - left and right spaces have size 4\xi_8
268     if (hasNotation(MathMLMencloseElement::Bottom)) {
269         LayoutUnit y = m_contentRect.maxY() + 7 * thickness / 2;
270         LayoutUnit xStart = m_contentRect.x() - 4 * thickness;
271         LayoutUnit xEnd = m_contentRect.maxX() + 4 * thickness;
272         drawLine(info, xStart, y, xEnd, y);
273     }
274
275     // In the MathML in HTML5 implementation note, the "vertical" notation is vertically centered.
276     if (hasNotation(MathMLMencloseElement::HorizontalStrike)) {
277         LayoutUnit y = m_contentRect.y() + (m_contentRect.height() - thickness) / 2;
278         LayoutUnit xStart = m_contentRect.x();
279         LayoutUnit xEnd = m_contentRect.maxX();
280         drawLine(info, xStart, y, xEnd, y);
281     }
282
283     // In the MathML in HTML5 implementation note, the "updiagonalstrike" goes from the bottom left corner
284     // to the top right corner.
285     if (hasNotation(MathMLMencloseElement::UpDiagonalStrike))
286         drawLine(info, m_contentRect.x(), m_contentRect.maxY(), m_contentRect.maxX(), m_contentRect.y());
287
288     // In the MathML in HTML5 implementation note, the "downdiagonalstrike" goes from the top left corner
289     // to the bottom right corner.
290     if (hasNotation(MathMLMencloseElement::DownDiagonalStrike))
291         drawLine(info, m_contentRect.x(), m_contentRect.y(), m_contentRect.maxX(), m_contentRect.maxY());
292
293     // In the MathML in HTML5 implementation note, the "roundedbox" has radii size 3\xi_8 and is obtained
294     // by inflating the content box by 3\xi_8 + \xi_8/2 = 7\xi_8/2
295     if (hasNotation(MathMLMencloseElement::RoundedBox)) {
296         LayoutSize radiiSize(3 * thickness, 3 * thickness);
297         RoundedRect::Radii radii(radiiSize, radiiSize, radiiSize, radiiSize);
298         RoundedRect roundedRect(m_contentRect, radii);
299         roundedRect.inflate(7 * thickness / 2);
300         Path path;
301         path.addRoundedRect(roundedRect);
302         paintInfo.context().strokePath(path);
303     }
304
305     // For longdiv, we use our own rules for now:
306     // - top space is like "top" notation
307     // - bottom space is like "bottom" notation
308     // - right space is like "right" notation
309     // - left space is longDivLeftSpace * \xi_8
310     // - We subtract half of the thickness from these spaces to obtain "top", "bottom", "left"
311     //   and "right" coordinates.
312     // - The top bar is drawn from "right" to "left" and positioned at vertical offset "top".
313     // - The left part is draw as a quadratic Bezier curve with end points going from "top" to
314     //   "bottom" and positioned at horizontal offset "left".
315     // - In order to force the curvature of the left part, we use a middle point that is vertically
316     //   centered and shifted towards the right by longDivLeftSpace * \xi_8
317     if (hasNotation(MathMLMencloseElement::LongDiv)) {
318         LayoutUnit top = m_contentRect.y() - 7 * thickness / 2;
319         LayoutUnit bottom = m_contentRect.maxY() + 7 * thickness / 2;
320         LayoutUnit left = m_contentRect.x() - longDivLeftSpace * thickness + thickness / 2;
321         LayoutUnit right = m_contentRect.maxX() + 4 * thickness;
322         LayoutUnit midX = left + longDivLeftSpace * thickness;
323         LayoutUnit midY = (top + bottom) / 2;
324         Path path;
325         path.moveTo(LayoutPoint(right, top));
326         path.addLineTo(LayoutPoint(left, top));
327         path.addQuadCurveTo(LayoutPoint(midX, midY), FloatPoint(left, bottom));
328         paintInfo.context().strokePath(path);
329     }
330
331     // In the MathML in HTML5 implementation note, the "circle" notation is described as follows:
332     // - The center and axes are the same as the content bounding box.
333     // - The width of the bounding box is \xi_8/2 + contentWidth * \sqrt{2} + \xi_8/2
334     // - The height is \xi_8/2 + contentHeight * \sqrt{2} + \xi_8/2
335     if (hasNotation(MathMLMencloseElement::Circle)) {
336         LayoutRect ellipseRect;
337         ellipseRect.setWidth(m_contentRect.width() * sqrtOfTwoFloat + thickness);
338         ellipseRect.setHeight(m_contentRect.height() * sqrtOfTwoFloat + thickness);
339         ellipseRect.setX(m_contentRect.x() - (ellipseRect.width() - m_contentRect.width()) / 2);
340         ellipseRect.setY(m_contentRect.y() - (ellipseRect.height() - m_contentRect.height()) / 2);
341         Path path;
342         path.addEllipse(ellipseRect);
343         paintInfo.context().strokePath(path);
344     }
345 }
346
347 }
348 #endif // ENABLE(MATHML)