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

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


// Main header first:
#include "SVGTextFrame.h"

// Keep others in (case-insensitive) order:
#include "DOMSVGPoint.h"
#include "gfx2DGlue.h"
#include "gfxContext.h"
#include "gfxFont.h"
#include "gfxSkipChars.h"
#include "gfxTypes.h"
#include "gfxUtils.h"
#include "LookAndFeel.h"
#include "nsBidiPresUtils.h"
#include "nsBlockFrame.h"
#include "nsCaret.h"
#include "nsContentUtils.h"
#include "nsGkAtoms.h"
#include "SVGPaintServerFrame.h"
#include "nsTArray.h"
#include "nsTextFrame.h"
#include "SVGAnimatedNumberList.h"
#include "SVGContentUtils.h"
#include "SVGContextPaint.h"
#include "SVGLengthList.h"
#include "SVGNumberList.h"
#include "nsLayoutUtils.h"
#include "nsFrameSelection.h"
#include "nsStyleStructInlines.h"
#include "mozilla/CaretAssociationHint.h"
#include "mozilla/DisplaySVGItem.h"
#include "mozilla/Likely.h"
#include "mozilla/PresShell.h"
#include "mozilla/SVGObserverUtils.h"
#include "mozilla/SVGOuterSVGFrame.h"
#include "mozilla/SVGUtils.h"
#include "mozilla/dom/DOMPointBinding.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/dom/SVGGeometryElement.h"
#include "mozilla/dom/SVGRect.h"
#include "mozilla/dom/SVGTextContentElementBinding.h"
#include "mozilla/dom/SVGTextPathElement.h"
#include "mozilla/dom/Text.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/PatternHelpers.h"
#include <algorithm>
#include <cmath>
#include <limits>

using namespace mozilla::dom;
using namespace mozilla::dom::SVGTextContentElement_Binding;
using namespace mozilla::gfx;
using namespace mozilla::image;

namespace mozilla {

// ============================================================================
// Utility functions

/**
 * Using the specified gfxSkipCharsIterator, converts an offset and length
 * in original char indexes to skipped char indexes.
 *
 * @param aIterator The gfxSkipCharsIterator to use for the conversion.
 * @param aOriginalOffset The original offset.
 * @param aOriginalLength The original length.
 */

static gfxTextRun::Range ConvertOriginalToSkipped(
    gfxSkipCharsIterator& aIterator, uint32_t aOriginalOffset,
    uint32_t aOriginalLength) {
  uint32_t start = aIterator.ConvertOriginalToSkipped(aOriginalOffset);
  aIterator.AdvanceOriginal(aOriginalLength);
  return gfxTextRun::Range(start, aIterator.GetSkippedOffset());
}

/**
 * Converts an nsPoint from app units to user space units using the specified
 * nsPresContext and returns it as a gfxPoint.
 */

static gfxPoint AppUnitsToGfxUnits(const nsPoint& aPoint,
                                   const nsPresContext* aContext) {
  return gfxPoint(aContext->AppUnitsToGfxUnits(aPoint.x),
                  aContext->AppUnitsToGfxUnits(aPoint.y));
}

/**
 * Converts a gfxRect that is in app units to CSS pixels using the specified
 * nsPresContext and returns it as a gfxRect.
 */

static gfxRect AppUnitsToFloatCSSPixels(const gfxRect& aRect,
                                        const nsPresContext* aContext) {
  return gfxRect(nsPresContext::AppUnitsToFloatCSSPixels(aRect.x),
                 nsPresContext::AppUnitsToFloatCSSPixels(aRect.y),
                 nsPresContext::AppUnitsToFloatCSSPixels(aRect.width),
                 nsPresContext::AppUnitsToFloatCSSPixels(aRect.height));
}

/**
 * Returns whether a gfxPoint lies within a gfxRect.
 */

static bool Inside(const gfxRect& aRect, const gfxPoint& aPoint) {
  return aPoint.x >= aRect.x && aPoint.x < aRect.XMost() &&
         aPoint.y >= aRect.y && aPoint.y < aRect.YMost();
}

/**
 * Gets the measured ascent and descent of the text in the given nsTextFrame
 * in app units.
 *
 * @param aFrame The text frame.
 * @param aAscent The ascent in app units (output).
 * @param aDescent The descent in app units (output).
 */

static void GetAscentAndDescentInAppUnits(nsTextFrame* aFrame,
                                          gfxFloat& aAscent,
                                          gfxFloat& aDescent) {
  gfxSkipCharsIterator it = aFrame->EnsureTextRun(nsTextFrame::eInflated);
  gfxTextRun* textRun = aFrame->GetTextRun(nsTextFrame::eInflated);

  gfxTextRun::Range range = ConvertOriginalToSkipped(
      it, aFrame->GetContentOffset(), aFrame->GetContentLength());

  textRun->GetLineHeightMetrics(range, aAscent, aDescent);
}

/**
 * Updates an interval by intersecting it with another interval.
 * The intervals are specified using a start index and a length.
 */

static void IntersectInterval(uint32_t& aStart, uint32_t& aLength,
                              uint32_t aStartOther, uint32_t aLengthOther) {
  uint32_t aEnd = aStart + aLength;
  uint32_t aEndOther = aStartOther + aLengthOther;

  if (aStartOther >= aEnd || aStart >= aEndOther) {
    aLength = 0;
  } else {
    if (aStartOther >= aStart) {
      aStart = aStartOther;
    }
    aLength = std::min(aEnd, aEndOther) - aStart;
  }
}

/**
 * Intersects an interval as IntersectInterval does but by taking
 * the offset and length of the other interval from a
 * nsTextFrame::TrimmedOffsets object.
 */

static void TrimOffsets(uint32_t& aStart, uint32_t& aLength,
                        const nsTextFrame::TrimmedOffsets& aTrimmedOffsets) {
  IntersectInterval(aStart, aLength, aTrimmedOffsets.mStart,
                    aTrimmedOffsets.mLength);
}

/**
 * Returns the closest ancestor-or-self node that is not an SVG <a>
 * element.
 */

static nsIContent* GetFirstNonAAncestor(nsIContent* aContent) {
  while (aContent && aContent->IsSVGElement(nsGkAtoms::a)) {
    aContent = aContent->GetParent();
  }
  return aContent;
}

/**
 * Returns whether the given node is a text content element[1], taking into
 * account whether it has a valid parent.
 *
 * For example, in:
 *
 *   <svg xmlns="http://www.w3.org/2000/svg">
 *     <text><a/><text/></text>
 *     <tspan/>
 *   </svg>
 *
 * true would be returned for the outer <text> element and the <a> element,
 * and false for the inner <text> element (since a <text> is not allowed
 * to be a child of another <text>) and the <tspan> element (because it
 * must be inside a <text> subtree).
 *
 * Note that we don't support the <tref> element yet and this function
 * returns false for it.
 *
 * [1] https://svgwg.org/svg2-draft/intro.html#TermTextContentElement
 */

static bool IsTextContentElement(nsIContent* aContent) {
  if (aContent->IsSVGElement(nsGkAtoms::text)) {
    nsIContent* parent = GetFirstNonAAncestor(aContent->GetParent());
    return !parent || !IsTextContentElement(parent);
  }

  if (aContent->IsSVGElement(nsGkAtoms::textPath)) {
    nsIContent* parent = GetFirstNonAAncestor(aContent->GetParent());
    return parent && parent->IsSVGElement(nsGkAtoms::text);
  }

  return aContent->IsAnyOfSVGElements(nsGkAtoms::a, nsGkAtoms::tspan);
}

/**
 * Returns whether the specified frame is an nsTextFrame that has some text
 * content.
 */

static bool IsNonEmptyTextFrame(nsIFrame* aFrame) {
  nsTextFrame* textFrame = do_QueryFrame(aFrame);
  if (!textFrame) {
    return false;
  }

  return textFrame->GetContentLength() != 0;
}

/**
 * Takes an nsIFrame and if it is a text frame that has some text content,
 * returns it as an nsTextFrame and its corresponding Text.
 *
 * @param aFrame The frame to look at.
 * @param aTextFrame aFrame as an nsTextFrame (output).
 * @param aTextNode The Text content of aFrame (output).
 * @return true if aFrame is a non-empty text frame, false otherwise.
 */

static bool GetNonEmptyTextFrameAndNode(nsIFrame* aFrame,
                                        nsTextFrame*& aTextFrame,
                                        Text*& aTextNode) {
  nsTextFrame* text = do_QueryFrame(aFrame);
  bool isNonEmptyTextFrame = text && text->GetContentLength() != 0;

  if (isNonEmptyTextFrame) {
    nsIContent* content = text->GetContent();
    NS_ASSERTION(content && content->IsText(),
                 "unexpected content type for nsTextFrame");

    Text* node = content->AsText();
    MOZ_ASSERT(node->TextLength() != 0,
               "frame's GetContentLength() should be 0 if the text node "
               "has no content");

    aTextFrame = text;
    aTextNode = node;
  }

  MOZ_ASSERT(IsNonEmptyTextFrame(aFrame) == isNonEmptyTextFrame,
             "our logic should agree with IsNonEmptyTextFrame");
  return isNonEmptyTextFrame;
}

/**
 * Returns whether the specified atom is for one of the five
 * glyph positioning attributes that can appear on SVG text
 * elements -- x, y, dx, dy or rotate.
 */

static bool IsGlyphPositioningAttribute(nsAtom* aAttribute) {
  return aAttribute == nsGkAtoms::x || aAttribute == nsGkAtoms::y ||
         aAttribute == nsGkAtoms::dx || aAttribute == nsGkAtoms::dy ||
         aAttribute == nsGkAtoms::rotate;
}

/**
 * Returns the position in app units of a given baseline (using an
 * SVG dominant-baseline property value) for a given nsTextFrame.
 *
 * @param aFrame The text frame to inspect.
 * @param aTextRun The text run of aFrame.
 * @param aDominantBaseline The dominant-baseline value to use.
 */

static nscoord GetBaselinePosition(nsTextFrame* aFrame, gfxTextRun* aTextRun,
                                   StyleDominantBaseline aDominantBaseline,
                                   float aFontSizeScaleFactor) {
  WritingMode writingMode = aFrame->GetWritingMode();
  gfxFloat ascent, descent;
  aTextRun->GetLineHeightMetrics(ascent, descent);

  auto convertIfVerticalRL = [&](gfxFloat dominantBaseline) {
    return writingMode.IsVerticalRL() ? ascent + descent - dominantBaseline
                                      : dominantBaseline;
  };

  switch (aDominantBaseline) {
    case StyleDominantBaseline::Hanging:
      return convertIfVerticalRL(ascent * 0.2);
    case StyleDominantBaseline::TextBeforeEdge:
      return convertIfVerticalRL(0);

    case StyleDominantBaseline::Alphabetic:
      return writingMode.IsVerticalRL()
                 ? ascent * 0.5
                 : aFrame->GetLogicalBaseline(writingMode);

    case StyleDominantBaseline::Auto:
      return convertIfVerticalRL(aFrame->GetLogicalBaseline(writingMode));

    case StyleDominantBaseline::Middle:
      return convertIfVerticalRL(aFrame->GetLogicalBaseline(writingMode) -
                                 SVGContentUtils::GetFontXHeight(aFrame) / 2.0 *
                                     AppUnitsPerCSSPixel() *
                                     aFontSizeScaleFactor);

    case StyleDominantBaseline::TextAfterEdge:
    case StyleDominantBaseline::Ideographic:
      return writingMode.IsVerticalLR() ? 0 : ascent + descent;

    case StyleDominantBaseline::Central:
      return (ascent + descent) / 2.0;
    case StyleDominantBaseline::Mathematical:
      return convertIfVerticalRL(ascent / 2.0);
  }

  MOZ_ASSERT_UNREACHABLE("unexpected dominant-baseline value");
  return convertIfVerticalRL(aFrame->GetLogicalBaseline(writingMode));
}

/**
 * Truncates an array to be at most the length of another array.
 *
 * @param aArrayToTruncate The array to truncate.
 * @param aReferenceArray The array whose length will be used to truncate
 *   aArrayToTruncate to.
 */

template <typename T, typename U>
static void TruncateTo(nsTArray<T>& aArrayToTruncate,
                       const nsTArray<U>& aReferenceArray) {
  uint32_t length = aReferenceArray.Length();
  if (aArrayToTruncate.Length() > length) {
    aArrayToTruncate.TruncateLength(length);
  }
}

/**
 * Asserts that the anonymous block child of the SVGTextFrame has been
 * reflowed (or does not exist).  Returns null if the child has not been
 * reflowed, and the frame otherwise.
 *
 * We check whether the kid has been reflowed and not the frame itself
 * since we sometimes need to call this function during reflow, after the
 * kid has been reflowed but before we have cleared the dirty bits on the
 * frame itself.
 */

static SVGTextFrame* FrameIfAnonymousChildReflowed(SVGTextFrame* aFrame) {
  MOZ_ASSERT(aFrame, "aFrame must not be null");
  nsIFrame* kid = aFrame->PrincipalChildList().FirstChild();
  if (kid->IsSubtreeDirty()) {
    MOZ_ASSERT(false"should have already reflowed the anonymous block child");
    return nullptr;
  }
  return aFrame;
}

// FIXME(emilio): SVG is a special-case where transforms affect layout. We don't
// want that to go outside the SVG stuff (and really we should aim to remove
// that).
static float GetContextScale(SVGTextFrame* aFrame) {
  if (aFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
    // When we are non-display, we could be painted in different coordinate
    // spaces, and we don't want to have to reflow for each of these.  We just
    // assume that the context scale is 1.0 for them all, so we don't get stuck
    // with a font size scale factor based on whichever referencing frame
    // happens to reflow first.
    return 1.0f;
  }
  auto matrix = nsLayoutUtils::GetTransformToAncestor(
      RelativeTo{aFrame}, RelativeTo{SVGUtils::GetOuterSVGFrame(aFrame)});
  Matrix transform2D;
  if (!matrix.CanDraw2D(&transform2D)) {
    return 1.0f;
  }
  auto scales = transform2D.ScaleFactors();
  return std::max(0.0f, std::max(scales.xScale, scales.yScale));
}

// ============================================================================
// Utility classes

// ----------------------------------------------------------------------------
// TextRenderedRun

/**
 * A run of text within a single nsTextFrame whose glyphs can all be painted
 * with a single call to nsTextFrame::PaintText.  A text rendered run can
 * be created for a sequence of two or more consecutive glyphs as long as:
 *
 *   - Only the first glyph has (or none of the glyphs have) been positioned
 *     with SVG text positioning attributes
 *   - All of the glyphs have zero rotation
 *   - The glyphs are not on a text path
 *   - The glyphs correspond to content within the one nsTextFrame
 *
 * A TextRenderedRunIterator produces TextRenderedRuns required for painting a
 * whole SVGTextFrame.
 */

struct TextRenderedRun {
  using Range = gfxTextRun::Range;

