Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/layout/generic/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 414 kB image not shown  

Quelle  nsTextFrame.cpp   Sprache: C

 
/* -*- 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/. */


/* rendering object for textual content of elements */

#include "nsTextFrame.h"

#include "gfx2DGlue.h"

#include "gfxUtils.h"
#include "mozilla/Attributes.h"
#include "mozilla/CaretAssociationHint.h"
#include "mozilla/ComputedStyle.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/Likely.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/PresShell.h"
#include "mozilla/StaticPrefs_layout.h"
#include "mozilla/StaticPresData.h"
#include "mozilla/SVGTextFrame.h"
#include "mozilla/TextEditor.h"
#include "mozilla/TextEvents.h"
#include "mozilla/BinarySearch.h"
#include "mozilla/IntegerRange.h"
#include "mozilla/Unused.h"
#include "mozilla/PodOperations.h"
#include "mozilla/dom/PerformanceMainThread.h"

#include "nsCOMPtr.h"
#include "nsBlockFrame.h"
#include "nsFontMetrics.h"
#include "nsSplittableFrame.h"
#include "nsLineLayout.h"
#include "nsString.h"
#include "nsUnicharUtils.h"
#include "nsPresContext.h"
#include "nsIContent.h"
#include "nsStyleConsts.h"
#include "nsStyleStruct.h"
#include "nsStyleStructInlines.h"
#include "nsCoord.h"
#include "gfxContext.h"
#include "nsTArray.h"
#include "nsCSSPseudoElements.h"
#include "nsCSSFrameConstructor.h"
#include "nsCompatibility.h"
#include "nsCSSColorUtils.h"
#include "nsLayoutUtils.h"
#include "nsDisplayList.h"
#include "nsIFrame.h"
#include "nsIMathMLFrame.h"
#include "nsFirstLetterFrame.h"
#include "nsPlaceholderFrame.h"
#include "nsTextFrameUtils.h"
#include "nsTextPaintStyle.h"
#include "nsTextRunTransformations.h"
#include "MathMLTextRunFactory.h"
#include "nsUnicodeProperties.h"
#include "nsStyleUtil.h"
#include "nsRubyFrame.h"
#include "PresShellInlines.h"
#include "TextDrawTarget.h"

#include "nsTextFragment.h"
#include "nsGkAtoms.h"
#include "nsFrameSelection.h"
#include "nsRange.h"
#include "nsCSSRendering.h"
#include "nsContentUtils.h"
#include "nsLineBreaker.h"
#include "nsIFrameInlines.h"
#include "mozilla/intl/Bidi.h"
#include "mozilla/intl/Segmenter.h"
#include "mozilla/intl/UnicodeProperties.h"
#include "mozilla/ServoStyleSet.h"

#include <algorithm>
#include <limits>
#include <type_traits>
#ifdef ACCESSIBILITY
#  include "nsAccessibilityService.h"
#endif

#include "nsPrintfCString.h"

#include "mozilla/gfx/DrawTargetRecording.h"

#include "mozilla/UniquePtr.h"
#include "mozilla/dom/Element.h"
#include "mozilla/LookAndFeel.h"
#include "mozilla/ProfilerLabels.h"

#ifdef DEBUG
#  undef NOISY_REFLOW
#  undef NOISY_TRIM
#else
#  undef NOISY_REFLOW
#  undef NOISY_TRIM
#endif

#ifdef DrawText
#  undef DrawText
#endif

using namespace mozilla;
using namespace mozilla::dom;
using namespace mozilla::gfx;

typedef mozilla::layout::TextDrawTarget TextDrawTarget;

static bool NeedsToMaskPassword(nsTextFrame* aFrame) {
  MOZ_ASSERT(aFrame);
  MOZ_ASSERT(aFrame->GetContent());
  if (!aFrame->GetContent()->HasFlag(NS_MAYBE_MASKED)) {
    return false;
  }
  nsIFrame* frame =
      nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::TextInput);
  MOZ_ASSERT(frame, "How do we have a masked text node without a text input?");
  return !frame || !frame->GetContent()->AsElement()->State().HasState(
                       ElementState::REVEALED);
}

struct TabWidth {
  TabWidth(uint32_t aOffset, uint32_t aWidth)
      : mOffset(aOffset), mWidth(float(aWidth)) {}

  uint32_t mOffset;  // DOM offset relative to the current frame's offset.
  float mWidth;      // extra space to be added at this position (in app units)
};

struct nsTextFrame::TabWidthStore {
  explicit TabWidthStore(int32_t aValidForContentOffset)
      : mLimit(0), mValidForContentOffset(aValidForContentOffset) {}

  // Apply tab widths to the aSpacing array, which corresponds to characters
  // beginning at aOffset and has length aLength. (Width records outside this
  // range will be ignored.)
  void ApplySpacing(gfxTextRun::PropertyProvider::Spacing* aSpacing,
                    uint32_t aOffset, uint32_t aLength);

  // Offset up to which tabs have been measured; positions beyond this have not
  // been calculated yet but may be appended if needed later.  It's a DOM
  // offset relative to the current frame's offset.
  uint32_t mLimit;

  // Need to recalc tab offsets if frame content offset differs from this.
  int32_t mValidForContentOffset;

  // A TabWidth record for each tab character measured so far.
  nsTArray<TabWidth> mWidths;
};

namespace {

struct TabwidthAdaptor {
  const nsTArray<TabWidth>& mWidths;
  explicit TabwidthAdaptor(const nsTArray<TabWidth>& aWidths)
      : mWidths(aWidths) {}
  uint32_t operator[](size_t aIdx) const { return mWidths[aIdx].mOffset; }
};

}  // namespace

void nsTextFrame::TabWidthStore::ApplySpacing(
    gfxTextRun::PropertyProvider::Spacing* aSpacing, uint32_t aOffset,
    uint32_t aLength) {
  size_t i = 0;
  const size_t len = mWidths.Length();

  // If aOffset is non-zero, do a binary search to find where to start
  // processing the tab widths, in case the list is really long. (See bug
  // 953247.)
  // We need to start from the first entry where mOffset >= aOffset.
  if (aOffset > 0) {
    mozilla::BinarySearch(TabwidthAdaptor(mWidths), 0, len, aOffset, &i);
  }

  uint32_t limit = aOffset + aLength;
  while (i < len) {
    const TabWidth& tw = mWidths[i];
    if (tw.mOffset >= limit) {
      break;
    }
    aSpacing[tw.mOffset - aOffset].mAfter += tw.mWidth;
    i++;
  }
}

NS_DECLARE_FRAME_PROPERTY_DELETABLE(TabWidthProperty,
                                    nsTextFrame::TabWidthStore)

NS_DECLARE_FRAME_PROPERTY_WITHOUT_DTOR(OffsetToFrameProperty, nsTextFrame)

NS_DECLARE_FRAME_PROPERTY_RELEASABLE(UninflatedTextRunProperty, gfxTextRun)

NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(FontSizeInflationProperty, float)

NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(HangableWhitespaceProperty, nscoord)
NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(TrimmableWhitespaceProperty,
                                      gfxTextRun::TrimmableWS)

struct nsTextFrame::PaintTextSelectionParams : nsTextFrame::PaintTextParams {
  Point textBaselinePt;
  PropertyProvider* provider = nullptr;
  Range contentRange;
  nsTextPaintStyle* textPaintStyle = nullptr;
  Range glyphRange;
  explicit PaintTextSelectionParams(const PaintTextParams& aParams)
      : PaintTextParams(aParams) {}
};

struct nsTextFrame::DrawTextRunParams {
  gfxContext* context;
  mozilla::gfx::PaletteCache& paletteCache;
  PropertyProvider* provider = nullptr;
  gfxFloat* advanceWidth = nullptr;
  mozilla::SVGContextPaint* contextPaint = nullptr;
  DrawPathCallbacks* callbacks = nullptr;
  nscolor textColor = NS_RGBA(0, 0, 0, 0);
  nscolor textStrokeColor = NS_RGBA(0, 0, 0, 0);
  nsAtom* fontPalette = nullptr;
  float textStrokeWidth = 0.0f;
  bool drawSoftHyphen = false;
  bool hasTextShadow = false;
  bool paintingShadows = false;
  DrawTextRunParams(gfxContext* aContext,
                    mozilla::gfx::PaletteCache& aPaletteCache)
      : context(aContext), paletteCache(aPaletteCache) {}
};

struct nsTextFrame::ClipEdges {
  ClipEdges(const nsIFrame* aFrame, const nsPoint& aToReferenceFrame,
            nscoord aVisIStartEdge, nscoord aVisIEndEdge) {
    nsRect r = aFrame->ScrollableOverflowRect() + aToReferenceFrame;
    if (aFrame->GetWritingMode().IsVertical()) {
      mVisIStart = aVisIStartEdge > 0 ? r.y + aVisIStartEdge : nscoord_MIN;
      mVisIEnd = aVisIEndEdge > 0
                     ? std::max(r.YMost() - aVisIEndEdge, mVisIStart)
                     : nscoord_MAX;
    } else {
      mVisIStart = aVisIStartEdge > 0 ? r.x + aVisIStartEdge : nscoord_MIN;
      mVisIEnd = aVisIEndEdge > 0
                     ? std::max(r.XMost() - aVisIEndEdge, mVisIStart)
                     : nscoord_MAX;
    }
  }

