/* -*- 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/. */
/** * 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));
}
/** * 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).
*/ staticvoid 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());
/** * Updates an interval by intersecting it with another interval. * The intervals are specified using a start index and a length.
*/ staticvoid IntersectInterval(uint32_t& aStart, uint32_t& aLength,
uint32_t aStartOther, uint32_t aLengthOther) {
uint32_t aEnd = aStart + aLength;
uint32_t aEndOther = aStartOther + aLengthOther;
/** * Intersects an interval as IntersectInterval does but by taking * the offset and length of the other interval from a * nsTextFrame::TrimmedOffsets object.
*/ staticvoid 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
*/ staticbool IsTextContentElement(nsIContent* aContent) { if (aContent->IsSVGElement(nsGkAtoms::text)) {
nsIContent* parent = GetFirstNonAAncestor(aContent->GetParent()); return !parent || !IsTextContentElement(parent);
}
/** * Returns whether the specified frame is an nsTextFrame that has some text * content.
*/ staticbool IsNonEmptyTextFrame(nsIFrame* aFrame) {
nsTextFrame* textFrame = do_QueryFrame(aFrame); if (!textFrame) { returnfalse;
}
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.
*/ staticbool 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.
*/ staticbool 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);
/** * 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> staticvoid 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). staticfloat 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));
}
/** * 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.
// 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;
}
// 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);
}
// 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;
}
// 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);
// 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);
// 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));
// 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;
}
/** * 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());
}
/** * 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) {}
/** * 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.
*/ staticvoid RecordCorrespondence(SVGTextFrame* aRoot);
/** * 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();
}
} elseif (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();
}
/** * 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();
}
/** * 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* 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));
}
/** * 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;
}
// 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
--> --------------------
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.33Angebot
¤
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.