  /**
   * Constructs a TextRenderedRun that is uninitialized except for mFrame
   * being null.
   */

  TextRenderedRun() : mFrame(nullptr) {}

  /**
   * Constructs a TextRenderedRun with all of the information required to
   * paint it.  See the comments documenting the member variables below
   * for descriptions of the arguments.
   */

  TextRenderedRun(nsTextFrame* aFrame, const gfxPoint& aPosition,
                  float aLengthAdjustScaleFactor, double aRotate,
                  float aFontSizeScaleFactor, nscoord aBaseline,
                  uint32_t aTextFrameContentOffset,
                  uint32_t aTextFrameContentLength,
                  uint32_t aTextElementCharIndex)
      : mFrame(aFrame),
        mPosition(aPosition),
        mLengthAdjustScaleFactor(aLengthAdjustScaleFactor),
        mRotate(static_cast<float>(aRotate)),
        mFontSizeScaleFactor(aFontSizeScaleFactor),
        mBaseline(aBaseline),
        mTextFrameContentOffset(aTextFrameContentOffset),
        mTextFrameContentLength(aTextFrameContentLength),
        mTextElementCharIndex(aTextElementCharIndex) {}

  /**
   * Returns the text run for the text frame that this rendered run is part of.
   */

  gfxTextRun* GetTextRun() const {
    mFrame->EnsureTextRun(nsTextFrame::eInflated);
    return mFrame->GetTextRun(nsTextFrame::eInflated);
  }

  /**
   * Returns whether this rendered run is RTL.
   */

  bool IsRightToLeft() const { return GetTextRun()->IsRightToLeft(); }

  /**
   * Returns whether this rendered run is vertical.
   */

  bool IsVertical() const { return GetTextRun()->IsVertical(); }