  void Intersect(nscoord* aVisIStart, nscoord* aVisISize) const {
    nscoord end = *aVisIStart + *aVisISize;
    *aVisIStart = std::max(*aVisIStart, mVisIStart);
    *aVisISize = std::max(std::min(end, mVisIEnd) - *aVisIStart, 0);
  }

  nscoord mVisIStart;
  nscoord mVisIEnd;
};

struct nsTextFrame::DrawTextParams : nsTextFrame::DrawTextRunParams {
  Point framePt;
  LayoutDeviceRect dirtyRect;
  const nsTextPaintStyle* textStyle = nullptr;
  const ClipEdges* clipEdges = nullptr;
  const nscolor* decorationOverrideColor = nullptr;
  Range glyphRange;
  DrawTextParams(gfxContext* aContext,
                 mozilla::gfx::PaletteCache& aPaletteCache)
      : DrawTextRunParams(aContext, aPaletteCache) {}
};

struct nsTextFrame::PaintShadowParams {
  gfxTextRun::Range range;
  LayoutDeviceRect dirtyRect;
  Point framePt;
  Point textBaselinePt;
  gfxContext* context;
  DrawPathCallbacks* callbacks = nullptr;
  nscolor foregroundColor = NS_RGBA(0, 0, 0, 0);
  const ClipEdges* clipEdges = nullptr;
  PropertyProvider* provider = nullptr;
  nscoord leftSideOffset = 0;
  explicit PaintShadowParams(const PaintTextParams& aParams)
      : dirtyRect(aParams.dirtyRect),
        framePt(aParams.framePt),
        context(aParams.context) {}
};

/**
 * A glyph observer for the change of a font glyph in a text run.
 *
 * This is stored in {Simple, Complex}TextRunUserData.
 */

class GlyphObserver final : public gfxFont::GlyphChangeObserver {
 public:
  GlyphObserver(gfxFont* aFont, gfxTextRun* aTextRun)
      : gfxFont::GlyphChangeObserver(aFont), mTextRun(aTextRun) {
    MOZ_ASSERT(aTextRun->GetUserData());
  }
  void NotifyGlyphsChanged() override;

 private:
  gfxTextRun* mTextRun;
};

static const nsFrameState TEXT_REFLOW_FLAGS =
    TEXT_FIRST_LETTER | TEXT_START_OF_LINE | TEXT_END_OF_LINE |
    TEXT_HYPHEN_BREAK | TEXT_TRIMMED_TRAILING_WHITESPACE |
    TEXT_JUSTIFICATION_ENABLED | TEXT_HAS_NONCOLLAPSED_CHARACTERS |
    TEXT_SELECTION_UNDERLINE_OVERFLOWED | TEXT_NO_RENDERED_GLYPHS;

static const nsFrameState TEXT_WHITESPACE_FLAGS =
    TEXT_IS_ONLY_WHITESPACE | TEXT_ISNOT_ONLY_WHITESPACE;

/*
 * Some general notes
 *
 * Text frames delegate work to gfxTextRun objects. The gfxTextRun object
 * transforms text to positioned glyphs. It can report the geometry of the
 * glyphs and paint them. Text frames configure gfxTextRuns by providing text,
 * spacing, language, and other information.
 *
 * A gfxTextRun can cover more than one DOM text node. This is necessary to
 * get kerning, ligatures and shaping for text that spans multiple text nodes
 * but is all the same font.
 *
 * The userdata for a gfxTextRun object can be:
 *
 *   - A nsTextFrame* in the case a text run maps to only one flow. In this
 *   case, the textrun's user data pointer is a pointer to mStartFrame for that
 *   flow, mDOMOffsetToBeforeTransformOffset is zero, and mContentLength is the
 *   length of the text node.
 *
 *   - A SimpleTextRunUserData in the case a text run maps to one flow, but we
 *   still have to keep a list of glyph observers.
 *
 *   - A ComplexTextRunUserData in the case a text run maps to multiple flows,
 *   but we need to keep a list of glyph observers.
 *
 *   - A TextRunUserData in the case a text run maps multiple flows, but it
 *   doesn't have any glyph observer for changes in SVG fonts.
 *
 * You can differentiate between the four different cases with the
 * IsSimpleFlow and MightHaveGlyphChanges flags.
 *
 * We go to considerable effort to make sure things work even if in-flow
 * siblings have different ComputedStyles (i.e., first-letter and first-line).
 *
 * Our convention is that unsigned integer character offsets are offsets into
 * the transformed string. Signed integer character offsets are offsets into
 * the DOM string.
 *
 * XXX currently we don't handle hyphenated breaks between text frames where the
 * hyphen occurs at the end of the first text frame, e.g.
 *   <b>Kit­</b>ty
 */


/**
 * This is our user data for the textrun, when textRun->GetFlags2() has
 * IsSimpleFlow set, and also MightHaveGlyphChanges.
 *
 * This allows having an array of observers if there are fonts whose glyphs
 * might change, but also avoid allocation in the simple case that there aren't.
 */

struct SimpleTextRunUserData {
  nsTArray<UniquePtr<GlyphObserver>> mGlyphObservers;
  nsTextFrame* mFrame;
  explicit SimpleTextRunUserData(nsTextFrame* aFrame) : mFrame(aFrame) {}
};

/**
 * We use an array of these objects to record which text frames
 * are associated with the textrun. mStartFrame is the start of a list of
 * text frames. Some sequence of its continuations are covered by the textrun.
 * A content textnode can have at most one TextRunMappedFlow associated with it
 * for a given textrun.
 *
 * mDOMOffsetToBeforeTransformOffset is added to DOM offsets for those frames to
 * obtain the offset into the before-transformation text of the textrun. It can
 * be positive (when a text node starts in the middle of a text run) or negative
 * (when a text run starts in the middle of a text node). Of course it can also
 * be zero.
 */

struct TextRunMappedFlow {
  nsTextFrame* mStartFrame;
  int32_t mDOMOffsetToBeforeTransformOffset;
  // The text mapped starts at mStartFrame->GetContentOffset() and is this long
  uint32_t mContentLength;
};

/**
 * This is the type in the gfxTextRun's userdata field in the common case that
 * the text run maps to multiple flows, but no fonts have been found with
 * animatable glyphs.
 *
 * This way, we avoid allocating and constructing the extra nsTArray.
 */

struct TextRunUserData {
#ifdef DEBUG
  TextRunMappedFlow* mMappedFlows;
#endif
  uint32_t mMappedFlowCount;
  uint32_t mLastFlowIndex;
};

/**
 * This is our user data for the textrun, when textRun->GetFlags2() does not
 * have IsSimpleFlow set and has the MightHaveGlyphChanges flag.
 */

struct ComplexTextRunUserData : public TextRunUserData {
  nsTArray<UniquePtr<GlyphObserver>> mGlyphObservers;
};

static TextRunUserData* CreateUserData(uint32_t aMappedFlowCount) {
  TextRunUserData* data = static_cast<TextRunUserData*>(moz_xmalloc(
      sizeof(TextRunUserData) + aMappedFlowCount * sizeof(TextRunMappedFlow)));
#ifdef DEBUG
  data->mMappedFlows = reinterpret_cast<TextRunMappedFlow*>(data + 1);
#endif
  data->mMappedFlowCount = aMappedFlowCount;
  data->mLastFlowIndex = 0;
  return data;
}

static void DestroyUserData(TextRunUserData* aUserData) {
  if (aUserData) {
    free(aUserData);
  }
}

static ComplexTextRunUserData* CreateComplexUserData(
    uint32_t aMappedFlowCount) {
  ComplexTextRunUserData* data = static_cast<ComplexTextRunUserData*>(
      moz_xmalloc(sizeof(ComplexTextRunUserData) +
                  aMappedFlowCount * sizeof(TextRunMappedFlow)));
  new (data) ComplexTextRunUserData();
#ifdef DEBUG
  data->mMappedFlows = reinterpret_cast<TextRunMappedFlow*>(data + 1);
#endif
  data->mMappedFlowCount = aMappedFlowCount;
  data->mLastFlowIndex = 0;
  return data;
}

static void DestroyComplexUserData(ComplexTextRunUserData* aUserData) {
  if (aUserData) {
    aUserData->~ComplexTextRunUserData();
    free(aUserData);
  }
}

static void DestroyTextRunUserData(gfxTextRun* aTextRun) {
  MOZ_ASSERT(aTextRun->GetUserData());
  if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
    if (aTextRun->GetFlags2() &
        nsTextFrameUtils::Flags::MightHaveGlyphChanges) {
      delete static_cast<SimpleTextRunUserData*>(aTextRun->GetUserData());
    }
  } else {
    if (aTextRun->GetFlags2() &
        nsTextFrameUtils::Flags::MightHaveGlyphChanges) {
      DestroyComplexUserData(
          static_cast<ComplexTextRunUserData*>(aTextRun->GetUserData()));
    } else {
      DestroyUserData(static_cast<TextRunUserData*>(aTextRun->GetUserData()));
    }
  }
  aTextRun->ClearFlagBits(nsTextFrameUtils::Flags::MightHaveGlyphChanges);
  aTextRun->SetUserData(nullptr);
}

