1/*
2 * Copyright (C) 2010 Alex Milowski (alex@milowski.com). All rights reserved.
3 * Copyright (C) 2010 François Sausset (sausset@gmail.com). All rights reserved.
4 * Copyright (C) 2013, 2016 Igalia S.L.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#include "config.h"
29#include "RenderMathMLOperator.h"
30
31#if ENABLE(MATHML)
32
33#include "FontSelector.h"
34#include "MathMLNames.h"
35#include "MathMLOperatorElement.h"
36#include "PaintInfo.h"
37#include "RenderBlockFlow.h"
38#include "RenderText.h"
39#include "ScaleTransformOperation.h"
40#include "TransformOperations.h"
41#include <cmath>
42#include <wtf/IsoMallocInlines.h>
43#include <wtf/MathExtras.h>
44#include <wtf/unicode/CharacterNames.h>
45
46namespace WebCore {
47
48using namespace MathMLNames;
49
50WTF_MAKE_ISO_ALLOCATED_IMPL(RenderMathMLOperator);
51
52RenderMathMLOperator::RenderMathMLOperator(MathMLOperatorElement& element, RenderStyle&& style)
53 : RenderMathMLToken(element, WTFMove(style))
54{
55 updateTokenContent();
56}
57
58RenderMathMLOperator::RenderMathMLOperator(Document& document, RenderStyle&& style)
59 : RenderMathMLToken(document, WTFMove(style))
60{
61}
62
63MathMLOperatorElement& RenderMathMLOperator::element() const
64{
65 return static_cast<MathMLOperatorElement&>(nodeForNonAnonymous());
66}
67
68UChar32 RenderMathMLOperator::textContent() const
69{
70 return element().operatorChar().character;
71}
72
73bool RenderMathMLOperator::isInvisibleOperator() const
74{
75 // The following operators are invisible: U+2061 FUNCTION APPLICATION, U+2062 INVISIBLE TIMES, U+2063 INVISIBLE SEPARATOR, U+2064 INVISIBLE PLUS.
76 UChar32 character = textContent();
77 return 0x2061 <= character && character <= 0x2064;
78}
79
80bool RenderMathMLOperator::hasOperatorFlag(MathMLOperatorDictionary::Flag flag) const
81{
82 return element().hasProperty(flag);
83}
84
85LayoutUnit RenderMathMLOperator::leadingSpace() const
86{
87 // FIXME: Negative leading spaces must be implemented (https://webkit.org/b/124830).
88 LayoutUnit leadingSpace = toUserUnits(element().defaultLeadingSpace(), style(), 0);
89 leadingSpace = toUserUnits(element().leadingSpace(), style(), leadingSpace);
90 return std::max<LayoutUnit>(0, leadingSpace);
91}
92
93LayoutUnit RenderMathMLOperator::trailingSpace() const
94{
95 // FIXME: Negative trailing spaces must be implemented (https://webkit.org/b/124830).
96 LayoutUnit trailingSpace = toUserUnits(element().defaultTrailingSpace(), style(), 0);
97 trailingSpace = toUserUnits(element().trailingSpace(), style(), trailingSpace);
98 return std::max<LayoutUnit>(0, trailingSpace);
99}
100
101LayoutUnit RenderMathMLOperator::minSize() const
102{
103 LayoutUnit minSize = style().fontCascade().size(); // Default minsize is "1em".
104 minSize = toUserUnits(element().minSize(), style(), minSize);
105 return std::max<LayoutUnit>(0, minSize);
106}
107
108LayoutUnit RenderMathMLOperator::maxSize() const
109{
110 LayoutUnit maxSize = intMaxForLayoutUnit; // Default maxsize is "infinity".
111 maxSize = toUserUnits(element().maxSize(), style(), maxSize);
112 return std::max<LayoutUnit>(0, maxSize);
113}
114
115bool RenderMathMLOperator::isVertical() const
116{
117 return element().operatorChar().isVertical;
118}
119
120
121void RenderMathMLOperator::stretchTo(LayoutUnit heightAboveBaseline, LayoutUnit depthBelowBaseline)
122{
123 ASSERT(isStretchy());
124 ASSERT(isVertical());
125 ASSERT(!isStretchWidthLocked());
126
127 if (!isVertical() || (heightAboveBaseline == m_stretchHeightAboveBaseline && depthBelowBaseline == m_stretchDepthBelowBaseline))
128 return;
129
130 m_stretchHeightAboveBaseline = heightAboveBaseline;
131 m_stretchDepthBelowBaseline = depthBelowBaseline;
132
133 if (hasOperatorFlag(MathMLOperatorDictionary::Symmetric)) {
134 // We make the operator stretch symmetrically above and below the axis.
135 LayoutUnit axis = mathAxisHeight();
136 LayoutUnit halfStretchSize = std::max(m_stretchHeightAboveBaseline - axis, m_stretchDepthBelowBaseline + axis);
137 m_stretchHeightAboveBaseline = halfStretchSize + axis;
138 m_stretchDepthBelowBaseline = halfStretchSize - axis;
139 }
140 // We try to honor the minsize/maxsize condition by increasing or decreasing both height and depth proportionately.
141 // The MathML specification does not indicate what to do when maxsize < minsize, so we follow Gecko and make minsize take precedence.
142 LayoutUnit size = stretchSize();
143 float aspect = 1.0;
144 if (size > 0) {
145 LayoutUnit minSizeValue = minSize();
146 if (size < minSizeValue)
147 aspect = minSizeValue.toFloat() / size;
148 else {
149 LayoutUnit maxSizeValue = maxSize();
150 if (maxSizeValue < size)
151 aspect = maxSizeValue.toFloat() / size;
152 }
153 }
154 m_stretchHeightAboveBaseline *= aspect;
155 m_stretchDepthBelowBaseline *= aspect;
156
157 m_mathOperator.stretchTo(style(), m_stretchHeightAboveBaseline + m_stretchDepthBelowBaseline);
158
159 setLogicalHeight(m_mathOperator.ascent() + m_mathOperator.descent());
160}
161
162void RenderMathMLOperator::stretchTo(LayoutUnit width)
163{
164 ASSERT(isStretchy());
165 ASSERT(!isVertical());
166 ASSERT(!isStretchWidthLocked());
167
168 if (isVertical() || m_stretchWidth == width)
169 return;
170
171 m_stretchWidth = width;
172 m_mathOperator.stretchTo(style(), width);
173
174 setLogicalWidth(leadingSpace() + width + trailingSpace());
175 setLogicalHeight(m_mathOperator.ascent() + m_mathOperator.descent());
176}
177
178void RenderMathMLOperator::resetStretchSize()
179{
180 ASSERT(!isStretchWidthLocked());
181
182 if (isVertical()) {
183 m_stretchHeightAboveBaseline = 0;
184 m_stretchDepthBelowBaseline = 0;
185 } else
186 m_stretchWidth = 0;
187}
188
189void RenderMathMLOperator::computePreferredLogicalWidths()
190{
191 ASSERT(preferredLogicalWidthsDirty());
192
193 LayoutUnit preferredWidth;
194
195 if (!useMathOperator()) {
196 RenderMathMLToken::computePreferredLogicalWidths();
197 preferredWidth = m_maxPreferredLogicalWidth;
198 if (isInvisibleOperator()) {
199 // In some fonts, glyphs for invisible operators have nonzero width. Consequently, we subtract that width here to avoid wide gaps.
200 GlyphData data = style().fontCascade().glyphDataForCharacter(textContent(), false);
201 float glyphWidth = data.font ? data.font->widthForGlyph(data.glyph) : 0;
202 ASSERT(glyphWidth <= preferredWidth);
203 preferredWidth -= glyphWidth;
204 }
205 } else
206 preferredWidth = m_mathOperator.maxPreferredWidth();
207
208 // FIXME: The spacing should be added to the whole embellished operator (https://webkit.org/b/124831).
209 // FIXME: The spacing should only be added inside (perhaps inferred) mrow (http://www.w3.org/TR/MathML/chapter3.html#presm.opspacing).
210 preferredWidth = leadingSpace() + preferredWidth + trailingSpace();
211
212 m_maxPreferredLogicalWidth = m_minPreferredLogicalWidth = preferredWidth;
213
214 setPreferredLogicalWidthsDirty(false);
215}
216
217void RenderMathMLOperator::layoutBlock(bool relayoutChildren, LayoutUnit pageLogicalHeight)
218{
219 ASSERT(needsLayout());
220
221 if (!relayoutChildren && simplifiedLayout())
222 return;
223
224 LayoutUnit leadingSpaceValue = leadingSpace();
225 LayoutUnit trailingSpaceValue = trailingSpace();
226
227 if (useMathOperator()) {
228 for (auto child = firstChildBox(); child; child = child->nextSiblingBox())
229 child->layoutIfNeeded();
230 setLogicalWidth(leadingSpaceValue + m_mathOperator.width() + trailingSpaceValue);
231 setLogicalHeight(m_mathOperator.ascent() + m_mathOperator.descent());
232 } else {
233 // We first do the normal layout without spacing.
234 recomputeLogicalWidth();
235 LayoutUnit width = logicalWidth();
236 setLogicalWidth(width - leadingSpaceValue - trailingSpaceValue);
237 RenderMathMLToken::layoutBlock(relayoutChildren, pageLogicalHeight);
238 setLogicalWidth(width);
239
240 // We then move the children to take spacing into account.
241 LayoutPoint horizontalShift(style().direction() == TextDirection::LTR ? leadingSpaceValue : -leadingSpaceValue, 0_lu);
242 for (auto* child = firstChildBox(); child; child = child->nextSiblingBox())
243 child->setLocation(child->location() + horizontalShift);
244 }
245
246 updateScrollInfoAfterLayout();
247
248 clearNeedsLayout();
249}
250
251void RenderMathMLOperator::updateMathOperator()
252{
253 ASSERT(useMathOperator());
254 MathOperator::Type type;
255 if (isStretchy())
256 type = isVertical() ? MathOperator::Type::VerticalOperator : MathOperator::Type::HorizontalOperator;
257 else if (textContent() && isLargeOperatorInDisplayStyle())
258 type = MathOperator::Type::DisplayOperator;
259 else
260 type = MathOperator::Type::NormalOperator;
261
262 m_mathOperator.setOperator(style(), textContent(), type);
263}
264
265void RenderMathMLOperator::updateTokenContent()
266{
267 ASSERT(!isAnonymous());
268 RenderMathMLToken::updateTokenContent();
269 if (useMathOperator())
270 updateMathOperator();
271}
272
273void RenderMathMLOperator::updateFromElement()
274{
275 updateTokenContent();
276}
277
278bool RenderMathMLOperator::useMathOperator() const
279{
280 // We use the MathOperator class to handle the following cases:
281 // 1) Stretchy and large operators, since they require special painting.
282 // 2) The minus sign, since it can be obtained from a hyphen in the DOM.
283 return isStretchy() || (textContent() && isLargeOperatorInDisplayStyle()) || textContent() == minusSign;
284}
285
286void RenderMathMLOperator::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
287{
288 RenderMathMLBlock::styleDidChange(diff, oldStyle);
289 m_mathOperator.reset(style());
290}
291
292LayoutUnit RenderMathMLOperator::verticalStretchedOperatorShift() const
293{
294 if (!isVertical() || !stretchSize())
295 return 0;
296
297 return (m_stretchDepthBelowBaseline - m_stretchHeightAboveBaseline - m_mathOperator.descent() + m_mathOperator.ascent()) / 2;
298}
299
300Optional<int> RenderMathMLOperator::firstLineBaseline() const
301{
302 if (useMathOperator())
303 return Optional<int>(std::lround(static_cast<float>(m_mathOperator.ascent() - verticalStretchedOperatorShift())));
304 return RenderMathMLToken::firstLineBaseline();
305}
306
307void RenderMathMLOperator::paint(PaintInfo& info, const LayoutPoint& paintOffset)
308{
309 RenderMathMLToken::paint(info, paintOffset);
310 if (!useMathOperator())
311 return;
312
313 LayoutPoint operatorTopLeft = paintOffset + location();
314 operatorTopLeft.move(style().isLeftToRightDirection() ? leadingSpace() : trailingSpace(), 0_lu);
315
316 m_mathOperator.paint(style(), info, operatorTopLeft);
317}
318
319void RenderMathMLOperator::paintChildren(PaintInfo& paintInfo, const LayoutPoint& paintOffset, PaintInfo& paintInfoForChild, bool usePrintRect)
320{
321 // We skip painting for invisible operators too to avoid some "missing character" glyph to appear if appropriate math fonts are not available.
322 if (useMathOperator() || isInvisibleOperator())
323 return;
324 RenderMathMLToken::paintChildren(paintInfo, paintOffset, paintInfoForChild, usePrintRect);
325}
326
327}
328
329#endif
330