  /**
   * Returns the transform that converts from a <text> element's user space into
   * the coordinate space that rendered runs can be painted directly in.
   *
   * The difference between this method and
   * GetTransformFromRunUserSpaceToUserSpace is that when calling in to
   * nsTextFrame::PaintText, it will already take into account any left clip
   * edge (that is, it doesn't just apply a visual clip to the rendered text, it
   * shifts the glyphs over so that they are painted with their left edge at the
   * x coordinate passed in to it). Thus we need to account for this in our
   * transform.
   *
   *
   * Assume that we have:
   *
   *   <text x="100" y="100" rotate="0 0 1 0 0 * 1">abcdef</text>.
   *
   * This would result in four text rendered runs:
   *
   *   - one for "ab"
   *   - one for "c"
   *   - one for "de"
   *   - one for "f"
   *
   * Assume now that we are painting the third TextRenderedRun.  It will have
   * a left clip edge that is the sum of the advances of "abc", and it will
   * have a right clip edge that is the advance of "f".  In
   * SVGTextFrame::PaintSVG(), we pass in nsPoint() (i.e., the origin)
   * as the point at which to paint the text frame, and we pass in the
   * clip edge values.  The nsTextFrame will paint the substring of its
   * text such that the top-left corner of the "d"'s glyph cell will be at
   * (0, 0) in the current coordinate system.
   *
   * Thus, GetTransformFromUserSpaceForPainting must return a transform from
   * whatever user space the <text> element is in to a coordinate space in
   * device pixels (as that's what nsTextFrame works in) where the origin is at
   * the same position as our user space mPositions[i].mPosition value for
   * the "d" glyph, which will be (100 + userSpaceAdvance("abc"), 100).
   * The translation required to do this (ignoring the scale to get from
   * user space to device pixels, and ignoring the
   * (100 + userSpaceAdvance("abc"), 100) translation) is:
   *
   *   (-leftEdge, -baseline)
   *
   * where baseline is the distance between the baseline of the text and the top
   * edge of the nsTextFrame.  We translate by -leftEdge horizontally because
   * the nsTextFrame will already shift the glyphs over by that amount and start
   * painting glyphs at x = 0.  We translate by -baseline vertically so that
   * painting the top edges of the glyphs at y = 0 will result in their
   * baselines being at our desired y position.
   *
   *
   * Now for an example with RTL text.  Assume our content is now
   * <text x="100" y="100" rotate="0 0 1 0 0 1">WERBEH</text>.  We'd have
   * the following text rendered runs:
   *
   *   - one for "EH"
   *   - one for "B"
   *   - one for "ER"
   *   - one for "W"
   *
   * Again, we are painting the third TextRenderedRun.  The left clip edge
   * is the advance of the "W" and the right clip edge is the sum of the
   * advances of "BEH".  Our translation to get the rendered "ER" glyphs
   * in the right place this time is:
   *
   *   (-frameWidth + rightEdge, -baseline)
   *
   * which is equivalent to:
   *
   *   (-(leftEdge + advance("ER")), -baseline)
   *
   * The reason we have to shift left additionally by the width of the run
   * of glyphs we are painting is that although the nsTextFrame is RTL,
   * we still supply the top-left corner to paint the frame at when calling
   * nsTextFrame::PaintText, even though our user space positions for each
   * glyph in mPositions specifies the origin of each glyph, which for RTL
   * glyphs is at the right edge of the glyph cell.
   *
   *
   * For any other use of an nsTextFrame in the context of a particular run
   * (such as hit testing, or getting its rectangle),
   * GetTransformFromRunUserSpaceToUserSpace should be used.
   *
   * @param aContext The context to use for unit conversions.
   */

  gfxMatrix GetTransformFromUserSpaceForPainting(
      nsPresContext* aContext, const nscoord aVisIStartEdge,
      const nscoord aVisIEndEdge) const;

  /**
   * Returns the transform that converts from "run user space" to a <text>
   * element's user space.  Run user space is a coordinate system that has the
   * same size as the <text>'s user space but rotated and translated such that
   * (0,0) is the top-left of the rectangle that bounds the text.
   *
   * @param aContext The context to use for unit conversions.
   */

  gfxMatrix GetTransformFromRunUserSpaceToUserSpace(
      nsPresContext* aContext) const;

  /**
   * Returns the transform that converts from "run user space" to float pixels
   * relative to the nsTextFrame that this rendered run is a part of.
   *
   * @param aContext The context to use for unit conversions.
   */

  gfxMatrix GetTransformFromRunUserSpaceToFrameUserSpace(
      nsPresContext* aContext) const;

  /**
   * Flag values used for the aFlags arguments of GetRunUserSpaceRect,
   * GetFrameUserSpaceRect and GetUserSpaceRect.
   */

  enum {
    // Includes the fill geometry of the text in the returned rectangle.
    eIncludeFill = 1,
    // Includes the stroke geometry of the text in the returned rectangle.
    eIncludeStroke = 2,
    // Don't include any horizontal glyph overflow in the returned rectangle.
    eNoHorizontalOverflow = 4
  };

  /**
   * Returns a rectangle that bounds the fill and/or stroke of the rendered run
   * in run user space.
   *
   * @param aContext The context to use for unit conversions.
   * @param aFlags A combination of the flags above (eIncludeFill and
   *   eIncludeStroke) indicating what parts of the text to include in
   *   the rectangle.
   */

  SVGBBox GetRunUserSpaceRect(nsPresContext* aContext, uint32_t aFlags) const;

  /**
   * Returns a rectangle that covers the fill and/or stroke of the rendered run
   * in "frame user space".
   *
   * Frame user space is a coordinate space of the same scale as the <text>
   * element's user space, but with its rotation set to the rotation of
   * the glyphs within this rendered run and its origin set to the position
   * such that placing the nsTextFrame there would result in the glyphs in
   * this rendered run being at their correct positions.
   *
   * For example, say we have <text x="100 150" y="100">ab</text>.  Assume
   * the advance of both the "a" and the "b" is 12 user units, and the
   * ascent of the text is 8 user units and its descent is 6 user units,
   * and that we are not measuing the stroke of the text, so that we stay
   * entirely within the glyph cells.
   *
   * There will be two text rendered runs, one for "a" and one for "b".
   *
   * The frame user space for the "a" run will have its origin at
   * (100, 100 - 8) in the <text> element's user space and will have its
   * axes aligned with the user space (since there is no rotate="" or
   * text path involve) and with its scale the same as the user space.
   * The rect returned by this method will be (0, 0, 12, 14), since the "a"
   * glyph is right at the left of the nsTextFrame.
   *
   * The frame user space for the "b" run will have its origin at
   * (150 - 12, 100 - 8), and scale/rotation the same as above.  The rect
   * returned by this method will be (12, 0, 12, 14), since we are
   * advance("a") horizontally in to the text frame.
   *
   * @param aContext The context to use for unit conversions.
   * @param aFlags A combination of the flags above (eIncludeFill and
   *   eIncludeStroke) indicating what parts of the text to include in
   *   the rectangle.
   */

  SVGBBox GetFrameUserSpaceRect(nsPresContext* aContext, uint32_t aFlags) const;

  /**
   * Returns a rectangle that covers the fill and/or stroke of the rendered run
   * in the <text> element's user space.
   *
   * @param aContext The context to use for unit conversions.
   * @param aFlags A combination of the flags above indicating what parts of
   *   the text to include in the rectangle.
   * @param aAdditionalTransform An additional transform to apply to the
   *   frame user space rectangle before its bounds are transformed into
   *   user space.
   */

  SVGBBox GetUserSpaceRect(
      nsPresContext* aContext, uint32_t aFlags,
      const gfxMatrix* aAdditionalTransform = nullptr) const;

  /**
   * Gets the app unit amounts to clip from the left and right edges of
   * the nsTextFrame in order to paint just this rendered run.
   *
   * Note that if clip edge amounts land in the middle of a glyph, the
   * glyph won't be painted at all.  The clip edges are thus more of
   * a selection mechanism for which glyphs will be painted, rather
   * than a geometric clip.
   */

  void GetClipEdges(nscoord& aVisIStartEdge, nscoord& aVisIEndEdge) const;

  /**
   * Returns the advance width of the whole rendered run.
   */

  nscoord GetAdvanceWidth() const;

  /**
   * Returns the index of the character into this rendered run whose
   * glyph cell contains the given point, or -1 if there is no such
   * character.  This does not hit test against any overflow.
   *
   * @param aContext The context to use for unit conversions.
   * @param aPoint The point in the user space of the <text> element.
   */

  int32_t GetCharNumAtPosition(nsPresContext* aContext,
                               const gfxPoint& aPoint) const;

  /**
   * The text frame that this rendered run lies within.
   */

  nsTextFrame* mFrame;

