1 | /* |
2 | * Copyright (C) 2009 Alex Milowski (alex@milowski.com). All rights reserved. |
3 | * Copyright (C) 2010 François Sausset (sausset@gmail.com). All rights reserved. |
4 | * Copyright (C) 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 "RenderMathMLFraction.h" |
30 | |
31 | #if ENABLE(MATHML) |
32 | |
33 | #include "GraphicsContext.h" |
34 | #include "MathMLFractionElement.h" |
35 | #include "PaintInfo.h" |
36 | #include <cmath> |
37 | #include <wtf/IsoMallocInlines.h> |
38 | |
39 | namespace WebCore { |
40 | |
41 | WTF_MAKE_ISO_ALLOCATED_IMPL(RenderMathMLFraction); |
42 | |
43 | RenderMathMLFraction::RenderMathMLFraction(MathMLFractionElement& element, RenderStyle&& style) |
44 | : RenderMathMLBlock(element, WTFMove(style)) |
45 | { |
46 | } |
47 | |
48 | bool RenderMathMLFraction::isValid() const |
49 | { |
50 | // Verify whether the list of children is valid: |
51 | // <mfrac> numerator denominator </mfrac> |
52 | auto* child = firstChildBox(); |
53 | if (!child) |
54 | return false; |
55 | child = child->nextSiblingBox(); |
56 | return child && !child->nextSiblingBox(); |
57 | } |
58 | |
59 | RenderBox& RenderMathMLFraction::numerator() const |
60 | { |
61 | ASSERT(isValid()); |
62 | return *firstChildBox(); |
63 | } |
64 | |
65 | RenderBox& RenderMathMLFraction::denominator() const |
66 | { |
67 | ASSERT(isValid()); |
68 | return *firstChildBox()->nextSiblingBox(); |
69 | } |
70 | |
71 | LayoutUnit RenderMathMLFraction::defaultLineThickness() const |
72 | { |
73 | const auto& primaryFont = style().fontCascade().primaryFont(); |
74 | if (const auto* mathData = primaryFont.mathData()) |
75 | return mathData->getMathConstant(primaryFont, OpenTypeMathData::FractionRuleThickness); |
76 | return ruleThicknessFallback(); |
77 | } |
78 | |
79 | LayoutUnit RenderMathMLFraction::lineThickness() const |
80 | { |
81 | return std::max<LayoutUnit>(toUserUnits(element().lineThickness(), style(), defaultLineThickness()), 0); |
82 | } |
83 | |
84 | float RenderMathMLFraction::relativeLineThickness() const |
85 | { |
86 | if (LayoutUnit defaultThickness = defaultLineThickness()) |
87 | return lineThickness() / defaultThickness; |
88 | return 0; |
89 | } |
90 | |
91 | RenderMathMLFraction::FractionParameters RenderMathMLFraction::fractionParameters() const |
92 | { |
93 | ASSERT(lineThickness()); |
94 | FractionParameters parameters; |
95 | |
96 | // We try and read constants to draw the fraction from the OpenType MATH and use fallback values otherwise. |
97 | const auto& primaryFont = style().fontCascade().primaryFont(); |
98 | const auto* mathData = style().fontCascade().primaryFont().mathData(); |
99 | bool display = mathMLStyle().displayStyle(); |
100 | if (mathData) { |
101 | parameters.numeratorGapMin = mathData->getMathConstant(primaryFont, display ? OpenTypeMathData::FractionNumDisplayStyleGapMin : OpenTypeMathData::FractionNumeratorGapMin); |
102 | parameters.denominatorGapMin = mathData->getMathConstant(primaryFont, display ? OpenTypeMathData::FractionDenomDisplayStyleGapMin : OpenTypeMathData::FractionDenominatorGapMin); |
103 | parameters.numeratorMinShiftUp = mathData->getMathConstant(primaryFont, display ? OpenTypeMathData::FractionNumeratorDisplayStyleShiftUp : OpenTypeMathData::FractionNumeratorShiftUp); |
104 | parameters.denominatorMinShiftDown = mathData->getMathConstant(primaryFont, display ? OpenTypeMathData::FractionDenominatorDisplayStyleShiftDown : OpenTypeMathData::FractionDenominatorShiftDown); |
105 | } else { |
106 | // The MATH table specification suggests default rule thickness or (in displaystyle) 3 times default rule thickness for the gaps. |
107 | parameters.numeratorGapMin = display ? 3 * ruleThicknessFallback() : ruleThicknessFallback(); |
108 | parameters.denominatorGapMin = parameters.numeratorGapMin; |
109 | |
110 | // The MATH table specification does not suggest any values for shifts, so we leave them at zero. |
111 | parameters.numeratorMinShiftUp = 0; |
112 | parameters.denominatorMinShiftDown = 0; |
113 | } |
114 | |
115 | return parameters; |
116 | } |
117 | |
118 | RenderMathMLFraction::StackParameters RenderMathMLFraction::stackParameters() const |
119 | { |
120 | ASSERT(!lineThickness()); |
121 | ASSERT(isValid()); |
122 | StackParameters parameters; |
123 | |
124 | // We try and read constants to draw the stack from the OpenType MATH and use fallback values otherwise. |
125 | const auto& primaryFont = style().fontCascade().primaryFont(); |
126 | const auto* mathData = style().fontCascade().primaryFont().mathData(); |
127 | bool display = mathMLStyle().displayStyle(); |
128 | if (mathData) { |
129 | parameters.gapMin = mathData->getMathConstant(primaryFont, display ? OpenTypeMathData::StackDisplayStyleGapMin : OpenTypeMathData::StackGapMin); |
130 | parameters.topShiftUp = mathData->getMathConstant(primaryFont, display ? OpenTypeMathData::StackTopDisplayStyleShiftUp : OpenTypeMathData::StackTopShiftUp); |
131 | parameters.bottomShiftDown = mathData->getMathConstant(primaryFont, display ? OpenTypeMathData::StackBottomDisplayStyleShiftDown : OpenTypeMathData::StackBottomShiftDown); |
132 | } else { |
133 | // We use the values suggested in the MATH table specification. |
134 | parameters.gapMin = display ? 7 * ruleThicknessFallback() : 3 * ruleThicknessFallback(); |
135 | |
136 | // The MATH table specification does not suggest any values for shifts, so we leave them at zero. |
137 | parameters.topShiftUp = 0; |
138 | parameters.bottomShiftDown = 0; |
139 | } |
140 | |
141 | LayoutUnit numeratorAscent = ascentForChild(numerator()); |
142 | LayoutUnit numeratorDescent = numerator().logicalHeight() - numeratorAscent; |
143 | LayoutUnit denominatorAscent = ascentForChild(denominator()); |
144 | LayoutUnit gap = parameters.topShiftUp - numeratorDescent + parameters.bottomShiftDown - denominatorAscent; |
145 | if (gap < parameters.gapMin) { |
146 | // If the gap is not large enough, we increase the shifts by the same value. |
147 | LayoutUnit delta = (parameters.gapMin - gap) / 2; |
148 | parameters.topShiftUp += delta; |
149 | parameters.bottomShiftDown += delta; |
150 | } |
151 | |
152 | return parameters; |
153 | } |
154 | |
155 | RenderMathMLOperator* RenderMathMLFraction::unembellishedOperator() const |
156 | { |
157 | if (!isValid() || !is<RenderMathMLBlock>(numerator())) |
158 | return nullptr; |
159 | |
160 | return downcast<RenderMathMLBlock>(numerator()).unembellishedOperator(); |
161 | } |
162 | |
163 | void RenderMathMLFraction::computePreferredLogicalWidths() |
164 | { |
165 | ASSERT(preferredLogicalWidthsDirty()); |
166 | |
167 | m_minPreferredLogicalWidth = 0; |
168 | m_maxPreferredLogicalWidth = 0; |
169 | |
170 | if (isValid()) { |
171 | LayoutUnit numeratorWidth = numerator().maxPreferredLogicalWidth(); |
172 | LayoutUnit denominatorWidth = denominator().maxPreferredLogicalWidth(); |
173 | m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = std::max(numeratorWidth, denominatorWidth); |
174 | } |
175 | |
176 | setPreferredLogicalWidthsDirty(false); |
177 | } |
178 | |
179 | LayoutUnit RenderMathMLFraction::horizontalOffset(RenderBox& child, MathMLFractionElement::FractionAlignment align) const |
180 | { |
181 | switch (align) { |
182 | case MathMLFractionElement::FractionAlignmentRight: |
183 | return LayoutUnit(logicalWidth() - child.logicalWidth()); |
184 | case MathMLFractionElement::FractionAlignmentCenter: |
185 | return LayoutUnit((logicalWidth() - child.logicalWidth()) / 2); |
186 | case MathMLFractionElement::FractionAlignmentLeft: |
187 | return 0_lu; |
188 | } |
189 | |
190 | ASSERT_NOT_REACHED(); |
191 | return 0_lu; |
192 | } |
193 | |
194 | LayoutUnit RenderMathMLFraction::ascentOverHorizontalAxis() const |
195 | { |
196 | ASSERT(isValid()); |
197 | |
198 | LayoutUnit numeratorAscent = ascentForChild(numerator()); |
199 | if (LayoutUnit thickness = lineThickness()) { |
200 | // For normal fraction layout, the axis is the middle of the fraction bar. |
201 | FractionParameters parameters = fractionParameters(); |
202 | return std::max(numerator().logicalHeight() + parameters.numeratorGapMin + thickness / 2, numeratorAscent + parameters.numeratorMinShiftUp); |
203 | } |
204 | |
205 | // For stack layout, the axis is the middle of the gap between numerator and denonimator. |
206 | StackParameters parameters = stackParameters(); |
207 | return numeratorAscent + parameters.topShiftUp; |
208 | } |
209 | |
210 | void RenderMathMLFraction::layoutBlock(bool relayoutChildren, LayoutUnit) |
211 | { |
212 | ASSERT(needsLayout()); |
213 | |
214 | if (!relayoutChildren && simplifiedLayout()) |
215 | return; |
216 | |
217 | if (!isValid()) { |
218 | layoutInvalidMarkup(relayoutChildren); |
219 | return; |
220 | } |
221 | |
222 | numerator().layoutIfNeeded(); |
223 | denominator().layoutIfNeeded(); |
224 | |
225 | setLogicalWidth(std::max(numerator().logicalWidth(), denominator().logicalWidth())); |
226 | |
227 | LayoutUnit verticalOffset; // This is the top of the renderer. |
228 | LayoutPoint numeratorLocation(horizontalOffset(numerator(), element().numeratorAlignment()), verticalOffset); |
229 | numerator().setLocation(numeratorLocation); |
230 | |
231 | LayoutUnit denominatorAscent = ascentForChild(denominator()); |
232 | LayoutUnit denominatorDescent = denominator().logicalHeight() - denominatorAscent; |
233 | LayoutUnit ascent = ascentOverHorizontalAxis(); |
234 | verticalOffset += ascent; |
235 | if (LayoutUnit thickness = lineThickness()) { |
236 | FractionParameters parameters = fractionParameters(); |
237 | verticalOffset += std::max(thickness / 2 + parameters.denominatorGapMin, parameters.denominatorMinShiftDown - denominatorAscent); |
238 | } else { |
239 | StackParameters parameters = stackParameters(); |
240 | verticalOffset += parameters.bottomShiftDown - denominatorAscent; |
241 | } |
242 | |
243 | LayoutPoint denominatorLocation(horizontalOffset(denominator(), element().denominatorAlignment()), verticalOffset); |
244 | denominator().setLocation(denominatorLocation); |
245 | |
246 | verticalOffset = std::max(verticalOffset + denominator().logicalHeight(), ascent + mathAxisHeight() + denominatorDescent); // This is the bottom of our renderer. |
247 | setLogicalHeight(verticalOffset); |
248 | |
249 | layoutPositionedObjects(relayoutChildren); |
250 | |
251 | updateScrollInfoAfterLayout(); |
252 | |
253 | clearNeedsLayout(); |
254 | } |
255 | |
256 | void RenderMathMLFraction::paint(PaintInfo& info, const LayoutPoint& paintOffset) |
257 | { |
258 | RenderMathMLBlock::paint(info, paintOffset); |
259 | LayoutUnit thickness = lineThickness(); |
260 | if (info.context().paintingDisabled() || info.phase != PaintPhase::Foreground || style().visibility() != Visibility::Visible || !isValid() || !thickness) |
261 | return; |
262 | |
263 | IntPoint adjustedPaintOffset = roundedIntPoint(paintOffset + location() + LayoutPoint(0_lu, ascentOverHorizontalAxis())); |
264 | |
265 | GraphicsContextStateSaver stateSaver(info.context()); |
266 | |
267 | info.context().setStrokeThickness(thickness); |
268 | info.context().setStrokeStyle(SolidStroke); |
269 | info.context().setStrokeColor(style().visitedDependentColorWithColorFilter(CSSPropertyColor)); |
270 | info.context().drawLine(adjustedPaintOffset, roundedIntPoint(LayoutPoint(adjustedPaintOffset.x() + logicalWidth(), adjustedPaintOffset.y()))); |
271 | } |
272 | |
273 | Optional<int> RenderMathMLFraction::firstLineBaseline() const |
274 | { |
275 | if (isValid()) |
276 | return Optional<int>(std::lround(static_cast<float>(ascentOverHorizontalAxis() + mathAxisHeight()))); |
277 | return RenderMathMLBlock::firstLineBaseline(); |
278 | } |
279 | |
280 | } |
281 | |
282 | #endif // ENABLE(MATHML) |
283 | |