[WTF] Import std::optional reference implementation as WTF::Optional
[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 #include "RenderMathMLBlock.h"
29
30 #if ENABLE(MATHML)
31
32 #include "CSSHelper.h"
33 #include "GraphicsContext.h"
34 #include "LayoutRepainter.h"
35 #include "MathMLElement.h"
36 #include "MathMLNames.h"
37 #include "MathMLPresentationElement.h"
38 #include "RenderView.h"
39
40 #if ENABLE(DEBUG_MATH_LAYOUT)
41 #include "PaintInfo.h"
42 #endif
43
44 namespace WebCore {
45
46 using namespace MathMLNames;
47
48 RenderMathMLBlock::RenderMathMLBlock(MathMLPresentationElement& container, RenderStyle&& style)
49     : RenderBlock(container, WTFMove(style), 0)
50     , m_mathMLStyle(MathMLStyle::create())
51 {
52     setChildrenInline(false); // All of our children must be block-level.
53 }
54
55 RenderMathMLBlock::RenderMathMLBlock(Document& document, RenderStyle&& style)
56     : RenderBlock(document, WTFMove(style), 0)
57     , m_mathMLStyle(MathMLStyle::create())
58 {
59     setChildrenInline(false); // All of our children must be block-level.
60 }
61
62 RenderMathMLBlock::~RenderMathMLBlock()
63 {
64 }
65
66 bool RenderMathMLBlock::isChildAllowed(const RenderObject& child, const RenderStyle&) const
67 {
68     return is<Element>(child.node());
69 }
70
71 static LayoutUnit axisHeight(const RenderStyle& style)
72 {
73     // If we have a MATH table we just return the AxisHeight constant.
74     const auto& primaryFont = style.fontCascade().primaryFont();
75     if (auto* mathData = primaryFont.mathData())
76         return mathData->getMathConstant(primaryFont, OpenTypeMathData::AxisHeight);
77
78     // Otherwise, the idea is to try and use the middle of operators as the math axis which we thus approximate by "half of the x-height".
79     // Note that Gecko has a slower but more accurate version that measures half of the height of U+2212 MINUS SIGN.
80     return style.fontMetrics().xHeight() / 2;
81 }
82
83 LayoutUnit RenderMathMLBlock::mathAxisHeight() const
84 {
85     return axisHeight(style());
86 }
87
88 LayoutUnit RenderMathMLBlock::mirrorIfNeeded(LayoutUnit horizontalOffset, LayoutUnit boxWidth) const
89 {
90     if (style().direction() == RTL)
91         return logicalWidth() - boxWidth - horizontalOffset;
92
93     return horizontalOffset;
94 }
95
96 int RenderMathMLBlock::baselinePosition(FontBaseline baselineType, bool firstLine, LineDirectionMode direction, LinePositionMode linePositionMode) const
97 {
98     // mathml.css sets math { -webkit-line-box-contain: glyphs replaced; line-height: 0; }, so when linePositionMode == PositionOfInteriorLineBoxes we want to
99     // return 0 here to match our line-height. This matters when RootInlineBox::ascentAndDescentForBox is called on a RootInlineBox for an inline-block.
100     if (linePositionMode == PositionOfInteriorLineBoxes)
101         return 0;
102
103     return firstLineBaseline().value_or(RenderBlock::baselinePosition(baselineType, firstLine, direction, linePositionMode));
104 }
105
106 #if ENABLE(DEBUG_MATH_LAYOUT)
107 void RenderMathMLBlock::paint(PaintInfo& info, const LayoutPoint& paintOffset)
108 {
109     RenderBlock::paint(info, paintOffset);
110
111     if (info.context().paintingDisabled() || info.phase != PaintPhaseForeground)
112         return;
113
114     IntPoint adjustedPaintOffset = roundedIntPoint(paintOffset + location());
115
116     GraphicsContextStateSaver stateSaver(info.context());
117
118     info.context().setStrokeThickness(1.0f);
119     info.context().setStrokeStyle(SolidStroke);
120     info.context().setStrokeColor(Color(0, 0, 255));
121
122     info.context().drawLine(adjustedPaintOffset, IntPoint(adjustedPaintOffset.x() + pixelSnappedOffsetWidth(), adjustedPaintOffset.y()));
123     info.context().drawLine(IntPoint(adjustedPaintOffset.x() + pixelSnappedOffsetWidth(), adjustedPaintOffset.y()), IntPoint(adjustedPaintOffset.x() + pixelSnappedOffsetWidth(), adjustedPaintOffset.y() + pixelSnappedOffsetHeight()));
124     info.context().drawLine(IntPoint(adjustedPaintOffset.x(), adjustedPaintOffset.y() + pixelSnappedOffsetHeight()), IntPoint(adjustedPaintOffset.x() + pixelSnappedOffsetWidth(), adjustedPaintOffset.y() + pixelSnappedOffsetHeight()));
125     info.context().drawLine(adjustedPaintOffset, IntPoint(adjustedPaintOffset.x(), adjustedPaintOffset.y() + pixelSnappedOffsetHeight()));
126
127     int topStart = paddingTop();
128
129     info.context().setStrokeColor(Color(0, 255, 0));
130
131     info.context().drawLine(IntPoint(adjustedPaintOffset.x(), adjustedPaintOffset.y() + topStart), IntPoint(adjustedPaintOffset.x() + pixelSnappedOffsetWidth(), adjustedPaintOffset.y() + topStart));
132
133     int baseline = roundToInt(baselinePosition(AlphabeticBaseline, true, HorizontalLine));
134
135     info.context().setStrokeColor(Color(255, 0, 0));
136
137     info.context().drawLine(IntPoint(adjustedPaintOffset.x(), adjustedPaintOffset.y() + baseline), IntPoint(adjustedPaintOffset.x() + pixelSnappedOffsetWidth(), adjustedPaintOffset.y() + baseline));
138 }
139 #endif // ENABLE(DEBUG_MATH_LAYOUT)
140
141 LayoutUnit toUserUnits(const MathMLElement::Length& length, const RenderStyle& style, const LayoutUnit& referenceValue)
142 {
143     switch (length.type) {
144     case MathMLElement::LengthType::Cm:
145         return length.value * cssPixelsPerInch / 2.54f;
146     case MathMLElement::LengthType::Em:
147         return length.value * style.fontCascade().size();
148     case MathMLElement::LengthType::Ex:
149         return length.value * style.fontMetrics().xHeight();
150     case MathMLElement::LengthType::In:
151         return length.value * cssPixelsPerInch;
152     case MathMLElement::LengthType::MathUnit:
153         return length.value * style.fontCascade().size() / 18;
154     case MathMLElement::LengthType::Mm:
155         return length.value * cssPixelsPerInch / 25.4f;
156     case MathMLElement::LengthType::Pc:
157         return length.value * cssPixelsPerInch / 6;
158     case MathMLElement::LengthType::Percentage:
159         return referenceValue * length.value / 100;
160     case MathMLElement::LengthType::Pt:
161         return length.value * cssPixelsPerInch / 72;
162     case MathMLElement::LengthType::Px:
163         return length.value;
164     case MathMLElement::LengthType::UnitLess:
165         return referenceValue * length.value;
166     case MathMLElement::LengthType::ParsingFailed:
167         return referenceValue;
168     case MathMLElement::LengthType::Infinity:
169         return intMaxForLayoutUnit;
170     default:
171         ASSERT_NOT_REACHED();
172         return referenceValue;
173     }
174 }
175
176 std::optional<int> RenderMathMLTable::firstLineBaseline() const
177 {
178     // By default the vertical center of <mtable> is aligned on the math axis.
179     // This is different than RenderTable::firstLineBoxBaseline, which returns the baseline of the first row of a <table>.
180     return std::optional<int>(logicalHeight() / 2 + axisHeight(style()));
181 }
182
183 void RenderMathMLBlock::layoutItems(bool relayoutChildren)
184 {
185     LayoutUnit verticalOffset = borderBefore() + paddingBefore();
186     LayoutUnit horizontalOffset = borderStart() + paddingStart();
187
188     LayoutUnit preferredHorizontalExtent = 0;
189     for (auto* child = firstChildBox(); child; child = child->nextSiblingBox()) {
190         LayoutUnit childHorizontalExtent = child->maxPreferredLogicalWidth() - child->horizontalBorderAndPaddingExtent();
191         LayoutUnit childHorizontalMarginBoxExtent = child->horizontalBorderAndPaddingExtent() + childHorizontalExtent;
192         childHorizontalMarginBoxExtent += child->horizontalMarginExtent();
193
194         preferredHorizontalExtent += childHorizontalMarginBoxExtent;
195     }
196
197     LayoutUnit currentHorizontalExtent = contentLogicalWidth();
198     for (auto* child = firstChildBox(); child; child = child->nextSiblingBox()) {
199         LayoutUnit childSize = child->maxPreferredLogicalWidth() - child->horizontalBorderAndPaddingExtent();
200
201         if (preferredHorizontalExtent > currentHorizontalExtent)
202             childSize = currentHorizontalExtent;
203
204         LayoutUnit childPreferredSize = childSize + child->horizontalBorderAndPaddingExtent();
205
206         if (childPreferredSize != child->width())
207             child->setChildNeedsLayout(MarkOnlyThis);
208
209         updateBlockChildDirtyBitsBeforeLayout(relayoutChildren, *child);
210         child->layoutIfNeeded();
211
212         LayoutUnit childVerticalMarginBoxExtent;
213         childVerticalMarginBoxExtent = child->height() + child->verticalMarginExtent();
214
215         setLogicalHeight(std::max(logicalHeight(), verticalOffset + borderAfter() + paddingAfter() + childVerticalMarginBoxExtent + horizontalScrollbarHeight()));
216
217         horizontalOffset += child->marginStart();
218
219         LayoutUnit childHorizontalExtent = child->width();
220         LayoutPoint childLocation(style().isLeftToRightDirection() ? horizontalOffset : width() - horizontalOffset - childHorizontalExtent,
221             verticalOffset + child->marginBefore());
222
223         child->setLocation(childLocation);
224         horizontalOffset += childHorizontalExtent + child->marginEnd();
225     }
226 }
227
228 void RenderMathMLBlock::layoutBlock(bool relayoutChildren, LayoutUnit)
229 {
230     ASSERT(needsLayout());
231
232     if (!relayoutChildren && simplifiedLayout())
233         return;
234
235     LayoutRepainter repainter(*this, checkForRepaintDuringLayout());
236
237     if (recomputeLogicalWidth())
238         relayoutChildren = true;
239
240     setLogicalHeight(borderAndPaddingLogicalHeight() + scrollbarLogicalHeight());
241
242     layoutItems(relayoutChildren);
243
244     updateLogicalHeight();
245
246     repainter.repaintAfterLayout();
247
248     clearNeedsLayout();
249 }
250
251 void RenderMathMLBlock::layoutInvalidMarkup()
252 {
253     // Invalid MathML subtrees are just renderered as empty boxes.
254     // FIXME: https://webkit.org/b/135460 - Should we display some "invalid" markup message instead?
255     ASSERT(needsLayout());
256     for (auto child = firstChildBox(); child; child = child->nextSiblingBox())
257         child->layoutIfNeeded();
258     setLogicalWidth(0);
259     setLogicalHeight(0);
260     clearNeedsLayout();
261 }
262
263 }
264
265 #endif