static TextRunMappedFlow* GetMappedFlows(const gfxTextRun* aTextRun) {
  MOZ_ASSERT(aTextRun->GetUserData(), "UserData must exist.");
  MOZ_ASSERT(!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow),
             "The method should not be called for simple flows.");
  TextRunMappedFlow* flows;
  if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::MightHaveGlyphChanges) {
    flows = reinterpret_cast<TextRunMappedFlow*>(
        static_cast<ComplexTextRunUserData*>(aTextRun->GetUserData()) + 1);
  } else {
    flows = reinterpret_cast<TextRunMappedFlow*>(
        static_cast<TextRunUserData*>(aTextRun->GetUserData()) + 1);
  }
  MOZ_ASSERT(
      static_cast<TextRunUserData*>(aTextRun->GetUserData())->mMappedFlows ==
          flows,
      "GetMappedFlows should return the same pointer as mMappedFlows.");
  return flows;
}

/**
 * These are utility functions just for helping with the complexity related with
 * the text runs user data.
 */

static nsTextFrame* GetFrameForSimpleFlow(const gfxTextRun* aTextRun) {
  MOZ_ASSERT(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow,
             "Not so simple flow?");
  if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::MightHaveGlyphChanges) {
    return static_cast<SimpleTextRunUserData*>(aTextRun->GetUserData())->mFrame;
  }

  return static_cast<nsTextFrame*>(aTextRun->GetUserData());
}

/**
 * Remove |aTextRun| from the frame continuation chain starting at
 * |aStartContinuation| if non-null, otherwise starting at |aFrame|.
 * Unmark |aFrame| as a text run owner if it's the frame we start at.
 * Return true if |aStartContinuation| is non-null and was found
 * in the next-continuation chain of |aFrame|.
 */

static bool ClearAllTextRunReferences(nsTextFrame* aFrame, gfxTextRun* aTextRun,
                                      nsTextFrame* aStartContinuation,
                                      nsFrameState aWhichTextRunState) {
  MOZ_ASSERT(aFrame, "null frame");
  MOZ_ASSERT(!aStartContinuation ||
                 (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) ||
                  aStartContinuation->GetTextRun(nsTextFrame::eInflated) ==
                      aTextRun) ||
                 (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) ||
                  aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) ==
                      aTextRun),
             "wrong aStartContinuation for this text run");

  if (!aStartContinuation || aStartContinuation == aFrame) {
    aFrame->RemoveStateBits(aWhichTextRunState);
  } else {
    do {
      NS_ASSERTION(aFrame->IsTextFrame(), "Bad frame");
      aFrame = aFrame->GetNextContinuation();
    } while (aFrame && aFrame != aStartContinuation);
  }
  bool found = aStartContinuation == aFrame;
  while (aFrame) {
    NS_ASSERTION(aFrame->IsTextFrame(), "Bad frame");
    if (!aFrame->RemoveTextRun(aTextRun)) {
      break;
    }
    aFrame = aFrame->GetNextContinuation();
  }

  MOZ_ASSERT(!found || aStartContinuation, "how did we find null?");
  return found;
}

/**
 * Kill all references to |aTextRun| starting at |aStartContinuation|.
 * It could be referenced by any of its owners, and all their in-flows.
 * If |aStartContinuation| is null then process all userdata frames
 * and their continuations.
 * @note the caller is expected to take care of possibly destroying the
 * text run if all userdata frames were reset (userdata is deallocated
 * by this function though). The caller can detect this has occured by
 * checking |aTextRun->GetUserData() == nullptr|.
 */

static void UnhookTextRunFromFrames(gfxTextRun* aTextRun,
                                    nsTextFrame* aStartContinuation) {
  if (!aTextRun->GetUserData()) {
    return;
  }

  if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
    nsTextFrame* userDataFrame = GetFrameForSimpleFlow(aTextRun);
    nsFrameState whichTextRunState =
        userDataFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun
            ? TEXT_IN_TEXTRUN_USER_DATA
            : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA;
    DebugOnly<bool> found = ClearAllTextRunReferences(
        userDataFrame, aTextRun, aStartContinuation, whichTextRunState);
    NS_ASSERTION(!aStartContinuation || found,
                 "aStartContinuation wasn't found in simple flow text run");
    if (!userDataFrame->HasAnyStateBits(whichTextRunState)) {
      DestroyTextRunUserData(aTextRun);
    }
  } else {
    auto userData = static_cast<TextRunUserData*>(aTextRun->GetUserData());
    TextRunMappedFlow* userMappedFlows = GetMappedFlows(aTextRun);
    int32_t destroyFromIndex = aStartContinuation ? -1 : 0;
    for (uint32_t i = 0; i < userData->mMappedFlowCount; ++i) {
      nsTextFrame* userDataFrame = userMappedFlows[i].mStartFrame;
      nsFrameState whichTextRunState =
          userDataFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun
              ? TEXT_IN_TEXTRUN_USER_DATA
              : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA;
      bool found = ClearAllTextRunReferences(
          userDataFrame, aTextRun, aStartContinuation, whichTextRunState);
      if (found) {
        if (userDataFrame->HasAnyStateBits(whichTextRunState)) {
          destroyFromIndex = i + 1;
        } else {
          destroyFromIndex = i;
        }
        aStartContinuation = nullptr;
      }
    }
    NS_ASSERTION(destroyFromIndex >= 0,
                 "aStartContinuation wasn't found in multi flow text run");
    if (destroyFromIndex == 0) {
      DestroyTextRunUserData(aTextRun);
    } else {
      userData->mMappedFlowCount = uint32_t(destroyFromIndex);
      if (userData->mLastFlowIndex >= uint32_t(destroyFromIndex)) {
        userData->mLastFlowIndex = uint32_t(destroyFromIndex) - 1;
      }
    }
  }
}

static void InvalidateFrameDueToGlyphsChanged(nsIFrame* aFrame) {
  MOZ_ASSERT(aFrame);

  PresShell* presShell = aFrame->PresShell();
  for (nsIFrame* f = aFrame; f;
       f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) {
    f->InvalidateFrame();

    // If this is a non-display text frame within SVG <text>, we need
    // to reflow the SVGTextFrame. (This is similar to reflowing the
    // SVGTextFrame in response to style changes, in
    // SVGTextFrame::DidSetComputedStyle.)
    if (f->IsInSVGTextSubtree() && f->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
      auto* svgTextFrame = static_cast<SVGTextFrame*>(
          nsLayoutUtils::GetClosestFrameOfType(f, LayoutFrameType::SVGText));
      svgTextFrame->ScheduleReflowSVGNonDisplayText(IntrinsicDirty::None);
    } else {
      // Theoretically we could just update overflow areas, perhaps using
      // OverflowChangedTracker, but that would do a bunch of work eagerly that
      // we should probably do lazily here since there could be a lot
      // of text frames affected and we'd like to coalesce the work. So that's
      // not easy to do well.
      presShell->FrameNeedsReflow(f, IntrinsicDirty::None, NS_FRAME_IS_DIRTY);
    }
  }
}

void GlyphObserver::NotifyGlyphsChanged() {
  if (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
    InvalidateFrameDueToGlyphsChanged(GetFrameForSimpleFlow(mTextRun));
    return;
  }

  auto data = static_cast<TextRunUserData*>(mTextRun->GetUserData());
  TextRunMappedFlow* userMappedFlows = GetMappedFlows(mTextRun);
  for (uint32_t i = 0; i < data->mMappedFlowCount; ++i) {
    InvalidateFrameDueToGlyphsChanged(userMappedFlows[i].mStartFrame);
  }
}

int32_t nsTextFrame::GetContentEnd() const {
  nsTextFrame* next = GetNextContinuation();
  // In case of allocation failure when setting/modifying the textfragment,
  // it's possible our text might be missing. So we check the fragment length,
  // in addition to the offset of the next continuation (if any).
  int32_t fragLen = TextFragment()->GetLength();
  return next ? std::min(fragLen, next->GetContentOffset()) : fragLen;
}

struct FlowLengthProperty {
  int32_t mStartOffset;
  // The offset of the next fixed continuation after mStartOffset, or
  // of the end of the text if there is none
  int32_t mEndFlowOffset;
};

