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 | |
46 | namespace WebCore { |
47 | |
48 | using namespace MathMLNames; |
49 | |
50 | WTF_MAKE_ISO_ALLOCATED_IMPL(RenderMathMLOperator); |
51 | |
52 | RenderMathMLOperator::RenderMathMLOperator(MathMLOperatorElement& element, RenderStyle&& style) |
53 | : RenderMathMLToken(element, WTFMove(style)) |
54 | { |
55 | updateTokenContent(); |
56 | } |
57 | |
58 | RenderMathMLOperator::RenderMathMLOperator(Document& document, RenderStyle&& style) |
59 | : RenderMathMLToken(document, WTFMove(style)) |
60 | { |
61 | } |
62 | |
63 | MathMLOperatorElement& RenderMathMLOperator::element() const |
64 | { |
65 | return static_cast<MathMLOperatorElement&>(nodeForNonAnonymous()); |
66 | } |
67 | |
68 | UChar32 RenderMathMLOperator::textContent() const |
69 | { |
70 | return element().operatorChar().character; |
71 | } |
72 | |
73 | bool 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 | |
80 | bool RenderMathMLOperator::hasOperatorFlag(MathMLOperatorDictionary::Flag flag) const |
81 | { |
82 | return element().hasProperty(flag); |
83 | } |
84 | |
85 | LayoutUnit 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 | |
93 | LayoutUnit 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 | |
101 | LayoutUnit 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 | |
108 | LayoutUnit 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 | |
115 | bool RenderMathMLOperator::isVertical() const |
116 | { |
117 | return element().operatorChar().isVertical; |
118 | } |
119 | |
120 | |
121 | void 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 | |
162 | void 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 | |
178 | void 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 | |
189 | void 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 | |
217 | void 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 | |
251 | void 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 | |
265 | void RenderMathMLOperator::updateTokenContent() |
266 | { |
267 | ASSERT(!isAnonymous()); |
268 | RenderMathMLToken::updateTokenContent(); |
269 | if (useMathOperator()) |
270 | updateMathOperator(); |
271 | } |
272 | |
273 | void RenderMathMLOperator::updateFromElement() |
274 | { |
275 | updateTokenContent(); |
276 | } |
277 | |
278 | bool 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 | |
286 | void RenderMathMLOperator::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) |
287 | { |
288 | RenderMathMLBlock::styleDidChange(diff, oldStyle); |
289 | m_mathOperator.reset(style()); |
290 | } |
291 | |
292 | LayoutUnit 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 | |
300 | Optional<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 | |
307 | void 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 | |
319 | void 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 | |