  /**
   * The point in user space that the text is positioned at.
   *
   * For a horizontal run:
   * The x coordinate is the left edge of a LTR run of text or the right edge of
   * an RTL run.  The y coordinate is the baseline of the text.
   * For a vertical run:
   * The x coordinate is the baseline of the text.
   * The y coordinate is the top edge of a LTR run, or bottom of RTL.
   */

  gfxPoint mPosition;

  /**
   * The horizontal scale factor to apply when painting glyphs to take
   * into account textLength="".
   */

  float mLengthAdjustScaleFactor;

  /**
   * The rotation in radians in the user coordinate system that the text has.
   */

  float mRotate;

  /**
   * The scale factor that was used to transform the text run's original font
   * size into a sane range for painting and measurement.
   */

  double mFontSizeScaleFactor;

  /**
   * The baseline in app units of this text run.  The measurement is from the
   * top of the text frame. (From the left edge if vertical.)
   */

  nscoord mBaseline;

  /**
   * The offset and length in mFrame's content Text that corresponds to
   * this text rendered run.  These are original char indexes.
   */

  uint32_t mTextFrameContentOffset;
  uint32_t mTextFrameContentLength;

  /**
   * The character index in the whole SVG <text> element that this text rendered
   * run begins at.
   */

  uint32_t mTextElementCharIndex;
};

gfxMatrix TextRenderedRun::GetTransformFromUserSpaceForPainting(
    nsPresContext* aContext, const nscoord aVisIStartEdge,
    const nscoord aVisIEndEdge) const {
  // We transform to device pixels positioned such that painting the text frame
  // at (0,0) with aItem will result in the text being in the right place.

  gfxMatrix m;
  if (!mFrame) {
    return m;
  }

  float cssPxPerDevPx =
      nsPresContext::AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel());

  // Glyph position in user space.
  m.PreTranslate(mPosition / cssPxPerDevPx);

  // Take into account any font size scaling and scaling due to textLength="".
  m.PreScale(1.0 / mFontSizeScaleFactor, 1.0 / mFontSizeScaleFactor);

  // Rotation due to rotate="" or a <textPath>.
  m.PreRotate(mRotate);

  // Scale for textLength="" and translate to get the text frame
  // to the right place.
  nsPoint t;
  if (IsVertical()) {
    m.PreScale(1.0, mLengthAdjustScaleFactor);
    t = nsPoint(-mBaseline, IsRightToLeft()
                                ? -mFrame->GetRect().height + aVisIEndEdge
                                : -aVisIStartEdge);
  } else {
    m.PreScale(mLengthAdjustScaleFactor, 1.0);
    t = nsPoint(IsRightToLeft() ? -mFrame->GetRect().width + aVisIEndEdge
                                : -aVisIStartEdge,
                -mBaseline);
  }
  m.PreTranslate(AppUnitsToGfxUnits(t, aContext));

  return m;
}

gfxMatrix TextRenderedRun::GetTransformFromRunUserSpaceToUserSpace(
    nsPresContext* aContext) const {
  gfxMatrix m;
  if (!mFrame) {
    return m;
  }

  float cssPxPerDevPx =
      nsPresContext::AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel());

  nscoord start, end;
  GetClipEdges(start, end);

  // Glyph position in user space.
  m.PreTranslate(mPosition);

  // Rotation due to rotate="" or a <textPath>.
  m.PreRotate(mRotate);

  // Scale for textLength="" and translate to get the text frame
  // to the right place.

  nsPoint t;
  if (IsVertical()) {
    m.PreScale(1.0, mLengthAdjustScaleFactor);
    t = nsPoint(-mBaseline,
                IsRightToLeft() ? -mFrame->GetRect().height + start + end : 0);
  } else {
    m.PreScale(mLengthAdjustScaleFactor, 1.0);
    t = nsPoint(IsRightToLeft() ? -mFrame->GetRect().width + start + end : 0,
                -mBaseline);
  }
  m.PreTranslate(AppUnitsToGfxUnits(t, aContext) * cssPxPerDevPx /
                 mFontSizeScaleFactor);

  return m;
}

gfxMatrix TextRenderedRun::GetTransformFromRunUserSpaceToFrameUserSpace(
    nsPresContext* aContext) const {
  gfxMatrix m;
  if (!mFrame) {
    return m;
  }

  nscoord start, end;
  GetClipEdges(start, end);

  // Translate by the horizontal distance into the text frame this
  // rendered run is.
  gfxFloat appPerCssPx = AppUnitsPerCSSPixel();
  gfxPoint t = IsVertical() ? gfxPoint(0, start / appPerCssPx)
                            : gfxPoint(start / appPerCssPx, 0);
  return m.PreTranslate(t);
}

SVGBBox TextRenderedRun::GetRunUserSpaceRect(nsPresContext* aContext,
                                             uint32_t aFlags) const {
  SVGBBox r;
  if (!mFrame) {
    return r;
  }

  // Determine the amount of overflow around frame's mRect.
  //
  // We need to call InkOverflowRectRelativeToSelf because this includes
  // overflowing decorations, which the MeasureText call below does not.
  nsRect self = mFrame->InkOverflowRectRelativeToSelf();
  nsRect rect = mFrame->GetRect();
  bool vertical = IsVertical();
  nsMargin inkOverflow(
      vertical ? -self.x : -self.y,
      vertical ? self.YMost() - rect.height : self.XMost() - rect.width,
      vertical ? self.XMost() - rect.width : self.YMost() - rect.height,
      vertical ? -self.y : -self.x);

  gfxSkipCharsIterator it = mFrame->EnsureTextRun(nsTextFrame::eInflated);
  gfxSkipCharsIterator start = it;
  gfxTextRun* textRun = mFrame->GetTextRun(nsTextFrame::eInflated);

  // Get the content range for this rendered run.
  Range range = ConvertOriginalToSkipped(it, mTextFrameContentOffset,
                                         mTextFrameContentLength);
  if (range.Length() == 0) {
    return r;
  }

  // FIXME(heycam): We could create a single PropertyProvider for all
  // TextRenderedRuns that correspond to the text frame, rather than recreate
  // it each time here.
  nsTextFrame::PropertyProvider provider(mFrame, start);

  // Measure that range.
  gfxTextRun::Metrics metrics = textRun->MeasureText(
      range, gfxFont::LOOSE_INK_EXTENTS, nullptr, &provider);
  // Make sure it includes the font-box.
  gfxRect fontBox(0, -metrics.mAscent, metrics.mAdvanceWidth,
                  metrics.mAscent + metrics.mDescent);
  metrics.mBoundingBox.UnionRect(metrics.mBoundingBox, fontBox);

  // Determine the rectangle that covers the rendered run's fill,
  // taking into account the measured overflow due to decorations.
  nscoord baseline =
      NSToCoordRoundWithClamp(metrics.mBoundingBox.y + metrics.mAscent);
  gfxFloat x, width;
  if (aFlags & eNoHorizontalOverflow) {
    x = 0.0;
    width = textRun->GetAdvanceWidth(range, &provider);
    if (width < 0.0) {
      x = width;
      width = -width;
    }
  } else {
    x = metrics.mBoundingBox.x;
    width = metrics.mBoundingBox.width;
  }
  nsRect fillInAppUnits(NSToCoordRoundWithClamp(x), baseline,
                        NSToCoordRoundWithClamp(width),
                        NSToCoordRoundWithClamp(metrics.mBoundingBox.height));
  fillInAppUnits.Inflate(inkOverflow);
  if (textRun->IsVertical()) {
    // Swap line-relative textMetrics dimensions to physical coordinates.
    std::swap(fillInAppUnits.x, fillInAppUnits.y);
    std::swap(fillInAppUnits.width, fillInAppUnits.height);
  }

  // Convert the app units rectangle to user units.
  gfxRect fill = AppUnitsToFloatCSSPixels(
      gfxRect(fillInAppUnits.x, fillInAppUnits.y, fillInAppUnits.width,
              fillInAppUnits.height),
      aContext);

  // Scale the rectangle up due to any mFontSizeScaleFactor.
  fill.Scale(1.0 / mFontSizeScaleFactor);

  // Include the fill if requested.
  if (aFlags & eIncludeFill) {
    r = fill;
  }

  // Include the stroke if requested.
  if ((aFlags & eIncludeStroke) && !fill.IsEmpty() &&
      SVGUtils::GetStrokeWidth(mFrame) > 0) {
    r.UnionEdges(
        SVGUtils::PathExtentsToMaxStrokeExtents(fill, mFrame, gfxMatrix()));
  }

  return r;
}