int32_t nsTextFrame::GetInFlowContentLength() {
  if (!HasAnyStateBits(NS_FRAME_IS_BIDI)) {
    return mContent->TextLength() - mContentOffset;
  }

  FlowLengthProperty* flowLength =
      mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)
          ? static_cast<FlowLengthProperty*>(
                mContent->GetProperty(nsGkAtoms::flowlength))
          : nullptr;
  MOZ_ASSERT(mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY) == !!flowLength,
             "incorrect NS_HAS_FLOWLENGTH_PROPERTY flag");
  /**
   * This frame must start inside the cached flow. If the flow starts at
   * mContentOffset but this frame is empty, logically it might be before the
   * start of the cached flow.
   */

  if (flowLength &&
      (flowLength->mStartOffset < mContentOffset ||
       (flowLength->mStartOffset == mContentOffset &&
        GetContentEnd() > mContentOffset)) &&
      flowLength->mEndFlowOffset > mContentOffset) {
#ifdef DEBUG
    NS_ASSERTION(flowLength->mEndFlowOffset >= GetContentEnd(),
                 "frame crosses fixed continuation boundary");
#endif
    return flowLength->mEndFlowOffset - mContentOffset;
  }

  nsTextFrame* nextBidi = LastInFlow()->GetNextContinuation();
  int32_t endFlow =
      nextBidi ? nextBidi->GetContentOffset() : GetContent()->TextLength();

  if (!flowLength) {
    flowLength = new FlowLengthProperty;
    if (NS_FAILED(mContent->SetProperty(
            nsGkAtoms::flowlength, flowLength,
            nsINode::DeleteProperty<FlowLengthProperty>))) {
      delete flowLength;
      flowLength = nullptr;
    } else {
      mContent->SetFlags(NS_HAS_FLOWLENGTH_PROPERTY);
    }
  }
  if (flowLength) {
    flowLength->mStartOffset = mContentOffset;
    flowLength->mEndFlowOffset = endFlow;
  }

  return endFlow - mContentOffset;
}

// Smarter versions of dom::IsSpaceCharacter.
// Unicode is really annoying; sometimes a space character isn't whitespace ---
// when it combines with another character
// So we have several versions of IsSpace for use in different contexts.

static bool IsSpaceCombiningSequenceTail(const nsTextFragment* aFrag,
                                         uint32_t aPos) {
  NS_ASSERTION(aPos <= aFrag->GetLength(), "Bad offset");
  if (!aFrag->Is2b()) {
    return false;
  }
  return nsTextFrameUtils::IsSpaceCombiningSequenceTail(
      aFrag->Get2b() + aPos, aFrag->GetLength() - aPos);
}

// Check whether aPos is a space for CSS 'word-spacing' purposes
static bool IsCSSWordSpacingSpace(const nsTextFragment* aFrag, uint32_t aPos,
                                  const nsTextFrame* aFrame,
                                  const nsStyleText* aStyleText) {
  NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!");

  char16_t ch = aFrag->CharAt(aPos);
  switch (ch) {
    case ' ':
    case CH_NBSP:
      return !IsSpaceCombiningSequenceTail(aFrag, aPos + 1);
    case '\r':
    case '\t':
      return !aStyleText->WhiteSpaceIsSignificant();
    case '\n':
      return !aStyleText->NewlineIsSignificant(aFrame);
    default:
      return false;
  }
}

constexpr char16_t kOghamSpaceMark = 0x1680;

// Check whether the string aChars/aLength starts with space that's
// trimmable according to CSS 'white-space:normal/nowrap'.
static bool IsTrimmableSpace(const char16_t* aChars, uint32_t aLength) {
  NS_ASSERTION(aLength > 0, "No text for IsSpace!");

  char16_t ch = *aChars;
  if (ch == ' ' || ch == kOghamSpaceMark) {
    return !nsTextFrameUtils::IsSpaceCombiningSequenceTail(aChars + 1,
                                                           aLength - 1);
  }
  return ch == '\t' || ch == '\f' || ch == '\n' || ch == '\r';
}

// Check whether the character aCh is trimmable according to CSS
// 'white-space:normal/nowrap'
static bool IsTrimmableSpace(char aCh) {
  return aCh == ' ' || aCh == '\t' || aCh == '\f' || aCh == '\n' || aCh == '\r';
}

static bool IsTrimmableSpace(const nsTextFragment* aFrag, uint32_t aPos,
                             const nsStyleText* aStyleText,
                             bool aAllowHangingWS = false) {
  NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!");

  switch (aFrag->CharAt(aPos)) {
    case ' ':
    case kOghamSpaceMark:
      return (!aStyleText->WhiteSpaceIsSignificant() || aAllowHangingWS) &&
             !IsSpaceCombiningSequenceTail(aFrag, aPos + 1);
    case '\n':
      return !aStyleText->NewlineIsSignificantStyle() &&
             aStyleText->mWhiteSpaceCollapse !=
                 StyleWhiteSpaceCollapse::PreserveSpaces;
    case '\t':
    case '\r':
    case '\f':
      return !aStyleText->WhiteSpaceIsSignificant() || aAllowHangingWS;
    default:
      return false;
  }
}

static bool IsSelectionInlineWhitespace(const nsTextFragment* aFrag,
                                        uint32_t aPos) {
  NS_ASSERTION(aPos < aFrag->GetLength(),
               "No text for IsSelectionInlineWhitespace!");
  char16_t ch = aFrag->CharAt(aPos);
  if (ch == ' ' || ch == CH_NBSP) {
    return !IsSpaceCombiningSequenceTail(aFrag, aPos + 1);
  }
  return ch == '\t' || ch == '\f';
}

static bool IsSelectionNewline(const nsTextFragment* aFrag, uint32_t aPos) {
  NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSelectionNewline!");
  char16_t ch = aFrag->CharAt(aPos);
  return ch == '\n' || ch == '\r';
}

// Count the amount of trimmable whitespace (as per CSS
// 'white-space:normal/nowrap') in a text fragment. The first
// character is at offset aStartOffset; the maximum number of characters
// to check is aLength. aDirection is -1 or 1 depending on whether we should
// progress backwards or forwards.
static uint32_t GetTrimmableWhitespaceCount(const nsTextFragment* aFrag,
                                            int32_t aStartOffset,
                                            int32_t aLength,
                                            int32_t aDirection) {
  if (!aLength) {
    return 0;
  }

  int32_t count = 0;
  if (aFrag->Is2b()) {
    const char16_t* str = aFrag->Get2b() + aStartOffset;
    int32_t fragLen = aFrag->GetLength() - aStartOffset;
    for (; count < aLength; ++count) {
      if (!IsTrimmableSpace(str, fragLen)) {
        break;
      }
      str += aDirection;
      fragLen -= aDirection;
    }
  } else {
    const char* str = aFrag->Get1b() + aStartOffset;
    for (; count < aLength; ++count) {
      if (!IsTrimmableSpace(*str)) {
        break;
      }
      str += aDirection;
    }
  }
  return count;
}

static bool IsAllWhitespace(const nsTextFragment* aFrag, bool aAllowNewline) {
  if (aFrag->Is2b()) {
    return false;
  }
  int32_t len = aFrag->GetLength();
  const char* str = aFrag->Get1b();
  for (int32_t i = 0; i < len; ++i) {
    char ch = str[i];
    if (ch == ' ' || ch == '\t' || ch == '\r' ||
        (ch == '\n' && aAllowNewline)) {
      continue;
    }
    return false;
  }
  return true;
}

static void ClearObserversFromTextRun(gfxTextRun* aTextRun) {
  if (!(aTextRun->GetFlags2() &
        nsTextFrameUtils::Flags::MightHaveGlyphChanges)) {
    return;
  }

  if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
    static_cast<SimpleTextRunUserData*>(aTextRun->GetUserData())
        ->mGlyphObservers.Clear();
  } else {
    static_cast<ComplexTextRunUserData*>(aTextRun->GetUserData())
        ->mGlyphObservers.Clear();
  }
}

static void CreateObserversForAnimatedGlyphs(gfxTextRun* aTextRun) {
  if (!aTextRun->GetUserData()) {
    return;
  }

  ClearObserversFromTextRun(aTextRun);

  nsTArray<gfxFont*> fontsWithAnimatedGlyphs;
  uint32_t numGlyphRuns;
  const gfxTextRun::GlyphRun* glyphRuns = aTextRun->GetGlyphRuns(&numGlyphRuns);
  for (uint32_t i = 0; i < numGlyphRuns; ++i) {
    gfxFont* font = glyphRuns[i].mFont;
    if (font->GlyphsMayChange() && !fontsWithAnimatedGlyphs.Contains(font)) {
      fontsWithAnimatedGlyphs.AppendElement(font);
    }
  }
  if (fontsWithAnimatedGlyphs.IsEmpty()) {
    // NB: Theoretically, we should clear the MightHaveGlyphChanges
    // here. That would involve de-allocating the simple user data struct if
    // present too, and resetting the pointer to the frame. In practice, I
    // don't think worth doing that work here, given the flag's only purpose is
    // to distinguish what kind of user data is there.
    return;
  }

  nsTArray<UniquePtr<GlyphObserver>>* observers;

  if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
    // Swap the frame pointer for a just-allocated SimpleTextRunUserData if
    // appropriate.
    if (!(aTextRun->GetFlags2() &
          nsTextFrameUtils::Flags::MightHaveGlyphChanges)) {
      auto frame = static_cast<nsTextFrame*>(aTextRun->GetUserData());
      aTextRun->SetUserData(new SimpleTextRunUserData(frame));
    }

    auto data = static_cast<SimpleTextRunUserData*>(aTextRun->GetUserData());
    observers = &data->mGlyphObservers;
  } else {
    if (!(aTextRun->GetFlags2() &
          nsTextFrameUtils::Flags::MightHaveGlyphChanges)) {
      auto oldData = static_cast<TextRunUserData*>(aTextRun->GetUserData());
      TextRunMappedFlow* oldMappedFlows = GetMappedFlows(aTextRun);
      ComplexTextRunUserData* data =
          CreateComplexUserData(oldData->mMappedFlowCount);
      TextRunMappedFlow* dataMappedFlows =
          reinterpret_cast<TextRunMappedFlow*>(data + 1);
      data->mLastFlowIndex = oldData->mLastFlowIndex;
      for (uint32_t i = 0; i < oldData->mMappedFlowCount; ++i) {
        dataMappedFlows[i] = oldMappedFlows[i];
      }
      DestroyUserData(oldData);
      aTextRun->SetUserData(data);
    }
    auto data = static_cast<ComplexTextRunUserData*>(aTextRun->GetUserData());
    observers = &data->mGlyphObservers;
  }

  aTextRun->SetFlagBits(nsTextFrameUtils::Flags::MightHaveGlyphChanges);

  observers->SetCapacity(observers->Length() +
                         fontsWithAnimatedGlyphs.Length());
  for (auto font : fontsWithAnimatedGlyphs) {
    observers->AppendElement(MakeUnique<GlyphObserver>(font, aTextRun));
  }
}

