tor-browser

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

nsMathMLmfracFrame.cpp (15860B)


      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 "nsMathMLmfracFrame.h"
      8 
      9 #include <algorithm>
     10 
     11 #include "gfxContext.h"
     12 #include "gfxMathTable.h"
     13 #include "gfxTextRun.h"
     14 #include "gfxUtils.h"
     15 #include "mozilla/PresShell.h"
     16 #include "mozilla/RefPtr.h"
     17 #include "mozilla/StaticPrefs_mathml.h"
     18 #include "mozilla/dom/Document.h"
     19 #include "mozilla/dom/MathMLElement.h"
     20 #include "mozilla/gfx/2D.h"
     21 #include "nsDisplayList.h"
     22 #include "nsLayoutUtils.h"
     23 #include "nsPresContext.h"
     24 
     25 using namespace mozilla;
     26 using namespace mozilla::gfx;
     27 
     28 //
     29 // <mfrac> -- form a fraction from two subexpressions - implementation
     30 //
     31 
     32 nsIFrame* NS_NewMathMLmfracFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
     33  return new (aPresShell)
     34      nsMathMLmfracFrame(aStyle, aPresShell->GetPresContext());
     35 }
     36 
     37 NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmfracFrame)
     38 
     39 nsMathMLmfracFrame::~nsMathMLmfracFrame() = default;
     40 
     41 MathMLFrameType nsMathMLmfracFrame::GetMathMLFrameType() {
     42  // frac is "inner" in TeXBook, Appendix G, rule 15e. See also page 170.
     43  return MathMLFrameType::Inner;
     44 }
     45 
     46 uint8_t nsMathMLmfracFrame::ScriptIncrement(nsIFrame* aFrame) {
     47  if (StyleFont()->mMathStyle == StyleMathStyle::Compact && aFrame &&
     48      (mFrames.FirstChild() == aFrame || mFrames.LastChild() == aFrame)) {
     49    return 1;
     50  }
     51  return 0;
     52 }
     53 
     54 NS_IMETHODIMP
     55 nsMathMLmfracFrame::TransmitAutomaticData() {
     56  // The TeXbook (Ch 17. p.141) says the numerator inherits the compression
     57  //  while the denominator is compressed
     58  if (!StaticPrefs::mathml_math_shift_enabled()) {
     59    UpdatePresentationDataFromChildAt(1, 1, MathMLPresentationFlag::Compressed,
     60                                      MathMLPresentationFlag::Compressed);
     61  }
     62 
     63  // If displaystyle is false, then scriptlevel is incremented, so notify the
     64  // children of this.
     65  if (StyleFont()->mMathStyle == StyleMathStyle::Compact) {
     66    PropagateFrameFlagFor(mFrames.FirstChild(),
     67                          NS_FRAME_MATHML_SCRIPT_DESCENDANT);
     68    PropagateFrameFlagFor(mFrames.LastChild(),
     69                          NS_FRAME_MATHML_SCRIPT_DESCENDANT);
     70  }
     71 
     72  // if our numerator is an embellished operator, let its state bubble to us
     73  GetEmbellishDataFrom(mFrames.FirstChild(), mEmbellishData);
     74  if (mEmbellishData.flags.contains(MathMLEmbellishFlag::EmbellishedOperator)) {
     75    // even when embellished, we need to record that <mfrac> won't fire
     76    // Stretch() on its embellished child
     77    mEmbellishData.direction = NS_STRETCH_DIRECTION_UNSUPPORTED;
     78  }
     79 
     80  return NS_OK;
     81 }
     82 
     83 nscoord nsMathMLmfracFrame::CalcLineThickness(nsString& aThicknessAttribute,
     84                                              nscoord onePixel,
     85                                              nscoord aDefaultRuleThickness,
     86                                              float aFontSizeInflation) {
     87  nscoord defaultThickness = aDefaultRuleThickness;
     88  nscoord lineThickness = aDefaultRuleThickness;
     89  nscoord minimumThickness = onePixel;
     90 
     91  // linethickness
     92  // https://w3c.github.io/mathml-core/#dfn-linethickness
     93  if (!aThicknessAttribute.IsEmpty()) {
     94    lineThickness = defaultThickness;
     95    ParseAndCalcNumericValue(aThicknessAttribute, &lineThickness,
     96                             dom::MathMLElement::PARSE_ALLOW_NEGATIVE,
     97                             aFontSizeInflation, this);
     98    // MathML Core says a negative value is interpreted as 0.
     99    if (lineThickness < 0) {
    100      lineThickness = 0;
    101    }
    102  }
    103  // use minimum if the lineThickness is a non-zero value less than minimun
    104  if (lineThickness && lineThickness < minimumThickness) {
    105    lineThickness = minimumThickness;
    106  }
    107 
    108  return lineThickness;
    109 }
    110 
    111 void nsMathMLmfracFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
    112                                          const nsDisplayListSet& aLists) {
    113  /////////////
    114  // paint the numerator and denominator
    115  nsMathMLContainerFrame::BuildDisplayList(aBuilder, aLists);
    116 
    117  /////////////
    118  // paint the fraction line
    119  DisplayBar(aBuilder, this, mLineRect, aLists);
    120 }
    121 
    122 nsresult nsMathMLmfracFrame::AttributeChanged(int32_t aNameSpaceID,
    123                                              nsAtom* aAttribute,
    124                                              AttrModType aModType) {
    125  if (aNameSpaceID == kNameSpaceID_None &&
    126      nsGkAtoms::linethickness == aAttribute) {
    127    // The thickness changes, so a repaint of the bar is needed.
    128    InvalidateFrame();
    129    // The thickness affects vertical offsets.
    130    PresShell()->FrameNeedsReflow(this, IntrinsicDirty::None,
    131                                  NS_FRAME_IS_DIRTY);
    132    return NS_OK;
    133  }
    134  return nsMathMLContainerFrame::AttributeChanged(aNameSpaceID, aAttribute,
    135                                                  aModType);
    136 }
    137 
    138 nscoord nsMathMLmfracFrame::FixInterFrameSpacing(ReflowOutput& aDesiredSize) {
    139  nscoord gap = nsMathMLContainerFrame::FixInterFrameSpacing(aDesiredSize);
    140  if (!gap) {
    141    return 0;
    142  }
    143 
    144  mLineRect.MoveBy(gap, 0);
    145  return gap;
    146 }
    147 
    148 /* virtual */
    149 void nsMathMLmfracFrame::Place(DrawTarget* aDrawTarget,
    150                               const PlaceFlags& aFlags,
    151                               ReflowOutput& aDesiredSize) {
    152  ////////////////////////////////////
    153  // Get the children's desired sizes
    154  nsBoundingMetrics bmNum, bmDen;
    155  ReflowOutput sizeNum(aDesiredSize.GetWritingMode());
    156  ReflowOutput sizeDen(aDesiredSize.GetWritingMode());
    157  nsIFrame* frameDen = nullptr;
    158  nsIFrame* frameNum = mFrames.FirstChild();
    159  if (frameNum) {
    160    frameDen = frameNum->GetNextSibling();
    161  }
    162  if (!frameNum || !frameDen || frameDen->GetNextSibling()) {
    163    // report an error, encourage people to get their markups in order
    164    if (!aFlags.contains(PlaceFlag::MeasureOnly)) {
    165      ReportChildCountError();
    166    }
    167    return PlaceAsMrow(aDrawTarget, aFlags, aDesiredSize);
    168  }
    169  GetReflowAndBoundingMetricsFor(frameNum, sizeNum, bmNum);
    170  GetReflowAndBoundingMetricsFor(frameDen, sizeDen, bmDen);
    171 
    172  nsMargin numMargin = GetMarginForPlace(aFlags, frameNum),
    173           denMargin = GetMarginForPlace(aFlags, frameDen);
    174 
    175  nsPresContext* presContext = PresContext();
    176  nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1);
    177 
    178  float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this);
    179  RefPtr<nsFontMetrics> fm =
    180      nsLayoutUtils::GetFontMetricsForFrame(this, fontSizeInflation);
    181 
    182  nscoord defaultRuleThickness, axisHeight;
    183  nscoord oneDevPixel = fm->AppUnitsPerDevPixel();
    184  RefPtr<gfxFont> mathFont = fm->GetThebesFontGroup()->GetFirstMathFont();
    185  if (mathFont) {
    186    defaultRuleThickness = mathFont->MathTable()->Constant(
    187        gfxMathTable::FractionRuleThickness, oneDevPixel);
    188  } else {
    189    GetRuleThickness(aDrawTarget, fm, defaultRuleThickness);
    190  }
    191  GetAxisHeight(aDrawTarget, fm, axisHeight);
    192 
    193  bool outermostEmbellished = false;
    194  if (mEmbellishData.coreFrame) {
    195    nsEmbellishData parentData;
    196    GetEmbellishDataFrom(GetParent(), parentData);
    197    outermostEmbellished = parentData.coreFrame != mEmbellishData.coreFrame;
    198  }
    199 
    200  // see if the linethickness attribute is there
    201  nsAutoString value;
    202  mContent->AsElement()->GetAttr(nsGkAtoms::linethickness, value);
    203  mLineThickness = CalcLineThickness(value, onePixel, defaultRuleThickness,
    204                                     fontSizeInflation);
    205 
    206  bool displayStyle = StyleFont()->mMathStyle == StyleMathStyle::Normal;
    207 
    208  mLineRect.height = mLineThickness;
    209 
    210  // Add lspace & rspace that may come from <mo> if we are an outermost
    211  // embellished container (we fetch values from the core since they may use
    212  // units that depend on style data, and style changes could have occurred
    213  // in the core since our last visit there)
    214  nscoord leftSpace = 0;
    215  nscoord rightSpace = 0;
    216  if (outermostEmbellished) {
    217    const bool isRTL = StyleVisibility()->mDirection == StyleDirection::Rtl;
    218    nsEmbellishData coreData;
    219    GetEmbellishDataFrom(mEmbellishData.coreFrame, coreData);
    220    leftSpace += isRTL ? coreData.trailingSpace : coreData.leadingSpace;
    221    rightSpace += isRTL ? coreData.leadingSpace : coreData.trailingSpace;
    222  }
    223 
    224  nscoord actualRuleThickness = mLineThickness;
    225 
    226  //////////////////
    227  // Get shifts
    228  nscoord numShift = 0;
    229  nscoord denShift = 0;
    230 
    231  // Rule 15b, App. G, TeXbook
    232  nscoord numShift1, numShift2, numShift3;
    233  nscoord denShift1, denShift2;
    234 
    235  GetNumeratorShifts(fm, numShift1, numShift2, numShift3);
    236  GetDenominatorShifts(fm, denShift1, denShift2);
    237 
    238  if (0 == actualRuleThickness) {
    239    numShift = displayStyle ? numShift1 : numShift3;
    240    denShift = displayStyle ? denShift1 : denShift2;
    241    if (mathFont) {
    242      numShift = mathFont->MathTable()->Constant(
    243          displayStyle ? gfxMathTable::StackTopDisplayStyleShiftUp
    244                       : gfxMathTable::StackTopShiftUp,
    245          oneDevPixel);
    246      denShift = mathFont->MathTable()->Constant(
    247          displayStyle ? gfxMathTable::StackBottomDisplayStyleShiftDown
    248                       : gfxMathTable::StackBottomShiftDown,
    249          oneDevPixel);
    250    }
    251  } else {
    252    numShift = displayStyle ? numShift1 : numShift2;
    253    denShift = displayStyle ? denShift1 : denShift2;
    254    if (mathFont) {
    255      numShift = mathFont->MathTable()->Constant(
    256          displayStyle ? gfxMathTable::FractionNumeratorDisplayStyleShiftUp
    257                       : gfxMathTable::FractionNumeratorShiftUp,
    258          oneDevPixel);
    259      denShift = mathFont->MathTable()->Constant(
    260          displayStyle ? gfxMathTable::FractionDenominatorDisplayStyleShiftDown
    261                       : gfxMathTable::FractionDenominatorShiftDown,
    262          oneDevPixel);
    263    }
    264  }
    265 
    266  if (0 == actualRuleThickness) {
    267    // Rule 15c, App. G, TeXbook
    268 
    269    // min clearance between numerator and denominator
    270    nscoord minClearance =
    271        displayStyle ? 7 * defaultRuleThickness : 3 * defaultRuleThickness;
    272    if (mathFont) {
    273      minClearance = mathFont->MathTable()->Constant(
    274          displayStyle ? gfxMathTable::StackDisplayStyleGapMin
    275                       : gfxMathTable::StackGapMin,
    276          oneDevPixel);
    277    }
    278 
    279    nscoord actualClearance = (numShift - bmNum.descent - numMargin.bottom) -
    280                              (bmDen.ascent + denMargin.top - denShift);
    281    // actualClearance should be >= minClearance
    282    if (actualClearance < minClearance) {
    283      nscoord halfGap = (minClearance - actualClearance) / 2;
    284      numShift += halfGap;
    285      denShift += halfGap;
    286    }
    287  } else {
    288    // Rule 15d, App. G, TeXbook
    289 
    290    // min clearance between numerator or denominator and middle of bar
    291 
    292    // TeX has a different interpretation of the thickness.
    293    // Try $a \above10pt b$ to see. Here is what TeX does:
    294    // minClearance = displayStyle ?
    295    //   3 * actualRuleThickness : actualRuleThickness;
    296 
    297    // we slightly depart from TeX here. We use the defaultRuleThickness
    298    // instead of the value coming from the linethickness attribute, i.e., we
    299    // recover what TeX does if the user hasn't set linethickness. But when
    300    // the linethickness is set, we avoid the wide gap problem.
    301    nscoord minClearanceNum = displayStyle ? 3 * defaultRuleThickness
    302                                           : defaultRuleThickness + onePixel;
    303    nscoord minClearanceDen = minClearanceNum;
    304    if (mathFont) {
    305      minClearanceNum = mathFont->MathTable()->Constant(
    306          displayStyle ? gfxMathTable::FractionNumDisplayStyleGapMin
    307                       : gfxMathTable::FractionNumeratorGapMin,
    308          oneDevPixel);
    309      minClearanceDen = mathFont->MathTable()->Constant(
    310          displayStyle ? gfxMathTable::FractionDenomDisplayStyleGapMin
    311                       : gfxMathTable::FractionDenominatorGapMin,
    312          oneDevPixel);
    313    }
    314 
    315    // adjust numShift to maintain minClearanceNum if needed
    316    nscoord actualClearanceNum = (numShift - bmNum.descent - numMargin.bottom) -
    317                                 (axisHeight + actualRuleThickness / 2);
    318    if (actualClearanceNum < minClearanceNum) {
    319      numShift += (minClearanceNum - actualClearanceNum);
    320    }
    321    // adjust denShift to maintain minClearanceDen if needed
    322    nscoord actualClearanceDen = (axisHeight - actualRuleThickness / 2) -
    323                                 (bmDen.ascent + denMargin.top - denShift);
    324    if (actualClearanceDen < minClearanceDen) {
    325      denShift += (minClearanceDen - actualClearanceDen);
    326    }
    327  }
    328 
    329  //////////////////
    330  // Place Children
    331 
    332  // XXX Need revisiting the width. TeX uses the exact width
    333  // e.g. in $$\huge\frac{\displaystyle\int}{i}$$
    334  nscoord width = std::max(bmNum.width + numMargin.LeftRight(),
    335                           bmDen.width + denMargin.LeftRight());
    336  nscoord dxNum =
    337      leftSpace + (width - sizeNum.Width() - numMargin.LeftRight()) / 2;
    338  nscoord dxDen =
    339      leftSpace + (width - sizeDen.Width() - denMargin.LeftRight()) / 2;
    340  width += leftSpace + rightSpace;
    341 
    342  mBoundingMetrics.rightBearing =
    343      std::max(dxNum + bmNum.rightBearing + numMargin.LeftRight(),
    344               dxDen + bmDen.rightBearing + denMargin.LeftRight());
    345  if (mBoundingMetrics.rightBearing < width - rightSpace) {
    346    mBoundingMetrics.rightBearing = width - rightSpace;
    347  }
    348  mBoundingMetrics.leftBearing =
    349      std::min(dxNum + bmNum.leftBearing, dxDen + bmDen.leftBearing);
    350  if (mBoundingMetrics.leftBearing > leftSpace) {
    351    mBoundingMetrics.leftBearing = leftSpace;
    352  }
    353  mBoundingMetrics.ascent = bmNum.ascent + numShift + numMargin.top;
    354  mBoundingMetrics.descent = bmDen.descent + denShift + denMargin.bottom;
    355  mBoundingMetrics.width = width;
    356 
    357  aDesiredSize.SetBlockStartAscent(numMargin.top + sizeNum.BlockStartAscent() +
    358                                   numShift);
    359  aDesiredSize.Height() = aDesiredSize.BlockStartAscent() + sizeDen.Height() +
    360                          denMargin.bottom - sizeDen.BlockStartAscent() +
    361                          denShift;
    362  aDesiredSize.Width() = mBoundingMetrics.width;
    363  aDesiredSize.mBoundingMetrics = mBoundingMetrics;
    364 
    365  // Apply width/height to math content box.
    366  auto sizes = GetWidthAndHeightForPlaceAdjustment(aFlags);
    367  auto shiftX = ApplyAdjustmentForWidthAndHeight(aFlags, sizes, aDesiredSize,
    368                                                 mBoundingMetrics);
    369  if (sizes.width) {
    370    // MathML Core says the math content box is horizontally centered
    371    // but the fraction bar still takes the full width of the content box.
    372    dxNum += shiftX;
    373    dxDen += shiftX;
    374    width = *sizes.width;
    375  }
    376 
    377  // Add padding+border.
    378  auto borderPadding = GetBorderPaddingForPlace(aFlags);
    379  InflateReflowAndBoundingMetrics(borderPadding, aDesiredSize,
    380                                  mBoundingMetrics);
    381  leftSpace += borderPadding.left;
    382  rightSpace += borderPadding.right;
    383  width += borderPadding.LeftRight();
    384  dxNum += borderPadding.left;
    385  dxDen += borderPadding.left;
    386 
    387  mReference.x = 0;
    388  mReference.y = aDesiredSize.BlockStartAscent();
    389 
    390  if (!aFlags.contains(PlaceFlag::MeasureOnly)) {
    391    nscoord dy;
    392    // place numerator
    393    dxNum += numMargin.left;
    394    dy = borderPadding.top + numMargin.top;
    395    FinishReflowChild(frameNum, presContext, sizeNum, nullptr, dxNum, dy,
    396                      ReflowChildFlags::Default);
    397    // place denominator
    398    dxDen += denMargin.left;
    399    dy =
    400        aDesiredSize.BlockStartAscent() + denShift - sizeDen.BlockStartAscent();
    401    FinishReflowChild(frameDen, presContext, sizeDen, nullptr, dxDen, dy,
    402                      ReflowChildFlags::Default);
    403    // place the fraction bar - dy is top of bar
    404    dy = aDesiredSize.BlockStartAscent() -
    405         (axisHeight + actualRuleThickness / 2);
    406    mLineRect.SetRect(leftSpace, dy, width - (leftSpace + rightSpace),
    407                      actualRuleThickness);
    408  }
    409 }