SVGBBox TextRenderedRun::GetFrameUserSpaceRect(nsPresContext* aContext,
                                               uint32_t aFlags) const {
  SVGBBox r = GetRunUserSpaceRect(aContext, aFlags);
  if (r.IsEmpty()) {
    return r;
  }
  gfxMatrix m = GetTransformFromRunUserSpaceToFrameUserSpace(aContext);
  return m.TransformBounds(r.ToThebesRect());
}

SVGBBox TextRenderedRun::GetUserSpaceRect(
    nsPresContext* aContext, uint32_t aFlags,
    const gfxMatrix* aAdditionalTransform) const {
  SVGBBox r = GetRunUserSpaceRect(aContext, aFlags);
  if (r.IsEmpty()) {
    return r;
  }
  gfxMatrix m = GetTransformFromRunUserSpaceToUserSpace(aContext);
  if (aAdditionalTransform) {
    m *= *aAdditionalTransform;
  }
  return m.TransformBounds(r.ToThebesRect());
}

void TextRenderedRun::GetClipEdges(nscoord& aVisIStartEdge,
                                   nscoord& aVisIEndEdge) const {
  uint32_t contentLength = mFrame->GetContentLength();
  if (mTextFrameContentOffset == 0 &&
      mTextFrameContentLength == contentLength) {
    // If the rendered run covers the entire content, we know we don't need
    // to clip without having to measure anything.
    aVisIStartEdge = 0;
    aVisIEndEdge = 0;
    return;
  }

  gfxSkipCharsIterator it = mFrame->EnsureTextRun(nsTextFrame::eInflated);
  gfxTextRun* textRun = mFrame->GetTextRun(nsTextFrame::eInflated);
  nsTextFrame::PropertyProvider provider(mFrame, it);

  // Get the covered content offset/length for this rendered run in skipped
  // characters, since that is what GetAdvanceWidth expects.
  Range runRange = ConvertOriginalToSkipped(it, mTextFrameContentOffset,
                                            mTextFrameContentLength);

  // Get the offset/length of the whole nsTextFrame.
  uint32_t frameOffset = mFrame->GetContentOffset();
  uint32_t frameLength = mFrame->GetContentLength();

  // Trim the whole-nsTextFrame offset/length to remove any leading/trailing
  // white space, as the nsTextFrame when painting does not include them when
  // interpreting clip edges.
  nsTextFrame::TrimmedOffsets trimmedOffsets =
      mFrame->GetTrimmedOffsets(mFrame->TextFragment());
  TrimOffsets(frameOffset, frameLength, trimmedOffsets);

  // Convert the trimmed whole-nsTextFrame offset/length into skipped
  // characters.
  Range frameRange = ConvertOriginalToSkipped(it, frameOffset, frameLength);

  // Measure the advance width in the text run between the start of
  // frame's content and the start of the rendered run's content,
  nscoord startEdge = textRun->GetAdvanceWidth(
      Range(frameRange.start, runRange.start), &provider);

  // and between the end of the rendered run's content and the end
  // of the frame's content.
  nscoord endEdge =
      textRun->GetAdvanceWidth(Range(runRange.end, frameRange.end), &provider);

  if (textRun->IsRightToLeft()) {
    aVisIStartEdge = endEdge;
    aVisIEndEdge = startEdge;
  } else {
    aVisIStartEdge = startEdge;
    aVisIEndEdge = endEdge;
  }
}

nscoord TextRenderedRun::GetAdvanceWidth() const {
  gfxSkipCharsIterator it = mFrame->EnsureTextRun(nsTextFrame::eInflated);
  gfxTextRun* textRun = mFrame->GetTextRun(nsTextFrame::eInflated);
  nsTextFrame::PropertyProvider provider(mFrame, it);

  Range range = ConvertOriginalToSkipped(it, mTextFrameContentOffset,
                                         mTextFrameContentLength);

  return textRun->GetAdvanceWidth(range, &provider);
}

int32_t TextRenderedRun::GetCharNumAtPosition(nsPresContext* aContext,
                                              const gfxPoint& aPoint) const {
  if (mTextFrameContentLength == 0) {
    return -1;
  }

  float cssPxPerDevPx =
      nsPresContext::AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel());

  // Convert the point from user space into run user space, and take
  // into account any mFontSizeScaleFactor.
  gfxMatrix m = GetTransformFromRunUserSpaceToUserSpace(aContext);
  if (!m.Invert()) {
    return -1;
  }
  gfxPoint p = m.TransformPoint(aPoint) / cssPxPerDevPx * mFontSizeScaleFactor;

  // First check that the point lies vertically between the top and bottom
  // edges of the text.
  gfxFloat ascent, descent;
  GetAscentAndDescentInAppUnits(mFrame, ascent, descent);

  WritingMode writingMode = mFrame->GetWritingMode();
  if (writingMode.IsVertical()) {
    gfxFloat leftEdge = mFrame->GetLogicalBaseline(writingMode) -
                        (writingMode.IsVerticalRL() ? ascent : descent);
    gfxFloat rightEdge = leftEdge + ascent + descent;
    if (p.x < aContext->AppUnitsToGfxUnits(leftEdge) ||
        p.x > aContext->AppUnitsToGfxUnits(rightEdge)) {
      return -1;
    }
  } else {
    gfxFloat topEdge = mFrame->GetLogicalBaseline(writingMode) - ascent;
    gfxFloat bottomEdge = topEdge + ascent + descent;
    if (p.y < aContext->AppUnitsToGfxUnits(topEdge) ||
        p.y > aContext->AppUnitsToGfxUnits(bottomEdge)) {
      return -1;
    }
  }

  gfxSkipCharsIterator it = mFrame->EnsureTextRun(nsTextFrame::eInflated);
  gfxTextRun* textRun = mFrame->GetTextRun(nsTextFrame::eInflated);
  nsTextFrame::PropertyProvider provider(mFrame, it);

  // Next check that the point lies horizontally within the left and right
  // edges of the text.
  Range range = ConvertOriginalToSkipped(it, mTextFrameContentOffset,
                                         mTextFrameContentLength);
  gfxFloat runAdvance =
      aContext->AppUnitsToGfxUnits(textRun->GetAdvanceWidth(range, &provider));

  gfxFloat pos = writingMode.IsVertical() ? p.y : p.x;
  if (pos < 0 || pos >= runAdvance) {
    return -1;
  }

  // Finally, measure progressively smaller portions of the rendered run to
  // find which glyph it lies within.  This will need to change once we
  // support letter-spacing and word-spacing.
  bool rtl = textRun->IsRightToLeft();
  for (int32_t i = mTextFrameContentLength - 1; i >= 0; i--) {
    range = ConvertOriginalToSkipped(it, mTextFrameContentOffset, i);
    gfxFloat advance = aContext->AppUnitsToGfxUnits(
        textRun->GetAdvanceWidth(range, &provider));
    if ((rtl && pos < runAdvance - advance) || (!rtl && pos >= advance)) {
      return i;
    }
  }
  return -1;
}

// ----------------------------------------------------------------------------
// TextNodeIterator

enum SubtreePosition { eBeforeSubtree, eWithinSubtree, eAfterSubtree };

/**
 * An iterator class for Text that are descendants of a given node, the
 * root.  Nodes are iterated in document order.  An optional subtree can be
 * specified, in which case the iterator will track whether the current state of
 * the traversal over the tree is within that subtree or is past that subtree.
 */

class TextNodeIterator {
 public:
  /**
   * Constructs a TextNodeIterator with the specified root node and optional
   * subtree.
   */

  explicit TextNodeIterator(nsIContent* aRoot, nsIContent* aSubtree = nullptr)
      : mRoot(aRoot),
        mSubtree(aSubtree == aRoot ? nullptr : aSubtree),
        mCurrent(aRoot),
        mSubtreePosition(mSubtree ? eBeforeSubtree : eWithinSubtree) {
    NS_ASSERTION(aRoot, "expected non-null root");
    if (!aRoot->IsText()) {
      Next();
    }
  }

  /**
   * Returns the current Text, or null if the iterator has finished.
   */

  Text* Current() const { return mCurrent ? mCurrent->AsText() : nullptr; }

  /**
   * Advances to the next Text and returns it, or null if the end of
   * iteration has been reached.
   */

  Text* Next();

  /**
   * Returns whether the iterator is currently within the subtree rooted
   * at mSubtree.  Returns true if we are not tracking a subtree (we consider
   * that we're always within the subtree).
   */

  bool IsWithinSubtree() const { return mSubtreePosition == eWithinSubtree; }

  /**
   * Returns whether the iterator is past the subtree rooted at mSubtree.
   * Returns false if we are not tracking a subtree.
   */