/**
 * This class accumulates state as we scan a paragraph of text. It detects
 * textrun boundaries (changes from text to non-text, hard
 * line breaks, and font changes) and builds a gfxTextRun at each boundary.
 * It also detects linebreaker run boundaries (changes from text to non-text,
 * and hard line breaks) and at each boundary runs the linebreaker to compute
 * potential line breaks. It also records actual line breaks to store them in
 * the textruns.
 */

class BuildTextRunsScanner {
 public:
  BuildTextRunsScanner(nsPresContext* aPresContext, DrawTarget* aDrawTarget,
                       nsIFrame* aLineContainer,
                       nsTextFrame::TextRunType aWhichTextRun,
                       bool aDoLineBreaking)
      : mDrawTarget(aDrawTarget),
        mLineContainer(aLineContainer),
        mCommonAncestorWithLastFrame(nullptr),
        mMissingFonts(aPresContext->MissingFontRecorder()),
        mBidiEnabled(aPresContext->BidiEnabled()),
        mStartOfLine(true),
        mSkipIncompleteTextRuns(false),
        mCanStopOnThisLine(false),
        mDoLineBreaking(aDoLineBreaking),
        mWhichTextRun(aWhichTextRun),
        mNextRunContextInfo(nsTextFrameUtils::INCOMING_NONE),
        mCurrentRunContextInfo(nsTextFrameUtils::INCOMING_NONE) {
    ResetRunInfo();
  }
  ~BuildTextRunsScanner() {
    NS_ASSERTION(mBreakSinks.IsEmpty(), "Should have been cleared");
    NS_ASSERTION(mLineBreakBeforeFrames.IsEmpty(), "Should have been cleared");
    NS_ASSERTION(mMappedFlows.IsEmpty(), "Should have been cleared");
  }

  void SetAtStartOfLine() {
    mStartOfLine = true;
    mCanStopOnThisLine = false;
  }
  void SetSkipIncompleteTextRuns(bool aSkip) {
    mSkipIncompleteTextRuns = aSkip;
  }
  void SetCommonAncestorWithLastFrame(nsIFrame* aFrame) {
    mCommonAncestorWithLastFrame = aFrame;
  }
  bool CanStopOnThisLine() { return mCanStopOnThisLine; }
  nsIFrame* GetCommonAncestorWithLastFrame() {
    return mCommonAncestorWithLastFrame;
  }
  void LiftCommonAncestorWithLastFrameToParent(nsIFrame* aFrame) {
    if (mCommonAncestorWithLastFrame &&
        mCommonAncestorWithLastFrame->GetParent() == aFrame) {
      mCommonAncestorWithLastFrame = aFrame;
    }
  }
  void ScanFrame(nsIFrame* aFrame);
  bool IsTextRunValidForMappedFlows(const gfxTextRun* aTextRun);
  void FlushFrames(bool aFlushLineBreaks, bool aSuppressTrailingBreak);
  void FlushLineBreaks(gfxTextRun* aTrailingTextRun);
  void ResetRunInfo() {
    mLastFrame = nullptr;
    mMappedFlows.Clear();
    mLineBreakBeforeFrames.Clear();
    mMaxTextLength = 0;
    mDoubleByteText = false;
  }
  void AccumulateRunInfo(nsTextFrame* aFrame);
  /**
   * @return null to indicate either textrun construction failed or
   * we constructed just a partial textrun to set up linebreaker and other
   * state for following textruns.
   */

  already_AddRefed<gfxTextRun> BuildTextRunForFrames(void* aTextBuffer);
  bool SetupLineBreakerContext(gfxTextRun* aTextRun);
  void AssignTextRun(gfxTextRun* aTextRun, float aInflation);
  nsTextFrame* GetNextBreakBeforeFrame(uint32_t* aIndex);
  void SetupBreakSinksForTextRun(gfxTextRun* aTextRun, const void* aTextPtr);
  void SetupTextEmphasisForTextRun(gfxTextRun* aTextRun, const void* aTextPtr);
  struct FindBoundaryState {
    nsIFrame* mStopAtFrame;
    nsTextFrame* mFirstTextFrame;
    nsTextFrame* mLastTextFrame;
    bool mSeenTextRunBoundaryOnLaterLine;
    bool mSeenTextRunBoundaryOnThisLine;
    bool mSeenSpaceForLineBreakingOnThisLine;
    nsTArray<char16_t>& mBuffer;
  };
  enum FindBoundaryResult {
    FB_CONTINUE,
    FB_STOPPED_AT_STOP_FRAME,
    FB_FOUND_VALID_TEXTRUN_BOUNDARY
  };
  FindBoundaryResult FindBoundaries(nsIFrame* aFrame,
                                    FindBoundaryState* aState);

  bool ContinueTextRunAcrossFrames(nsTextFrame* aFrame1, nsTextFrame* aFrame2);

  // Like TextRunMappedFlow but with some differences. mStartFrame to mEndFrame
  // (exclusive) are a sequence of in-flow frames (if mEndFrame is null, then
  // continuations starting from mStartFrame are a sequence of in-flow frames).
  struct MappedFlow {
    nsTextFrame* mStartFrame;
    nsTextFrame* mEndFrame;
    // When we consider breaking between elements, the nearest common
    // ancestor of the elements containing the characters is the one whose
    // CSS 'white-space' property governs. So this records the nearest common
    // ancestor of mStartFrame and the previous text frame, or null if there
    // was no previous text frame on this line.
    nsIFrame* mAncestorControllingInitialBreak;

    int32_t GetContentEnd() const {
      int32_t fragLen = mStartFrame->TextFragment()->GetLength();
      return mEndFrame ? std::min(fragLen, mEndFrame->GetContentOffset())
                       : fragLen;
    }
  };

  class BreakSink final : public nsILineBreakSink {
   public:
    BreakSink(gfxTextRun* aTextRun, DrawTarget* aDrawTarget,
              uint32_t aOffsetIntoTextRun)
        : mTextRun(aTextRun),
          mDrawTarget(aDrawTarget),
          mOffsetIntoTextRun(aOffsetIntoTextRun) {}

    void SetBreaks(uint32_t aOffset, uint32_t aLength,
                   uint8_t* aBreakBefore) final {
      gfxTextRun::Range range(aOffset + mOffsetIntoTextRun,
                              aOffset + mOffsetIntoTextRun + aLength);
      if (mTextRun->SetPotentialLineBreaks(range, aBreakBefore)) {
        // Be conservative and assume that some breaks have been set
        mTextRun->ClearFlagBits(nsTextFrameUtils::Flags::NoBreaks);
      }
    }

    void SetCapitalization(uint32_t aOffset, uint32_t aLength,
                           bool* aCapitalize) final {
      MOZ_ASSERT(mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed,
                 "Text run should be transformed!");
      if (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed) {
        nsTransformedTextRun* transformedTextRun =
            static_cast<nsTransformedTextRun*>(mTextRun.get());
        transformedTextRun->SetCapitalization(aOffset + mOffsetIntoTextRun,
                                              aLength, aCapitalize);
      }
    }

    void Finish(gfxMissingFontRecorder* aMFR) {
      if (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed) {
        nsTransformedTextRun* transformedTextRun =
            static_cast<nsTransformedTextRun*>(mTextRun.get());
        transformedTextRun->FinishSettingProperties(mDrawTarget, aMFR);
      }
      // The way nsTransformedTextRun is implemented, its glyph runs aren't
      // available until after nsTransformedTextRun::FinishSettingProperties()
      // is called. So that's why we defer checking for animated glyphs to here.
      CreateObserversForAnimatedGlyphs(mTextRun);
    }

