Source code
Revision control
Copy as Markdown
Other Tools
/* -*- 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
// 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:
*
* <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.
*
*/
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) {
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