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
38namespace WebCore {
39
40using namespace MathMLNames;
41
42WTF_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.
46const unsigned short longDivLeftSpace = 10;
47
48RenderMathMLMenclose::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
60LayoutUnit RenderMathMLMenclose::ruleThickness() const
61{
62 return 0.05f * style().fontCascade().size();
63}
64
65RenderMathMLMenclose::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
155void 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
169void 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 updateScrollInfoAfterLayout();
194
195 clearNeedsLayout();
196}
197
198// GraphicsContext::drawLine does not seem appropriate to draw menclose lines.
199// To avoid unexpected behaviors and inconsistency with other notations, we just use strokePath.
200static void drawLine(PaintInfo& info, const LayoutUnit& xStart, const LayoutUnit& yStart, const LayoutUnit& xEnd, const LayoutUnit& yEnd)
201{
202 Path line;
203 line.moveTo(LayoutPoint(xStart, yStart));
204 line.addLineTo(LayoutPoint(xEnd, yEnd));
205 info.context().strokePath(line);
206}
207
208void RenderMathMLMenclose::paint(PaintInfo& info, const LayoutPoint& paintOffset)
209{
210 RenderMathMLRow::paint(info, paintOffset);
211
212 if (info.context().paintingDisabled() || info.phase != PaintPhase::Foreground || style().visibility() != Visibility::Visible)
213 return;
214
215 LayoutUnit thickness = ruleThickness();
216
217 // Make a copy of the PaintInfo because applyTransform will modify its rect.
218 PaintInfo paintInfo(info);
219 GraphicsContextStateSaver stateSaver(paintInfo.context());
220
221 paintInfo.context().setStrokeThickness(thickness);
222 paintInfo.context().setStrokeStyle(SolidStroke);
223 paintInfo.context().setStrokeColor(style().visitedDependentColorWithColorFilter(CSSPropertyColor));
224 paintInfo.context().setFillColor(Color::transparent);
225 paintInfo.applyTransform(AffineTransform().translate(paintOffset + location()));
226
227 // In the MathML in HTML5 implementation note, the "left" notation is described as follows:
228 // - center of the left vertical bar is at 3\xi_8 padding + \xi_8 border/2 = 7\xi_8/2
229 // - top space is Overbar Vertical Gap + Overbar Rule Thickness = 3\xi_8 + \xi_8 = 4\xi_8
230 // - bottom space is Underbar Vertical Gap + Underbar Rule Thickness = 3\xi_8 + \xi_8 = 4\xi_8
231 if (hasNotation(MathMLMencloseElement::Left)) {
232 LayoutUnit x = m_contentRect.x() - 7 * thickness / 2;
233 LayoutUnit yStart = m_contentRect.y() - 4 * thickness;
234 LayoutUnit yEnd = m_contentRect.maxY() + 4 * thickness;
235 drawLine(info, x, yStart, x, yEnd);
236 }
237
238 // In the MathML in HTML5 implementation note, the "right" notation is described as follows:
239 // - center of the right vertical bar is at 3\xi_8 padding + \xi_8 border/2 = 7\xi_8/2
240 // - top space is Overbar Vertical Gap + Overbar Rule Thickness = 3\xi_8 + \xi_8 = 4\xi_8
241 // - bottom space is Underbar Vertical Gap + Underbar Rule Thickness = 3\xi_8 + \xi_8 = 4\xi_8
242 if (hasNotation(MathMLMencloseElement::Right)) {
243 LayoutUnit x = m_contentRect.maxX() + 7 * thickness / 2;
244 LayoutUnit yStart = m_contentRect.y() - 4 * thickness;
245 LayoutUnit yEnd = m_contentRect.maxY() + 4 * thickness;
246 drawLine(info, x, yStart, x, yEnd);
247 }
248
249 // In the MathML in HTML5 implementation note, the "vertical" notation is horizontally centered.
250 if (hasNotation(MathMLMencloseElement::VerticalStrike)) {
251 LayoutUnit x = m_contentRect.x() + (m_contentRect.width() - thickness) / 2;
252 LayoutUnit yStart = m_contentRect.y();
253 LayoutUnit yEnd = m_contentRect.maxY();
254 drawLine(info, x, yStart, x, yEnd);
255 }
256
257 // In the MathML in HTML5 implementation note, the "top" notation is described as follows:
258 // - middle of the top horizontal bar is at Vertical Gap + Rule Thickness / 2 = 7\xi_8/2
259 // - left and right spaces have size 4\xi_8
260 if (hasNotation(MathMLMencloseElement::Top)) {
261 LayoutUnit y = m_contentRect.y() - 7 * thickness / 2;
262 LayoutUnit xStart = m_contentRect.x() - 4 * thickness;
263 LayoutUnit xEnd = m_contentRect.maxX() + 4 * thickness;
264 drawLine(info, xStart, y, xEnd, y);
265 }
266
267 // In the MathML in HTML5 implementation note, the "bottom" notation is described as follows:
268 // - middle of the bottom horizontal bar is at Vertical Gap + Rule Thickness / 2 = 7\xi_8/2
269 // - left and right spaces have size 4\xi_8
270 if (hasNotation(MathMLMencloseElement::Bottom)) {
271 LayoutUnit y = m_contentRect.maxY() + 7 * thickness / 2;
272 LayoutUnit xStart = m_contentRect.x() - 4 * thickness;
273 LayoutUnit xEnd = m_contentRect.maxX() + 4 * thickness;
274 drawLine(info, xStart, y, xEnd, y);
275 }
276
277 // In the MathML in HTML5 implementation note, the "vertical" notation is vertically centered.
278 if (hasNotation(MathMLMencloseElement::HorizontalStrike)) {
279 LayoutUnit y = m_contentRect.y() + (m_contentRect.height() - thickness) / 2;
280 LayoutUnit xStart = m_contentRect.x();
281 LayoutUnit xEnd = m_contentRect.maxX();
282 drawLine(info, xStart, y, xEnd, y);
283 }
284
285 // In the MathML in HTML5 implementation note, the "updiagonalstrike" goes from the bottom left corner
286 // to the top right corner.
287 if (hasNotation(MathMLMencloseElement::UpDiagonalStrike))
288 drawLine(info, m_contentRect.x(), m_contentRect.maxY(), m_contentRect.maxX(), m_contentRect.y());
289
290 // In the MathML in HTML5 implementation note, the "downdiagonalstrike" goes from the top left corner
291 // to the bottom right corner.
292 if (hasNotation(MathMLMencloseElement::DownDiagonalStrike))
293 drawLine(info, m_contentRect.x(), m_contentRect.y(), m_contentRect.maxX(), m_contentRect.maxY());
294
295 // In the MathML in HTML5 implementation note, the "roundedbox" has radii size 3\xi_8 and is obtained
296 // by inflating the content box by 3\xi_8 + \xi_8/2 = 7\xi_8/2
297 if (hasNotation(MathMLMencloseElement::RoundedBox)) {
298 LayoutSize radiiSize(3 * thickness, 3 * thickness);
299 RoundedRect::Radii radii(radiiSize, radiiSize, radiiSize, radiiSize);
300 RoundedRect roundedRect(m_contentRect, radii);
301 roundedRect.inflate(7 * thickness / 2);
302 Path path;
303 path.addRoundedRect(roundedRect);
304 paintInfo.context().strokePath(path);
305 }
306
307 // For longdiv, we use our own rules for now:
308 // - top space is like "top" notation
309 // - bottom space is like "bottom" notation
310 // - right space is like "right" notation
311 // - left space is longDivLeftSpace * \xi_8
312 // - We subtract half of the thickness from these spaces to obtain "top", "bottom", "left"
313 // and "right" coordinates.
314 // - The top bar is drawn from "right" to "left" and positioned at vertical offset "top".
315 // - The left part is draw as a quadratic Bezier curve with end points going from "top" to
316 // "bottom" and positioned at horizontal offset "left".
317 // - In order to force the curvature of the left part, we use a middle point that is vertically
318 // centered and shifted towards the right by longDivLeftSpace * \xi_8
319 if (hasNotation(MathMLMencloseElement::LongDiv)) {
320 LayoutUnit top = m_contentRect.y() - 7 * thickness / 2;
321 LayoutUnit bottom = m_contentRect.maxY() + 7 * thickness / 2;
322 LayoutUnit left = m_contentRect.x() - longDivLeftSpace * thickness + thickness / 2;
323 LayoutUnit right = m_contentRect.maxX() + 4 * thickness;
324 LayoutUnit midX = left + longDivLeftSpace * thickness;
325 LayoutUnit midY = (top + bottom) / 2;
326 Path path;
327 path.moveTo(LayoutPoint(right, top));
328 path.addLineTo(LayoutPoint(left, top));
329 path.addQuadCurveTo(LayoutPoint(midX, midY), FloatPoint(left, bottom));
330 paintInfo.context().strokePath(path);
331 }
332
333 // In the MathML in HTML5 implementation note, the "circle" notation is described as follows:
334 // - The center and axes are the same as the content bounding box.
335 // - The width of the bounding box is \xi_8/2 + contentWidth * \sqrt{2} + \xi_8/2
336 // - The height is \xi_8/2 + contentHeight * \sqrt{2} + \xi_8/2
337 if (hasNotation(MathMLMencloseElement::Circle)) {
338 LayoutRect ellipseRect;
339 ellipseRect.setWidth(m_contentRect.width() * sqrtOfTwoFloat + thickness);
340 ellipseRect.setHeight(m_contentRect.height() * sqrtOfTwoFloat + thickness);
341 ellipseRect.setX(m_contentRect.x() - (ellipseRect.width() - m_contentRect.width()) / 2);
342 ellipseRect.setY(m_contentRect.y() - (ellipseRect.height() - m_contentRect.height()) / 2);
343 Path path;
344 path.addEllipse(ellipseRect);
345 paintInfo.context().strokePath(path);
346 }
347}
348
349}
350#endif // ENABLE(MATHML)
351