  bool IsAfterSubtree() const { return mSubtreePosition == eAfterSubtree; }

 private:
  /**
   * The root under which all Text will be iterated over.
   */

  nsIContent* const mRoot;

  /**
   * The node rooting the subtree to track.
   */

  nsIContent* const mSubtree;

  /**
   * The current node during iteration.
   */

  nsIContent* mCurrent;

  /**
   * The current iterator position relative to mSubtree.
   */

  SubtreePosition mSubtreePosition;
};

Text* TextNodeIterator::Next() {
  // Starting from mCurrent, we do a non-recursive traversal to the next
  // Text beneath mRoot, updating mSubtreePosition appropriately if we
  // encounter mSubtree.
  if (mCurrent) {
    do {
      nsIContent* next =
          IsTextContentElement(mCurrent) ? mCurrent->GetFirstChild() : nullptr;
      if (next) {
        mCurrent = next;
        if (mCurrent == mSubtree) {
          mSubtreePosition = eWithinSubtree;
        }
      } else {
        for (;;) {
          if (mCurrent == mRoot) {
            mCurrent = nullptr;
            break;
          }
          if (mCurrent == mSubtree) {
            mSubtreePosition = eAfterSubtree;
          }
          next = mCurrent->GetNextSibling();
          if (next) {
            mCurrent = next;
            if (mCurrent == mSubtree) {
              mSubtreePosition = eWithinSubtree;
            }
            break;
          }
          if (mCurrent == mSubtree) {
            mSubtreePosition = eAfterSubtree;
          }
          mCurrent = mCurrent->GetParent();
        }
      }
    } while (mCurrent && !mCurrent->IsText());
  }

  return mCurrent ? mCurrent->AsText() : nullptr;
}

// ----------------------------------------------------------------------------
// TextNodeCorrespondenceRecorder

/**
 * TextNodeCorrespondence is used as the value of a frame property that
 * is stored on all its descendant nsTextFrames.  It stores the number of DOM
 * characters between it and the previous nsTextFrame that did not have an
 * nsTextFrame created for them, due to either not being in a correctly
 * parented text content element, or because they were display:none.
 * These are called "undisplayed characters".
 *
 * See also TextNodeCorrespondenceRecorder below, which is what sets the
 * frame property.
 */

struct TextNodeCorrespondence {
  explicit TextNodeCorrespondence(uint32_t aUndisplayedCharacters)
      : mUndisplayedCharacters(aUndisplayedCharacters) {}

  uint32_t mUndisplayedCharacters;
};

NS_DECLARE_FRAME_PROPERTY_DELETABLE(TextNodeCorrespondenceProperty,
                                    TextNodeCorrespondence)

/**
 * Returns the number of undisplayed characters before the specified
 * nsTextFrame.
 */

static uint32_t GetUndisplayedCharactersBeforeFrame(nsTextFrame* aFrame) {
  void* value = aFrame->GetProperty(TextNodeCorrespondenceProperty());
  TextNodeCorrespondence* correspondence =
      static_cast<TextNodeCorrespondence*>(value);
  if (!correspondence) {
    // FIXME bug 903785
    NS_ERROR(
        "expected a TextNodeCorrespondenceProperty on nsTextFrame "
        "used for SVG text");
    return 0;
  }
  return correspondence->mUndisplayedCharacters;
}

/**
 * Traverses the nsTextFrames for an SVGTextFrame and records a
 * TextNodeCorrespondenceProperty on each for the number of undisplayed DOM
 * characters between each frame.  This is done by iterating simultaneously
 * over the Text and nsTextFrames and noting when Text (or
 * parts of them) are skipped when finding the next nsTextFrame.
 */

class TextNodeCorrespondenceRecorder {
 public:
  /**
   * Entry point for the TextNodeCorrespondenceProperty recording.
   */

  static void RecordCorrespondence(SVGTextFrame* aRoot);

 private:
  explicit TextNodeCorrespondenceRecorder(SVGTextFrame* aRoot)
      : mNodeIterator(aRoot->GetContent()),
        mPreviousNode(nullptr),
        mNodeCharIndex(0) {}

  void Record(SVGTextFrame* aRoot);
  void TraverseAndRecord(nsIFrame* aFrame);

  /**
   * Returns the next non-empty Text.
   */

  Text* NextNode();

  /**
   * The iterator over the Text that we use as we simultaneously
   * iterate over the nsTextFrames.
   */

  TextNodeIterator mNodeIterator;

  /**
   * The previous Text we iterated over.
   */

  Text* mPreviousNode;

  /**
   * The index into the current Text's character content.
   */

  uint32_t mNodeCharIndex;
};

/* static */
void TextNodeCorrespondenceRecorder::RecordCorrespondence(SVGTextFrame* aRoot) {
  if (aRoot->HasAnyStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY)) {
    // Resolve bidi so that continuation frames are created if necessary:
    aRoot->MaybeResolveBidiForAnonymousBlockChild();
    TextNodeCorrespondenceRecorder recorder(aRoot);
    recorder.Record(aRoot);
    aRoot->RemoveStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY);
  }
}

void TextNodeCorrespondenceRecorder::Record(SVGTextFrame* aRoot) {
  if (!mNodeIterator.Current()) {
    // If there are no Text nodes then there is nothing to do.
    return;
  }

  // Traverse over all the nsTextFrames and record the number of undisplayed
  // characters.
  TraverseAndRecord(aRoot);

  // Find how many undisplayed characters there are after the final nsTextFrame.
  uint32_t undisplayed = 0;
  if (mNodeIterator.Current()) {
    if (mPreviousNode && mPreviousNode->TextLength() != mNodeCharIndex) {
      // The last nsTextFrame ended part way through a Text node.  The
      // remaining characters count as undisplayed.
      NS_ASSERTION(mNodeCharIndex < mPreviousNode->TextLength(),
                   "incorrect tracking of undisplayed characters in "
                   "text nodes");
      undisplayed += mPreviousNode->TextLength() - mNodeCharIndex;
    }
    // All the remaining Text that we iterate must also be undisplayed.
    for (Text* textNode = mNodeIterator.Current(); textNode;
         textNode = NextNode()) {
      undisplayed += textNode->TextLength();
    }
  }

  // Record the trailing number of undisplayed characters on the
  // SVGTextFrame.
  aRoot->mTrailingUndisplayedCharacters = undisplayed;
}

Text* TextNodeCorrespondenceRecorder::NextNode() {
  mPreviousNode = mNodeIterator.Current();
  Text* next;
  do {
    next = mNodeIterator.Next();
  } while (next && next->TextLength() == 0);
  return next;
}

void TextNodeCorrespondenceRecorder::TraverseAndRecord(nsIFrame* aFrame) {
  // Recursively iterate over the frame tree, for frames that correspond
  // to text content elements.
  if (IsTextContentElement(aFrame->GetContent())) {
    for (nsIFrame* f : aFrame->PrincipalChildList()) {
      TraverseAndRecord(f);
    }
    return;
  }

  nsTextFrame* frame;  // The current text frame.
  Text* node;          // The text node for the current text frame.
  if (!GetNonEmptyTextFrameAndNode(aFrame, frame, node)) {
    // If this isn't an nsTextFrame, or is empty, nothing to do.
    return;
  }

  NS_ASSERTION(frame->GetContentOffset() >= 0,
               "don't know how to handle negative content indexes");

  uint32_t undisplayed = 0;
  if (!mPreviousNode) {
    // Must be the very first text frame.
    NS_ASSERTION(mNodeCharIndex == 0,
                 "incorrect tracking of undisplayed "
                 "characters in text nodes");
    if (!mNodeIterator.Current()) {
      MOZ_ASSERT_UNREACHABLE(
          "incorrect tracking of correspondence between "
          "text frames and text nodes");
    } else {
      // Each whole Text we find before we get to the text node for the
      // first text frame must be undisplayed.
      while (mNodeIterator.Current() != node) {
        undisplayed += mNodeIterator.Current()->TextLength();
        NextNode();
      }
      // If the first text frame starts at a non-zero content offset, then those
      // earlier characters are also undisplayed.
      undisplayed += frame->GetContentOffset();
      NextNode();
    }
  } else if (mPreviousNode == node) {
    // Same text node as last time.
    if (static_cast<uint32_t>(frame->GetContentOffset()) != mNodeCharIndex) {
      // We have some characters in the middle of the text node
      // that are undisplayed.
      NS_ASSERTION(
          mNodeCharIndex < static_cast<uint32_t>(frame->GetContentOffset()),
          "incorrect tracking of undisplayed characters in "
          "text nodes");
      undisplayed = frame->GetContentOffset() - mNodeCharIndex;
    }
  } else {
    // Different text node from last time.
    if (mPreviousNode->TextLength() != mNodeCharIndex) {
      NS_ASSERTION(mNodeCharIndex < mPreviousNode->TextLength(),
                   "incorrect tracking of undisplayed characters in "
                   "text nodes");
      // Any trailing characters at the end of the previous Text are
      // undisplayed.
      undisplayed = mPreviousNode->TextLength() - mNodeCharIndex;
    }
    // Each whole Text we find before we get to the text node for
    // the current text frame must be undisplayed.
    while (mNodeIterator.Current() && mNodeIterator.Current() != node) {
      undisplayed += mNodeIterator.Current()->TextLength();
      NextNode();
    }
    // If the current text frame starts at a non-zero content offset, then those
    // earlier characters are also undisplayed.
    undisplayed += frame->GetContentOffset();
    NextNode();
  }

  // Set the frame property.
  frame->SetProperty(TextNodeCorrespondenceProperty(),
                     new TextNodeCorrespondence(undisplayed));

  // Remember how far into the current Text we are.
  mNodeCharIndex = frame->GetContentEnd();
}

