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