c3c7b030314e7759d6ca57c987f729bbe45a9e61
[WebKit-https.git] / Source / WebCore / rendering / mathml / RenderMathMLFraction.cpp
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 LayoutUnit(0);
188     }
189
190     ASSERT_NOT_REACHED();
191     return LayoutUnit(0);
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 = 0; // 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     clearNeedsLayout();
252 }
253
254 void RenderMathMLFraction::paint(PaintInfo& info, const LayoutPoint& paintOffset)
255 {
256     RenderMathMLBlock::paint(info, paintOffset);
257     LayoutUnit thickness = lineThickness();
258     if (info.context().paintingDisabled() || info.phase != PaintPhaseForeground || style().visibility() != Visibility::Visible || !isValid() || !thickness)
259         return;
260
261     IntPoint adjustedPaintOffset = roundedIntPoint(paintOffset + location() + LayoutPoint(0, ascentOverHorizontalAxis()));
262
263     GraphicsContextStateSaver stateSaver(info.context());
264
265     info.context().setStrokeThickness(thickness);
266     info.context().setStrokeStyle(SolidStroke);
267     info.context().setStrokeColor(style().visitedDependentColorWithColorFilter(CSSPropertyColor));
268     info.context().drawLine(adjustedPaintOffset, roundedIntPoint(LayoutPoint(adjustedPaintOffset.x() + logicalWidth(), adjustedPaintOffset.y())));
269 }
270
271 std::optional<int> RenderMathMLFraction::firstLineBaseline() const
272 {
273     if (isValid())
274         return std::optional<int>(std::lround(static_cast<float>(ascentOverHorizontalAxis() + mathAxisHeight())));
275     return RenderMathMLBlock::firstLineBaseline();
276 }
277
278 }
279
280 #endif // ENABLE(MATHML)