    RefPtr<gfxTextRun> mTextRun;
    DrawTarget* mDrawTarget;
    uint32_t mOffsetIntoTextRun;
  };

 private:
  AutoTArray<MappedFlow, 10> mMappedFlows;
  AutoTArray<nsTextFrame*, 50> mLineBreakBeforeFrames;
  AutoTArray<UniquePtr<BreakSink>, 10> mBreakSinks;
  nsLineBreaker mLineBreaker;
  RefPtr<gfxTextRun> mCurrentFramesAllSameTextRun;
  DrawTarget* mDrawTarget;
  nsIFrame* mLineContainer;
  nsTextFrame* mLastFrame;
  // The common ancestor of the current frame and the previous leaf frame
  // on the line, or null if there was no previous leaf frame.
  nsIFrame* mCommonAncestorWithLastFrame;
  gfxMissingFontRecorder* mMissingFonts;
  // mMaxTextLength is an upper bound on the size of the text in all mapped
  // frames The value UINT32_MAX represents overflow; text will be discarded
  uint32_t mMaxTextLength;
  bool mDoubleByteText;
  bool mBidiEnabled;
  bool mStartOfLine;
  bool mSkipIncompleteTextRuns;
  bool mCanStopOnThisLine;
  bool mDoLineBreaking;
  nsTextFrame::TextRunType mWhichTextRun;
  uint8_t mNextRunContextInfo;
  uint8_t mCurrentRunContextInfo;
};

static const nsIFrame* FindLineContainer(const nsIFrame* aFrame) {
  while (aFrame &&
         (aFrame->IsLineParticipant() || aFrame->CanContinueTextRun())) {
    aFrame = aFrame->GetParent();
  }
  return aFrame;
}

static nsIFrame* FindLineContainer(nsIFrame* aFrame) {
  return const_cast<nsIFrame*>(
      FindLineContainer(const_cast<const nsIFrame*>(aFrame)));
}

static bool IsLineBreakingWhiteSpace(char16_t aChar) {
  // 0x0A (\n) is not handled as white-space by the line breaker, since
  // we break before it, if it isn't transformed to a normal space.
  // (If we treat it as normal white-space then we'd only break after it.)
  // However, it does induce a line break or is converted to a regular
  // space, and either way it can be used to bound the region of text
  // that needs to be analyzed for line breaking.
  return nsLineBreaker::IsSpace(aChar) || aChar == 0x0A;
}

static bool TextContainsLineBreakerWhiteSpace(const void* aText,
                                              uint32_t aLength,
                                              bool aIsDoubleByte) {
  if (aIsDoubleByte) {
    const char16_t* chars = static_cast<const char16_t*>(aText);
    for (uint32_t i = 0; i < aLength; ++i) {
      if (IsLineBreakingWhiteSpace(chars[i])) {
        return true;
      }
    }
    return false;
  } else {
    const uint8_t* chars = static_cast<const uint8_t*>(aText);
    for (uint32_t i = 0; i < aLength; ++i) {
      if (IsLineBreakingWhiteSpace(chars[i])) {
        return true;
      }
    }
    return false;
  }
}

static nsTextFrameUtils::CompressionMode GetCSSWhitespaceToCompressionMode(
    nsTextFrame* aFrame, const nsStyleText* aStyleText) {
  switch (aStyleText->mWhiteSpaceCollapse) {
    case StyleWhiteSpaceCollapse::Collapse:
      return nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE;
    case StyleWhiteSpaceCollapse::PreserveBreaks:
      return nsTextFrameUtils::COMPRESS_WHITESPACE;
    case StyleWhiteSpaceCollapse::Preserve:
    case StyleWhiteSpaceCollapse::PreserveSpaces:
    case StyleWhiteSpaceCollapse::BreakSpaces:
      if (!aStyleText->NewlineIsSignificant(aFrame)) {
        // If newline is set to be preserved, but then suppressed,
        // transform newline to space.
        return nsTextFrameUtils::COMPRESS_NONE_TRANSFORM_TO_SPACE;
      }
      return nsTextFrameUtils::COMPRESS_NONE;
  }
  MOZ_ASSERT_UNREACHABLE("Unknown white-space-collapse value");
  return nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE;
}

struct FrameTextTraversal {
  FrameTextTraversal()
      : mFrameToScan(nullptr),
        mOverflowFrameToScan(nullptr),
        mScanSiblings(false),
        mLineBreakerCanCrossFrameBoundary(false),
        mTextRunCanCrossFrameBoundary(false) {}

  // These fields identify which frames should be recursively scanned
  // The first normal frame to scan (or null, if no such frame should be
  // scanned)
  nsIFrame* mFrameToScan;
  // The first overflow frame to scan (or null, if no such frame should be
  // scanned)
  nsIFrame* mOverflowFrameToScan;
  // Whether to scan the siblings of
  // mFrameToDescendInto/mOverflowFrameToDescendInto
  bool mScanSiblings;

  // These identify the boundaries of the context required for
  // line breaking or textrun construction
  bool mLineBreakerCanCrossFrameBoundary;
  bool mTextRunCanCrossFrameBoundary;

  nsIFrame* NextFrameToScan() {
    nsIFrame* f;
    if (mFrameToScan) {
      f = mFrameToScan;
      mFrameToScan = mScanSiblings ? f->GetNextSibling() : nullptr;
    } else if (mOverflowFrameToScan) {
      f = mOverflowFrameToScan;
      mOverflowFrameToScan = mScanSiblings ? f->GetNextSibling() : nullptr;
    } else {
      f = nullptr;
    }
    return f;
  }
};

static FrameTextTraversal CanTextCrossFrameBoundary(nsIFrame* aFrame) {
  FrameTextTraversal result;

  bool continuesTextRun = aFrame->CanContinueTextRun();
  if (aFrame->IsPlaceholderFrame()) {
    // placeholders are "invisible", so a text run should be able to span
    // across one. But don't descend into the out-of-flow.
    result.mLineBreakerCanCrossFrameBoundary = true;
    if (continuesTextRun) {
      // ... Except for first-letter floats, which are really in-flow
      // from the point of view of capitalization etc, so we'd better
      // descend into them. But we actually need to break the textrun for
      // first-letter floats since things look bad if, say, we try to make a
      // ligature across the float boundary.
      result.mFrameToScan =
          (static_cast<nsPlaceholderFrame*>(aFrame))->GetOutOfFlowFrame();
    } else {
      result.mTextRunCanCrossFrameBoundary = true;
    }
  } else {
    if (continuesTextRun) {
      result.mFrameToScan = aFrame->PrincipalChildList().FirstChild();
      result.mOverflowFrameToScan =
          aFrame->GetChildList(FrameChildListID::Overflow).FirstChild();
      NS_WARNING_ASSERTION(
          !result.mOverflowFrameToScan,
          "Scanning overflow inline frames is something we should avoid");
      result.mScanSiblings = true;
      result.mTextRunCanCrossFrameBoundary = true;
      result.mLineBreakerCanCrossFrameBoundary = true;
    } else {
      MOZ_ASSERT(!aFrame->IsRubyTextContainerFrame(),
                 "Shouldn't call this method for ruby text container");
    }
  }
  return result;
}

BuildTextRunsScanner::FindBoundaryResult BuildTextRunsScanner::FindBoundaries(
    nsIFrame* aFrame, FindBoundaryState* aState) {
  LayoutFrameType frameType = aFrame->Type();
  if (frameType == LayoutFrameType::RubyTextContainer) {
    // Don't stop a text run for ruby text container. We want ruby text
    // containers to be skipped, but continue the text run across them.
    return FB_CONTINUE;
  }

  nsTextFrame* textFrame = frameType == LayoutFrameType::Text
                               ? static_cast<nsTextFrame*>(aFrame)
                               : nullptr;
  if (textFrame) {
    if (aState->mLastTextFrame &&
        textFrame != aState->mLastTextFrame->GetNextInFlow() &&
        !ContinueTextRunAcrossFrames(aState->mLastTextFrame, textFrame)) {
      aState->mSeenTextRunBoundaryOnThisLine = true;
      if (aState->mSeenSpaceForLineBreakingOnThisLine) {
        return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
      }
    }
    if (!aState->mFirstTextFrame) {
      aState->mFirstTextFrame = textFrame;
    }
    aState->mLastTextFrame = textFrame;
  }

  if (aFrame == aState->mStopAtFrame) {
    return FB_STOPPED_AT_STOP_FRAME;
  }

  if (textFrame) {
    if (aState->mSeenSpaceForLineBreakingOnThisLine) {
      return FB_CONTINUE;
    }
    const nsTextFragment* frag = textFrame->TextFragment();
    uint32_t start = textFrame->GetContentOffset();
    uint32_t length = textFrame->GetContentLength();
    const void* text;
    const nsAtom* language = textFrame->StyleFont()->mLanguage;
    if (frag->Is2b()) {
      // It is possible that we may end up removing all whitespace in
      // a piece of text because of The White Space Processing Rules,
      // so we need to transform it before we can check existence of
      // such whitespaces.
      aState->mBuffer.EnsureLengthAtLeast(length);
      nsTextFrameUtils::CompressionMode compression =
          GetCSSWhitespaceToCompressionMode(textFrame, textFrame->StyleText());
      uint8_t incomingFlags = 0;
      gfxSkipChars skipChars;
      nsTextFrameUtils::Flags analysisFlags;
      char16_t* bufStart = aState->mBuffer.Elements();
      char16_t* bufEnd = nsTextFrameUtils::TransformText(
          frag->Get2b() + start, length, bufStart, compression, &incomingFlags,
          &skipChars, &analysisFlags, language);
      text = bufStart;
      length = bufEnd - bufStart;
    } else {
      // If the text only contains ASCII characters, it is currently
      // impossible that TransformText would remove all whitespaces,
      // and thus the check below should return the same result for
      // transformed text and original text. So we don't need to try
      // transforming it here.
      text = static_cast<const void*>(frag->Get1b() + start);
    }
    if (TextContainsLineBreakerWhiteSpace(text, length, frag->Is2b())) {
      aState->mSeenSpaceForLineBreakingOnThisLine = true;
      if (aState->mSeenTextRunBoundaryOnLaterLine) {
        return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
      }
    }
    return FB_CONTINUE;
  }

  FrameTextTraversal traversal = CanTextCrossFrameBoundary(aFrame);
  if (!traversal.mTextRunCanCrossFrameBoundary) {
    aState->mSeenTextRunBoundaryOnThisLine = true;
    if (aState->mSeenSpaceForLineBreakingOnThisLine) {
      return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
    }
  }

  for (nsIFrame* f = traversal.NextFrameToScan(); f;
       f = traversal.NextFrameToScan()) {
    FindBoundaryResult result = FindBoundaries(f, aState);
    if (result != FB_CONTINUE) {
      return result;
    }
  }

  if (!traversal.mTextRunCanCrossFrameBoundary) {
    aState->mSeenTextRunBoundaryOnThisLine = true;
    if (aState->mSeenSpaceForLineBreakingOnThisLine) {
      return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
    }
  }

  return FB_CONTINUE;
}

