Implement an internal style property for displaystyle.
[WebKit-https.git] / Source / WebCore / rendering / mathml / RenderMathMLBlock.cpp
1 /*
2  * Copyright (C) 2009 Alex Milowski (alex@milowski.com). All rights reserved.
3  * Copyright (C) 2012 David Barton (dbarton@mathscribe.com). All rights reserved.
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
29 #if ENABLE(MATHML)
30
31 #include "RenderMathMLBlock.h"
32
33 #include "GraphicsContext.h"
34 #include "LayoutRepainter.h"
35 #include "MathMLNames.h"
36 #include "RenderView.h"
37 #include <wtf/text/StringBuilder.h>
38
39 #if ENABLE(DEBUG_MATH_LAYOUT)
40 #include "PaintInfo.h"
41 #endif
42
43 namespace WebCore {
44
45 using namespace MathMLNames;
46
47 RenderMathMLBlock::RenderMathMLBlock(Element& container, RenderStyle&& style)
48     : RenderBlock(container, WTFMove(style), 0)
49     , m_mathMLStyle(MathMLStyle::create())
50 {
51     setChildrenInline(false); // All of our children must be block-level.
52 }
53
54 RenderMathMLBlock::RenderMathMLBlock(Document& document, RenderStyle&& style)
55     : RenderBlock(document, WTFMove(style), 0)
56     , m_mathMLStyle(MathMLStyle::create())
57 {
58     setChildrenInline(false); // All of our children must be block-level.
59 }
60
61 RenderMathMLBlock::~RenderMathMLBlock()
62 {
63 }
64
65 bool RenderMathMLBlock::isChildAllowed(const RenderObject& child, const RenderStyle&) const
66 {
67     return is<Element>(child.node());
68 }
69
70 LayoutUnit RenderMathMLBlock::mathAxisHeight() const
71 {
72     const auto& primaryFont = style().fontCascade().primaryFont();
73     if (auto* mathData = primaryFont.mathData())
74         return mathData->getMathConstant(primaryFont, OpenTypeMathData::AxisHeight);
75
76     return style().fontMetrics().xHeight() / 2;
77 }
78
79 LayoutUnit RenderMathMLBlock::mirrorIfNeeded(LayoutUnit horizontalOffset, LayoutUnit boxWidth) const
80 {
81     if (style().direction() == RTL)
82         return logicalWidth() - boxWidth - horizontalOffset;
83
84     return horizontalOffset;
85 }
86
87 int RenderMathMLBlock::baselinePosition(FontBaseline baselineType, bool firstLine, LineDirectionMode direction, LinePositionMode linePositionMode) const
88 {
89     // mathml.css sets math { -webkit-line-box-contain: glyphs replaced; line-height: 0; }, so when linePositionMode == PositionOfInteriorLineBoxes we want to
90     // return 0 here to match our line-height. This matters when RootInlineBox::ascentAndDescentForBox is called on a RootInlineBox for an inline-block.
91     if (linePositionMode == PositionOfInteriorLineBoxes)
92         return 0;
93
94     return firstLineBaseline().valueOr(RenderBlock::baselinePosition(baselineType, firstLine, direction, linePositionMode));
95 }
96
97 #if ENABLE(DEBUG_MATH_LAYOUT)
98 void RenderMathMLBlock::paint(PaintInfo& info, const LayoutPoint& paintOffset)
99 {
100     RenderBlock::paint(info, paintOffset);
101
102     if (info.context().paintingDisabled() || info.phase != PaintPhaseForeground)
103         return;
104
105     IntPoint adjustedPaintOffset = roundedIntPoint(paintOffset + location());
106
107     GraphicsContextStateSaver stateSaver(info.context());
108
109     info.context().setStrokeThickness(1.0f);
110     info.context().setStrokeStyle(SolidStroke);
111     info.context().setStrokeColor(Color(0, 0, 255));
112
113     info.context().drawLine(adjustedPaintOffset, IntPoint(adjustedPaintOffset.x() + pixelSnappedOffsetWidth(), adjustedPaintOffset.y()));
114     info.context().drawLine(IntPoint(adjustedPaintOffset.x() + pixelSnappedOffsetWidth(), adjustedPaintOffset.y()), IntPoint(adjustedPaintOffset.x() + pixelSnappedOffsetWidth(), adjustedPaintOffset.y() + pixelSnappedOffsetHeight()));
115     info.context().drawLine(IntPoint(adjustedPaintOffset.x(), adjustedPaintOffset.y() + pixelSnappedOffsetHeight()), IntPoint(adjustedPaintOffset.x() + pixelSnappedOffsetWidth(), adjustedPaintOffset.y() + pixelSnappedOffsetHeight()));
116     info.context().drawLine(adjustedPaintOffset, IntPoint(adjustedPaintOffset.x(), adjustedPaintOffset.y() + pixelSnappedOffsetHeight()));
117
118     int topStart = paddingTop();
119
120     info.context().setStrokeColor(Color(0, 255, 0));
121
122     info.context().drawLine(IntPoint(adjustedPaintOffset.x(), adjustedPaintOffset.y() + topStart), IntPoint(adjustedPaintOffset.x() + pixelSnappedOffsetWidth(), adjustedPaintOffset.y() + topStart));
123
124     int baseline = roundToInt(baselinePosition(AlphabeticBaseline, true, HorizontalLine));
125
126     info.context().setStrokeColor(Color(255, 0, 0));
127
128     info.context().drawLine(IntPoint(adjustedPaintOffset.x(), adjustedPaintOffset.y() + baseline), IntPoint(adjustedPaintOffset.x() + pixelSnappedOffsetWidth(), adjustedPaintOffset.y() + baseline));
129 }
130 #endif // ENABLE(DEBUG_MATH_LAYOUT)
131
132 //
133 // The MathML specification says:
134 // (http://www.w3.org/TR/MathML/chapter2.html#fund.units)
135 //
136 // "Most presentation elements have attributes that accept values representing
137 // lengths to be used for size, spacing or similar properties. The syntax of a
138 // length is specified as
139 //
140 // number | number unit | namedspace
141 //
142 // There should be no space between the number and the unit of a length."
143 //
144 // "A trailing '%' represents a percent of the default value. The default
145 // value, or how it is obtained, is listed in the table of attributes for each
146 // element. [...] A number without a unit is intepreted as a multiple of the
147 // default value."
148 //
149 // "The possible units in MathML are:
150 //
151 // Unit Description
152 // em   an em (font-relative unit traditionally used for horizontal lengths)
153 // ex   an ex (font-relative unit traditionally used for vertical lengths)
154 // px   pixels, or size of a pixel in the current display
155 // in   inches (1 inch = 2.54 centimeters)
156 // cm   centimeters
157 // mm   millimeters
158 // pt   points (1 point = 1/72 inch)
159 // pc   picas (1 pica = 12 points)
160 // %    percentage of default value"
161 //
162 // The numbers are defined that way:
163 // - unsigned-number: "a string of decimal digits with up to one decimal point
164 //   (U+002E), representing a non-negative terminating decimal number (a type of
165 //   rational number)"
166 // - number: "an optional prefix of '-' (U+002D), followed by an unsigned
167 //   number, representing a terminating decimal number (a type of rational
168 //   number)"
169 //
170 bool parseMathMLLength(const String& string, LayoutUnit& lengthValue, const RenderStyle* style, bool allowNegative)
171 {
172     String s = string.simplifyWhiteSpace();
173
174     int stringLength = s.length();
175     if (!stringLength)
176         return false;
177
178     if (parseMathMLNamedSpace(s, lengthValue, style, allowNegative))
179         return true;
180
181     StringBuilder number;
182     String unit;
183
184     // This verifies whether the negative sign is there.
185     int i = 0;
186     UChar c = s[0];
187     if (c == '-') {
188         number.append(c);
189         i++;
190     }
191
192     // This gathers up characters that make up the number.
193     bool gotDot = false;
194     for ( ; i < stringLength; i++) {
195         c = s[i];
196         // The string is invalid if it contains two dots.
197         if (gotDot && c == '.')
198             return false;
199         if (c == '.')
200             gotDot = true;
201         else if (!isASCIIDigit(c)) {
202             unit = s.substring(i, stringLength - i);
203             // Some authors leave blanks before the unit, but that shouldn't
204             // be allowed, so don't simplifyWhitespace on 'unit'.
205             break;
206         }
207         number.append(c);
208     }
209
210     // Convert number to floating point
211     bool ok;
212     float floatValue = number.toString().toFloat(&ok);
213     if (!ok)
214         return false;
215     if (floatValue < 0 && !allowNegative)
216         return false;
217
218     if (unit.isEmpty()) {
219         // no explicit unit, this is a number that will act as a multiplier
220         lengthValue *= floatValue;
221         return true;
222     }
223     if (unit == "%") {
224         lengthValue *= floatValue / 100;
225         return true;
226     }
227     if (unit == "em") {
228         lengthValue = floatValue * style->fontCascade().size();
229         return true;
230     }
231     if (unit == "ex") {
232         lengthValue = floatValue * style->fontMetrics().xHeight();
233         return true;
234     }
235     if (unit == "px") {
236         lengthValue = floatValue;
237         return true;
238     }
239     if (unit == "pt") {
240         lengthValue = 4 * (floatValue / 3);
241         return true;
242     }
243     if (unit == "pc") {
244         lengthValue = 16 * floatValue;
245         return true;
246     }
247     if (unit == "in") {
248         lengthValue = 96 * floatValue;
249         return true;
250     }
251     if (unit == "cm") {
252         lengthValue = 96 * (floatValue / 2.54);
253         return true;
254     }
255     if (unit == "mm") {
256         lengthValue = 96 * (floatValue / 25.4);
257         return true;
258     }
259
260     // unexpected unit
261     return false;
262 }
263
264 bool parseMathMLNamedSpace(const String& string, LayoutUnit& lengthValue, const RenderStyle* style, bool allowNegative)
265 {
266     float length = 0;
267     // See if it is one of the namedspaces (ranging -7/18em, -6/18, ... 7/18em)
268     if (string == "veryverythinmathspace")
269         length = 1;
270     else if (string == "verythinmathspace")
271         length = 2;
272     else if (string == "thinmathspace")
273         length = 3;
274     else if (string == "mediummathspace")
275         length = 4;
276     else if (string == "thickmathspace")
277         length = 5;
278     else if (string == "verythickmathspace")
279         length = 6;
280     else if (string == "veryverythickmathspace")
281         length = 7;
282     else if (allowNegative) {
283         if (string == "negativeveryverythinmathspace")
284             length = -1;
285         else if (string == "negativeverythinmathspace")
286             length = -2;
287         else if (string == "negativethinmathspace")
288             length = -3;
289         else if (string == "negativemediummathspace")
290             length = -4;
291         else if (string == "negativethickmathspace")
292             length = -5;
293         else if (string == "negativeverythickmathspace")
294             length = -6;
295         else if (string == "negativeveryverythickmathspace")
296             length = -7;
297     }
298     if (length) {
299         lengthValue = length * style->fontCascade().size() / 18;
300         return true;
301     }
302     return false;
303 }
304
305 Optional<int> RenderMathMLTable::firstLineBaseline() const
306 {
307     // In legal MathML, we'll have a MathML parent. That RenderFlexibleBox parent will use our firstLineBaseline() for baseline alignment, per
308     // http://dev.w3.org/csswg/css3-flexbox/#flex-baselines. We want to vertically center an <mtable>, such as a matrix. Essentially the whole <mtable> element fits on a
309     // single line, whose baseline gives this centering. This is different than RenderTable::firstLineBoxBaseline, which returns the baseline of the first row of a <table>.
310     return (logicalHeight() + style().fontMetrics().xHeight()) / 2;
311 }
312
313 void RenderMathMLBlock::layoutItems(bool relayoutChildren)
314 {
315     LayoutUnit verticalOffset = borderBefore() + paddingBefore();
316     LayoutUnit horizontalOffset = borderStart() + paddingStart();
317
318     LayoutUnit preferredHorizontalExtent = 0;
319     for (auto* child = firstChildBox(); child; child = child->nextSiblingBox()) {
320         LayoutUnit childHorizontalExtent = child->maxPreferredLogicalWidth() - child->horizontalBorderAndPaddingExtent();
321         LayoutUnit childHorizontalMarginBoxExtent = child->horizontalBorderAndPaddingExtent() + childHorizontalExtent;
322         childHorizontalMarginBoxExtent += child->horizontalMarginExtent();
323
324         preferredHorizontalExtent += childHorizontalMarginBoxExtent;
325     }
326
327     LayoutUnit currentHorizontalExtent = contentLogicalWidth();
328     for (auto* child = firstChildBox(); child; child = child->nextSiblingBox()) {
329         LayoutUnit childSize = child->maxPreferredLogicalWidth() - child->horizontalBorderAndPaddingExtent();
330
331         if (preferredHorizontalExtent > currentHorizontalExtent)
332             childSize = currentHorizontalExtent;
333
334         LayoutUnit childPreferredSize = childSize + child->horizontalBorderAndPaddingExtent();
335
336         if (childPreferredSize != child->width())
337             child->setChildNeedsLayout(MarkOnlyThis);
338
339         updateBlockChildDirtyBitsBeforeLayout(relayoutChildren, *child);
340         child->layoutIfNeeded();
341
342         LayoutUnit childVerticalMarginBoxExtent;
343         childVerticalMarginBoxExtent = child->height() + child->verticalMarginExtent();
344
345         setLogicalHeight(std::max(logicalHeight(), verticalOffset + borderAfter() + paddingAfter() + childVerticalMarginBoxExtent + horizontalScrollbarHeight()));
346
347         horizontalOffset += child->marginStart();
348
349         LayoutUnit childHorizontalExtent = child->width();
350         LayoutPoint childLocation(style().isLeftToRightDirection() ? horizontalOffset : width() - horizontalOffset - childHorizontalExtent,
351             verticalOffset + child->marginBefore());
352
353         child->setLocation(childLocation);
354         horizontalOffset += childHorizontalExtent + child->marginEnd();
355     }
356 }
357
358 void RenderMathMLBlock::layoutBlock(bool relayoutChildren, LayoutUnit)
359 {
360     ASSERT(needsLayout());
361
362     if (!relayoutChildren && simplifiedLayout())
363         return;
364
365     LayoutRepainter repainter(*this, checkForRepaintDuringLayout());
366
367     if (recomputeLogicalWidth())
368         relayoutChildren = true;
369
370     setLogicalHeight(borderAndPaddingLogicalHeight() + scrollbarLogicalHeight());
371
372     layoutItems(relayoutChildren);
373
374     updateLogicalHeight();
375
376     repainter.repaintAfterLayout();
377
378     clearNeedsLayout();
379 }
380
381 }
382
383 #endif