tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsMathMLmpaddedFrame.cpp (14966B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "nsMathMLmpaddedFrame.h"
      8 
      9 #include <algorithm>
     10 
     11 #include "mozilla/PresShell.h"
     12 #include "mozilla/TextUtils.h"
     13 #include "mozilla/dom/MathMLElement.h"
     14 #include "mozilla/gfx/2D.h"
     15 #include "nsLayoutUtils.h"
     16 
     17 using namespace mozilla;
     18 
     19 //
     20 // <mpadded> -- adjust space around content - implementation
     21 //
     22 
     23 nsIFrame* NS_NewMathMLmpaddedFrame(PresShell* aPresShell,
     24                                   ComputedStyle* aStyle) {
     25  return new (aPresShell)
     26      nsMathMLmpaddedFrame(aStyle, aPresShell->GetPresContext());
     27 }
     28 
     29 NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmpaddedFrame)
     30 
     31 nsMathMLmpaddedFrame::~nsMathMLmpaddedFrame() = default;
     32 
     33 NS_IMETHODIMP
     34 nsMathMLmpaddedFrame::InheritAutomaticData(nsIFrame* aParent) {
     35  // let the base class get the default from our parent
     36  nsMathMLContainerFrame::InheritAutomaticData(aParent);
     37 
     38  mPresentationData.flags +=
     39      MathMLPresentationFlag::StretchAllChildrenVertically;
     40 
     41  return NS_OK;
     42 }
     43 
     44 nsresult nsMathMLmpaddedFrame::AttributeChanged(int32_t aNameSpaceID,
     45                                                nsAtom* aAttribute,
     46                                                AttrModType aModType) {
     47  if (aNameSpaceID == kNameSpaceID_None) {
     48    bool hasDirtyAttributes = false;
     49    IntrinsicDirty intrinsicDirty = IntrinsicDirty::None;
     50    if (aAttribute == nsGkAtoms::width) {
     51      mWidth.mState = Attribute::ParsingState::Dirty;
     52      hasDirtyAttributes = true;
     53      intrinsicDirty = IntrinsicDirty::FrameAndAncestors;
     54    } else if (aAttribute == nsGkAtoms::height) {
     55      mHeight.mState = Attribute::ParsingState::Dirty;
     56      hasDirtyAttributes = true;
     57    } else if (aAttribute == nsGkAtoms::depth) {
     58      mDepth.mState = Attribute::ParsingState::Dirty;
     59      hasDirtyAttributes = true;
     60    } else if (aAttribute == nsGkAtoms::lspace) {
     61      mLeadingSpace.mState = Attribute::ParsingState::Dirty;
     62      hasDirtyAttributes = true;
     63      intrinsicDirty = IntrinsicDirty::FrameAndAncestors;
     64    } else if (aAttribute == nsGkAtoms::voffset) {
     65      mVerticalOffset.mState = Attribute::ParsingState::Dirty;
     66      hasDirtyAttributes = true;
     67    }
     68    if (hasDirtyAttributes) {
     69      PresShell()->FrameNeedsReflow(this, intrinsicDirty, NS_FRAME_IS_DIRTY);
     70    }
     71    return NS_OK;
     72  }
     73  return nsMathMLContainerFrame::AttributeChanged(aNameSpaceID, aAttribute,
     74                                                  aModType);
     75 }
     76 
     77 void nsMathMLmpaddedFrame::ParseAttribute(nsAtom* aAtom,
     78                                          Attribute& aAttribute) {
     79  if (aAttribute.mState != Attribute::ParsingState::Dirty) {
     80    return;
     81  }
     82  nsAutoString value;
     83  aAttribute.mState = Attribute::ParsingState::Invalid;
     84  mContent->AsElement()->GetAttr(aAtom, value);
     85  if (!value.IsEmpty()) {
     86    if (!ParseAttribute(value, aAttribute)) {
     87      ReportParseError(aAtom->GetUTF16String(), value.get());
     88    }
     89  }
     90 }
     91 
     92 bool nsMathMLmpaddedFrame::ParseAttribute(nsString& aString,
     93                                          Attribute& aAttribute) {
     94  // See https://www.w3.org/TR/MathML3/chapter3.html#presm.mpaddedatt
     95  aAttribute.Reset();
     96  aAttribute.mState = Attribute::ParsingState::Invalid;
     97 
     98  aString.CompressWhitespace();  // aString is not a const in this code
     99 
    100  int32_t stringLength = aString.Length();
    101  if (!stringLength) {
    102    return false;
    103  }
    104 
    105  nsAutoString number, unit;
    106 
    107  //////////////////////
    108  // see if the sign is there
    109 
    110  int32_t i = 0;
    111 
    112  if (aString[0] == '+') {
    113    aAttribute.mSign = Attribute::Sign::Plus;
    114    i++;
    115  } else if (aString[0] == '-') {
    116    aAttribute.mSign = Attribute::Sign::Minus;
    117    i++;
    118  } else {
    119    aAttribute.mSign = Attribute::Sign::Unspecified;
    120  }
    121 
    122  // get the number
    123  bool gotDot = false, gotPercent = false;
    124  for (; i < stringLength; i++) {
    125    char16_t c = aString[i];
    126    if (gotDot && c == '.') {
    127      // error - two dots encountered
    128      return false;
    129    }
    130 
    131    if (c == '.') {
    132      gotDot = true;
    133    } else if (!IsAsciiDigit(c)) {
    134      break;
    135    }
    136    number.Append(c);
    137  }
    138 
    139  // catch error if we didn't enter the loop above... we could simply initialize
    140  // floatValue = 1, to cater for cases such as width="height", but that
    141  // wouldn't be in line with the spec which requires an explicit number
    142  if (number.IsEmpty()) {
    143    return false;
    144  }
    145 
    146  nsresult errorCode;
    147  float floatValue = number.ToFloat(&errorCode);
    148  if (NS_FAILED(errorCode)) {
    149    return false;
    150  }
    151 
    152  // see if this is a percentage-based value
    153  if (i < stringLength && aString[i] == '%') {
    154    i++;
    155    gotPercent = true;
    156  }
    157 
    158  // the remainder now should be a css-unit, or a pseudo-unit, or a named-space
    159  aString.Right(unit, stringLength - i);
    160 
    161  if (unit.IsEmpty()) {
    162    if (gotPercent) {
    163      // case ["+"|"-"] unsigned-number "%"
    164      aAttribute.mValue.SetPercentValue(floatValue / 100.0f);
    165      aAttribute.mPseudoUnit = Attribute::PseudoUnit::ItSelf;
    166      aAttribute.mState = Attribute::ParsingState::Valid;
    167      return true;
    168    } else {
    169      // case ["+"|"-"] unsigned-number
    170      // XXXfredw: should we allow non-zero unitless values? See bug 757703.
    171      if (!floatValue) {
    172        aAttribute.mValue.SetFloatValue(floatValue, eCSSUnit_Number);
    173        aAttribute.mPseudoUnit = Attribute::PseudoUnit::ItSelf;
    174        aAttribute.mState = Attribute::ParsingState::Valid;
    175        return true;
    176      }
    177    }
    178  } else if (unit.EqualsLiteral("width")) {
    179    aAttribute.mPseudoUnit = Attribute::PseudoUnit::Width;
    180  } else if (unit.EqualsLiteral("height")) {
    181    aAttribute.mPseudoUnit = Attribute::PseudoUnit::Height;
    182  } else if (unit.EqualsLiteral("depth")) {
    183    aAttribute.mPseudoUnit = Attribute::PseudoUnit::Depth;
    184  } else if (!gotPercent) {  // percentage can only apply to a pseudo-unit
    185 
    186    // see if the unit is a named-space
    187    if (dom::MathMLElement::ParseNamedSpaceValue(
    188            unit, aAttribute.mValue, dom::MathMLElement::PARSE_ALLOW_NEGATIVE,
    189            *mContent->OwnerDoc())) {
    190      // re-scale properly, and we know that the unit of the named-space is 'em'
    191      floatValue *= aAttribute.mValue.GetFloatValue();
    192      aAttribute.mValue.SetFloatValue(floatValue, eCSSUnit_EM);
    193      aAttribute.mPseudoUnit = Attribute::PseudoUnit::NamedSpace;
    194      aAttribute.mState = Attribute::ParsingState::Valid;
    195      return true;
    196    }
    197 
    198    // see if the input was just a CSS value
    199    // We are not supposed to have a unitless, percent, negative or namedspace
    200    // value here.
    201    number.Append(unit);  // leave the sign out if it was there
    202    if (dom::MathMLElement::ParseNumericValue(
    203            number, aAttribute.mValue,
    204            dom::MathMLElement::PARSE_SUPPRESS_WARNINGS, nullptr)) {
    205      aAttribute.mState = Attribute::ParsingState::Valid;
    206      return true;
    207    }
    208  }
    209 
    210  // if we enter here, we have a number that will act as a multiplier on a
    211  // pseudo-unit
    212  if (aAttribute.mPseudoUnit != Attribute::PseudoUnit::Unspecified) {
    213    if (gotPercent) {
    214      aAttribute.mValue.SetPercentValue(floatValue / 100.0f);
    215    } else {
    216      aAttribute.mValue.SetFloatValue(floatValue, eCSSUnit_Number);
    217    }
    218 
    219    aAttribute.mState = Attribute::ParsingState::Valid;
    220    return true;
    221  }
    222 
    223 #ifdef DEBUG
    224  printf("mpadded: attribute with bad numeric value: %s\n",
    225         NS_LossyConvertUTF16toASCII(aString).get());
    226 #endif
    227  // if we reach here, it means we encounter an unexpected input
    228  return false;
    229 }
    230 
    231 void nsMathMLmpaddedFrame::UpdateValue(const Attribute& aAttribute,
    232                                       Attribute::PseudoUnit aSelfUnit,
    233                                       const ReflowOutput& aDesiredSize,
    234                                       nscoord& aValueToUpdate,
    235                                       float aFontSizeInflation) {
    236  nsCSSUnit unit = aAttribute.mValue.GetUnit();
    237  if (aAttribute.IsValid() && eCSSUnit_Null != unit) {
    238    nscoord scaler = 0, amount = 0;
    239 
    240    if (eCSSUnit_Percent == unit || eCSSUnit_Number == unit) {
    241      auto pseudoUnit = aAttribute.mPseudoUnit;
    242      if (pseudoUnit == Attribute::PseudoUnit::ItSelf) {
    243        pseudoUnit = aSelfUnit;
    244      }
    245      switch (pseudoUnit) {
    246        case Attribute::PseudoUnit::Width:
    247          scaler = aDesiredSize.Width();
    248          break;
    249 
    250        case Attribute::PseudoUnit::Height:
    251          scaler = aDesiredSize.BlockStartAscent();
    252          break;
    253 
    254        case Attribute::PseudoUnit::Depth:
    255          scaler = aDesiredSize.Height() - aDesiredSize.BlockStartAscent();
    256          break;
    257 
    258        default:
    259          // if we ever reach here, it would mean something is wrong
    260          // somewhere with the setup and/or the caller
    261          NS_ERROR("Unexpected Pseudo Unit");
    262          return;
    263      }
    264    }
    265 
    266    if (eCSSUnit_Number == unit) {
    267      amount =
    268          NSToCoordRound(float(scaler) * aAttribute.mValue.GetFloatValue());
    269    } else if (eCSSUnit_Percent == unit) {
    270      amount =
    271          NSToCoordRound(float(scaler) * aAttribute.mValue.GetPercentValue());
    272    } else {
    273      amount = CalcLength(aAttribute.mValue, aFontSizeInflation, this);
    274    }
    275 
    276    switch (aAttribute.mSign) {
    277      case Attribute::Sign::Plus:
    278        aValueToUpdate += amount;
    279        break;
    280      case Attribute::Sign::Minus:
    281        aValueToUpdate -= amount;
    282        break;
    283      case Attribute::Sign::Unspecified:
    284        aValueToUpdate = amount;
    285        break;
    286    }
    287  }
    288 }
    289 
    290 /* virtual */
    291 void nsMathMLmpaddedFrame::Place(DrawTarget* aDrawTarget,
    292                                 const PlaceFlags& aFlags,
    293                                 ReflowOutput& aDesiredSize) {
    294  // First perform normal row layout without border/padding.
    295  PlaceFlags flags = aFlags + PlaceFlag::MeasureOnly +
    296                     PlaceFlag::IgnoreBorderPadding +
    297                     PlaceFlag::DoNotAdjustForWidthAndHeight;
    298  nsMathMLContainerFrame::Place(aDrawTarget, flags, aDesiredSize);
    299 
    300  nscoord height = aDesiredSize.BlockStartAscent();
    301  nscoord depth = aDesiredSize.Height() - aDesiredSize.BlockStartAscent();
    302  // The REC says:
    303  //
    304  // "The lspace attribute ('leading' space) specifies the horizontal location
    305  // of the positioning point of the child content with respect to the
    306  // positioning point of the mpadded element. By default they coincide, and
    307  // therefore absolute values for lspace have the same effect as relative
    308  // values."
    309  //
    310  // "MathML renderers should ensure that, except for the effects of the
    311  // attributes, the relative spacing between the contents of the mpadded
    312  // element and surrounding MathML elements would not be modified by replacing
    313  // an mpadded element with an mrow element with the same content, even if
    314  // linebreaking occurs within the mpadded element."
    315  //
    316  // (http://www.w3.org/TR/MathML/chapter3.html#presm.mpadded)
    317  //
    318  // "In those discussions, the terms leading and trailing are used to specify
    319  // a side of an object when which side to use depends on the directionality;
    320  // ie. leading means left in LTR but right in RTL."
    321  // (http://www.w3.org/TR/MathML/chapter3.html#presm.bidi.math)
    322  nscoord lspace = 0;
    323  // In MathML3, "width" will be the bounding box width and "advancewidth" will
    324  // refer "to the horizontal distance between the positioning point of the
    325  // mpadded and the positioning point for the following content".  MathML2
    326  // doesn't make the distinction.
    327  nscoord width = aDesiredSize.Width();
    328  nscoord voffset = 0;
    329 
    330  nscoord initialWidth = width;
    331  float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this);
    332 
    333  // update width
    334  ParseAttribute(nsGkAtoms::width, mWidth);
    335  UpdateValue(mWidth, Attribute::PseudoUnit::Width, aDesiredSize, width,
    336              fontSizeInflation);
    337  width = std::max(0, width);
    338 
    339  // update "height" (this is the ascent in the terminology of the REC)
    340  ParseAttribute(nsGkAtoms::height, mHeight);
    341  UpdateValue(mHeight, Attribute::PseudoUnit::Height, aDesiredSize, height,
    342              fontSizeInflation);
    343  height = std::max(0, height);
    344 
    345  // update "depth" (this is the descent in the terminology of the REC)
    346  ParseAttribute(nsGkAtoms::depth, mDepth);
    347  UpdateValue(mDepth, Attribute::PseudoUnit::Depth, aDesiredSize, depth,
    348              fontSizeInflation);
    349  depth = std::max(0, depth);
    350 
    351  // update lspace
    352  ParseAttribute(nsGkAtoms::lspace, mLeadingSpace);
    353  if (mLeadingSpace.mPseudoUnit != Attribute::PseudoUnit::ItSelf) {
    354    UpdateValue(mLeadingSpace, Attribute::PseudoUnit::Unspecified, aDesiredSize,
    355                lspace, fontSizeInflation);
    356  }
    357 
    358  // update voffset
    359  ParseAttribute(nsGkAtoms::voffset, mVerticalOffset);
    360  if (mVerticalOffset.mPseudoUnit != Attribute::PseudoUnit::ItSelf) {
    361    UpdateValue(mVerticalOffset, Attribute::PseudoUnit::Unspecified,
    362                aDesiredSize, voffset, fontSizeInflation);
    363  }
    364 
    365  // do the padding now that we have everything
    366  // The idea here is to maintain the invariant that <mpadded>...</mpadded>
    367  // (i.e., with no attributes) looks the same as <mrow>...</mrow>. But when
    368  // there are attributes, tweak our metrics and move children to achieve the
    369  // desired visual effects.
    370 
    371  const bool isRTL = StyleVisibility()->mDirection == StyleDirection::Rtl;
    372  if (isRTL ? mWidth.IsValid() : mLeadingSpace.IsValid()) {
    373    // there was padding on the left. dismiss the left italic correction now
    374    // (so that our parent won't correct us)
    375    mBoundingMetrics.leftBearing = 0;
    376  }
    377 
    378  if (isRTL ? mLeadingSpace.IsValid() : mWidth.IsValid()) {
    379    // there was padding on the right. dismiss the right italic correction now
    380    // (so that our parent won't correct us)
    381    mBoundingMetrics.width = width;
    382    mBoundingMetrics.rightBearing = mBoundingMetrics.width;
    383  }
    384 
    385  nscoord dx = (isRTL ? width - initialWidth - lspace : lspace);
    386 
    387  aDesiredSize.SetBlockStartAscent(height);
    388  aDesiredSize.Width() = mBoundingMetrics.width;
    389  aDesiredSize.Height() = depth + aDesiredSize.BlockStartAscent();
    390  mBoundingMetrics.ascent = height;
    391  mBoundingMetrics.descent = depth;
    392  aDesiredSize.mBoundingMetrics = mBoundingMetrics;
    393 
    394  // Apply width/height to math content box.
    395  auto sizes = GetWidthAndHeightForPlaceAdjustment(aFlags);
    396  dx += ApplyAdjustmentForWidthAndHeight(aFlags, sizes, aDesiredSize,
    397                                         mBoundingMetrics);
    398 
    399  // Add padding+border.
    400  auto borderPadding = GetBorderPaddingForPlace(aFlags);
    401  InflateReflowAndBoundingMetrics(borderPadding, aDesiredSize,
    402                                  mBoundingMetrics);
    403  dx += borderPadding.left;
    404 
    405  mReference.x = 0;
    406  mReference.y = aDesiredSize.BlockStartAscent();
    407 
    408  if (!aFlags.contains(PlaceFlag::MeasureOnly)) {
    409    // Finish reflowing child frames, positioning their origins.
    410    PositionRowChildFrames(dx, aDesiredSize.BlockStartAscent() - voffset);
    411  }
    412 }