// build text runs for the 200 lines following aForFrame, and stop after that
// when we get a chance.
#define NUM_LINES_TO_BUILD_TEXT_RUNS 200

/**
 * General routine for building text runs. This is hairy because of the need
 * to build text runs that span content nodes.
 *
 * @param aContext The gfxContext we're using to construct this text run.
 * @param aForFrame The nsTextFrame for which we're building this text run.
 * @param aLineContainer the line container containing aForFrame; if null,
 *        we'll walk the ancestors to find it.  It's required to be non-null
 *        when aForFrameLine is non-null.
 * @param aForFrameLine the line containing aForFrame; if null, we'll figure
 *        out the line (slowly)
 * @param aWhichTextRun The type of text run we want to build. If font inflation
 *        is enabled, this will be eInflated, otherwise it's eNotInflated.
 */

static void BuildTextRuns(DrawTarget* aDrawTarget, nsTextFrame* aForFrame,
                          nsIFrame* aLineContainer,
                          const nsLineList::iterator* aForFrameLine,
                          nsTextFrame::TextRunType aWhichTextRun) {
  MOZ_ASSERT(aForFrame, "for no frame?");
  NS_ASSERTION(!aForFrameLine || aLineContainer, "line but no line container");

  nsIFrame* lineContainerChild = aForFrame;
  if (!aLineContainer) {
    if (aForFrame->IsFloatingFirstLetterChild()) {
      lineContainerChild = aForFrame->GetParent()->GetPlaceholderFrame();
    }
    aLineContainer = FindLineContainer(lineContainerChild);
  } else {
    NS_ASSERTION(
        (aLineContainer == FindLineContainer(aForFrame) ||
         (aLineContainer->IsLetterFrame() && aLineContainer->IsFloating())),
        "Wrong line container hint");
  }

  if (aForFrame->HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) {
    aLineContainer->AddStateBits(TEXT_IS_IN_TOKEN_MATHML);
    if (aForFrame->HasAnyStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI)) {
      aLineContainer->AddStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI);
    }
  }
  if (aForFrame->HasAnyStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT)) {
    aLineContainer->AddStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT);
  }

  nsPresContext* presContext = aLineContainer->PresContext();
  bool doLineBreaking = !aForFrame->IsInSVGTextSubtree();
  BuildTextRunsScanner scanner(presContext, aDrawTarget, aLineContainer,
                               aWhichTextRun, doLineBreaking);

  nsBlockFrame* block = do_QueryFrame(aLineContainer);

  if (!block) {
    nsIFrame* textRunContainer = aLineContainer;
    if (aLineContainer->IsRubyTextContainerFrame()) {
      textRunContainer = aForFrame;
      while (textRunContainer && !textRunContainer->IsRubyTextFrame()) {
        textRunContainer = textRunContainer->GetParent();
      }
      MOZ_ASSERT(textRunContainer &&
                 textRunContainer->GetParent() == aLineContainer);
    } else {
      NS_ASSERTION(
          !aLineContainer->GetPrevInFlow() && !aLineContainer->GetNextInFlow(),
          "Breakable non-block line containers other than "
          "ruby text container is not supported");
    }
    // Just loop through all the children of the linecontainer ... it's really
    // just one line
    scanner.SetAtStartOfLine();
    scanner.SetCommonAncestorWithLastFrame(nullptr);
    for (nsIFrame* child : textRunContainer->PrincipalChildList()) {
      scanner.ScanFrame(child);
    }
    // Set mStartOfLine so FlushFrames knows its textrun ends a line
    scanner.SetAtStartOfLine();
    scanner.FlushFrames(truefalse);
    return;
  }

  // Find the line containing 'lineContainerChild'.

  bool isValid = true;
  nsBlockInFlowLineIterator backIterator(block, &isValid);
  if (aForFrameLine) {
    backIterator = nsBlockInFlowLineIterator(block, *aForFrameLine);
  } else {
    backIterator =
        nsBlockInFlowLineIterator(block, lineContainerChild, &isValid);
    NS_ASSERTION(isValid, "aForFrame not found in block, someone lied to us");
    NS_ASSERTION(backIterator.GetContainer() == block,
                 "Someone lied to us about the block");
  }
  nsBlockFrame::LineIterator startLine = backIterator.GetLine();

  // Find a line where we can start building text runs. We choose the last line
  // where:
  // -- there is a textrun boundary between the start of the line and the
  // start of aForFrame
  // -- there is a space between the start of the line and the textrun boundary
  // (this is so we can be sure the line breaks will be set properly
  // on the textruns we construct).
  // The possibly-partial text runs up to and including the first space
  // are not reconstructed. We construct partial text runs for that text ---
  // for the sake of simplifying the code and feeding the linebreaker ---
  // but we discard them instead of assigning them to frames.
  // This is a little awkward because we traverse lines in the reverse direction
  // but we traverse the frames in each line in the forward direction.
  nsBlockInFlowLineIterator forwardIterator = backIterator;
  nsIFrame* stopAtFrame = lineContainerChild;
  nsTextFrame* nextLineFirstTextFrame = nullptr;
  AutoTArray<char16_t, BIG_TEXT_NODE_SIZE> buffer;
  bool seenTextRunBoundaryOnLaterLine = false;
  bool mayBeginInTextRun = true;
  while (true) {
    forwardIterator = backIterator;
    nsBlockFrame::LineIterator line = backIterator.GetLine();
    if (!backIterator.Prev() || backIterator.GetLine()->IsBlock()) {
      mayBeginInTextRun = false;
      break;
    }

    BuildTextRunsScanner::FindBoundaryState state = {
        stopAtFrame, nullptr, nullptr, bool(seenTextRunBoundaryOnLaterLine),
        false,       false,   buffer};
    nsIFrame* child = line->mFirstChild;
    bool foundBoundary = false;
    for (int32_t i = line->GetChildCount() - 1; i >= 0; --i) {
      BuildTextRunsScanner::FindBoundaryResult result =
          scanner.FindBoundaries(child, &state);
      if (result == BuildTextRunsScanner::FB_FOUND_VALID_TEXTRUN_BOUNDARY) {
        foundBoundary = true;
        break;
      } else if (result == BuildTextRunsScanner::FB_STOPPED_AT_STOP_FRAME) {
        break;
      }
      child = child->GetNextSibling();
    }
    if (foundBoundary) {
      break;
    }
    if (!stopAtFrame && state.mLastTextFrame && nextLineFirstTextFrame &&
        !scanner.ContinueTextRunAcrossFrames(state.mLastTextFrame,
                                             nextLineFirstTextFrame)) {
      // Found a usable textrun boundary at the end of the line
      if (state.mSeenSpaceForLineBreakingOnThisLine) {
        break;
      }
      seenTextRunBoundaryOnLaterLine = true;
    } else if (state.mSeenTextRunBoundaryOnThisLine) {
      seenTextRunBoundaryOnLaterLine = true;
    }
    stopAtFrame = nullptr;
    if (state.mFirstTextFrame) {
      nextLineFirstTextFrame = state.mFirstTextFrame;
    }
  }
  scanner.SetSkipIncompleteTextRuns(mayBeginInTextRun);

  // Now iterate over all text frames starting from the current line.
  // First-in-flow text frames will be accumulated into textRunFrames as we go.
  // When a text run boundary is required we flush textRunFrames ((re)building
  // their gfxTextRuns as necessary).
  bool seenStartLine = false;
  uint32_t linesAfterStartLine = 0;
  do {
    nsBlockFrame::LineIterator line = forwardIterator.GetLine();
    if (line->IsBlock()) {
      break;
    }
    line->SetInvalidateTextRuns(false);
    scanner.SetAtStartOfLine();
    scanner.SetCommonAncestorWithLastFrame(nullptr);
    nsIFrame* child = line->mFirstChild;
    for (int32_t i = line->GetChildCount() - 1; i >= 0; --i) {
      scanner.ScanFrame(child);
      child = child->GetNextSibling();
    }
    if (line.get() == startLine.get()) {
      seenStartLine = true;
    }
    if (seenStartLine) {
      ++linesAfterStartLine;
      if (linesAfterStartLine >= NUM_LINES_TO_BUILD_TEXT_RUNS &&
          scanner.CanStopOnThisLine()) {
        // Don't flush frames; we may be in the middle of a textrun
        // that we can't end here. That's OK, we just won't build it.
        // Note that we must already have finished the textrun for aForFrame,
        // because we've seen the end of a textrun in a line after the line
        // containing aForFrame.
        scanner.FlushLineBreaks(nullptr);
        // This flushes out mMappedFlows and mLineBreakBeforeFrames, which
        // silences assertions in the scanner destructor.
        scanner.ResetRunInfo();
        return;
      }
    }
  } while (forwardIterator.Next());

  // Set mStartOfLine so FlushFrames knows its textrun ends a line
  scanner.SetAtStartOfLine();
  scanner.FlushFrames(truefalse);
}

