/* * Copyright (C) 2010 Alex Milowski (alex@milowski.com). All rights reserved. * Copyright (C) 2010 François Sausset (sausset@gmail.com). All rights reserved. * Copyright (C) 2013, 2016 Igalia S.L. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "RenderMathMLOperator.h" #if ENABLE(MATHML) #include "FontSelector.h" #include "MathMLNames.h" #include "MathMLOperatorElement.h" #include "PaintInfo.h" #include "RenderBlockFlow.h" #include "RenderText.h" #include "ScaleTransformOperation.h" #include "TransformOperations.h" #include #include #include namespace WebCore { using namespace MathMLNames; RenderMathMLOperator::RenderMathMLOperator(MathMLOperatorElement& element, RenderStyle&& style) : RenderMathMLToken(element, WTFMove(style)) { updateTokenContent(); } RenderMathMLOperator::RenderMathMLOperator(Document& document, RenderStyle&& style) : RenderMathMLToken(document, WTFMove(style)) { } MathMLOperatorElement& RenderMathMLOperator::element() const { return static_cast(nodeForNonAnonymous()); } UChar32 RenderMathMLOperator::textContent() const { return element().operatorChar().character; } bool RenderMathMLOperator::isInvisibleOperator() const { // The following operators are invisible: U+2061 FUNCTION APPLICATION, U+2062 INVISIBLE TIMES, U+2063 INVISIBLE SEPARATOR, U+2064 INVISIBLE PLUS. UChar32 character = textContent(); return 0x2061 <= character && character <= 0x2064; } bool RenderMathMLOperator::hasOperatorFlag(MathMLOperatorDictionary::Flag flag) const { return element().hasProperty(flag); } LayoutUnit RenderMathMLOperator::leadingSpace() const { // FIXME: Negative leading spaces must be implemented (https://webkit.org/b/124830). LayoutUnit leadingSpace = toUserUnits(element().defaultLeadingSpace(), style(), 0); leadingSpace = toUserUnits(element().leadingSpace(), style(), leadingSpace); return std::max(0, leadingSpace); } LayoutUnit RenderMathMLOperator::trailingSpace() const { // FIXME: Negative trailing spaces must be implemented (https://webkit.org/b/124830). LayoutUnit trailingSpace = toUserUnits(element().defaultTrailingSpace(), style(), 0); trailingSpace = toUserUnits(element().trailingSpace(), style(), trailingSpace); return std::max(0, trailingSpace); } LayoutUnit RenderMathMLOperator::minSize() const { LayoutUnit minSize = style().fontCascade().size(); // Default minsize is "1em". minSize = toUserUnits(element().minSize(), style(), minSize); return std::max(0, minSize); } LayoutUnit RenderMathMLOperator::maxSize() const { LayoutUnit maxSize = intMaxForLayoutUnit; // Default maxsize is "infinity". maxSize = toUserUnits(element().maxSize(), style(), maxSize); return std::max(0, maxSize); } bool RenderMathMLOperator::isVertical() const { return element().operatorChar().isVertical; } void RenderMathMLOperator::stretchTo(LayoutUnit heightAboveBaseline, LayoutUnit depthBelowBaseline) { ASSERT(isStretchy()); ASSERT(isVertical()); if (!isVertical() || (heightAboveBaseline == m_stretchHeightAboveBaseline && depthBelowBaseline == m_stretchDepthBelowBaseline)) return; m_stretchHeightAboveBaseline = heightAboveBaseline; m_stretchDepthBelowBaseline = depthBelowBaseline; if (hasOperatorFlag(MathMLOperatorDictionary::Symmetric)) { // We make the operator stretch symmetrically above and below the axis. LayoutUnit axis = mathAxisHeight(); LayoutUnit halfStretchSize = std::max(m_stretchHeightAboveBaseline - axis, m_stretchDepthBelowBaseline + axis); m_stretchHeightAboveBaseline = halfStretchSize + axis; m_stretchDepthBelowBaseline = halfStretchSize - axis; } // We try to honor the minsize/maxsize condition by increasing or decreasing both height and depth proportionately. // The MathML specification does not indicate what to do when maxsize < minsize, so we follow Gecko and make minsize take precedence. LayoutUnit size = stretchSize(); float aspect = 1.0; if (size > 0) { LayoutUnit minSizeValue = minSize(); if (size < minSizeValue) aspect = minSizeValue.toFloat() / size; else { LayoutUnit maxSizeValue = maxSize(); if (maxSizeValue < size) aspect = maxSizeValue.toFloat() / size; } } m_stretchHeightAboveBaseline *= aspect; m_stretchDepthBelowBaseline *= aspect; m_mathOperator.stretchTo(style(), m_stretchHeightAboveBaseline + m_stretchDepthBelowBaseline); setLogicalHeight(m_mathOperator.ascent() + m_mathOperator.descent()); } void RenderMathMLOperator::stretchTo(LayoutUnit width) { ASSERT(isStretchy()); ASSERT(!isVertical()); if (isVertical() || m_stretchWidth == width) return; m_stretchWidth = width; m_mathOperator.stretchTo(style(), width); setLogicalHeight(m_mathOperator.ascent() + m_mathOperator.descent()); } void RenderMathMLOperator::resetStretchSize() { if (isVertical()) { m_stretchHeightAboveBaseline = 0; m_stretchDepthBelowBaseline = 0; } else m_stretchWidth = 0; } void RenderMathMLOperator::computePreferredLogicalWidths() { ASSERT(preferredLogicalWidthsDirty()); LayoutUnit preferredWidth = 0; if (!useMathOperator()) { RenderMathMLToken::computePreferredLogicalWidths(); preferredWidth = m_maxPreferredLogicalWidth; if (isInvisibleOperator()) { // In some fonts, glyphs for invisible operators have nonzero width. Consequently, we subtract that width here to avoid wide gaps. GlyphData data = style().fontCascade().glyphDataForCharacter(textContent(), false); float glyphWidth = data.font ? data.font->widthForGlyph(data.glyph) : 0; ASSERT(glyphWidth <= preferredWidth); preferredWidth -= glyphWidth; } } else preferredWidth = m_mathOperator.maxPreferredWidth(); // FIXME: The spacing should be added to the whole embellished operator (https://webkit.org/b/124831). // FIXME: The spacing should only be added inside (perhaps inferred) mrow (http://www.w3.org/TR/MathML/chapter3.html#presm.opspacing). preferredWidth = leadingSpace() + preferredWidth + trailingSpace(); m_maxPreferredLogicalWidth = m_minPreferredLogicalWidth = preferredWidth; setPreferredLogicalWidthsDirty(false); } void RenderMathMLOperator::layoutBlock(bool relayoutChildren, LayoutUnit pageLogicalHeight) { ASSERT(needsLayout()); if (!relayoutChildren && simplifiedLayout()) return; LayoutUnit leadingSpaceValue = leadingSpace(); LayoutUnit trailingSpaceValue = trailingSpace(); if (useMathOperator()) { for (auto child = firstChildBox(); child; child = child->nextSiblingBox()) child->layoutIfNeeded(); setLogicalWidth(leadingSpaceValue + m_mathOperator.width() + trailingSpaceValue); setLogicalHeight(m_mathOperator.ascent() + m_mathOperator.descent()); } else { // We first do the normal layout without spacing. recomputeLogicalWidth(); LayoutUnit width = logicalWidth(); setLogicalWidth(width - leadingSpaceValue - trailingSpaceValue); RenderMathMLToken::layoutBlock(relayoutChildren, pageLogicalHeight); setLogicalWidth(width); // We then move the children to take spacing into account. LayoutPoint horizontalShift(style().direction() == LTR ? leadingSpaceValue : -leadingSpaceValue, 0); for (auto* child = firstChildBox(); child; child = child->nextSiblingBox()) child->setLocation(child->location() + horizontalShift); } clearNeedsLayout(); } void RenderMathMLOperator::updateMathOperator() { ASSERT(useMathOperator()); MathOperator::Type type; if (isStretchy()) type = isVertical() ? MathOperator::Type::VerticalOperator : MathOperator::Type::HorizontalOperator; else if (textContent() && isLargeOperatorInDisplayStyle()) type = MathOperator::Type::DisplayOperator; else type = MathOperator::Type::NormalOperator; m_mathOperator.setOperator(style(), textContent(), type); } void RenderMathMLOperator::updateTokenContent() { ASSERT(!isAnonymous()); RenderMathMLToken::updateTokenContent(); if (useMathOperator()) updateMathOperator(); } void RenderMathMLOperator::updateFromElement() { updateTokenContent(); } bool RenderMathMLOperator::useMathOperator() const { // We use the MathOperator class to handle the following cases: // 1) Stretchy and large operators, since they require special painting. // 2) The minus sign, since it can be obtained from a hyphen in the DOM. return isStretchy() || (textContent() && isLargeOperatorInDisplayStyle()) || textContent() == minusSign; } void RenderMathMLOperator::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) { RenderMathMLBlock::styleDidChange(diff, oldStyle); m_mathOperator.reset(style()); } LayoutUnit RenderMathMLOperator::verticalStretchedOperatorShift() const { if (!isVertical() || !stretchSize()) return 0; return (m_stretchDepthBelowBaseline - m_stretchHeightAboveBaseline - m_mathOperator.descent() + m_mathOperator.ascent()) / 2; } std::optional RenderMathMLOperator::firstLineBaseline() const { if (useMathOperator()) return std::optional(std::lround(static_cast(m_mathOperator.ascent() - verticalStretchedOperatorShift()))); return RenderMathMLToken::firstLineBaseline(); } void RenderMathMLOperator::paint(PaintInfo& info, const LayoutPoint& paintOffset) { RenderMathMLToken::paint(info, paintOffset); if (!useMathOperator()) return; LayoutPoint operatorTopLeft = paintOffset + location(); operatorTopLeft.move(style().isLeftToRightDirection() ? leadingSpace() : trailingSpace(), 0); // Center horizontal operators. if (!isVertical()) operatorTopLeft.move(-(m_mathOperator.width() - width()) / 2, 0); m_mathOperator.paint(style(), info, operatorTopLeft); } void RenderMathMLOperator::paintChildren(PaintInfo& paintInfo, const LayoutPoint& paintOffset, PaintInfo& paintInfoForChild, bool usePrintRect) { // We skip painting for invisible operators too to avoid some "missing character" glyph to appear if appropriate math fonts are not available. if (useMathOperator() || isInvisibleOperator()) return; RenderMathMLToken::paintChildren(paintInfo, paintOffset, paintInfoForChild, usePrintRect); } } #endif