tor-browser

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

nsRubyFrame.cpp (20426B)


      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 /* rendering object for CSS "display: ruby" */
      8 
      9 #include "nsRubyFrame.h"
     10 
     11 #include "RubyUtils.h"
     12 #include "mozilla/ComputedStyle.h"
     13 #include "mozilla/Maybe.h"
     14 #include "mozilla/PresShell.h"
     15 #include "mozilla/StaticPrefs_layout.h"
     16 #include "mozilla/WritingModes.h"
     17 #include "nsContainerFrameInlines.h"
     18 #include "nsLayoutUtils.h"
     19 #include "nsLineLayout.h"
     20 #include "nsPresContext.h"
     21 #include "nsRubyBaseContainerFrame.h"
     22 #include "nsRubyTextContainerFrame.h"
     23 
     24 using namespace mozilla;
     25 
     26 //----------------------------------------------------------------------
     27 
     28 // Frame class boilerplate
     29 // =======================
     30 
     31 NS_QUERYFRAME_HEAD(nsRubyFrame)
     32  NS_QUERYFRAME_ENTRY(nsRubyFrame)
     33 NS_QUERYFRAME_TAIL_INHERITING(nsInlineFrame)
     34 
     35 NS_IMPL_FRAMEARENA_HELPERS(nsRubyFrame)
     36 
     37 nsContainerFrame* NS_NewRubyFrame(PresShell* aPresShell,
     38                                  ComputedStyle* aStyle) {
     39  return new (aPresShell) nsRubyFrame(aStyle, aPresShell->GetPresContext());
     40 }
     41 
     42 //----------------------------------------------------------------------
     43 
     44 // nsRubyFrame Method Implementations
     45 // ==================================
     46 
     47 #ifdef DEBUG_FRAME_DUMP
     48 nsresult nsRubyFrame::GetFrameName(nsAString& aResult) const {
     49  return MakeFrameName(u"Ruby"_ns, aResult);
     50 }
     51 #endif
     52 
     53 /* virtual */
     54 void nsRubyFrame::AddInlineMinISize(const IntrinsicSizeInput& aInput,
     55                                    InlineMinISizeData* aData) {
     56  auto handleChildren = [&](auto frame, auto data) {
     57    // Ruby frames shouldn't have percentage block sizes that require a
     58    // percentage basis for resolution.
     59    const IntrinsicSizeInput input(aInput.mContext, Nothing(), Nothing());
     60    for (RubySegmentEnumerator e(static_cast<nsRubyFrame*>(frame)); !e.AtEnd();
     61         e.Next()) {
     62      e.GetBaseContainer()->AddInlineMinISize(input, data);
     63    }
     64  };
     65  DoInlineIntrinsicISize(aData, handleChildren);
     66 }
     67 
     68 /* virtual */
     69 void nsRubyFrame::AddInlinePrefISize(const IntrinsicSizeInput& aInput,
     70                                     InlinePrefISizeData* aData) {
     71  auto handleChildren = [&](auto frame, auto data) {
     72    // Ruby frames shouldn't have percentage block sizes that require a
     73    // percentage basis for resolution.
     74    const IntrinsicSizeInput input(aInput.mContext, Nothing(), Nothing());
     75    for (RubySegmentEnumerator e(static_cast<nsRubyFrame*>(frame)); !e.AtEnd();
     76         e.Next()) {
     77      e.GetBaseContainer()->AddInlinePrefISize(input, data);
     78    }
     79  };
     80  DoInlineIntrinsicISize(aData, handleChildren);
     81  aData->mLineIsEmpty = false;
     82 }
     83 
     84 static nsRubyBaseContainerFrame* FindRubyBaseContainerAncestor(
     85    nsIFrame* aFrame) {
     86  for (nsIFrame* ancestor = aFrame->GetParent();
     87       ancestor && ancestor->IsLineParticipant();
     88       ancestor = ancestor->GetParent()) {
     89    if (ancestor->IsRubyBaseContainerFrame()) {
     90      return static_cast<nsRubyBaseContainerFrame*>(ancestor);
     91    }
     92  }
     93  return nullptr;
     94 }
     95 
     96 /* virtual */
     97 void nsRubyFrame::Reflow(nsPresContext* aPresContext,
     98                         ReflowOutput& aDesiredSize,
     99                         const ReflowInput& aReflowInput,
    100                         nsReflowStatus& aStatus) {
    101  MarkInReflow();
    102  DO_GLOBAL_REFLOW_COUNT("nsRubyFrame");
    103  MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
    104 
    105  if (!aReflowInput.mLineLayout) {
    106    NS_ASSERTION(aReflowInput.mLineLayout,
    107                 "No line layout provided to RubyFrame reflow method.");
    108    return;
    109  }
    110 
    111  // Grab overflow frames from prev-in-flow and its own.
    112  MoveInlineOverflowToChildList(aReflowInput.mLineLayout->LineContainerFrame());
    113 
    114  // Clear leadings and ruby-positioning metrics.
    115  mLeadings.Reset();
    116  mRubyMetrics = mozilla::RubyMetrics();
    117 
    118  // Begin the span for the ruby frame
    119  WritingMode frameWM = aReflowInput.GetWritingMode();
    120  WritingMode lineWM = aReflowInput.mLineLayout->GetWritingMode();
    121  LogicalMargin borderPadding =
    122      aReflowInput.ComputedLogicalBorderPadding(frameWM);
    123  nsLayoutUtils::SetBSizeFromFontMetrics(this, aDesiredSize, borderPadding,
    124                                         lineWM, frameWM);
    125 
    126  nscoord startEdge = 0;
    127  const bool boxDecorationBreakClone =
    128      StyleBorder()->mBoxDecorationBreak == StyleBoxDecorationBreak::Clone;
    129  if (boxDecorationBreakClone || !GetPrevContinuation()) {
    130    startEdge = borderPadding.IStart(frameWM);
    131  }
    132  NS_ASSERTION(aReflowInput.AvailableISize() != NS_UNCONSTRAINEDSIZE,
    133               "should no longer use available widths");
    134  nscoord endEdge = aReflowInput.AvailableISize() - borderPadding.IEnd(frameWM);
    135  aReflowInput.mLineLayout->BeginSpan(this, &aReflowInput, startEdge, endEdge,
    136                                      &mBaseline);
    137 
    138  for (RubySegmentEnumerator e(this); !e.AtEnd(); e.Next()) {
    139    ReflowSegment(aPresContext, aReflowInput, aDesiredSize.BlockStartAscent(),
    140                  aDesiredSize.BSize(lineWM), e.GetBaseContainer(), aStatus);
    141 
    142    if (aStatus.IsInlineBreak()) {
    143      // A break occurs when reflowing the segment.
    144      // Don't continue reflowing more segments.
    145      break;
    146    }
    147  }
    148 
    149  ContinuationTraversingState pullState(this);
    150  while (aStatus.IsEmpty()) {
    151    nsRubyBaseContainerFrame* baseContainer =
    152        PullOneSegment(aReflowInput.mLineLayout, pullState);
    153    if (!baseContainer) {
    154      // No more continuations after, finish now.
    155      break;
    156    }
    157    ReflowSegment(aPresContext, aReflowInput, aDesiredSize.BlockStartAscent(),
    158                  aDesiredSize.BSize(lineWM), baseContainer, aStatus);
    159  }
    160  // We never handle overflow in ruby.
    161  MOZ_ASSERT(!aStatus.IsOverflowIncomplete());
    162 
    163  aDesiredSize.ISize(lineWM) = aReflowInput.mLineLayout->EndSpan(this);
    164  if (boxDecorationBreakClone || !GetPrevContinuation()) {
    165    aDesiredSize.ISize(lineWM) += borderPadding.IStart(frameWM);
    166  }
    167  if (boxDecorationBreakClone || aStatus.IsComplete()) {
    168    aDesiredSize.ISize(lineWM) += borderPadding.IEnd(frameWM);
    169  }
    170 
    171  // Update descendant leadings of ancestor ruby base container.
    172  if (nsRubyBaseContainerFrame* rbc = FindRubyBaseContainerAncestor(this)) {
    173    rbc->UpdateDescendantLeadings(mLeadings);
    174  }
    175 
    176  ReflowAbsoluteFrames(aPresContext, aDesiredSize, aReflowInput, aStatus);
    177 }
    178 
    179 void nsRubyFrame::ReflowSegment(nsPresContext* aPresContext,
    180                                const ReflowInput& aReflowInput,
    181                                nscoord aBlockStartAscent, nscoord aBlockSize,
    182                                nsRubyBaseContainerFrame* aBaseContainer,
    183                                nsReflowStatus& aStatus) {
    184  WritingMode lineWM = aReflowInput.mLineLayout->GetWritingMode();
    185  LogicalSize availSize(lineWM, aReflowInput.AvailableISize(),
    186                        aReflowInput.AvailableBSize());
    187  NS_ASSERTION(!GetWritingMode().IsOrthogonalTo(lineWM),
    188               "Ruby frame writing-mode shouldn't be orthogonal to its line");
    189 
    190  AutoRubyTextContainerArray textContainers(aBaseContainer);
    191  const uint32_t rtcCount = textContainers.Length();
    192 
    193  ReflowOutput baseMetrics(aReflowInput);
    194  bool pushedFrame;
    195  aReflowInput.mLineLayout->ReflowFrame(aBaseContainer, aStatus, &baseMetrics,
    196                                        pushedFrame);
    197 
    198  if (aStatus.IsInlineBreakBefore()) {
    199    if (aBaseContainer != mFrames.FirstChild()) {
    200      // Some segments may have been reflowed before, hence it is not
    201      // a break-before for the ruby container.
    202      aStatus.Reset();
    203      aStatus.SetInlineLineBreakAfter();
    204      aStatus.SetIncomplete();
    205      PushChildrenToOverflow(aBaseContainer, aBaseContainer->GetPrevSibling());
    206      aReflowInput.mLineLayout->SetDirtyNextLine();
    207    }
    208    // This base container is not placed at all, we can skip all
    209    // text containers paired with it.
    210    return;
    211  }
    212  if (aStatus.IsIncomplete()) {
    213    // It always promise that if the status is incomplete, there is a
    214    // break occurs. Break before has been processed above. However,
    215    // it is possible that break after happens with the frame reflow
    216    // completed. It happens if there is a force break at the end.
    217    MOZ_ASSERT(aStatus.IsInlineBreakAfter());
    218    // Find the previous sibling which we will
    219    // insert new continuations after.
    220    nsIFrame* lastChild;
    221    if (rtcCount > 0) {
    222      lastChild = textContainers.LastElement();
    223    } else {
    224      lastChild = aBaseContainer;
    225    }
    226 
    227    // Create continuations for the base container
    228    nsIFrame* newBaseContainer = CreateNextInFlow(aBaseContainer);
    229    // newBaseContainer is null if there are existing next-in-flows.
    230    // We only need to move and push if there were not.
    231    if (newBaseContainer) {
    232      // Move the new frame after all the text containers
    233      mFrames.RemoveFrame(newBaseContainer);
    234      mFrames.InsertFrame(nullptr, lastChild, newBaseContainer);
    235 
    236      // Create continuations for text containers
    237      nsIFrame* newLastChild = newBaseContainer;
    238      for (uint32_t i = 0; i < rtcCount; i++) {
    239        nsIFrame* newTextContainer = CreateNextInFlow(textContainers[i]);
    240        MOZ_ASSERT(newTextContainer,
    241                   "Next-in-flow of rtc should not exist "
    242                   "if the corresponding rbc does not");
    243        mFrames.RemoveFrame(newTextContainer);
    244        mFrames.InsertFrame(nullptr, newLastChild, newTextContainer);
    245        newLastChild = newTextContainer;
    246      }
    247    }
    248    if (lastChild != mFrames.LastChild()) {
    249      // Always push the next frame after the last child in this segment.
    250      // It is possible that we pulled it back before our next-in-flow
    251      // drain our overflow.
    252      PushChildrenToOverflow(lastChild->GetNextSibling(), lastChild);
    253      aReflowInput.mLineLayout->SetDirtyNextLine();
    254    }
    255  } else if (rtcCount) {
    256    DestroyContext context(PresShell());
    257    // If the ruby base container is reflowed completely, the line
    258    // layout will remove the next-in-flows of that frame. But the
    259    // line layout is not aware of the ruby text containers, hence
    260    // it is necessary to remove them here.
    261    for (uint32_t i = 0; i < rtcCount; i++) {
    262      if (nsIFrame* nextRTC = textContainers[i]->GetNextInFlow()) {
    263        nextRTC->GetParent()->DeleteNextInFlowChild(context, nextRTC, true);
    264      }
    265    }
    266  }
    267 
    268  nscoord segmentISize = baseMetrics.ISize(lineWM);
    269  const nsSize dummyContainerSize;
    270  LogicalRect baseRect =
    271      aBaseContainer->GetLogicalRect(lineWM, dummyContainerSize);
    272  // We need to position our rtc frames on one side or the other of the
    273  // base container's rect, using a coordinate space that's relative to
    274  // the ruby frame. Right now, the base container's rect's block-axis
    275  // position is relative to the block container frame containing the
    276  // lines, so here we reset it to the different between the ascents of
    277  // the ruby container and the ruby base container, assuming they are
    278  // aligned with the baseline.
    279  // XXX We may need to add border/padding here. See bug 1055667.
    280  baseRect.BStart(lineWM) = aBlockStartAscent - baseMetrics.BlockStartAscent();
    281  // The rect for offsets of text containers.
    282  LogicalRect offsetRect = baseRect;
    283 
    284  // Should we do ruby positioning based on em-normalized ascent/descent?
    285  // This seems to be roughly what Chrome does, and with many fonts it gives
    286  // a better result (ruby closer to the base text) than using the font's
    287  // declared metrics, if its ascent/descent incorporate lots of extra space.
    288  bool normalizeRubyMetrics = aPresContext->NormalizeRubyMetrics();
    289  float rubyMetricsFactor =
    290      normalizeRubyMetrics ? aPresContext->RubyPositioningFactor() : 0.0f;
    291  mozilla::RubyMetrics rubyMetrics;
    292 
    293  if (normalizeRubyMetrics) {
    294    // Get base container's ascent & descent, normalized to 1em (scaled by the
    295    // ruby positioning factor) overall extent, and adjust offsetRect to match.
    296    rubyMetrics = aBaseContainer->RubyMetrics(rubyMetricsFactor);
    297    offsetRect.BStart(lineWM) +=
    298        baseMetrics.BlockStartAscent() - rubyMetrics.mAscent;
    299    offsetRect.BSize(lineWM) = rubyMetrics.mAscent + rubyMetrics.mDescent;
    300  } else {
    301    RubyBlockLeadings descLeadings = aBaseContainer->GetDescendantLeadings();
    302    offsetRect.BStart(lineWM) -= descLeadings.mStart;
    303    offsetRect.BSize(lineWM) += descLeadings.mStart + descLeadings.mEnd;
    304  }
    305 
    306  Maybe<LineRelativeDir> lastLineSide;
    307 
    308  // Keep track of any leading required on block-start and/or block-end sides
    309  // of the base frame to accommodate annotations that project beyond it.
    310  nscoord startLeading = 0, endLeading = 0;
    311 
    312  for (uint32_t i = 0; i < rtcCount; i++) {
    313    nsRubyTextContainerFrame* textContainer = textContainers[i];
    314    WritingMode rtcWM = textContainer->GetWritingMode();
    315    nsReflowStatus textReflowStatus;
    316    ReflowOutput textMetrics(aReflowInput);
    317    ReflowInput textReflowInput(aPresContext, aReflowInput, textContainer,
    318                                availSize.ConvertTo(rtcWM, lineWM));
    319    textContainer->Reflow(aPresContext, textMetrics, textReflowInput,
    320                          textReflowStatus);
    321    // Ruby text containers always return complete reflow status even when
    322    // they have continuations, because the breaking has already been
    323    // handled when reflowing the base containers.
    324    NS_ASSERTION(textReflowStatus.IsEmpty(),
    325                 "Ruby text container must not break itself inside");
    326 
    327    nscoord textEmHeight = 0;
    328    nscoord ascentDelta = 0;
    329    nscoord bStartMargin = 0;
    330    if (normalizeRubyMetrics) {
    331      auto [ascent, descent] = textContainer->RubyMetrics(rubyMetricsFactor);
    332      const auto* firstChild = textContainer->PrincipalChildList().FirstChild();
    333      textEmHeight = ascent + descent;
    334      nscoord textBlockStartAscent =
    335          firstChild && textMetrics.BlockStartAscent() ==
    336                            ReflowOutput::ASK_FOR_BASELINE
    337              ? firstChild->GetLogicalBaseline(lineWM)
    338              : textMetrics.BlockStartAscent();
    339      ascentDelta = textBlockStartAscent - ascent;
    340      bStartMargin =
    341          firstChild ? firstChild->GetLogicalUsedMargin(lineWM).BStart(lineWM)
    342                     : 0;
    343    }
    344 
    345    const LogicalSize size = textMetrics.Size(lineWM);
    346    textContainer->SetSize(lineWM, size);
    347 
    348    nscoord reservedISize = RubyUtils::GetReservedISize(textContainer);
    349    segmentISize = std::max(segmentISize, size.ISize(lineWM) + reservedISize);
    350 
    351    Maybe<LineRelativeDir> lineSide;
    352    switch (textContainer->StyleText()->mRubyPosition) {
    353      case StyleRubyPosition::Over:
    354        lineSide.emplace(LineRelativeDir::Over);
    355        break;
    356      case StyleRubyPosition::Under:
    357        lineSide.emplace(LineRelativeDir::Under);
    358        break;
    359      case StyleRubyPosition::AlternateOver:
    360        if (lastLineSide.isSome() &&
    361            lastLineSide.value() == LineRelativeDir::Over) {
    362          lineSide.emplace(LineRelativeDir::Under);
    363        } else {
    364          lineSide.emplace(LineRelativeDir::Over);
    365        }
    366        break;
    367      case StyleRubyPosition::AlternateUnder:
    368        if (lastLineSide.isSome() &&
    369            lastLineSide.value() == LineRelativeDir::Under) {
    370          lineSide.emplace(LineRelativeDir::Over);
    371        } else {
    372          lineSide.emplace(LineRelativeDir::Under);
    373        }
    374        break;
    375      default:
    376        // XXX inter-character support in bug 1055672
    377        MOZ_ASSERT_UNREACHABLE("Unsupported ruby-position");
    378    }
    379    lastLineSide = lineSide;
    380 
    381    LogicalPoint position(lineWM);
    382    if (lineSide.isSome()) {
    383      LogicalSide logicalSide =
    384          lineWM.LogicalSideForLineRelativeDir(lineSide.value());
    385      if (StaticPrefs::layout_css_ruby_intercharacter_enabled() &&
    386          rtcWM.IsVerticalRL() &&
    387          lineWM.GetInlineDir() == WritingMode::InlineDir::LTR) {
    388        // Inter-character ruby annotations are only supported for vertical-rl
    389        // in ltr horizontal writing. Fall back to non-inter-character behavior
    390        // otherwise.
    391        LogicalPoint offset(
    392            lineWM, offsetRect.ISize(lineWM),
    393            offsetRect.BSize(lineWM) > size.BSize(lineWM)
    394                ? (offsetRect.BSize(lineWM) - size.BSize(lineWM)) / 2
    395                : 0);
    396        position = offsetRect.Origin(lineWM) + offset;
    397        aReflowInput.mLineLayout->AdvanceICoord(size.ISize(lineWM));
    398      } else if (logicalSide == LogicalSide::BStart) {
    399        if (normalizeRubyMetrics) {
    400          offsetRect.BStart(lineWM) -= textEmHeight;
    401          offsetRect.BSize(lineWM) += textEmHeight;
    402          position.I(lineWM) = offsetRect.IStart(lineWM);
    403          position.B(lineWM) = offsetRect.BStart(lineWM) - ascentDelta;
    404          rubyMetrics.mAscent += textEmHeight;
    405        } else {
    406          offsetRect.BStart(lineWM) -= size.BSize(lineWM);
    407          offsetRect.BSize(lineWM) += size.BSize(lineWM);
    408          position = offsetRect.Origin(lineWM);
    409        }
    410        // Record leading that will be needed on the block-start side to
    411        // accommodate the ruby text.
    412        startLeading = -position.B(lineWM);
    413      } else if (logicalSide == LogicalSide::BEnd) {
    414        if (normalizeRubyMetrics) {
    415          position.I(lineWM) = offsetRect.IStart(lineWM);
    416          position.B(lineWM) =
    417              offsetRect.BEnd(lineWM) - ascentDelta - bStartMargin;
    418          offsetRect.BSize(lineWM) += textEmHeight;
    419          rubyMetrics.mDescent += textEmHeight;
    420        } else {
    421          position = offsetRect.Origin(lineWM) +
    422                     LogicalPoint(lineWM, 0, offsetRect.BSize(lineWM));
    423          offsetRect.BSize(lineWM) += size.BSize(lineWM);
    424        }
    425        // Record leading that will be needed on the block-end side to
    426        // accommodate the ruby text.
    427        endLeading = position.B(lineWM) + size.BSize(lineWM) - aBlockSize;
    428      } else {
    429        MOZ_ASSERT_UNREACHABLE("???");
    430      }
    431    }
    432    // Using a dummy container-size here, so child positioning may not be
    433    // correct. We will fix it in nsLineLayout after the whole line is
    434    // reflowed.
    435    FinishReflowChild(textContainer, aPresContext, textMetrics,
    436                      &textReflowInput, lineWM, position, dummyContainerSize,
    437                      ReflowChildFlags::Default);
    438  }
    439  MOZ_ASSERT(baseRect.ISize(lineWM) == offsetRect.ISize(lineWM),
    440             "Annotations should only be placed on the block directions");
    441 
    442  nscoord deltaISize = segmentISize - baseMetrics.ISize(lineWM);
    443  if (deltaISize <= 0) {
    444    RubyUtils::ClearReservedISize(aBaseContainer);
    445  } else {
    446    RubyUtils::SetReservedISize(aBaseContainer, deltaISize);
    447    aReflowInput.mLineLayout->AdvanceICoord(deltaISize);
    448  }
    449 
    450  // Record leadings needed for the block container.
    451  // The leadings are the difference between the rect of this ruby container,
    452  // which has block start zero and block size aBlockSize, and the block-start/
    453  // block-end edges of the outermost annotations.
    454  // We clamp the values here because if there was significant leading built
    455  // into the font's original metrics, the annotations may be entirely within
    456  // this space, but we don't want to introduce negative leading.
    457  mLeadings.Update(std::max(0, startLeading), std::max(0, endLeading));
    458 
    459  // Record metrics to be used if any further annotations are added.
    460  mRubyMetrics.CombineWith(rubyMetrics);
    461 }
    462 
    463 nsRubyBaseContainerFrame* nsRubyFrame::PullOneSegment(
    464    const nsLineLayout* aLineLayout, ContinuationTraversingState& aState) {
    465  // Pull a ruby base container
    466  nsIFrame* baseFrame = GetNextInFlowChild(aState);
    467  if (!baseFrame) {
    468    return nullptr;
    469  }
    470  MOZ_ASSERT(baseFrame->IsRubyBaseContainerFrame());
    471 
    472  // Get the float containing block of the base frame before we pull it.
    473  nsBlockFrame* oldFloatCB = nsLayoutUtils::GetFloatContainingBlock(baseFrame);
    474  PullNextInFlowChild(aState);
    475 
    476  // Pull all ruby text containers following the base container
    477  nsIFrame* nextFrame;
    478  while ((nextFrame = GetNextInFlowChild(aState)) != nullptr &&
    479         nextFrame->IsRubyTextContainerFrame()) {
    480    PullNextInFlowChild(aState);
    481  }
    482 
    483  if (nsBlockFrame* newFloatCB =
    484          do_QueryFrame(aLineLayout->LineContainerFrame())) {
    485    if (oldFloatCB && oldFloatCB != newFloatCB) {
    486      newFloatCB->ReparentFloats(baseFrame, oldFloatCB, true);
    487    }
    488  }
    489 
    490  return static_cast<nsRubyBaseContainerFrame*>(baseFrame);
    491 }