summaryrefslogtreecommitdiffstats
path: root/layout/mathml/nsMathMLmpaddedFrame.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'layout/mathml/nsMathMLmpaddedFrame.cpp')
-rw-r--r--layout/mathml/nsMathMLmpaddedFrame.cpp441
1 files changed, 441 insertions, 0 deletions
diff --git a/layout/mathml/nsMathMLmpaddedFrame.cpp b/layout/mathml/nsMathMLmpaddedFrame.cpp
new file mode 100644
index 0000000000..2c73d60ab9
--- /dev/null
+++ b/layout/mathml/nsMathMLmpaddedFrame.cpp
@@ -0,0 +1,441 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMathMLmpaddedFrame.h"
+
+#include "mozilla/dom/MathMLElement.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/TextUtils.h"
+#include "nsLayoutUtils.h"
+#include <algorithm>
+
+using namespace mozilla;
+
+//
+// <mpadded> -- adjust space around content - implementation
+//
+
+#define NS_MATHML_SIGN_INVALID -1 // if the attribute is not there
+#define NS_MATHML_SIGN_UNSPECIFIED 0
+#define NS_MATHML_SIGN_MINUS 1
+#define NS_MATHML_SIGN_PLUS 2
+
+#define NS_MATHML_PSEUDO_UNIT_UNSPECIFIED 0
+#define NS_MATHML_PSEUDO_UNIT_ITSELF 1 // special
+#define NS_MATHML_PSEUDO_UNIT_WIDTH 2
+#define NS_MATHML_PSEUDO_UNIT_HEIGHT 3
+#define NS_MATHML_PSEUDO_UNIT_DEPTH 4
+#define NS_MATHML_PSEUDO_UNIT_NAMEDSPACE 5
+
+nsIFrame* NS_NewMathMLmpaddedFrame(PresShell* aPresShell,
+ ComputedStyle* aStyle) {
+ return new (aPresShell)
+ nsMathMLmpaddedFrame(aStyle, aPresShell->GetPresContext());
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmpaddedFrame)
+
+nsMathMLmpaddedFrame::~nsMathMLmpaddedFrame() = default;
+
+NS_IMETHODIMP
+nsMathMLmpaddedFrame::InheritAutomaticData(nsIFrame* aParent) {
+ // let the base class get the default from our parent
+ nsMathMLContainerFrame::InheritAutomaticData(aParent);
+
+ mPresentationData.flags |= NS_MATHML_STRETCH_ALL_CHILDREN_VERTICALLY;
+
+ return NS_OK;
+}
+
+void nsMathMLmpaddedFrame::ProcessAttributes() {
+ // clang-format off
+ /*
+ parse the attributes
+
+ width = [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | h-unit | namedspace)
+ height = [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | v-unit | namedspace)
+ depth = [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | v-unit | namedspace)
+ lspace = [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | h-unit | namedspace)
+ voffset= [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | v-unit | namedspace)
+ */
+ // clang-format on
+
+ nsAutoString value;
+
+ // width
+ mWidthSign = NS_MATHML_SIGN_INVALID;
+ mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::width, value);
+ if (!value.IsEmpty()) {
+ if (!ParseAttribute(value, mWidthSign, mWidth, mWidthPseudoUnit)) {
+ ReportParseError(nsGkAtoms::width->GetUTF16String(), value.get());
+ }
+ }
+
+ // height
+ mHeightSign = NS_MATHML_SIGN_INVALID;
+ mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::height, value);
+ if (!value.IsEmpty()) {
+ if (!ParseAttribute(value, mHeightSign, mHeight, mHeightPseudoUnit)) {
+ ReportParseError(nsGkAtoms::height->GetUTF16String(), value.get());
+ }
+ }
+
+ // depth
+ mDepthSign = NS_MATHML_SIGN_INVALID;
+ mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::depth_, value);
+ if (!value.IsEmpty()) {
+ if (!ParseAttribute(value, mDepthSign, mDepth, mDepthPseudoUnit)) {
+ ReportParseError(nsGkAtoms::depth_->GetUTF16String(), value.get());
+ }
+ }
+
+ // lspace
+ mLeadingSpaceSign = NS_MATHML_SIGN_INVALID;
+ mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::lspace_, value);
+ if (!value.IsEmpty()) {
+ if (!ParseAttribute(value, mLeadingSpaceSign, mLeadingSpace,
+ mLeadingSpacePseudoUnit)) {
+ ReportParseError(nsGkAtoms::lspace_->GetUTF16String(), value.get());
+ }
+ }
+
+ // voffset
+ mVerticalOffsetSign = NS_MATHML_SIGN_INVALID;
+ mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::voffset_, value);
+ if (!value.IsEmpty()) {
+ if (!ParseAttribute(value, mVerticalOffsetSign, mVerticalOffset,
+ mVerticalOffsetPseudoUnit)) {
+ ReportParseError(nsGkAtoms::voffset_->GetUTF16String(), value.get());
+ }
+ }
+}
+
+// parse an input string in the following format (see bug 148326 for testcases):
+// [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | css-unit | namedspace)
+bool nsMathMLmpaddedFrame::ParseAttribute(nsString& aString, int32_t& aSign,
+ nsCSSValue& aCSSValue,
+ int32_t& aPseudoUnit) {
+ aCSSValue.Reset();
+ aSign = NS_MATHML_SIGN_INVALID;
+ aPseudoUnit = NS_MATHML_PSEUDO_UNIT_UNSPECIFIED;
+ aString.CompressWhitespace(); // aString is not a const in this code
+
+ int32_t stringLength = aString.Length();
+ if (!stringLength) return false;
+
+ nsAutoString number, unit;
+
+ //////////////////////
+ // see if the sign is there
+
+ int32_t i = 0;
+
+ if (aString[0] == '+') {
+ aSign = NS_MATHML_SIGN_PLUS;
+ i++;
+ } else if (aString[0] == '-') {
+ aSign = NS_MATHML_SIGN_MINUS;
+ i++;
+ } else
+ aSign = NS_MATHML_SIGN_UNSPECIFIED;
+
+ // get the number
+ bool gotDot = false, gotPercent = false;
+ for (; i < stringLength; i++) {
+ char16_t c = aString[i];
+ if (gotDot && c == '.') {
+ // error - two dots encountered
+ aSign = NS_MATHML_SIGN_INVALID;
+ return false;
+ }
+
+ if (c == '.')
+ gotDot = true;
+ else if (!IsAsciiDigit(c)) {
+ break;
+ }
+ number.Append(c);
+ }
+
+ // catch error if we didn't enter the loop above... we could simply initialize
+ // floatValue = 1, to cater for cases such as width="height", but that
+ // wouldn't be in line with the spec which requires an explicit number
+ if (number.IsEmpty()) {
+ aSign = NS_MATHML_SIGN_INVALID;
+ return false;
+ }
+
+ nsresult errorCode;
+ float floatValue = number.ToFloat(&errorCode);
+ if (NS_FAILED(errorCode)) {
+ aSign = NS_MATHML_SIGN_INVALID;
+ return false;
+ }
+
+ // see if this is a percentage-based value
+ if (i < stringLength && aString[i] == '%') {
+ i++;
+ gotPercent = true;
+ }
+
+ // the remainder now should be a css-unit, or a pseudo-unit, or a named-space
+ aString.Right(unit, stringLength - i);
+
+ if (unit.IsEmpty()) {
+ if (gotPercent) {
+ // case ["+"|"-"] unsigned-number "%"
+ aCSSValue.SetPercentValue(floatValue / 100.0f);
+ aPseudoUnit = NS_MATHML_PSEUDO_UNIT_ITSELF;
+ return true;
+ } else {
+ // case ["+"|"-"] unsigned-number
+ // XXXfredw: should we allow non-zero unitless values? See bug 757703.
+ if (!floatValue) {
+ aCSSValue.SetFloatValue(floatValue, eCSSUnit_Number);
+ aPseudoUnit = NS_MATHML_PSEUDO_UNIT_ITSELF;
+ return true;
+ }
+ }
+ } else if (unit.EqualsLiteral("width"))
+ aPseudoUnit = NS_MATHML_PSEUDO_UNIT_WIDTH;
+ else if (unit.EqualsLiteral("height"))
+ aPseudoUnit = NS_MATHML_PSEUDO_UNIT_HEIGHT;
+ else if (unit.EqualsLiteral("depth"))
+ aPseudoUnit = NS_MATHML_PSEUDO_UNIT_DEPTH;
+ else if (!gotPercent) { // percentage can only apply to a pseudo-unit
+
+ // see if the unit is a named-space
+ if (dom::MathMLElement::ParseNamedSpaceValue(
+ unit, aCSSValue, dom::MathMLElement::PARSE_ALLOW_NEGATIVE,
+ *mContent->OwnerDoc())) {
+ // re-scale properly, and we know that the unit of the named-space is 'em'
+ floatValue *= aCSSValue.GetFloatValue();
+ aCSSValue.SetFloatValue(floatValue, eCSSUnit_EM);
+ aPseudoUnit = NS_MATHML_PSEUDO_UNIT_NAMEDSPACE;
+ return true;
+ }
+
+ // see if the input was just a CSS value
+ // We are not supposed to have a unitless, percent, negative or namedspace
+ // value here.
+ number.Append(unit); // leave the sign out if it was there
+ if (dom::MathMLElement::ParseNumericValue(
+ number, aCSSValue, dom::MathMLElement::PARSE_SUPPRESS_WARNINGS,
+ nullptr))
+ return true;
+ }
+
+ // if we enter here, we have a number that will act as a multiplier on a
+ // pseudo-unit
+ if (aPseudoUnit != NS_MATHML_PSEUDO_UNIT_UNSPECIFIED) {
+ if (gotPercent)
+ aCSSValue.SetPercentValue(floatValue / 100.0f);
+ else
+ aCSSValue.SetFloatValue(floatValue, eCSSUnit_Number);
+
+ return true;
+ }
+
+#ifdef DEBUG
+ printf("mpadded: attribute with bad numeric value: %s\n",
+ NS_LossyConvertUTF16toASCII(aString).get());
+#endif
+ // if we reach here, it means we encounter an unexpected input
+ aSign = NS_MATHML_SIGN_INVALID;
+ return false;
+}
+
+void nsMathMLmpaddedFrame::UpdateValue(int32_t aSign, int32_t aPseudoUnit,
+ const nsCSSValue& aCSSValue,
+ const ReflowOutput& aDesiredSize,
+ nscoord& aValueToUpdate,
+ float aFontSizeInflation) const {
+ nsCSSUnit unit = aCSSValue.GetUnit();
+ if (NS_MATHML_SIGN_INVALID != aSign && eCSSUnit_Null != unit) {
+ nscoord scaler = 0, amount = 0;
+
+ if (eCSSUnit_Percent == unit || eCSSUnit_Number == unit) {
+ switch (aPseudoUnit) {
+ case NS_MATHML_PSEUDO_UNIT_WIDTH:
+ scaler = aDesiredSize.Width();
+ break;
+
+ case NS_MATHML_PSEUDO_UNIT_HEIGHT:
+ scaler = aDesiredSize.BlockStartAscent();
+ break;
+
+ case NS_MATHML_PSEUDO_UNIT_DEPTH:
+ scaler = aDesiredSize.Height() - aDesiredSize.BlockStartAscent();
+ break;
+
+ default:
+ // if we ever reach here, it would mean something is wrong
+ // somewhere with the setup and/or the caller
+ NS_ERROR("Unexpected Pseudo Unit");
+ return;
+ }
+ }
+
+ if (eCSSUnit_Number == unit)
+ amount = NSToCoordRound(float(scaler) * aCSSValue.GetFloatValue());
+ else if (eCSSUnit_Percent == unit)
+ amount = NSToCoordRound(float(scaler) * aCSSValue.GetPercentValue());
+ else
+ amount = CalcLength(PresContext(), mComputedStyle, aCSSValue,
+ aFontSizeInflation);
+
+ if (NS_MATHML_SIGN_PLUS == aSign)
+ aValueToUpdate += amount;
+ else if (NS_MATHML_SIGN_MINUS == aSign)
+ aValueToUpdate -= amount;
+ else
+ aValueToUpdate = amount;
+ }
+}
+
+void nsMathMLmpaddedFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) {
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+
+ mPresentationData.flags &= ~NS_MATHML_ERROR;
+ ProcessAttributes();
+
+ ///////////////
+ // Let the base class format our content like an inferred mrow
+ nsMathMLContainerFrame::Reflow(aPresContext, aDesiredSize, aReflowInput,
+ aStatus);
+ // NS_ASSERTION(aStatus.IsComplete(), "bad status");
+}
+
+/* virtual */
+nsresult nsMathMLmpaddedFrame::Place(DrawTarget* aDrawTarget, bool aPlaceOrigin,
+ ReflowOutput& aDesiredSize) {
+ nsresult rv = nsMathMLContainerFrame::Place(aDrawTarget, false, aDesiredSize);
+ if (NS_MATHML_HAS_ERROR(mPresentationData.flags) || NS_FAILED(rv)) {
+ DidReflowChildren(PrincipalChildList().FirstChild());
+ return rv;
+ }
+
+ nscoord height = aDesiredSize.BlockStartAscent();
+ nscoord depth = aDesiredSize.Height() - aDesiredSize.BlockStartAscent();
+ // The REC says:
+ //
+ // "The lspace attribute ('leading' space) specifies the horizontal location
+ // of the positioning point of the child content with respect to the
+ // positioning point of the mpadded element. By default they coincide, and
+ // therefore absolute values for lspace have the same effect as relative
+ // values."
+ //
+ // "MathML renderers should ensure that, except for the effects of the
+ // attributes, the relative spacing between the contents of the mpadded
+ // element and surrounding MathML elements would not be modified by replacing
+ // an mpadded element with an mrow element with the same content, even if
+ // linebreaking occurs within the mpadded element."
+ //
+ // (http://www.w3.org/TR/MathML/chapter3.html#presm.mpadded)
+ //
+ // "In those discussions, the terms leading and trailing are used to specify
+ // a side of an object when which side to use depends on the directionality;
+ // ie. leading means left in LTR but right in RTL."
+ // (http://www.w3.org/TR/MathML/chapter3.html#presm.bidi.math)
+ nscoord lspace = 0;
+ // In MathML3, "width" will be the bounding box width and "advancewidth" will
+ // refer "to the horizontal distance between the positioning point of the
+ // mpadded and the positioning point for the following content". MathML2
+ // doesn't make the distinction.
+ nscoord width = aDesiredSize.Width();
+ nscoord voffset = 0;
+
+ int32_t pseudoUnit;
+ nscoord initialWidth = width;
+ float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this);
+
+ // update width
+ pseudoUnit = (mWidthPseudoUnit == NS_MATHML_PSEUDO_UNIT_ITSELF)
+ ? NS_MATHML_PSEUDO_UNIT_WIDTH
+ : mWidthPseudoUnit;
+ UpdateValue(mWidthSign, pseudoUnit, mWidth, aDesiredSize, width,
+ fontSizeInflation);
+ width = std::max(0, width);
+
+ // update "height" (this is the ascent in the terminology of the REC)
+ pseudoUnit = (mHeightPseudoUnit == NS_MATHML_PSEUDO_UNIT_ITSELF)
+ ? NS_MATHML_PSEUDO_UNIT_HEIGHT
+ : mHeightPseudoUnit;
+ UpdateValue(mHeightSign, pseudoUnit, mHeight, aDesiredSize, height,
+ fontSizeInflation);
+ height = std::max(0, height);
+
+ // update "depth" (this is the descent in the terminology of the REC)
+ pseudoUnit = (mDepthPseudoUnit == NS_MATHML_PSEUDO_UNIT_ITSELF)
+ ? NS_MATHML_PSEUDO_UNIT_DEPTH
+ : mDepthPseudoUnit;
+ UpdateValue(mDepthSign, pseudoUnit, mDepth, aDesiredSize, depth,
+ fontSizeInflation);
+ depth = std::max(0, depth);
+
+ // update lspace
+ if (mLeadingSpacePseudoUnit != NS_MATHML_PSEUDO_UNIT_ITSELF) {
+ pseudoUnit = mLeadingSpacePseudoUnit;
+ UpdateValue(mLeadingSpaceSign, pseudoUnit, mLeadingSpace, aDesiredSize,
+ lspace, fontSizeInflation);
+ }
+
+ // update voffset
+ if (mVerticalOffsetPseudoUnit != NS_MATHML_PSEUDO_UNIT_ITSELF) {
+ pseudoUnit = mVerticalOffsetPseudoUnit;
+ UpdateValue(mVerticalOffsetSign, pseudoUnit, mVerticalOffset, aDesiredSize,
+ voffset, fontSizeInflation);
+ }
+ // do the padding now that we have everything
+ // The idea here is to maintain the invariant that <mpadded>...</mpadded>
+ // (i.e., with no attributes) looks the same as <mrow>...</mrow>. But when
+ // there are attributes, tweak our metrics and move children to achieve the
+ // desired visual effects.
+
+ const bool isRTL = StyleVisibility()->mDirection == StyleDirection::Rtl;
+ if ((isRTL ? mWidthSign : mLeadingSpaceSign) != NS_MATHML_SIGN_INVALID) {
+ // there was padding on the left. dismiss the left italic correction now
+ // (so that our parent won't correct us)
+ mBoundingMetrics.leftBearing = 0;
+ }
+
+ if ((isRTL ? mLeadingSpaceSign : mWidthSign) != NS_MATHML_SIGN_INVALID) {
+ // there was padding on the right. dismiss the right italic correction now
+ // (so that our parent won't correct us)
+ mBoundingMetrics.width = width;
+ mBoundingMetrics.rightBearing = mBoundingMetrics.width;
+ }
+
+ nscoord dx = (isRTL ? width - initialWidth - lspace : lspace);
+
+ aDesiredSize.SetBlockStartAscent(height);
+ aDesiredSize.Width() = mBoundingMetrics.width;
+ aDesiredSize.Height() = depth + aDesiredSize.BlockStartAscent();
+ mBoundingMetrics.ascent = height;
+ mBoundingMetrics.descent = depth;
+ aDesiredSize.mBoundingMetrics = mBoundingMetrics;
+
+ mReference.x = 0;
+ mReference.y = aDesiredSize.BlockStartAscent();
+
+ if (aPlaceOrigin) {
+ // Finish reflowing child frames, positioning their origins.
+ PositionRowChildFrames(dx, aDesiredSize.BlockStartAscent() - voffset);
+ }
+
+ return NS_OK;
+}
+
+/* virtual */
+nsresult nsMathMLmpaddedFrame::MeasureForWidth(DrawTarget* aDrawTarget,
+ ReflowOutput& aDesiredSize) {
+ ProcessAttributes();
+ return Place(aDrawTarget, false, aDesiredSize);
+}