static char16_t* ExpandBuffer(char16_t* aDest, uint8_t* aSrc, uint32_t aCount) {
  while (aCount) {
    *aDest = *aSrc;
    ++aDest;
    ++aSrc;
    --aCount;
  }
  return aDest;
}

bool BuildTextRunsScanner::IsTextRunValidForMappedFlows(
    const gfxTextRun* aTextRun) {
  if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
    return mMappedFlows.Length() == 1 &&
           mMappedFlows[0].mStartFrame == GetFrameForSimpleFlow(aTextRun) &&
           mMappedFlows[0].mEndFrame == nullptr;
  }

  auto userData = static_cast<TextRunUserData*>(aTextRun->GetUserData());
  TextRunMappedFlow* userMappedFlows = GetMappedFlows(aTextRun);
  if (userData->mMappedFlowCount != mMappedFlows.Length()) {
    return false;
  }
  for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
    if (userMappedFlows[i].mStartFrame != mMappedFlows[i].mStartFrame ||
        int32_t(userMappedFlows[i].mContentLength) !=
            mMappedFlows[i].GetContentEnd() -
                mMappedFlows[i].mStartFrame->GetContentOffset()) {
      return false;
    }
  }
  return true;
}

/**
 * This gets called when we need to make a text run for the current list of
 * frames.
 */

void BuildTextRunsScanner::FlushFrames(bool aFlushLineBreaks,
                                       bool aSuppressTrailingBreak) {
  RefPtr<gfxTextRun> textRun;
  if (!mMappedFlows.IsEmpty()) {
    if (!mSkipIncompleteTextRuns && mCurrentFramesAllSameTextRun &&
        !!(mCurrentFramesAllSameTextRun->GetFlags2() &
           nsTextFrameUtils::Flags::IncomingWhitespace) ==
            !!(mCurrentRunContextInfo &
               nsTextFrameUtils::INCOMING_WHITESPACE) &&
        !!(mCurrentFramesAllSameTextRun->GetFlags() &
           gfx::ShapedTextFlags::TEXT_INCOMING_ARABICCHAR) ==
            !!(mCurrentRunContextInfo &
               nsTextFrameUtils::INCOMING_ARABICCHAR) &&
        IsTextRunValidForMappedFlows(mCurrentFramesAllSameTextRun)) {
      // Optimization: We do not need to (re)build the textrun.
      textRun = mCurrentFramesAllSameTextRun;

      if (mDoLineBreaking) {
        // Feed this run's text into the linebreaker to provide context.
        if (!SetupLineBreakerContext(textRun)) {
          return;
        }
      }

      // Update mNextRunContextInfo appropriately
      mNextRunContextInfo = nsTextFrameUtils::INCOMING_NONE;
      if (textRun->GetFlags2() & nsTextFrameUtils::Flags::TrailingWhitespace) {
        mNextRunContextInfo |= nsTextFrameUtils::INCOMING_WHITESPACE;
      }
      if (textRun->GetFlags() &
          gfx::ShapedTextFlags::TEXT_TRAILING_ARABICCHAR) {
        mNextRunContextInfo |= nsTextFrameUtils::INCOMING_ARABICCHAR;
      }
    } else {
      AutoTArray<uint8_t, BIG_TEXT_NODE_SIZE> buffer;
      uint32_t bufferSize = mMaxTextLength * (mDoubleByteText ? 2 : 1);
      if (bufferSize < mMaxTextLength || bufferSize == UINT32_MAX ||
          !buffer.AppendElements(bufferSize, fallible)) {
        return;
      }
      textRun = BuildTextRunForFrames(buffer.Elements());
    }
  }

  if (aFlushLineBreaks) {
    FlushLineBreaks(aSuppressTrailingBreak ? nullptr : textRun.get());
    if (!mDoLineBreaking && textRun) {
      CreateObserversForAnimatedGlyphs(textRun.get());
    }
  }

  mCanStopOnThisLine = true;
  ResetRunInfo();
}

void BuildTextRunsScanner::FlushLineBreaks(gfxTextRun* aTrailingTextRun) {
  // If the line-breaker is buffering a potentially-unfinished word,
  // preserve the state of being in-word so that we don't spuriously
  // capitalize the next letter.
  bool inWord = mLineBreaker.InWord();
  bool trailingLineBreak;
  nsresult rv = mLineBreaker.Reset(&trailingLineBreak);
  mLineBreaker.SetWordContinuation(inWord);
  // textRun may be null for various reasons, including because we constructed
  // a partial textrun just to get the linebreaker and other state set up
  // to build the next textrun.
  if (NS_SUCCEEDED(rv) && trailingLineBreak && aTrailingTextRun) {
    aTrailingTextRun->SetFlagBits(nsTextFrameUtils::Flags::HasTrailingBreak);
  }

  for (uint32_t i = 0; i < mBreakSinks.Length(); ++i) {
    // TODO cause frames associated with the textrun to be reflowed, if they
    // aren't being reflowed already!
    mBreakSinks[i]->Finish(mMissingFonts);
  }
  mBreakSinks.Clear();
}

void BuildTextRunsScanner::AccumulateRunInfo(nsTextFrame* aFrame) {
  if (mMaxTextLength != UINT32_MAX) {
    NS_ASSERTION(mMaxTextLength < UINT32_MAX - aFrame->GetContentLength(),
                 "integer overflow");
    if (mMaxTextLength >= UINT32_MAX - aFrame->GetContentLength()) {
      mMaxTextLength = UINT32_MAX;
    } else {
      mMaxTextLength += aFrame->GetContentLength();
    }
  }
  mDoubleByteText |= aFrame->TextFragment()->Is2b();
  mLastFrame = aFrame;
  mCommonAncestorWithLastFrame = aFrame->GetParent();

  MappedFlow* mappedFlow = &mMappedFlows[mMappedFlows.Length() - 1];
  NS_ASSERTION(mappedFlow->mStartFrame == aFrame ||
                   mappedFlow->GetContentEnd() == aFrame->GetContentOffset(),
               "Overlapping or discontiguous frames => BAD");
  mappedFlow->mEndFrame = aFrame->GetNextContinuation();
  if (mCurrentFramesAllSameTextRun != aFrame->GetTextRun(mWhichTextRun)) {
    mCurrentFramesAllSameTextRun = nullptr;
  }

  if (mStartOfLine) {
    mLineBreakBeforeFrames.AppendElement(aFrame);
    mStartOfLine = false;
  }

  // Default limits used by `hyphenate-limit-chars` for `auto` components, as
  // suggested by the CSS Text spec.
  // TODO: consider making these sensitive to the context, e.g. increasing the
  // values for long line lengths to reduce the tendency to hyphenate too much.
  const uint32_t kDefaultHyphenateTotalWordLength = 5;
  const uint32_t kDefaultHyphenatePreBreakLength = 2;
  const uint32_t kDefaultHyphenatePostBreakLength = 2;

  const auto& hyphenateLimitChars = aFrame->StyleText()->mHyphenateLimitChars;
  uint32_t pre =
      hyphenateLimitChars.pre_hyphen_length.IsAuto()
          ? kDefaultHyphenatePreBreakLength
          : std::max(0, hyphenateLimitChars.pre_hyphen_length.AsNumber());
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=89 H=90 G=89

¤ Dauer der Verarbeitung: 0.17 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.