// ----------------------------------------------------------------------------
// TextFrameIterator

/**
 * An iterator class for nsTextFrames that are descendants of an
 * SVGTextFrame.  The iterator can optionally track whether the
 * current nsTextFrame is for a descendant of, or past, a given subtree
 * content node or frame.  (This functionality is used for example by the SVG
 * DOM text methods to get only the nsTextFrames for a particular <tspan>.)
 *
 * TextFrameIterator also tracks and exposes other information about the
 * current nsTextFrame:
 *
 *   * how many undisplayed characters came just before it
 *   * its position (in app units) relative to the SVGTextFrame's anonymous
 *     block frame
 *   * what nsInlineFrame corresponding to a <textPath> element it is a
 *     descendant of
 *   * what computed dominant-baseline value applies to it
 *
 * Note that any text frames that are empty -- whose ContentLength() is 0 --
 * will be skipped over.
 */

class MOZ_STACK_CLASS TextFrameIterator {
 public:
  /**
   * Constructs a TextFrameIterator for the specified SVGTextFrame
   * with an optional frame subtree to restrict iterated text frames to.
   */

  explicit TextFrameIterator(SVGTextFrame* aRoot,
                             const nsIFrame* aSubtree = nullptr)
      : mRootFrame(aRoot),
        mSubtree(aSubtree),
        mCurrentFrame(aRoot),
        mSubtreePosition(mSubtree ? eBeforeSubtree : eWithinSubtree) {
    Init();
  }

  /**
   * Constructs a TextFrameIterator for the specified SVGTextFrame
   * with an optional frame content subtree to restrict iterated text frames to.
   */

  TextFrameIterator(SVGTextFrame* aRoot, nsIContent* aSubtree)
      : mRootFrame(aRoot),
        mSubtree(aRoot && aSubtree && aSubtree != aRoot->GetContent()
                     ? aSubtree->GetPrimaryFrame()
                     : nullptr),
        mCurrentFrame(aRoot),
        mSubtreePosition(mSubtree ? eBeforeSubtree : eWithinSubtree) {
    Init();
  }

  /**
   * Returns the root SVGTextFrame this TextFrameIterator is iterating over.
   */

  SVGTextFrame* Root() const { return mRootFrame; }

  /**
   * Returns the current nsTextFrame.
   */

  nsTextFrame* Current() const { return do_QueryFrame(mCurrentFrame); }

  /**
   * Returns the number of undisplayed characters in the DOM just before the
   * current frame.
   */

  uint32_t UndisplayedCharacters() const;

  /**
   * Returns the current frame's position, in app units, relative to the
   * root SVGTextFrame's anonymous block frame.
   */

  nsPoint Position() const { return mCurrentPosition; }

  /**
   * Advances to the next nsTextFrame and returns it.
   */

  nsTextFrame* Next();

  /**
   * Returns whether the iterator is within the subtree.
   */

  bool IsWithinSubtree() const { return mSubtreePosition == eWithinSubtree; }

  /**
   * Returns whether the iterator is past the subtree.
   */

  bool IsAfterSubtree() const { return mSubtreePosition == eAfterSubtree; }

  /**
   * Returns the frame corresponding to the <textPath> element, if we
   * are inside one.
   */

  nsIFrame* TextPathFrame() const {
    return mTextPathFrames.IsEmpty() ? nullptr : mTextPathFrames.LastElement();
  }

  /**
   * Returns the current frame's computed dominant-baseline value.
   */

  StyleDominantBaseline DominantBaseline() const {
    return mBaselines.LastElement();
  }

  /**
   * Finishes the iterator.
   */

  void Close() { mCurrentFrame = nullptr; }

 private:
  /**
   * Initializes the iterator and advances to the first item.
   */

  void Init() {
    if (!mRootFrame) {
      return;
    }

    mBaselines.AppendElement(mRootFrame->StyleSVG()->mDominantBaseline);
    Next();
  }

  /**
   * Pushes the specified frame's computed dominant-baseline value.
   * If the value of the property is "auto", then the parent frame's
   * computed value is used.
   */

  void PushBaseline(nsIFrame* aNextFrame);

  /**
   * Pops the current dominant-baseline off the stack.
   */

  void PopBaseline();

  /**
   * The root frame we are iterating through.
   */

  SVGTextFrame* const mRootFrame;

  /**
   * The frame for the subtree we are also interested in tracking.
   */

  const nsIFrame* const mSubtree;

  /**
   * The current value of the iterator.
   */

  nsIFrame* mCurrentFrame;

  /**
   * The position, in app units, of the current frame relative to mRootFrame.
   */

  nsPoint mCurrentPosition;

  /**
   * Stack of frames corresponding to <textPath> elements that are in scope
   * for the current frame.
   */

  AutoTArray<nsIFrame*, 1> mTextPathFrames;

  /**
   * Stack of dominant-baseline values to record as we traverse through the
   * frame tree.
   */

  AutoTArray<StyleDominantBaseline, 8> mBaselines;

  /**
   * The iterator's current position relative to mSubtree.
   */

  SubtreePosition mSubtreePosition;
};

uint32_t TextFrameIterator::UndisplayedCharacters() const {
  MOZ_ASSERT(
      !mRootFrame->HasAnyStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY),
      "Text correspondence must be up to date");

  if (!mCurrentFrame) {
    return mRootFrame->mTrailingUndisplayedCharacters;
  }

  nsTextFrame* frame = do_QueryFrame(mCurrentFrame);
  return GetUndisplayedCharactersBeforeFrame(frame);
}

nsTextFrame* TextFrameIterator::Next() {
  // Starting from mCurrentFrame, we do a non-recursive traversal to the next
  // nsTextFrame beneath mRoot, updating mSubtreePosition appropriately if we
  // encounter mSubtree.
  if (mCurrentFrame) {
    do {
      nsIFrame* next = IsTextContentElement(mCurrentFrame->GetContent())
                           ? mCurrentFrame->PrincipalChildList().FirstChild()
                           : nullptr;
      if (next) {
        // Descend into this frame, and accumulate its position.
        mCurrentPosition += next->GetPosition();
        if (next->GetContent()->IsSVGElement(nsGkAtoms::textPath)) {
          // Record this <textPath> frame.
          mTextPathFrames.AppendElement(next);
        }
        // Record the frame's baseline.
        PushBaseline(next);
        mCurrentFrame = next;
        if (mCurrentFrame == mSubtree) {
          // If the current frame is mSubtree, we have now moved into it.
          mSubtreePosition = eWithinSubtree;
        }
      } else {
        for (;;) {
          // We want to move past the current frame.
          if (mCurrentFrame == mRootFrame) {
            // If we've reached the root frame, we're finished.
            mCurrentFrame = nullptr;
            break;
          }
          // Remove the current frame's position.
          mCurrentPosition -= mCurrentFrame->GetPosition();
          if (mCurrentFrame->GetContent()->IsSVGElement(nsGkAtoms::textPath)) {
            // Pop off the <textPath> frame if this is a <textPath>.
            mTextPathFrames.RemoveLastElement();
          }
          // Pop off the current baseline.
          PopBaseline();
          if (mCurrentFrame == mSubtree) {
            // If this was mSubtree, we have now moved past it.
            mSubtreePosition = eAfterSubtree;
          }
          next = mCurrentFrame->GetNextSibling();
          if (next) {
            // Moving to the next sibling.
            mCurrentPosition += next->GetPosition();
            if (next->GetContent()->IsSVGElement(nsGkAtoms::textPath)) {
              // Record this <textPath> frame.
              mTextPathFrames.AppendElement(next);
            }
            // Record the frame's baseline.
            PushBaseline(next);
            mCurrentFrame = next;
            if (mCurrentFrame == mSubtree) {
              // If the current frame is mSubtree, we have now moved into it.
              mSubtreePosition = eWithinSubtree;
            }
            break;
          }
          if (mCurrentFrame == mSubtree) {
            // If there is no next sibling frame, and the current frame is
            // mSubtree, we have now moved past it.
            mSubtreePosition = eAfterSubtree;
          }
          // Ascend out of this frame.
          mCurrentFrame = mCurrentFrame->GetParent();
        }
      }
    } while (mCurrentFrame && !IsNonEmptyTextFrame(mCurrentFrame));
  }

  return Current();
}

