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
45namespace WebCore {
46
47using namespace MathMLNames;
48
49WTF_MAKE_ISO_ALLOCATED_IMPL(RenderMathMLBlock);
50WTF_MAKE_ISO_ALLOCATED_IMPL(RenderMathMLTable);
51
52RenderMathMLBlock::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
59RenderMathMLBlock::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
66RenderMathMLBlock::~RenderMathMLBlock() = default;
67
68bool RenderMathMLBlock::isChildAllowed(const RenderObject& child, const RenderStyle&) const
69{
70 return is<Element>(child.node());
71}
72
73static 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
85LayoutUnit RenderMathMLBlock::mathAxisHeight() const
86{
87 return axisHeight(style());
88}
89
90LayoutUnit 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
98int 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)
109void 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
143LayoutUnit 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
181Optional<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
188void 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
233void 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 updateScrollInfoAfterLayout();
256
257 clearNeedsLayout();
258}
259
260void RenderMathMLBlock::layoutInvalidMarkup(bool relayoutChildren)
261{
262 // Invalid MathML subtrees are just renderered as empty boxes.
263 // FIXME: https://webkit.org/b/135460 - Should we display some "invalid" markup message instead?
264 ASSERT(needsLayout());
265 for (auto child = firstChildBox(); child; child = child->nextSiblingBox())
266 child->layoutIfNeeded();
267 setLogicalWidth(0);
268 setLogicalHeight(0);
269 layoutPositionedObjects(relayoutChildren);
270 updateScrollInfoAfterLayout();
271 clearNeedsLayout();
272}
273
274}
275
276#endif
277