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
39namespace WebCore {
40
41WTF_MAKE_ISO_ALLOCATED_IMPL(RenderMathMLFraction);
42
43RenderMathMLFraction::RenderMathMLFraction(MathMLFractionElement& element, RenderStyle&& style)
44 : RenderMathMLBlock(element, WTFMove(style))
45{
46}
47
48bool 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
59RenderBox& RenderMathMLFraction::numerator() const
60{
61 ASSERT(isValid());
62 return *firstChildBox();
63}
64
65RenderBox& RenderMathMLFraction::denominator() const
66{
67 ASSERT(isValid());
68 return *firstChildBox()->nextSiblingBox();
69}
70
71LayoutUnit 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
79LayoutUnit RenderMathMLFraction::lineThickness() const
80{
81 return std::max<LayoutUnit>(toUserUnits(element().lineThickness(), style(), defaultLineThickness()), 0);
82}
83
84float RenderMathMLFraction::relativeLineThickness() const
85{
86 if (LayoutUnit defaultThickness = defaultLineThickness())
87 return lineThickness() / defaultThickness;
88 return 0;
89}
90
91RenderMathMLFraction::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
118RenderMathMLFraction::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
155RenderMathMLOperator* RenderMathMLFraction::unembellishedOperator() const
156{
157 if (!isValid() || !is<RenderMathMLBlock>(numerator()))
158 return nullptr;
159
160 return downcast<RenderMathMLBlock>(numerator()).unembellishedOperator();
161}
162
163void 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
179LayoutUnit 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
194LayoutUnit 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
210void 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
256void 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
273Optional<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