void TextFrameIterator::PushBaseline(nsIFrame* aNextFrame) {
  StyleDominantBaseline baseline = aNextFrame->StyleSVG()->mDominantBaseline;
  mBaselines.AppendElement(baseline);
}

void TextFrameIterator::PopBaseline() {
  NS_ASSERTION(!mBaselines.IsEmpty(), "popped too many baselines");
  mBaselines.RemoveLastElement();
}

// -----------------------------------------------------------------------------
// TextRenderedRunIterator

/**
 * Iterator for TextRenderedRun objects for the SVGTextFrame.
 */

class TextRenderedRunIterator {
 public:
  /**
   * Values for the aFilter argument of the constructor, to indicate which
   * frames we should be limited to iterating TextRenderedRun objects for.
   */

  enum RenderedRunFilter {
    // Iterate TextRenderedRuns for all nsTextFrames.
    eAllFrames,
    // Iterate only TextRenderedRuns for nsTextFrames that are
    // visibility:visible.
    eVisibleFrames
  };

  /**
   * Constructs a TextRenderedRunIterator with an optional frame subtree to
   * restrict iterated rendered runs to.
   *
   * @param aSVGTextFrame The SVGTextFrame whose rendered runs to iterate
   *   through.
   * @param aFilter Indicates whether to iterate rendered runs for non-visible
   *   nsTextFrames.
   * @param aSubtree An optional frame subtree to restrict iterated rendered
   *   runs to.
   */

  explicit TextRenderedRunIterator(SVGTextFrame* aSVGTextFrame,
                                   RenderedRunFilter aFilter = eAllFrames,
                                   const nsIFrame* aSubtree = nullptr)
      : mFrameIterator(FrameIfAnonymousChildReflowed(aSVGTextFrame), aSubtree),
        mFilter(aFilter),
        mTextElementCharIndex(0),
        mFrameStartTextElementCharIndex(0),
        mFontSizeScaleFactor(aSVGTextFrame->mFontSizeScaleFactor),
        mCurrent(First()) {}

  /**
   * Constructs a TextRenderedRunIterator with a content subtree to restrict
   * iterated rendered runs to.
   *
   * @param aSVGTextFrame The SVGTextFrame whose rendered runs to iterate
   *   through.
   * @param aFilter Indicates whether to iterate rendered runs for non-visible
   *   nsTextFrames.
   * @param aSubtree A content subtree to restrict iterated rendered runs to.
   */

  TextRenderedRunIterator(SVGTextFrame* aSVGTextFrame,
                          RenderedRunFilter aFilter, nsIContent* aSubtree)
      : mFrameIterator(FrameIfAnonymousChildReflowed(aSVGTextFrame), aSubtree),
        mFilter(aFilter),
        mTextElementCharIndex(0),
        mFrameStartTextElementCharIndex(0),
        mFontSizeScaleFactor(aSVGTextFrame->mFontSizeScaleFactor),
        mCurrent(First()) {}

  /**
   * Returns the current TextRenderedRun.
   */

  TextRenderedRun Current() const { return mCurrent; }

  /**
   * Advances to the next TextRenderedRun and returns it.
   */

  TextRenderedRun Next();

 private:
  /**
   * Returns the root SVGTextFrame this iterator is for.
   */

  SVGTextFrame* Root() const { return mFrameIterator.Root(); }

  /**
   * Advances to the first TextRenderedRun and returns it.
   */

  TextRenderedRun First();

  /**
   * The frame iterator to use.
   */

  TextFrameIterator mFrameIterator;

  /**
   * The filter indicating which TextRenderedRuns to return.
   */

  RenderedRunFilter mFilter;

  /**
   * The character index across the entire <text> element we are currently
   * up to.
   */

  uint32_t mTextElementCharIndex;

  /**
   * The character index across the entire <text> for the start of the current
   * frame.
   */

  uint32_t mFrameStartTextElementCharIndex;

  /**
   * The font-size scale factor we used when constructing the nsTextFrames.
   */

  double mFontSizeScaleFactor;

  /**
   * The current TextRenderedRun.
   */

  TextRenderedRun mCurrent;
};

TextRenderedRun TextRenderedRunIterator::Next() {
  if (!mFrameIterator.Current()) {
    // If there are no more frames, then there are no more rendered runs to
    // return.
    mCurrent = TextRenderedRun();
    return mCurrent;
  }

  // The values we will use to initialize the TextRenderedRun with.
  nsTextFrame* frame;
  gfxPoint pt;
  double rotate;
  nscoord baseline;
  uint32_t offset, length;
  uint32_t charIndex;

  // We loop, because we want to skip over rendered runs that either aren't
  // within our subtree of interest, because they don't match the filter,
  // or because they are hidden due to having fallen off the end of a
  // <textPath>.
  for (;;) {
    if (mFrameIterator.IsAfterSubtree()) {
      mCurrent = TextRenderedRun();
      return mCurrent;
    }

    frame = mFrameIterator.Current();

    charIndex = mTextElementCharIndex;

    // Find the end of the rendered run, by looking through the
    // SVGTextFrame's positions array until we find one that is recorded
    // as a run boundary.
    uint32_t runStart,
        runEnd;  // XXX Replace runStart with mTextElementCharIndex.
    runStart = mTextElementCharIndex;
    runEnd = runStart + 1;
    while (runEnd < Root()->mPositions.Length() &&
           !Root()->mPositions[runEnd].mRunBoundary) {
      runEnd++;
    }

    // Convert the global run start/end indexes into an offset/length into the
    // current frame's Text.
    offset =
        frame->GetContentOffset() + runStart - mFrameStartTextElementCharIndex;
    length = runEnd - runStart;

    // If the end of the frame's content comes before the run boundary we found
    // in SVGTextFrame's position array, we need to shorten the rendered run.
    uint32_t contentEnd = frame->GetContentEnd();
    if (offset + length > contentEnd) {
      length = contentEnd - offset;
    }

    NS_ASSERTION(offset >= uint32_t(frame->GetContentOffset()),
                 "invalid offset");
    NS_ASSERTION(offset + length <= contentEnd, "invalid offset or length");

    // Get the frame's baseline position.
    frame->EnsureTextRun(nsTextFrame::eInflated);
    baseline = GetBaselinePosition(
        frame, frame->GetTextRun(nsTextFrame::eInflated),
        mFrameIterator.DominantBaseline(), mFontSizeScaleFactor);

    // Trim the offset/length to remove any leading/trailing white space.
    uint32_t untrimmedOffset = offset;
    uint32_t untrimmedLength = length;
    nsTextFrame::TrimmedOffsets trimmedOffsets =
        frame->GetTrimmedOffsets(frame->TextFragment());
    TrimOffsets(offset, length, trimmedOffsets);
    charIndex += offset - untrimmedOffset;

    // Get the position and rotation of the character that begins this
    // rendered run.
    pt = Root()->mPositions[charIndex].mPosition;
    rotate = Root()->mPositions[charIndex].mAngle;

    // Determine if we should skip this rendered run.
    bool skip = !mFrameIterator.IsWithinSubtree() ||
                Root()->mPositions[mTextElementCharIndex].mHidden;
    if (mFilter == eVisibleFrames) {
      skip = skip || !frame->StyleVisibility()->IsVisible();
    }

    // Update our global character index to move past the characters
    // corresponding to this rendered run.
    mTextElementCharIndex += untrimmedLength;

    // If we have moved past the end of the current frame's content, we need to
--> --------------------

--> maximum size reached

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

92%


¤ Dauer der Verarbeitung: 0.52 Sekunden  (vorverarbeitet)  ¤

*© 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 ist noch experimentell.