tor-browser

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

nsMathMLmrootFrame.cpp (15494B)


      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 "nsMathMLmrootFrame.h"
      8 
      9 #include <algorithm>
     10 
     11 #include "gfxContext.h"
     12 #include "gfxMathTable.h"
     13 #include "mozilla/PresShell.h"
     14 #include "mozilla/StaticPrefs_mathml.h"
     15 #include "nsLayoutUtils.h"
     16 #include "nsPresContext.h"
     17 
     18 using namespace mozilla;
     19 
     20 //
     21 // <mroot> -- form a radical - implementation
     22 //
     23 
     24 static const char16_t kSqrChar = char16_t(0x221A);
     25 
     26 nsIFrame* NS_NewMathMLmrootFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
     27  return new (aPresShell)
     28      nsMathMLmrootFrame(aStyle, aPresShell->GetPresContext());
     29 }
     30 
     31 NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmrootFrame)
     32 
     33 nsMathMLmrootFrame::nsMathMLmrootFrame(ComputedStyle* aStyle,
     34                                       nsPresContext* aPresContext)
     35    : nsMathMLContainerFrame(aStyle, aPresContext, kClassID) {}
     36 
     37 nsMathMLmrootFrame::~nsMathMLmrootFrame() = default;
     38 
     39 void nsMathMLmrootFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
     40                              nsIFrame* aPrevInFlow) {
     41  nsMathMLContainerFrame::Init(aContent, aParent, aPrevInFlow);
     42 
     43  nsAutoString sqrChar;
     44  sqrChar.Assign(kSqrChar);
     45  mSqrChar.SetData(sqrChar);
     46  mSqrChar.SetComputedStyle(Style());
     47 }
     48 
     49 bool nsMathMLmrootFrame::ShouldUseRowFallback() {
     50  bool isRootWithIndex = GetContent()->IsMathMLElement(nsGkAtoms::mroot);
     51  if (!isRootWithIndex) {
     52    return false;
     53  }
     54  // An mroot element expects exactly two children.
     55  nsIFrame* baseFrame = mFrames.FirstChild();
     56  if (!baseFrame) {
     57    return true;
     58  }
     59  nsIFrame* indexFrame = baseFrame->GetNextSibling();
     60  return !indexFrame || indexFrame->GetNextSibling();
     61 }
     62 
     63 bool nsMathMLmrootFrame::IsMrowLike() {
     64  bool isRootWithIndex = GetContent()->IsMathMLElement(nsGkAtoms::mroot);
     65  if (isRootWithIndex) {
     66    return false;
     67  }
     68  return mFrames.FirstChild() != mFrames.LastChild() || !mFrames.FirstChild();
     69 }
     70 
     71 NS_IMETHODIMP
     72 nsMathMLmrootFrame::InheritAutomaticData(nsIFrame* aParent) {
     73  nsMathMLContainerFrame::InheritAutomaticData(aParent);
     74 
     75  bool isRootWithIndex = GetContent()->IsMathMLElement(nsGkAtoms::mroot);
     76  if (!isRootWithIndex) {
     77    mPresentationData.flags +=
     78        MathMLPresentationFlag::StretchAllChildrenVertically;
     79  }
     80 
     81  return NS_OK;
     82 }
     83 
     84 NS_IMETHODIMP
     85 nsMathMLmrootFrame::TransmitAutomaticData() {
     86  bool isRootWithIndex = GetContent()->IsMathMLElement(nsGkAtoms::mroot);
     87  if (isRootWithIndex) {
     88    // 1. The REC says:
     89    //    The <mroot> element increments scriptlevel by 2, and sets displaystyle
     90    //    to "false", within index, but leaves both attributes unchanged within
     91    //    base.
     92    // 2. The TeXbook (Ch 17. p.141) says \sqrt is compressed
     93    if (!StaticPrefs::mathml_math_shift_enabled()) {
     94      UpdatePresentationDataFromChildAt(1, 1,
     95                                        MathMLPresentationFlag::Compressed,
     96                                        MathMLPresentationFlag::Compressed);
     97      UpdatePresentationDataFromChildAt(0, 0,
     98                                        MathMLPresentationFlag::Compressed,
     99                                        MathMLPresentationFlag::Compressed);
    100    }
    101 
    102    PropagateFrameFlagFor(mFrames.LastChild(),
    103                          NS_FRAME_MATHML_SCRIPT_DESCENDANT);
    104  } else {
    105    // The TeXBook (Ch 17. p.141) says that \sqrt is cramped
    106    if (!StaticPrefs::mathml_math_shift_enabled()) {
    107      UpdatePresentationDataFromChildAt(0, -1,
    108                                        MathMLPresentationFlag::Compressed,
    109                                        MathMLPresentationFlag::Compressed);
    110    }
    111  }
    112 
    113  return NS_OK;
    114 }
    115 
    116 void nsMathMLmrootFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
    117                                          const nsDisplayListSet& aLists) {
    118  /////////////
    119  // paint the content we are square-rooting
    120  nsMathMLContainerFrame::BuildDisplayList(aBuilder, aLists);
    121 
    122  if (ShouldUseRowFallback()) {
    123    return;
    124  }
    125 
    126  /////////////
    127  // paint the sqrt symbol
    128  mSqrChar.Display(aBuilder, this, aLists, 0);
    129 
    130  DisplayBar(aBuilder, this, mBarRect, aLists);
    131 }
    132 
    133 void nsMathMLmrootFrame::GetRadicalXOffsets(nscoord aIndexWidth,
    134                                            nscoord aSqrWidth,
    135                                            nsFontMetrics* aFontMetrics,
    136                                            nscoord* aIndexOffset,
    137                                            nscoord* aSqrOffset) {
    138  // The index is tucked in closer to the radical while making sure
    139  // that the kern does not make the index and radical collide
    140  nscoord dxIndex, dxSqr, radicalKernBeforeDegree, radicalKernAfterDegree;
    141  nscoord oneDevPixel = aFontMetrics->AppUnitsPerDevPixel();
    142  RefPtr<gfxFont> mathFont =
    143      aFontMetrics->GetThebesFontGroup()->GetFirstMathFont();
    144 
    145  if (mathFont) {
    146    radicalKernBeforeDegree = mathFont->MathTable()->Constant(
    147        gfxMathTable::RadicalKernBeforeDegree, oneDevPixel);
    148    radicalKernAfterDegree = mathFont->MathTable()->Constant(
    149        gfxMathTable::RadicalKernAfterDegree, oneDevPixel);
    150  } else {
    151    nscoord em;
    152    GetEmHeight(aFontMetrics, em);
    153    radicalKernBeforeDegree = NSToCoordRound(5.0f * em / 18);
    154    radicalKernAfterDegree = NSToCoordRound(-10.0f * em / 18);
    155  }
    156 
    157  // Clamp radical kern degrees according to spec:
    158  // https://w3c.github.io/mathml-core/#root-with-index
    159  radicalKernBeforeDegree = std::max(0, radicalKernBeforeDegree);
    160  radicalKernAfterDegree = std::max(-aIndexWidth, radicalKernAfterDegree);
    161 
    162  dxIndex = radicalKernBeforeDegree;
    163  dxSqr = radicalKernBeforeDegree + aIndexWidth + radicalKernAfterDegree;
    164  if (aIndexOffset) {
    165    *aIndexOffset = dxIndex;
    166  }
    167  if (aSqrOffset) {
    168    *aSqrOffset = dxSqr;
    169  }
    170 }
    171 
    172 void nsMathMLmrootFrame::Place(DrawTarget* aDrawTarget,
    173                               const PlaceFlags& aFlags,
    174                               ReflowOutput& aDesiredSize) {
    175  if (ShouldUseRowFallback()) {
    176    // report an error, encourage people to get their markups in order
    177    if (!aFlags.contains(PlaceFlag::MeasureOnly)) {
    178      ReportChildCountError();
    179    }
    180    return PlaceAsMrow(aDrawTarget, aFlags, aDesiredSize);
    181  }
    182 
    183  const bool isRootWithIndex = GetContent()->IsMathMLElement(nsGkAtoms::mroot);
    184  nsBoundingMetrics bmSqr, bmBase, bmIndex;
    185  nsIFrame *baseFrame = nullptr, *indexFrame = nullptr;
    186  nsMargin baseMargin, indexMargin;
    187  ReflowOutput baseSize(aDesiredSize.GetWritingMode());
    188  ReflowOutput indexSize(aDesiredSize.GetWritingMode());
    189  if (isRootWithIndex) {
    190    baseFrame = mFrames.FirstChild();
    191    indexFrame = baseFrame->GetNextSibling();
    192    baseMargin = GetMarginForPlace(aFlags, baseFrame);
    193    indexMargin = GetMarginForPlace(aFlags, indexFrame);
    194    GetReflowAndBoundingMetricsFor(baseFrame, baseSize, bmBase);
    195    GetReflowAndBoundingMetricsFor(indexFrame, indexSize, bmIndex);
    196  } else {
    197    // Format our content as an mrow without border/padding to obtain the
    198    // square root base. The metrics/frame for the index are ignored.
    199    PlaceFlags flags = aFlags + PlaceFlag::MeasureOnly +
    200                       PlaceFlag::IgnoreBorderPadding +
    201                       PlaceFlag::DoNotAdjustForWidthAndHeight;
    202    nsMathMLContainerFrame::Place(aDrawTarget, flags, baseSize);
    203    bmBase = baseSize.mBoundingMetrics;
    204  }
    205 
    206  ////////////
    207  // Prepare the radical symbol and the overline bar
    208 
    209  float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this);
    210  RefPtr<nsFontMetrics> fm =
    211      nsLayoutUtils::GetFontMetricsForFrame(this, fontSizeInflation);
    212 
    213  nscoord ruleThickness, leading, psi;
    214  GetRadicalParameters(fm, StyleFont()->mMathStyle == StyleMathStyle::Normal,
    215                       ruleThickness, leading, psi);
    216 
    217  // built-in: adjust clearance psi to emulate \mathstrut using '1' (TexBook,
    218  // p.131)
    219  char16_t one = '1';
    220  nsBoundingMetrics bmOne =
    221      nsLayoutUtils::AppUnitBoundsOfString(&one, 1, *fm, aDrawTarget);
    222  if (bmOne.ascent > bmBase.ascent + baseMargin.top) {
    223    psi += bmOne.ascent - bmBase.ascent - baseMargin.top;
    224  }
    225 
    226  // make sure that the rule appears on on screen
    227  nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1);
    228  if (ruleThickness < onePixel) {
    229    ruleThickness = onePixel;
    230  }
    231 
    232  // adjust clearance psi to get an exact number of pixels -- this
    233  // gives a nicer & uniform look on stacked radicals (bug 130282)
    234  nscoord delta = psi % onePixel;
    235  if (delta) {
    236    psi += onePixel - delta;  // round up
    237  }
    238 
    239  // Stretch the radical symbol to the appropriate height if it is not big
    240  // enough.
    241  nsBoundingMetrics contSize = bmBase;
    242  contSize.descent =
    243      bmBase.ascent + bmBase.descent + baseMargin.TopBottom() + psi;
    244  contSize.ascent = ruleThickness;
    245 
    246  // height(radical) should be >= height(base) + psi + ruleThickness
    247  nsBoundingMetrics radicalSize;
    248  if (aFlags.contains(PlaceFlag::IntrinsicSize)) {
    249    nscoord radical_width =
    250        mSqrChar.GetMaxWidth(this, aDrawTarget, fontSizeInflation);
    251    bmSqr.leftBearing = 0;
    252    bmSqr.rightBearing = radical_width;
    253    bmSqr.width = radical_width;
    254    bmSqr.ascent = bmSqr.descent = 0;
    255  } else {
    256    mSqrChar.Stretch(this, aDrawTarget, fontSizeInflation,
    257                     NS_STRETCH_DIRECTION_VERTICAL, contSize, radicalSize,
    258                     NS_STRETCH_LARGER,
    259                     StyleVisibility()->mDirection == StyleDirection::Rtl);
    260    // radicalSize have changed at this point, and should match with
    261    // the bounding metrics of the char
    262    mSqrChar.GetBoundingMetrics(bmSqr);
    263  }
    264 
    265  // Update the desired size for the container (like msqrt, index is not yet
    266  // included) the baseline will be that of the base.
    267  mBoundingMetrics.ascent =
    268      bmBase.ascent + baseMargin.top + psi + ruleThickness;
    269  mBoundingMetrics.descent =
    270      std::max(bmBase.descent + baseMargin.bottom,
    271               (bmSqr.ascent + bmSqr.descent - mBoundingMetrics.ascent));
    272  mBoundingMetrics.width = bmSqr.width + bmBase.width + baseMargin.LeftRight();
    273  mBoundingMetrics.leftBearing = bmSqr.leftBearing;
    274  mBoundingMetrics.rightBearing =
    275      bmSqr.width +
    276      std::max(
    277          bmBase.width + baseMargin.LeftRight(),
    278          bmBase.rightBearing + baseMargin.left);  // take also care of the rule
    279 
    280  aDesiredSize.SetBlockStartAscent(mBoundingMetrics.ascent + leading);
    281  aDesiredSize.Height() =
    282      aDesiredSize.BlockStartAscent() +
    283      std::max(baseSize.Height() - baseSize.BlockStartAscent(),
    284               mBoundingMetrics.descent + ruleThickness);
    285  aDesiredSize.Width() = mBoundingMetrics.width;
    286 
    287  nscoord indexClearance = 0, dxIndex = 0, dxSqr = 0, indexRaisedAscent = 0;
    288  if (isRootWithIndex) {
    289    /////////////
    290    // Re-adjust the desired size to include the index.
    291 
    292    // the index is raised by some fraction of the height
    293    // of the radical, see \mroot macro in App. B, TexBook
    294    float raiseIndexPercent = 0.6f;
    295    RefPtr<gfxFont> mathFont = fm->GetThebesFontGroup()->GetFirstMathFont();
    296    if (mathFont) {
    297      raiseIndexPercent = mathFont->MathTable()->Constant(
    298          gfxMathTable::RadicalDegreeBottomRaisePercent);
    299    }
    300    nscoord raiseIndexDelta =
    301        NSToCoordRound(raiseIndexPercent * (bmSqr.ascent + bmSqr.descent));
    302    indexRaisedAscent = mBoundingMetrics.ascent  // top of radical
    303                        -
    304                        (bmSqr.ascent + bmSqr.descent)  // to bottom of radical
    305                        + raiseIndexDelta + bmIndex.ascent + bmIndex.descent +
    306                        indexMargin.TopBottom();  // to top of raised index
    307 
    308    if (mBoundingMetrics.ascent < indexRaisedAscent) {
    309      indexClearance =
    310          indexRaisedAscent -
    311          mBoundingMetrics.ascent;  // excess gap introduced by a tall index
    312      mBoundingMetrics.ascent = indexRaisedAscent;
    313      nscoord descent = aDesiredSize.Height() - aDesiredSize.BlockStartAscent();
    314      aDesiredSize.SetBlockStartAscent(mBoundingMetrics.ascent + leading);
    315      aDesiredSize.Height() = aDesiredSize.BlockStartAscent() + descent;
    316    }
    317 
    318    GetRadicalXOffsets(bmIndex.width + indexMargin.LeftRight(), bmSqr.width, fm,
    319                       &dxIndex, &dxSqr);
    320 
    321    mBoundingMetrics.width =
    322        dxSqr + bmSqr.width + bmBase.width + baseMargin.LeftRight();
    323    mBoundingMetrics.leftBearing =
    324        std::min(dxIndex + bmIndex.leftBearing, dxSqr + bmSqr.leftBearing);
    325    mBoundingMetrics.rightBearing =
    326        dxSqr + bmSqr.width +
    327        std::max(bmBase.width + baseMargin.LeftRight(),
    328                 bmBase.rightBearing + baseMargin.left);
    329 
    330    aDesiredSize.Width() = mBoundingMetrics.width;
    331  }
    332 
    333  aDesiredSize.mBoundingMetrics = mBoundingMetrics;
    334 
    335  // Apply width/height to math content box.
    336  const PlaceFlags flags;
    337  auto sizes = GetWidthAndHeightForPlaceAdjustment(flags);
    338  nscoord shiftX = ApplyAdjustmentForWidthAndHeight(flags, sizes, aDesiredSize,
    339                                                    mBoundingMetrics);
    340 
    341  // Add padding+border around the final layout.
    342  auto borderPadding = GetBorderPaddingForPlace(aFlags);
    343  InflateReflowAndBoundingMetrics(borderPadding, aDesiredSize,
    344                                  mBoundingMetrics);
    345 
    346  if (!aFlags.contains(PlaceFlag::MeasureOnly)) {
    347    nsPresContext* presContext = PresContext();
    348    const bool isRTL = StyleVisibility()->mDirection == StyleDirection::Rtl;
    349    nscoord borderPaddingInlineStart =
    350        isRTL ? borderPadding.right : borderPadding.left;
    351    nscoord dx, dy;
    352 
    353    if (isRootWithIndex) {
    354      // place the index
    355      dx = borderPaddingInlineStart + dxIndex +
    356           indexMargin.Side(isRTL ? eSideRight : eSideLeft);
    357      dy = aDesiredSize.BlockStartAscent() -
    358           (indexRaisedAscent + indexSize.BlockStartAscent() - bmIndex.ascent);
    359      FinishReflowChild(
    360          indexFrame, presContext, indexSize, nullptr,
    361          MirrorIfRTL(aDesiredSize.Width(), indexSize.Width(), dx),
    362          dy + indexMargin.top, ReflowChildFlags::Default);
    363    }
    364 
    365    // place the radical symbol and the radical bar
    366    dx = borderPaddingInlineStart + dxSqr;
    367    dy = borderPadding.top + indexClearance +
    368         leading;  // leave a leading at the top
    369    mSqrChar.SetRect(nsRect(MirrorIfRTL(aDesiredSize.Width(), bmSqr.width, dx),
    370                            dy, bmSqr.width, bmSqr.ascent + bmSqr.descent));
    371    dx += bmSqr.width;
    372    mBarRect.SetRect(MirrorIfRTL(aDesiredSize.Width(),
    373                                 bmBase.width + baseMargin.LeftRight(), dx),
    374                     dy, bmBase.width + baseMargin.LeftRight(), ruleThickness);
    375 
    376    // place the base
    377    if (isRootWithIndex) {
    378      dx += isRTL ? baseMargin.right : baseMargin.left;
    379      dy = aDesiredSize.BlockStartAscent() - baseSize.BlockStartAscent();
    380      FinishReflowChild(baseFrame, presContext, baseSize, nullptr,
    381                        MirrorIfRTL(aDesiredSize.Width(), baseSize.Width(), dx),
    382                        dy, ReflowChildFlags::Default);
    383    } else {
    384      nscoord dx_left = borderPadding.left + shiftX;
    385      if (!isRTL) {
    386        dx_left += bmSqr.width;
    387      }
    388      PositionRowChildFrames(dx_left, aDesiredSize.BlockStartAscent());
    389    }
    390  }
    391 
    392  mReference.x = 0;
    393  mReference.y = aDesiredSize.BlockStartAscent();
    394 }
    395 
    396 void nsMathMLmrootFrame::DidSetComputedStyle(ComputedStyle* aOldStyle) {
    397  nsMathMLContainerFrame::DidSetComputedStyle(aOldStyle);
    398  mSqrChar.SetComputedStyle(Style());
    399 }