| 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 | |