michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: // Main header first: michael@0: #include "SVGTextFrame.h" michael@0: michael@0: // Keep others in (case-insensitive) order: michael@0: #include "DOMSVGPoint.h" michael@0: #include "gfx2DGlue.h" michael@0: #include "gfxFont.h" michael@0: #include "gfxSkipChars.h" michael@0: #include "gfxTypes.h" michael@0: #include "LookAndFeel.h" michael@0: #include "mozilla/gfx/2D.h" michael@0: #include "nsAlgorithm.h" michael@0: #include "nsBlockFrame.h" michael@0: #include "nsCaret.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsIDOMSVGLength.h" michael@0: #include "nsISelection.h" michael@0: #include "nsQuickSort.h" michael@0: #include "nsRenderingContext.h" michael@0: #include "nsSVGEffects.h" michael@0: #include "nsSVGOuterSVGFrame.h" michael@0: #include "nsSVGPaintServerFrame.h" michael@0: #include "mozilla/dom/SVGRect.h" michael@0: #include "nsSVGIntegrationUtils.h" michael@0: #include "nsSVGUtils.h" michael@0: #include "nsTArray.h" michael@0: #include "nsTextFrame.h" michael@0: #include "nsTextNode.h" michael@0: #include "SVGAnimatedNumberList.h" michael@0: #include "SVGContentUtils.h" michael@0: #include "SVGLengthList.h" michael@0: #include "SVGNumberList.h" michael@0: #include "SVGPathElement.h" michael@0: #include "SVGTextPathElement.h" michael@0: #include "nsLayoutUtils.h" michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: using namespace mozilla::gfx; michael@0: michael@0: // ============================================================================ michael@0: // Utility functions michael@0: michael@0: /** michael@0: * Using the specified gfxSkipCharsIterator, converts an offset and length michael@0: * in original char indexes to skipped char indexes. michael@0: * michael@0: * @param aIterator The gfxSkipCharsIterator to use for the conversion. michael@0: * @param aOriginalOffset The original offset (input). michael@0: * @param aOriginalLength The original length (input). michael@0: * @param aSkippedOffset The skipped offset (output). michael@0: * @param aSkippedLength The skipped length (output). michael@0: */ michael@0: static void michael@0: ConvertOriginalToSkipped(gfxSkipCharsIterator& aIterator, michael@0: uint32_t aOriginalOffset, uint32_t aOriginalLength, michael@0: uint32_t& aSkippedOffset, uint32_t& aSkippedLength) michael@0: { michael@0: aSkippedOffset = aIterator.ConvertOriginalToSkipped(aOriginalOffset); michael@0: aIterator.AdvanceOriginal(aOriginalLength); michael@0: aSkippedLength = aIterator.GetSkippedOffset() - aSkippedOffset; michael@0: } michael@0: michael@0: /** michael@0: * Using the specified gfxSkipCharsIterator, converts an offset and length michael@0: * in original char indexes to skipped char indexes in place. michael@0: * michael@0: * @param aIterator The gfxSkipCharsIterator to use for the conversion. michael@0: * @param aOriginalOffset The offset to convert from original to skipped. michael@0: * @param aOriginalLength The length to convert from original to skipped. michael@0: */ michael@0: static void michael@0: ConvertOriginalToSkipped(gfxSkipCharsIterator& aIterator, michael@0: uint32_t& aOffset, uint32_t& aLength) michael@0: { michael@0: ConvertOriginalToSkipped(aIterator, aOffset, aLength, aOffset, aLength); michael@0: } michael@0: michael@0: /** michael@0: * Converts an nsPoint from app units to user space units using the specified michael@0: * nsPresContext and returns it as a gfxPoint. michael@0: */ michael@0: static gfxPoint michael@0: AppUnitsToGfxUnits(const nsPoint& aPoint, const nsPresContext* aContext) michael@0: { michael@0: return gfxPoint(aContext->AppUnitsToGfxUnits(aPoint.x), michael@0: aContext->AppUnitsToGfxUnits(aPoint.y)); michael@0: } michael@0: michael@0: /** michael@0: * Converts a gfxRect that is in app units to CSS pixels using the specified michael@0: * nsPresContext and returns it as a gfxRect. michael@0: */ michael@0: static gfxRect michael@0: AppUnitsToFloatCSSPixels(const gfxRect& aRect, const nsPresContext* aContext) michael@0: { michael@0: return gfxRect(aContext->AppUnitsToFloatCSSPixels(aRect.x), michael@0: aContext->AppUnitsToFloatCSSPixels(aRect.y), michael@0: aContext->AppUnitsToFloatCSSPixels(aRect.width), michael@0: aContext->AppUnitsToFloatCSSPixels(aRect.height)); michael@0: } michael@0: michael@0: /** michael@0: * Scales a gfxRect around a given point. michael@0: * michael@0: * @param aRect The rectangle to scale. michael@0: * @param aPoint The point around which to scale. michael@0: * @param aScale The scale amount. michael@0: */ michael@0: static void michael@0: ScaleAround(gfxRect& aRect, const gfxPoint& aPoint, double aScale) michael@0: { michael@0: aRect.x = aPoint.x - aScale * (aPoint.x - aRect.x); michael@0: aRect.y = aPoint.y - aScale * (aPoint.y - aRect.y); michael@0: aRect.width *= aScale; michael@0: aRect.height *= aScale; michael@0: } michael@0: michael@0: /** michael@0: * Returns whether a gfxPoint lies within a gfxRect. michael@0: */ michael@0: static bool michael@0: Inside(const gfxRect& aRect, const gfxPoint& aPoint) michael@0: { michael@0: return aPoint.x >= aRect.x && michael@0: aPoint.x < aRect.XMost() && michael@0: aPoint.y >= aRect.y && michael@0: aPoint.y < aRect.YMost(); michael@0: } michael@0: michael@0: /** michael@0: * Gets the measured ascent and descent of the text in the given nsTextFrame michael@0: * in app units. michael@0: * michael@0: * @param aFrame The text frame. michael@0: * @param aAscent The ascent in app units (output). michael@0: * @param aDescent The descent in app units (output). michael@0: */ michael@0: static void michael@0: GetAscentAndDescentInAppUnits(nsTextFrame* aFrame, michael@0: gfxFloat& aAscent, gfxFloat& aDescent) michael@0: { michael@0: gfxSkipCharsIterator it = aFrame->EnsureTextRun(nsTextFrame::eInflated); michael@0: gfxTextRun* textRun = aFrame->GetTextRun(nsTextFrame::eInflated); michael@0: michael@0: uint32_t offset, length; michael@0: ConvertOriginalToSkipped(it, michael@0: aFrame->GetContentOffset(), michael@0: aFrame->GetContentLength(), michael@0: offset, length); michael@0: michael@0: gfxTextRun::Metrics metrics = michael@0: textRun->MeasureText(offset, length, gfxFont::LOOSE_INK_EXTENTS, nullptr, michael@0: nullptr); michael@0: michael@0: aAscent = metrics.mAscent; michael@0: aDescent = metrics.mDescent; michael@0: } michael@0: michael@0: /** michael@0: * Updates an interval by intersecting it with another interval. michael@0: * The intervals are specified using a start index and a length. michael@0: */ michael@0: static void michael@0: IntersectInterval(uint32_t& aStart, uint32_t& aLength, michael@0: uint32_t aStartOther, uint32_t aLengthOther) michael@0: { michael@0: uint32_t aEnd = aStart + aLength; michael@0: uint32_t aEndOther = aStartOther + aLengthOther; michael@0: michael@0: if (aStartOther >= aEnd || aStart >= aEndOther) { michael@0: aLength = 0; michael@0: } else { michael@0: if (aStartOther >= aStart) michael@0: aStart = aStartOther; michael@0: aLength = std::min(aEnd, aEndOther) - aStart; michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Intersects an interval as IntersectInterval does but by taking michael@0: * the offset and length of the other interval from a michael@0: * nsTextFrame::TrimmedOffsets object. michael@0: */ michael@0: static void michael@0: TrimOffsets(uint32_t& aStart, uint32_t& aLength, michael@0: const nsTextFrame::TrimmedOffsets& aTrimmedOffsets) michael@0: { michael@0: IntersectInterval(aStart, aLength, michael@0: aTrimmedOffsets.mStart, aTrimmedOffsets.mLength); michael@0: } michael@0: michael@0: /** michael@0: * Returns the closest ancestor-or-self node that is not an SVG michael@0: * element. michael@0: */ michael@0: static nsIContent* michael@0: GetFirstNonAAncestor(nsIContent* aContent) michael@0: { michael@0: while (aContent && aContent->IsSVG(nsGkAtoms::a)) { michael@0: aContent = aContent->GetParent(); michael@0: } michael@0: return aContent; michael@0: } michael@0: michael@0: /** michael@0: * Returns whether the given node is a text content element[1], taking into michael@0: * account whether it has a valid parent. michael@0: * michael@0: * For example, in: michael@0: * michael@0: * michael@0: * michael@0: * michael@0: * michael@0: * michael@0: * true would be returned for the outer element and the element, michael@0: * and false for the inner element (since a is not allowed michael@0: * to be a child of another ) and the element (because it michael@0: * must be inside a subtree). michael@0: * michael@0: * Note that we don't support the element yet and this function michael@0: * returns false for it. michael@0: * michael@0: * [1] https://svgwg.org/svg2-draft/intro.html#TermTextContentElement michael@0: */ michael@0: static bool michael@0: IsTextContentElement(nsIContent* aContent) michael@0: { michael@0: if (!aContent->IsSVG()) { michael@0: return false; michael@0: } michael@0: michael@0: if (aContent->Tag() == nsGkAtoms::text) { michael@0: nsIContent* parent = GetFirstNonAAncestor(aContent->GetParent()); michael@0: return !parent || !IsTextContentElement(parent); michael@0: } michael@0: michael@0: if (aContent->Tag() == nsGkAtoms::textPath) { michael@0: nsIContent* parent = GetFirstNonAAncestor(aContent->GetParent()); michael@0: return parent && parent->IsSVG(nsGkAtoms::text); michael@0: } michael@0: michael@0: if (aContent->Tag() == nsGkAtoms::a || michael@0: aContent->Tag() == nsGkAtoms::tspan || michael@0: aContent->Tag() == nsGkAtoms::altGlyph) { michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: /** michael@0: * Returns whether the specified frame is an nsTextFrame that has some text michael@0: * content. michael@0: */ michael@0: static bool michael@0: IsNonEmptyTextFrame(nsIFrame* aFrame) michael@0: { michael@0: nsTextFrame* textFrame = do_QueryFrame(aFrame); michael@0: if (!textFrame) { michael@0: return false; michael@0: } michael@0: michael@0: return textFrame->GetContentLength() != 0; michael@0: } michael@0: michael@0: /** michael@0: * Takes an nsIFrame and if it is a text frame that has some text content, michael@0: * returns it as an nsTextFrame and its corresponding nsTextNode. michael@0: * michael@0: * @param aFrame The frame to look at. michael@0: * @param aTextFrame aFrame as an nsTextFrame (output). michael@0: * @param aTextNode The nsTextNode content of aFrame (output). michael@0: * @return true if aFrame is a non-empty text frame, false otherwise. michael@0: */ michael@0: static bool michael@0: GetNonEmptyTextFrameAndNode(nsIFrame* aFrame, michael@0: nsTextFrame*& aTextFrame, michael@0: nsTextNode*& aTextNode) michael@0: { michael@0: nsTextFrame* text = do_QueryFrame(aFrame); michael@0: if (!text) { michael@0: return false; michael@0: } michael@0: michael@0: nsIContent* content = text->GetContent(); michael@0: NS_ASSERTION(content && content->IsNodeOfType(nsINode::eTEXT), michael@0: "unexpected content type for nsTextFrame"); michael@0: michael@0: nsTextNode* node = static_cast(content); michael@0: if (node->TextLength() == 0) { michael@0: return false; michael@0: } michael@0: michael@0: aTextFrame = text; michael@0: aTextNode = node; michael@0: return true; michael@0: } michael@0: michael@0: /** michael@0: * Returns whether the specified atom is for one of the five michael@0: * glyph positioning attributes that can appear on SVG text michael@0: * elements -- x, y, dx, dy or rotate. michael@0: */ michael@0: static bool michael@0: IsGlyphPositioningAttribute(nsIAtom* aAttribute) michael@0: { michael@0: return aAttribute == nsGkAtoms::x || michael@0: aAttribute == nsGkAtoms::y || michael@0: aAttribute == nsGkAtoms::dx || michael@0: aAttribute == nsGkAtoms::dy || michael@0: aAttribute == nsGkAtoms::rotate; michael@0: } michael@0: michael@0: /** michael@0: * Returns the position in app units of a given baseline (using an michael@0: * SVG dominant-baseline property value) for a given nsTextFrame. michael@0: * michael@0: * @param aFrame The text frame to inspect. michael@0: * @param aTextRun The text run of aFrame. michael@0: * @param aDominantBaseline The dominant-baseline value to use. michael@0: */ michael@0: static nscoord michael@0: GetBaselinePosition(nsTextFrame* aFrame, michael@0: gfxTextRun* aTextRun, michael@0: uint8_t aDominantBaseline) michael@0: { michael@0: switch (aDominantBaseline) { michael@0: case NS_STYLE_DOMINANT_BASELINE_HANGING: michael@0: case NS_STYLE_DOMINANT_BASELINE_TEXT_BEFORE_EDGE: michael@0: return 0; michael@0: case NS_STYLE_DOMINANT_BASELINE_USE_SCRIPT: michael@0: case NS_STYLE_DOMINANT_BASELINE_NO_CHANGE: michael@0: case NS_STYLE_DOMINANT_BASELINE_RESET_SIZE: michael@0: // These three should not simply map to 'baseline', but we don't michael@0: // support the complex baseline model that SVG 1.1 has and which michael@0: // css3-linebox now defines. michael@0: // (fall through) michael@0: case NS_STYLE_DOMINANT_BASELINE_AUTO: michael@0: case NS_STYLE_DOMINANT_BASELINE_ALPHABETIC: michael@0: return aFrame->GetBaseline(); michael@0: } michael@0: michael@0: gfxTextRun::Metrics metrics = michael@0: aTextRun->MeasureText(0, aTextRun->GetLength(), gfxFont::LOOSE_INK_EXTENTS, michael@0: nullptr, nullptr); michael@0: michael@0: switch (aDominantBaseline) { michael@0: case NS_STYLE_DOMINANT_BASELINE_TEXT_AFTER_EDGE: michael@0: case NS_STYLE_DOMINANT_BASELINE_IDEOGRAPHIC: michael@0: return metrics.mAscent + metrics.mDescent; michael@0: case NS_STYLE_DOMINANT_BASELINE_CENTRAL: michael@0: case NS_STYLE_DOMINANT_BASELINE_MIDDLE: michael@0: case NS_STYLE_DOMINANT_BASELINE_MATHEMATICAL: michael@0: return (metrics.mAscent + metrics.mDescent) / 2.0; michael@0: } michael@0: michael@0: NS_NOTREACHED("unexpected dominant-baseline value"); michael@0: return aFrame->GetBaseline(); michael@0: } michael@0: michael@0: /** michael@0: * For a given text run, returns the number of skipped characters that comprise michael@0: * the ligature group and/or cluster that includes the character represented michael@0: * by the specified gfxSkipCharsIterator. michael@0: * michael@0: * @param aTextRun The text run to use for determining whether a given character michael@0: * is part of a ligature or cluster. michael@0: * @param aIterator The gfxSkipCharsIterator to use for the current position michael@0: * in the text run. michael@0: */ michael@0: static uint32_t michael@0: ClusterLength(gfxTextRun* aTextRun, const gfxSkipCharsIterator& aIterator) michael@0: { michael@0: uint32_t start = aIterator.GetSkippedOffset(); michael@0: uint32_t end = start + 1; michael@0: while (end < aTextRun->GetLength() && michael@0: (!aTextRun->IsLigatureGroupStart(end) || michael@0: !aTextRun->IsClusterStart(end))) { michael@0: end++; michael@0: } michael@0: return end - start; michael@0: } michael@0: michael@0: /** michael@0: * Truncates an array to be at most the length of another array. michael@0: * michael@0: * @param aArrayToTruncate The array to truncate. michael@0: * @param aReferenceArray The array whose length will be used to truncate michael@0: * aArrayToTruncate to. michael@0: */ michael@0: template michael@0: static void michael@0: TruncateTo(nsTArray& aArrayToTruncate, const nsTArray& aReferenceArray) michael@0: { michael@0: uint32_t length = aReferenceArray.Length(); michael@0: if (aArrayToTruncate.Length() > length) { michael@0: aArrayToTruncate.TruncateLength(length); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Asserts that the anonymous block child of the SVGTextFrame has been michael@0: * reflowed (or does not exist). Returns null if the child has not been michael@0: * reflowed, and the frame otherwise. michael@0: * michael@0: * We check whether the kid has been reflowed and not the frame itself michael@0: * since we sometimes need to call this function during reflow, after the michael@0: * kid has been reflowed but before we have cleared the dirty bits on the michael@0: * frame itself. michael@0: */ michael@0: static SVGTextFrame* michael@0: FrameIfAnonymousChildReflowed(SVGTextFrame* aFrame) michael@0: { michael@0: NS_PRECONDITION(aFrame, "aFrame must not be null"); michael@0: nsIFrame* kid = aFrame->GetFirstPrincipalChild(); michael@0: if (NS_SUBTREE_DIRTY(kid)) { michael@0: MOZ_ASSERT(false, "should have already reflowed the anonymous block child"); michael@0: return nullptr; michael@0: } michael@0: return aFrame; michael@0: } michael@0: michael@0: static double michael@0: GetContextScale(const gfxMatrix& aMatrix) michael@0: { michael@0: // The context scale is the ratio of the length of the transformed michael@0: // diagonal vector (1,1) to the length of the untransformed diagonal michael@0: // (which is sqrt(2)). michael@0: gfxPoint p = aMatrix.Transform(gfxPoint(1, 1)) - michael@0: aMatrix.Transform(gfxPoint(0, 0)); michael@0: return SVGContentUtils::ComputeNormalizedHypotenuse(p.x, p.y); michael@0: } michael@0: michael@0: // ============================================================================ michael@0: // Utility classes michael@0: michael@0: namespace mozilla { michael@0: michael@0: // ---------------------------------------------------------------------------- michael@0: // TextRenderedRun michael@0: michael@0: /** michael@0: * A run of text within a single nsTextFrame whose glyphs can all be painted michael@0: * with a single call to nsTextFrame::PaintText. A text rendered run can michael@0: * be created for a sequence of two or more consecutive glyphs as long as: michael@0: * michael@0: * - Only the first glyph has (or none of the glyphs have) been positioned michael@0: * with SVG text positioning attributes michael@0: * - All of the glyphs have zero rotation michael@0: * - The glyphs are not on a text path michael@0: * - The glyphs correspond to content within the one nsTextFrame michael@0: * michael@0: * A TextRenderedRunIterator produces TextRenderedRuns required for painting a michael@0: * whole SVGTextFrame. michael@0: */ michael@0: struct TextRenderedRun michael@0: { michael@0: /** michael@0: * Constructs a TextRenderedRun that is uninitialized except for mFrame michael@0: * being null. michael@0: */ michael@0: TextRenderedRun() michael@0: : mFrame(nullptr) michael@0: { michael@0: } michael@0: michael@0: /** michael@0: * Constructs a TextRenderedRun with all of the information required to michael@0: * paint it. See the comments documenting the member variables below michael@0: * for descriptions of the arguments. michael@0: */ michael@0: TextRenderedRun(nsTextFrame* aFrame, const gfxPoint& aPosition, michael@0: float aLengthAdjustScaleFactor, double aRotate, michael@0: float aFontSizeScaleFactor, nscoord aBaseline, michael@0: uint32_t aTextFrameContentOffset, michael@0: uint32_t aTextFrameContentLength, michael@0: uint32_t aTextElementCharIndex) michael@0: : mFrame(aFrame), michael@0: mPosition(aPosition), michael@0: mLengthAdjustScaleFactor(aLengthAdjustScaleFactor), michael@0: mRotate(static_cast(aRotate)), michael@0: mFontSizeScaleFactor(aFontSizeScaleFactor), michael@0: mBaseline(aBaseline), michael@0: mTextFrameContentOffset(aTextFrameContentOffset), michael@0: mTextFrameContentLength(aTextFrameContentLength), michael@0: mTextElementCharIndex(aTextElementCharIndex) michael@0: { michael@0: } michael@0: michael@0: /** michael@0: * Returns the text run for the text frame that this rendered run is part of. michael@0: */ michael@0: gfxTextRun* GetTextRun() const michael@0: { michael@0: mFrame->EnsureTextRun(nsTextFrame::eInflated); michael@0: return mFrame->GetTextRun(nsTextFrame::eInflated); michael@0: } michael@0: michael@0: /** michael@0: * Returns whether this rendered run is RTL. michael@0: */ michael@0: bool IsRightToLeft() const michael@0: { michael@0: return GetTextRun()->IsRightToLeft(); michael@0: } michael@0: michael@0: /** michael@0: * Returns the transform that converts from a element's user space into michael@0: * the coordinate space that rendered runs can be painted directly in. michael@0: * michael@0: * The difference between this method and GetTransformFromRunUserSpaceToUserSpace michael@0: * is that when calling in to nsTextFrame::PaintText, it will already take michael@0: * into account any left clip edge (that is, it doesn't just apply a visual michael@0: * clip to the rendered text, it shifts the glyphs over so that they are michael@0: * painted with their left edge at the x coordinate passed in to it). michael@0: * Thus we need to account for this in our transform. michael@0: * michael@0: * michael@0: * Assume that we have abcdef. michael@0: * This would result in four text rendered runs: michael@0: * michael@0: * - one for "ab" michael@0: * - one for "c" michael@0: * - one for "de" michael@0: * - one for "f" michael@0: * michael@0: * Assume now that we are painting the third TextRenderedRun. It will have michael@0: * a left clip edge that is the sum of the advances of "abc", and it will michael@0: * have a right clip edge that is the advance of "f". In michael@0: * SVGTextFrame::PaintSVG(), we pass in nsPoint() (i.e., the origin) michael@0: * as the point at which to paint the text frame, and we pass in the michael@0: * clip edge values. The nsTextFrame will paint the substring of its michael@0: * text such that the top-left corner of the "d"'s glyph cell will be at michael@0: * (0, 0) in the current coordinate system. michael@0: * michael@0: * Thus, GetTransformFromUserSpaceForPainting must return a transform from michael@0: * whatever user space the element is in to a coordinate space in michael@0: * device pixels (as that's what nsTextFrame works in) where the origin is at michael@0: * the same position as our user space mPositions[i].mPosition value for michael@0: * the "d" glyph, which will be (100 + userSpaceAdvance("abc"), 100). michael@0: * The translation required to do this (ignoring the scale to get from michael@0: * user space to device pixels, and ignoring the michael@0: * (100 + userSpaceAdvance("abc"), 100) translation) is: michael@0: * michael@0: * (-leftEdge, -baseline) michael@0: * michael@0: * where baseline is the distance between the baseline of the text and the top michael@0: * edge of the nsTextFrame. We translate by -leftEdge horizontally because michael@0: * the nsTextFrame will already shift the glyphs over by that amount and start michael@0: * painting glyphs at x = 0. We translate by -baseline vertically so that michael@0: * painting the top edges of the glyphs at y = 0 will result in their michael@0: * baselines being at our desired y position. michael@0: * michael@0: * michael@0: * Now for an example with RTL text. Assume our content is now michael@0: * WERBEH. We'd have michael@0: * the following text rendered runs: michael@0: * michael@0: * - one for "EH" michael@0: * - one for "B" michael@0: * - one for "ER" michael@0: * - one for "W" michael@0: * michael@0: * Again, we are painting the third TextRenderedRun. The left clip edge michael@0: * is the advance of the "W" and the right clip edge is the sum of the michael@0: * advances of "BEH". Our translation to get the rendered "ER" glyphs michael@0: * in the right place this time is: michael@0: * michael@0: * (-frameWidth + rightEdge, -baseline) michael@0: * michael@0: * which is equivalent to: michael@0: * michael@0: * (-(leftEdge + advance("ER")), -baseline) michael@0: * michael@0: * The reason we have to shift left additionally by the width of the run michael@0: * of glyphs we are painting is that although the nsTextFrame is RTL, michael@0: * we still supply the top-left corner to paint the frame at when calling michael@0: * nsTextFrame::PaintText, even though our user space positions for each michael@0: * glyph in mPositions specifies the origin of each glyph, which for RTL michael@0: * glyphs is at the right edge of the glyph cell. michael@0: * michael@0: * michael@0: * For any other use of an nsTextFrame in the context of a particular run michael@0: * (such as hit testing, or getting its rectangle), michael@0: * GetTransformFromRunUserSpaceToUserSpace should be used. michael@0: * michael@0: * @param aContext The context to use for unit conversions. michael@0: * @param aItem The nsCharClipDisplayItem that holds the amount of clipping michael@0: * from the left and right edges of the text frame for this rendered run. michael@0: * An appropriate nsCharClipDisplayItem can be obtained by constructing an michael@0: * SVGCharClipDisplayItem for the TextRenderedRun. michael@0: */ michael@0: gfxMatrix GetTransformFromUserSpaceForPainting( michael@0: nsPresContext* aContext, michael@0: const nsCharClipDisplayItem& aItem) const; michael@0: michael@0: /** michael@0: * Returns the transform that converts from "run user space" to a michael@0: * element's user space. Run user space is a coordinate system that has the michael@0: * same size as the 's user space but rotated and translated such that michael@0: * (0,0) is the top-left of the rectangle that bounds the text. michael@0: * michael@0: * @param aContext The context to use for unit conversions. michael@0: */ michael@0: gfxMatrix GetTransformFromRunUserSpaceToUserSpace(nsPresContext* aContext) const; michael@0: michael@0: /** michael@0: * Returns the transform that converts from "run user space" to float pixels michael@0: * relative to the nsTextFrame that this rendered run is a part of. michael@0: * michael@0: * @param aContext The context to use for unit conversions. michael@0: */ michael@0: gfxMatrix GetTransformFromRunUserSpaceToFrameUserSpace(nsPresContext* aContext) const; michael@0: michael@0: /** michael@0: * Flag values used for the aFlags arguments of GetRunUserSpaceRect, michael@0: * GetFrameUserSpaceRect and GetUserSpaceRect. michael@0: */ michael@0: enum { michael@0: // Includes the fill geometry of the text in the returned rectangle. michael@0: eIncludeFill = 1, michael@0: // Includes the stroke geometry of the text in the returned rectangle. michael@0: eIncludeStroke = 2, michael@0: // Includes any text shadow in the returned rectangle. michael@0: eIncludeTextShadow = 4, michael@0: // Don't include any horizontal glyph overflow in the returned rectangle. michael@0: eNoHorizontalOverflow = 8 michael@0: }; michael@0: michael@0: /** michael@0: * Returns a rectangle that bounds the fill and/or stroke of the rendered run michael@0: * in run user space. michael@0: * michael@0: * @param aContext The context to use for unit conversions. michael@0: * @param aFlags A combination of the flags above (eIncludeFill and michael@0: * eIncludeStroke) indicating what parts of the text to include in michael@0: * the rectangle. michael@0: */ michael@0: SVGBBox GetRunUserSpaceRect(nsPresContext* aContext, uint32_t aFlags) const; michael@0: michael@0: /** michael@0: * Returns a rectangle that covers the fill and/or stroke of the rendered run michael@0: * in "frame user space". michael@0: * michael@0: * Frame user space is a coordinate space of the same scale as the michael@0: * element's user space, but with its rotation set to the rotation of michael@0: * the glyphs within this rendered run and its origin set to the position michael@0: * such that placing the nsTextFrame there would result in the glyphs in michael@0: * this rendered run being at their correct positions. michael@0: * michael@0: * For example, say we have ab. Assume michael@0: * the advance of both the "a" and the "b" is 12 user units, and the michael@0: * ascent of the text is 8 user units and its descent is 6 user units, michael@0: * and that we are not measuing the stroke of the text, so that we stay michael@0: * entirely within the glyph cells. michael@0: * michael@0: * There will be two text rendered runs, one for "a" and one for "b". michael@0: * michael@0: * The frame user space for the "a" run will have its origin at michael@0: * (100, 100 - 8) in the element's user space and will have its michael@0: * axes aligned with the user space (since there is no rotate="" or michael@0: * text path involve) and with its scale the same as the user space. michael@0: * The rect returned by this method will be (0, 0, 12, 14), since the "a" michael@0: * glyph is right at the left of the nsTextFrame. michael@0: * michael@0: * The frame user space for the "b" run will have its origin at michael@0: * (150 - 12, 100 - 8), and scale/rotation the same as above. The rect michael@0: * returned by this method will be (12, 0, 12, 14), since we are michael@0: * advance("a") horizontally in to the text frame. michael@0: * michael@0: * @param aContext The context to use for unit conversions. michael@0: * @param aFlags A combination of the flags above (eIncludeFill and michael@0: * eIncludeStroke) indicating what parts of the text to include in michael@0: * the rectangle. michael@0: */ michael@0: SVGBBox GetFrameUserSpaceRect(nsPresContext* aContext, uint32_t aFlags) const; michael@0: michael@0: /** michael@0: * Returns a rectangle that covers the fill and/or stroke of the rendered run michael@0: * in the element's user space. michael@0: * michael@0: * @param aContext The context to use for unit conversions. michael@0: * @param aFlags A combination of the flags above indicating what parts of the michael@0: * text to include in the rectangle. michael@0: * @param aAdditionalTransform An additional transform to apply to the michael@0: * frame user space rectangle before its bounds are transformed into michael@0: * user space. michael@0: */ michael@0: SVGBBox GetUserSpaceRect(nsPresContext* aContext, uint32_t aFlags, michael@0: const gfxMatrix* aAdditionalTransform = nullptr) const; michael@0: michael@0: /** michael@0: * Gets the app unit amounts to clip from the left and right edges of michael@0: * the nsTextFrame in order to paint just this rendered run. michael@0: * michael@0: * Note that if clip edge amounts land in the middle of a glyph, the michael@0: * glyph won't be painted at all. The clip edges are thus more of michael@0: * a selection mechanism for which glyphs will be painted, rather michael@0: * than a geometric clip. michael@0: */ michael@0: void GetClipEdges(nscoord& aLeftEdge, nscoord& aRightEdge) const; michael@0: michael@0: /** michael@0: * Returns the advance width of the whole rendered run. michael@0: */ michael@0: nscoord GetAdvanceWidth() const; michael@0: michael@0: /** michael@0: * Returns the index of the character into this rendered run whose michael@0: * glyph cell contains the given point, or -1 if there is no such michael@0: * character. This does not hit test against any overflow. michael@0: * michael@0: * @param aContext The context to use for unit conversions. michael@0: * @param aPoint The point in the user space of the element. michael@0: */ michael@0: int32_t GetCharNumAtPosition(nsPresContext* aContext, michael@0: const gfxPoint& aPoint) const; michael@0: michael@0: /** michael@0: * The text frame that this rendered run lies within. michael@0: */ michael@0: nsTextFrame* mFrame; michael@0: michael@0: /** michael@0: * The point in user space that the text is positioned at. michael@0: * michael@0: * The x coordinate is the left edge of a LTR run of text or the right edge of michael@0: * an RTL run. The y coordinate is the baseline of the text. michael@0: */ michael@0: gfxPoint mPosition; michael@0: michael@0: /** michael@0: * The horizontal scale factor to apply when painting glyphs to take michael@0: * into account textLength="". michael@0: */ michael@0: float mLengthAdjustScaleFactor; michael@0: michael@0: /** michael@0: * The rotation in radians in the user coordinate system that the text has. michael@0: */ michael@0: float mRotate; michael@0: michael@0: /** michael@0: * The scale factor that was used to transform the text run's original font michael@0: * size into a sane range for painting and measurement. michael@0: */ michael@0: double mFontSizeScaleFactor; michael@0: michael@0: /** michael@0: * The baseline in app units of this text run. The measurement is from the michael@0: * top of the text frame. michael@0: */ michael@0: nscoord mBaseline; michael@0: michael@0: /** michael@0: * The offset and length in mFrame's content nsTextNode that corresponds to michael@0: * this text rendered run. These are original char indexes. michael@0: */ michael@0: uint32_t mTextFrameContentOffset; michael@0: uint32_t mTextFrameContentLength; michael@0: michael@0: /** michael@0: * The character index in the whole SVG element that this text rendered michael@0: * run begins at. michael@0: */ michael@0: uint32_t mTextElementCharIndex; michael@0: }; michael@0: michael@0: gfxMatrix michael@0: TextRenderedRun::GetTransformFromUserSpaceForPainting( michael@0: nsPresContext* aContext, michael@0: const nsCharClipDisplayItem& aItem) const michael@0: { michael@0: // We transform to device pixels positioned such that painting the text frame michael@0: // at (0,0) with aItem will result in the text being in the right place. michael@0: michael@0: gfxMatrix m; michael@0: if (!mFrame) { michael@0: return m; michael@0: } michael@0: michael@0: float cssPxPerDevPx = aContext-> michael@0: AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel()); michael@0: michael@0: // Glyph position in user space. michael@0: m.Translate(mPosition / cssPxPerDevPx); michael@0: michael@0: // Take into account any font size scaling and scaling due to textLength="". michael@0: m.Scale(1.0 / mFontSizeScaleFactor, 1.0 / mFontSizeScaleFactor); michael@0: michael@0: // Rotation due to rotate="" or a . michael@0: m.Rotate(mRotate); michael@0: michael@0: m.Scale(mLengthAdjustScaleFactor, 1.0); michael@0: michael@0: // Translation to get the text frame in the right place. michael@0: nsPoint t(IsRightToLeft() ? michael@0: -mFrame->GetRect().width + aItem.mRightEdge : michael@0: -aItem.mLeftEdge, michael@0: -mBaseline); michael@0: m.Translate(AppUnitsToGfxUnits(t, aContext)); michael@0: michael@0: return m; michael@0: } michael@0: michael@0: gfxMatrix michael@0: TextRenderedRun::GetTransformFromRunUserSpaceToUserSpace( michael@0: nsPresContext* aContext) const michael@0: { michael@0: gfxMatrix m; michael@0: if (!mFrame) { michael@0: return m; michael@0: } michael@0: michael@0: float cssPxPerDevPx = aContext-> michael@0: AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel()); michael@0: michael@0: nscoord left, right; michael@0: GetClipEdges(left, right); michael@0: michael@0: // Glyph position in user space. michael@0: m.Translate(mPosition); michael@0: michael@0: // Rotation due to rotate="" or a . michael@0: m.Rotate(mRotate); michael@0: michael@0: // Scale due to textLength="". michael@0: m.Scale(mLengthAdjustScaleFactor, 1.0); michael@0: michael@0: // Translation to get the text frame in the right place. michael@0: nsPoint t(IsRightToLeft() ? michael@0: -mFrame->GetRect().width + left + right : michael@0: 0, michael@0: -mBaseline); michael@0: m.Translate(AppUnitsToGfxUnits(t, aContext) * michael@0: cssPxPerDevPx / mFontSizeScaleFactor); michael@0: michael@0: return m; michael@0: } michael@0: michael@0: gfxMatrix michael@0: TextRenderedRun::GetTransformFromRunUserSpaceToFrameUserSpace( michael@0: nsPresContext* aContext) const michael@0: { michael@0: gfxMatrix m; michael@0: if (!mFrame) { michael@0: return m; michael@0: } michael@0: michael@0: nscoord left, right; michael@0: GetClipEdges(left, right); michael@0: michael@0: // Translate by the horizontal distance into the text frame this michael@0: // rendered run is. michael@0: return m.Translate(gfxPoint(gfxFloat(left) / aContext->AppUnitsPerCSSPixel(), michael@0: 0)); michael@0: } michael@0: michael@0: SVGBBox michael@0: TextRenderedRun::GetRunUserSpaceRect(nsPresContext* aContext, michael@0: uint32_t aFlags) const michael@0: { michael@0: SVGBBox r; michael@0: if (!mFrame) { michael@0: return r; michael@0: } michael@0: michael@0: // Determine the amount of overflow above and below the frame's mRect. michael@0: // michael@0: // We need to call GetVisualOverflowRectRelativeToSelf because this includes michael@0: // overflowing decorations, which the MeasureText call below does not. We michael@0: // assume here the decorations only overflow above and below the frame, never michael@0: // horizontally. michael@0: nsRect self = mFrame->GetVisualOverflowRectRelativeToSelf(); michael@0: nsRect rect = mFrame->GetRect(); michael@0: nscoord above = -self.y; michael@0: nscoord below = self.YMost() - rect.height; michael@0: michael@0: gfxSkipCharsIterator it = mFrame->EnsureTextRun(nsTextFrame::eInflated); michael@0: gfxTextRun* textRun = mFrame->GetTextRun(nsTextFrame::eInflated); michael@0: michael@0: // Get the content range for this rendered run. michael@0: uint32_t offset, length; michael@0: ConvertOriginalToSkipped(it, mTextFrameContentOffset, mTextFrameContentLength, michael@0: offset, length); michael@0: michael@0: // Measure that range. michael@0: gfxTextRun::Metrics metrics = michael@0: textRun->MeasureText(offset, length, gfxFont::LOOSE_INK_EXTENTS, michael@0: nullptr, nullptr); michael@0: michael@0: // Determine the rectangle that covers the rendered run's fill, michael@0: // taking into account the measured vertical overflow due to michael@0: // decorations. michael@0: nscoord baseline = metrics.mBoundingBox.y + metrics.mAscent; michael@0: gfxFloat x, width; michael@0: if (aFlags & eNoHorizontalOverflow) { michael@0: x = 0.0; michael@0: width = textRun->GetAdvanceWidth(offset, length, nullptr); michael@0: } else { michael@0: x = metrics.mBoundingBox.x; michael@0: width = metrics.mBoundingBox.width; michael@0: } michael@0: nsRect fillInAppUnits(x, baseline - above, michael@0: width, metrics.mBoundingBox.height + above + below); michael@0: michael@0: // Account for text-shadow. michael@0: if (aFlags & eIncludeTextShadow) { michael@0: fillInAppUnits = michael@0: nsLayoutUtils::GetTextShadowRectsUnion(fillInAppUnits, mFrame); michael@0: } michael@0: michael@0: // Convert the app units rectangle to user units. michael@0: gfxRect fill = AppUnitsToFloatCSSPixels(gfxRect(fillInAppUnits.x, michael@0: fillInAppUnits.y, michael@0: fillInAppUnits.width, michael@0: fillInAppUnits.height), michael@0: aContext); michael@0: michael@0: // Scale the rectangle up due to any mFontSizeScaleFactor. We scale michael@0: // it around the text's origin. michael@0: ScaleAround(fill, michael@0: gfxPoint(0.0, aContext->AppUnitsToFloatCSSPixels(baseline)), michael@0: 1.0 / mFontSizeScaleFactor); michael@0: michael@0: // Include the fill if requested. michael@0: if (aFlags & eIncludeFill) { michael@0: r = fill; michael@0: } michael@0: michael@0: // Include the stroke if requested. michael@0: if ((aFlags & eIncludeStroke) && michael@0: nsSVGUtils::GetStrokeWidth(mFrame) > 0) { michael@0: r.UnionEdges(nsSVGUtils::PathExtentsToMaxStrokeExtents(fill, mFrame, michael@0: gfxMatrix())); michael@0: } michael@0: michael@0: return r; michael@0: } michael@0: michael@0: SVGBBox michael@0: TextRenderedRun::GetFrameUserSpaceRect(nsPresContext* aContext, michael@0: uint32_t aFlags) const michael@0: { michael@0: SVGBBox r = GetRunUserSpaceRect(aContext, aFlags); michael@0: if (r.IsEmpty()) { michael@0: return r; michael@0: } michael@0: gfxMatrix m = GetTransformFromRunUserSpaceToFrameUserSpace(aContext); michael@0: return m.TransformBounds(r.ToThebesRect()); michael@0: } michael@0: michael@0: SVGBBox michael@0: TextRenderedRun::GetUserSpaceRect(nsPresContext* aContext, michael@0: uint32_t aFlags, michael@0: const gfxMatrix* aAdditionalTransform) const michael@0: { michael@0: SVGBBox r = GetRunUserSpaceRect(aContext, aFlags); michael@0: if (r.IsEmpty()) { michael@0: return r; michael@0: } michael@0: gfxMatrix m = GetTransformFromRunUserSpaceToUserSpace(aContext); michael@0: if (aAdditionalTransform) { michael@0: m.Multiply(*aAdditionalTransform); michael@0: } michael@0: return m.TransformBounds(r.ToThebesRect()); michael@0: } michael@0: michael@0: void michael@0: TextRenderedRun::GetClipEdges(nscoord& aLeftEdge, nscoord& aRightEdge) const michael@0: { michael@0: uint32_t contentLength = mFrame->GetContentLength(); michael@0: if (mTextFrameContentOffset == 0 && michael@0: mTextFrameContentLength == contentLength) { michael@0: // If the rendered run covers the entire content, we know we don't need michael@0: // to clip without having to measure anything. michael@0: aLeftEdge = 0; michael@0: aRightEdge = 0; michael@0: return; michael@0: } michael@0: michael@0: gfxSkipCharsIterator it = mFrame->EnsureTextRun(nsTextFrame::eInflated); michael@0: gfxTextRun* textRun = mFrame->GetTextRun(nsTextFrame::eInflated); michael@0: michael@0: // Get the covered content offset/length for this rendered run in skipped michael@0: // characters, since that is what GetAdvanceWidth expects. michael@0: uint32_t runOffset, runLength, frameOffset, frameLength; michael@0: ConvertOriginalToSkipped(it, mTextFrameContentOffset, mTextFrameContentLength, michael@0: runOffset, runLength); michael@0: michael@0: // Get the offset/length of the whole nsTextFrame. michael@0: frameOffset = mFrame->GetContentOffset(); michael@0: frameLength = mFrame->GetContentLength(); michael@0: michael@0: // Trim the whole-nsTextFrame offset/length to remove any leading/trailing michael@0: // white space, as the nsTextFrame when painting does not include them when michael@0: // interpreting clip edges. michael@0: nsTextFrame::TrimmedOffsets trimmedOffsets = michael@0: mFrame->GetTrimmedOffsets(mFrame->GetContent()->GetText(), true); michael@0: TrimOffsets(frameOffset, frameLength, trimmedOffsets); michael@0: michael@0: // Convert the trimmed whole-nsTextFrame offset/length into skipped michael@0: // characters. michael@0: ConvertOriginalToSkipped(it, frameOffset, frameLength); michael@0: michael@0: // Measure the advance width in the text run between the start of michael@0: // frame's content and the start of the rendered run's content, michael@0: nscoord leftEdge = michael@0: textRun->GetAdvanceWidth(frameOffset, runOffset - frameOffset, nullptr); michael@0: michael@0: // and between the end of the rendered run's content and the end michael@0: // of the frame's content. michael@0: nscoord rightEdge = michael@0: textRun->GetAdvanceWidth(runOffset + runLength, michael@0: frameOffset + frameLength - (runOffset + runLength), michael@0: nullptr); michael@0: michael@0: if (textRun->IsRightToLeft()) { michael@0: aLeftEdge = rightEdge; michael@0: aRightEdge = leftEdge; michael@0: } else { michael@0: aLeftEdge = leftEdge; michael@0: aRightEdge = rightEdge; michael@0: } michael@0: } michael@0: michael@0: nscoord michael@0: TextRenderedRun::GetAdvanceWidth() const michael@0: { michael@0: gfxSkipCharsIterator it = mFrame->EnsureTextRun(nsTextFrame::eInflated); michael@0: gfxTextRun* textRun = mFrame->GetTextRun(nsTextFrame::eInflated); michael@0: michael@0: uint32_t offset, length; michael@0: ConvertOriginalToSkipped(it, mTextFrameContentOffset, mTextFrameContentLength, michael@0: offset, length); michael@0: michael@0: return textRun->GetAdvanceWidth(offset, length, nullptr); michael@0: } michael@0: michael@0: int32_t michael@0: TextRenderedRun::GetCharNumAtPosition(nsPresContext* aContext, michael@0: const gfxPoint& aPoint) const michael@0: { michael@0: if (mTextFrameContentLength == 0) { michael@0: return -1; michael@0: } michael@0: michael@0: float cssPxPerDevPx = aContext-> michael@0: AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel()); michael@0: michael@0: // Convert the point from user space into run user space, and take michael@0: // into account any mFontSizeScaleFactor. michael@0: gfxMatrix m = GetTransformFromRunUserSpaceToUserSpace(aContext).Invert(); michael@0: gfxPoint p = m.Transform(aPoint) / cssPxPerDevPx * mFontSizeScaleFactor; michael@0: michael@0: // First check that the point lies vertically between the top and bottom michael@0: // edges of the text. michael@0: gfxFloat ascent, descent; michael@0: GetAscentAndDescentInAppUnits(mFrame, ascent, descent); michael@0: michael@0: gfxFloat topEdge = mFrame->GetBaseline() - ascent; michael@0: gfxFloat bottomEdge = topEdge + ascent + descent; michael@0: michael@0: if (p.y < aContext->AppUnitsToGfxUnits(topEdge) || michael@0: p.y >= aContext->AppUnitsToGfxUnits(bottomEdge)) { michael@0: return -1; michael@0: } michael@0: michael@0: gfxSkipCharsIterator it = mFrame->EnsureTextRun(nsTextFrame::eInflated); michael@0: gfxTextRun* textRun = mFrame->GetTextRun(nsTextFrame::eInflated); michael@0: michael@0: // Next check that the point lies horizontally within the left and right michael@0: // edges of the text. michael@0: uint32_t offset, length; michael@0: ConvertOriginalToSkipped(it, mTextFrameContentOffset, mTextFrameContentLength, michael@0: offset, length); michael@0: gfxFloat runAdvance = michael@0: aContext->AppUnitsToGfxUnits(textRun->GetAdvanceWidth(offset, length, michael@0: nullptr)); michael@0: michael@0: if (p.x < 0 || p.x >= runAdvance) { michael@0: return -1; michael@0: } michael@0: michael@0: // Finally, measure progressively smaller portions of the rendered run to michael@0: // find which glyph it lies within. This will need to change once we michael@0: // support letter-spacing and word-spacing. michael@0: bool rtl = textRun->IsRightToLeft(); michael@0: for (int32_t i = mTextFrameContentLength - 1; i >= 0; i--) { michael@0: ConvertOriginalToSkipped(it, mTextFrameContentOffset, i, offset, length); michael@0: gfxFloat advance = michael@0: aContext->AppUnitsToGfxUnits(textRun->GetAdvanceWidth(offset, length, michael@0: nullptr)); michael@0: if ((rtl && p.x < runAdvance - advance) || michael@0: (!rtl && p.x >= advance)) { michael@0: return i; michael@0: } michael@0: } michael@0: return -1; michael@0: } michael@0: michael@0: // ---------------------------------------------------------------------------- michael@0: // TextNodeIterator michael@0: michael@0: enum SubtreePosition michael@0: { michael@0: eBeforeSubtree, michael@0: eWithinSubtree, michael@0: eAfterSubtree michael@0: }; michael@0: michael@0: /** michael@0: * An iterator class for nsTextNodes that are descendants of a given node, the michael@0: * root. Nodes are iterated in document order. An optional subtree can be michael@0: * specified, in which case the iterator will track whether the current state of michael@0: * the traversal over the tree is within that subtree or is past that subtree. michael@0: */ michael@0: class TextNodeIterator michael@0: { michael@0: public: michael@0: /** michael@0: * Constructs a TextNodeIterator with the specified root node and optional michael@0: * subtree. michael@0: */ michael@0: TextNodeIterator(nsIContent* aRoot, nsIContent* aSubtree = nullptr) michael@0: : mRoot(aRoot), michael@0: mSubtree(aSubtree == aRoot ? nullptr : aSubtree), michael@0: mCurrent(aRoot), michael@0: mSubtreePosition(mSubtree ? eBeforeSubtree : eWithinSubtree) michael@0: { michael@0: NS_ASSERTION(aRoot, "expected non-null root"); michael@0: if (!aRoot->IsNodeOfType(nsINode::eTEXT)) { michael@0: Next(); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Returns the current nsTextNode, or null if the iterator has finished. michael@0: */ michael@0: nsTextNode* Current() const michael@0: { michael@0: return static_cast(mCurrent); michael@0: } michael@0: michael@0: /** michael@0: * Advances to the next nsTextNode and returns it, or null if the end of michael@0: * iteration has been reached. michael@0: */ michael@0: nsTextNode* Next(); michael@0: michael@0: /** michael@0: * Returns whether the iterator is currently within the subtree rooted michael@0: * at mSubtree. Returns true if we are not tracking a subtree (we consider michael@0: * that we're always within the subtree). michael@0: */ michael@0: bool IsWithinSubtree() const michael@0: { michael@0: return mSubtreePosition == eWithinSubtree; michael@0: } michael@0: michael@0: /** michael@0: * Returns whether the iterator is past the subtree rooted at mSubtree. michael@0: * Returns false if we are not tracking a subtree. michael@0: */ michael@0: bool IsAfterSubtree() const michael@0: { michael@0: return mSubtreePosition == eAfterSubtree; michael@0: } michael@0: michael@0: private: michael@0: /** michael@0: * The root under which all nsTextNodes will be iterated over. michael@0: */ michael@0: nsIContent* mRoot; michael@0: michael@0: /** michael@0: * The node rooting the subtree to track. michael@0: */ michael@0: nsIContent* mSubtree; michael@0: michael@0: /** michael@0: * The current node during iteration. michael@0: */ michael@0: nsIContent* mCurrent; michael@0: michael@0: /** michael@0: * The current iterator position relative to mSubtree. michael@0: */ michael@0: SubtreePosition mSubtreePosition; michael@0: }; michael@0: michael@0: nsTextNode* michael@0: TextNodeIterator::Next() michael@0: { michael@0: // Starting from mCurrent, we do a non-recursive traversal to the next michael@0: // nsTextNode beneath mRoot, updating mSubtreePosition appropriately if we michael@0: // encounter mSubtree. michael@0: if (mCurrent) { michael@0: do { michael@0: nsIContent* next = IsTextContentElement(mCurrent) ? michael@0: mCurrent->GetFirstChild() : michael@0: nullptr; michael@0: if (next) { michael@0: mCurrent = next; michael@0: if (mCurrent == mSubtree) { michael@0: mSubtreePosition = eWithinSubtree; michael@0: } michael@0: } else { michael@0: for (;;) { michael@0: if (mCurrent == mRoot) { michael@0: mCurrent = nullptr; michael@0: break; michael@0: } michael@0: if (mCurrent == mSubtree) { michael@0: mSubtreePosition = eAfterSubtree; michael@0: } michael@0: next = mCurrent->GetNextSibling(); michael@0: if (next) { michael@0: mCurrent = next; michael@0: if (mCurrent == mSubtree) { michael@0: mSubtreePosition = eWithinSubtree; michael@0: } michael@0: break; michael@0: } michael@0: if (mCurrent == mSubtree) { michael@0: mSubtreePosition = eAfterSubtree; michael@0: } michael@0: mCurrent = mCurrent->GetParent(); michael@0: } michael@0: } michael@0: } while (mCurrent && !mCurrent->IsNodeOfType(nsINode::eTEXT)); michael@0: } michael@0: michael@0: return static_cast(mCurrent); michael@0: } michael@0: michael@0: // ---------------------------------------------------------------------------- michael@0: // TextNodeCorrespondenceRecorder michael@0: michael@0: /** michael@0: * TextNodeCorrespondence is used as the value of a frame property that michael@0: * is stored on all its descendant nsTextFrames. It stores the number of DOM michael@0: * characters between it and the previous nsTextFrame that did not have an michael@0: * nsTextFrame created for them, due to either not being in a correctly michael@0: * parented text content element, or because they were display:none. michael@0: * These are called "undisplayed characters". michael@0: * michael@0: * See also TextNodeCorrespondenceRecorder below, which is what sets the michael@0: * frame property. michael@0: */ michael@0: struct TextNodeCorrespondence michael@0: { michael@0: TextNodeCorrespondence(uint32_t aUndisplayedCharacters) michael@0: : mUndisplayedCharacters(aUndisplayedCharacters) michael@0: { michael@0: } michael@0: michael@0: uint32_t mUndisplayedCharacters; michael@0: }; michael@0: michael@0: static void DestroyTextNodeCorrespondence(void* aPropertyValue) michael@0: { michael@0: delete static_cast(aPropertyValue); michael@0: } michael@0: michael@0: NS_DECLARE_FRAME_PROPERTY(TextNodeCorrespondenceProperty, DestroyTextNodeCorrespondence) michael@0: michael@0: /** michael@0: * Returns the number of undisplayed characters before the specified michael@0: * nsTextFrame. michael@0: */ michael@0: static uint32_t michael@0: GetUndisplayedCharactersBeforeFrame(nsTextFrame* aFrame) michael@0: { michael@0: void* value = aFrame->Properties().Get(TextNodeCorrespondenceProperty()); michael@0: TextNodeCorrespondence* correspondence = michael@0: static_cast(value); michael@0: if (!correspondence) { michael@0: NS_NOTREACHED("expected a TextNodeCorrespondenceProperty on nsTextFrame " michael@0: "used for SVG text"); michael@0: return 0; michael@0: } michael@0: return correspondence->mUndisplayedCharacters; michael@0: } michael@0: michael@0: /** michael@0: * Traverses the nsTextFrames for an SVGTextFrame and records a michael@0: * TextNodeCorrespondenceProperty on each for the number of undisplayed DOM michael@0: * characters between each frame. This is done by iterating simultaenously michael@0: * over the nsTextNodes and nsTextFrames and noting when nsTextNodes (or michael@0: * parts of them) are skipped when finding the next nsTextFrame. michael@0: */ michael@0: class TextNodeCorrespondenceRecorder michael@0: { michael@0: public: michael@0: /** michael@0: * Entry point for the TextNodeCorrespondenceProperty recording. michael@0: */ michael@0: static void RecordCorrespondence(SVGTextFrame* aRoot); michael@0: michael@0: private: michael@0: TextNodeCorrespondenceRecorder(SVGTextFrame* aRoot) michael@0: : mNodeIterator(aRoot->GetContent()), michael@0: mPreviousNode(nullptr), michael@0: mNodeCharIndex(0) michael@0: { michael@0: } michael@0: michael@0: void Record(SVGTextFrame* aRoot); michael@0: void TraverseAndRecord(nsIFrame* aFrame); michael@0: michael@0: /** michael@0: * Returns the next non-empty nsTextNode. michael@0: */ michael@0: nsTextNode* NextNode(); michael@0: michael@0: /** michael@0: * The iterator over the nsTextNodes that we use as we simultaneously michael@0: * iterate over the nsTextFrames. michael@0: */ michael@0: TextNodeIterator mNodeIterator; michael@0: michael@0: /** michael@0: * The previous nsTextNode we iterated over. michael@0: */ michael@0: nsTextNode* mPreviousNode; michael@0: michael@0: /** michael@0: * The index into the current nsTextNode's character content. michael@0: */ michael@0: uint32_t mNodeCharIndex; michael@0: }; michael@0: michael@0: /* static */ void michael@0: TextNodeCorrespondenceRecorder::RecordCorrespondence(SVGTextFrame* aRoot) michael@0: { michael@0: TextNodeCorrespondenceRecorder recorder(aRoot); michael@0: recorder.Record(aRoot); michael@0: } michael@0: michael@0: void michael@0: TextNodeCorrespondenceRecorder::Record(SVGTextFrame* aRoot) michael@0: { michael@0: if (!mNodeIterator.Current()) { michael@0: // If there are no nsTextNodes then there is nothing to do. michael@0: return; michael@0: } michael@0: michael@0: // Traverse over all the nsTextFrames and record the number of undisplayed michael@0: // characters. michael@0: TraverseAndRecord(aRoot); michael@0: michael@0: // Find how many undisplayed characters there are after the final nsTextFrame. michael@0: uint32_t undisplayed = 0; michael@0: if (mNodeIterator.Current()) { michael@0: if (mPreviousNode && mPreviousNode->TextLength() != mNodeCharIndex) { michael@0: // The last nsTextFrame ended part way through an nsTextNode. The michael@0: // remaining characters count as undisplayed. michael@0: NS_ASSERTION(mNodeCharIndex < mPreviousNode->TextLength(), michael@0: "incorrect tracking of undisplayed characters in " michael@0: "text nodes"); michael@0: undisplayed += mPreviousNode->TextLength() - mNodeCharIndex; michael@0: } michael@0: // All the remaining nsTextNodes that we iterate must also be undisplayed. michael@0: for (nsTextNode* textNode = mNodeIterator.Current(); michael@0: textNode; michael@0: textNode = NextNode()) { michael@0: undisplayed += textNode->TextLength(); michael@0: } michael@0: } michael@0: michael@0: // Record the trailing number of undisplayed characters on the michael@0: // SVGTextFrame. michael@0: aRoot->mTrailingUndisplayedCharacters = undisplayed; michael@0: } michael@0: michael@0: nsTextNode* michael@0: TextNodeCorrespondenceRecorder::NextNode() michael@0: { michael@0: mPreviousNode = mNodeIterator.Current(); michael@0: nsTextNode* next; michael@0: do { michael@0: next = mNodeIterator.Next(); michael@0: } while (next && next->TextLength() == 0); michael@0: return next; michael@0: } michael@0: michael@0: void michael@0: TextNodeCorrespondenceRecorder::TraverseAndRecord(nsIFrame* aFrame) michael@0: { michael@0: // Recursively iterate over the frame tree, for frames that correspond michael@0: // to text content elements. michael@0: if (IsTextContentElement(aFrame->GetContent())) { michael@0: for (nsIFrame* f = aFrame->GetFirstPrincipalChild(); michael@0: f; michael@0: f = f->GetNextSibling()) { michael@0: TraverseAndRecord(f); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: nsTextFrame* frame; // The current text frame. michael@0: nsTextNode* node; // The text node for the current text frame. michael@0: if (!GetNonEmptyTextFrameAndNode(aFrame, frame, node)) { michael@0: // If this isn't an nsTextFrame, or is empty, nothing to do. michael@0: return; michael@0: } michael@0: michael@0: NS_ASSERTION(frame->GetContentOffset() >= 0, michael@0: "don't know how to handle negative content indexes"); michael@0: michael@0: uint32_t undisplayed = 0; michael@0: if (!mPreviousNode) { michael@0: // Must be the very first text frame. michael@0: NS_ASSERTION(mNodeCharIndex == 0, "incorrect tracking of undisplayed " michael@0: "characters in text nodes"); michael@0: if (!mNodeIterator.Current()) { michael@0: NS_NOTREACHED("incorrect tracking of correspondence between text frames " michael@0: "and text nodes"); michael@0: } else { michael@0: // Each whole nsTextNode we find before we get to the text node for the michael@0: // first text frame must be undisplayed. michael@0: while (mNodeIterator.Current() != node) { michael@0: undisplayed += mNodeIterator.Current()->TextLength(); michael@0: NextNode(); michael@0: } michael@0: // If the first text frame starts at a non-zero content offset, then those michael@0: // earlier characters are also undisplayed. michael@0: undisplayed += frame->GetContentOffset(); michael@0: NextNode(); michael@0: } michael@0: } else if (mPreviousNode == node) { michael@0: // Same text node as last time. michael@0: if (static_cast(frame->GetContentOffset()) != mNodeCharIndex) { michael@0: // We have some characters in the middle of the text node michael@0: // that are undisplayed. michael@0: NS_ASSERTION(mNodeCharIndex < michael@0: static_cast(frame->GetContentOffset()), michael@0: "incorrect tracking of undisplayed characters in " michael@0: "text nodes"); michael@0: undisplayed = frame->GetContentOffset() - mNodeCharIndex; michael@0: } michael@0: } else { michael@0: // Different text node from last time. michael@0: if (mPreviousNode->TextLength() != mNodeCharIndex) { michael@0: NS_ASSERTION(mNodeCharIndex < mPreviousNode->TextLength(), michael@0: "incorrect tracking of undisplayed characters in " michael@0: "text nodes"); michael@0: // Any trailing characters at the end of the previous nsTextNode are michael@0: // undisplayed. michael@0: undisplayed = mPreviousNode->TextLength() - mNodeCharIndex; michael@0: } michael@0: // Each whole nsTextNode we find before we get to the text node for michael@0: // the current text frame must be undisplayed. michael@0: while (mNodeIterator.Current() != node) { michael@0: undisplayed += mNodeIterator.Current()->TextLength(); michael@0: NextNode(); michael@0: } michael@0: // If the current text frame starts at a non-zero content offset, then those michael@0: // earlier characters are also undisplayed. michael@0: undisplayed += frame->GetContentOffset(); michael@0: NextNode(); michael@0: } michael@0: michael@0: // Set the frame property. michael@0: frame->Properties().Set(TextNodeCorrespondenceProperty(), michael@0: new TextNodeCorrespondence(undisplayed)); michael@0: michael@0: // Remember how far into the current nsTextNode we are. michael@0: mNodeCharIndex = frame->GetContentEnd(); michael@0: } michael@0: michael@0: // ---------------------------------------------------------------------------- michael@0: // TextFrameIterator michael@0: michael@0: /** michael@0: * An iterator class for nsTextFrames that are descendants of an michael@0: * SVGTextFrame. The iterator can optionally track whether the michael@0: * current nsTextFrame is for a descendant of, or past, a given subtree michael@0: * content node or frame. (This functionality is used for example by the SVG michael@0: * DOM text methods to get only the nsTextFrames for a particular .) michael@0: * michael@0: * TextFrameIterator also tracks and exposes other information about the michael@0: * current nsTextFrame: michael@0: * michael@0: * * how many undisplayed characters came just before it michael@0: * * its position (in app units) relative to the SVGTextFrame's anonymous michael@0: * block frame michael@0: * * what nsInlineFrame corresponding to a element it is a michael@0: * descendant of michael@0: * * what computed dominant-baseline value applies to it michael@0: * michael@0: * Note that any text frames that are empty -- whose ContentLength() is 0 -- michael@0: * will be skipped over. michael@0: */ michael@0: class TextFrameIterator michael@0: { michael@0: public: michael@0: /** michael@0: * Constructs a TextFrameIterator for the specified SVGTextFrame michael@0: * with an optional frame subtree to restrict iterated text frames to. michael@0: */ michael@0: TextFrameIterator(SVGTextFrame* aRoot, nsIFrame* aSubtree = nullptr) michael@0: : mRootFrame(aRoot), michael@0: mSubtree(aSubtree), michael@0: mCurrentFrame(aRoot), michael@0: mCurrentPosition(), michael@0: mSubtreePosition(mSubtree ? eBeforeSubtree : eWithinSubtree) michael@0: { michael@0: Init(); michael@0: } michael@0: michael@0: /** michael@0: * Constructs a TextFrameIterator for the specified SVGTextFrame michael@0: * with an optional frame content subtree to restrict iterated text frames to. michael@0: */ michael@0: TextFrameIterator(SVGTextFrame* aRoot, nsIContent* aSubtree) michael@0: : mRootFrame(aRoot), michael@0: mSubtree(aRoot && aSubtree && aSubtree != aRoot->GetContent() ? michael@0: aSubtree->GetPrimaryFrame() : michael@0: nullptr), michael@0: mCurrentFrame(aRoot), michael@0: mCurrentPosition(), michael@0: mSubtreePosition(mSubtree ? eBeforeSubtree : eWithinSubtree) michael@0: { michael@0: Init(); michael@0: } michael@0: michael@0: /** michael@0: * Returns the root SVGTextFrame this TextFrameIterator is iterating over. michael@0: */ michael@0: SVGTextFrame* Root() const michael@0: { michael@0: return mRootFrame; michael@0: } michael@0: michael@0: /** michael@0: * Returns the current nsTextFrame. michael@0: */ michael@0: nsTextFrame* Current() const michael@0: { michael@0: return do_QueryFrame(mCurrentFrame); michael@0: } michael@0: michael@0: /** michael@0: * Returns the number of undisplayed characters in the DOM just before the michael@0: * current frame. michael@0: */ michael@0: uint32_t UndisplayedCharacters() const; michael@0: michael@0: /** michael@0: * Returns the current frame's position, in app units, relative to the michael@0: * root SVGTextFrame's anonymous block frame. michael@0: */ michael@0: nsPoint Position() const michael@0: { michael@0: return mCurrentPosition; michael@0: } michael@0: michael@0: /** michael@0: * Advances to the next nsTextFrame and returns it. michael@0: */ michael@0: nsTextFrame* Next(); michael@0: michael@0: /** michael@0: * Returns whether the iterator is within the subtree. michael@0: */ michael@0: bool IsWithinSubtree() const michael@0: { michael@0: return mSubtreePosition == eWithinSubtree; michael@0: } michael@0: michael@0: /** michael@0: * Returns whether the iterator is past the subtree. michael@0: */ michael@0: bool IsAfterSubtree() const michael@0: { michael@0: return mSubtreePosition == eAfterSubtree; michael@0: } michael@0: michael@0: /** michael@0: * Returns the frame corresponding to the element, if we michael@0: * are inside one. michael@0: */ michael@0: nsIFrame* TextPathFrame() const michael@0: { michael@0: return mTextPathFrames.IsEmpty() ? michael@0: nullptr : michael@0: mTextPathFrames.ElementAt(mTextPathFrames.Length() - 1); michael@0: } michael@0: michael@0: /** michael@0: * Returns the current frame's computed dominant-baseline value. michael@0: */ michael@0: uint8_t DominantBaseline() const michael@0: { michael@0: return mBaselines.ElementAt(mBaselines.Length() - 1); michael@0: } michael@0: michael@0: /** michael@0: * Finishes the iterator. michael@0: */ michael@0: void Close() michael@0: { michael@0: mCurrentFrame = nullptr; michael@0: } michael@0: michael@0: private: michael@0: /** michael@0: * Initializes the iterator and advances to the first item. michael@0: */ michael@0: void Init() michael@0: { michael@0: if (!mRootFrame) { michael@0: return; michael@0: } michael@0: michael@0: mBaselines.AppendElement(mRootFrame->StyleSVGReset()->mDominantBaseline); michael@0: Next(); michael@0: } michael@0: michael@0: /** michael@0: * Pushes the specified frame's computed dominant-baseline value. michael@0: * If the value of the property is "auto", then the parent frame's michael@0: * computed value is used. michael@0: */ michael@0: void PushBaseline(nsIFrame* aNextFrame); michael@0: michael@0: /** michael@0: * Pops the current dominant-baseline off the stack. michael@0: */ michael@0: void PopBaseline(); michael@0: michael@0: /** michael@0: * The root frame we are iterating through. michael@0: */ michael@0: SVGTextFrame* mRootFrame; michael@0: michael@0: /** michael@0: * The frame for the subtree we are also interested in tracking. michael@0: */ michael@0: nsIFrame* mSubtree; michael@0: michael@0: /** michael@0: * The current value of the iterator. michael@0: */ michael@0: nsIFrame* mCurrentFrame; michael@0: michael@0: /** michael@0: * The position, in app units, of the current frame relative to mRootFrame. michael@0: */ michael@0: nsPoint mCurrentPosition; michael@0: michael@0: /** michael@0: * Stack of frames corresponding to elements that are in scope michael@0: * for the current frame. michael@0: */ michael@0: nsAutoTArray mTextPathFrames; michael@0: michael@0: /** michael@0: * Stack of dominant-baseline values to record as we traverse through the michael@0: * frame tree. michael@0: */ michael@0: nsAutoTArray mBaselines; michael@0: michael@0: /** michael@0: * The iterator's current position relative to mSubtree. michael@0: */ michael@0: SubtreePosition mSubtreePosition; michael@0: }; michael@0: michael@0: uint32_t michael@0: TextFrameIterator::UndisplayedCharacters() const michael@0: { michael@0: MOZ_ASSERT(!(mRootFrame->GetFirstPrincipalChild() && michael@0: NS_SUBTREE_DIRTY(mRootFrame->GetFirstPrincipalChild())), michael@0: "should have already reflowed the anonymous block child"); michael@0: michael@0: if (!mCurrentFrame) { michael@0: return mRootFrame->mTrailingUndisplayedCharacters; michael@0: } michael@0: michael@0: nsTextFrame* frame = do_QueryFrame(mCurrentFrame); michael@0: return GetUndisplayedCharactersBeforeFrame(frame); michael@0: } michael@0: michael@0: nsTextFrame* michael@0: TextFrameIterator::Next() michael@0: { michael@0: // Starting from mCurrentFrame, we do a non-recursive traversal to the next michael@0: // nsTextFrame beneath mRoot, updating mSubtreePosition appropriately if we michael@0: // encounter mSubtree. michael@0: if (mCurrentFrame) { michael@0: do { michael@0: nsIFrame* next = IsTextContentElement(mCurrentFrame->GetContent()) ? michael@0: mCurrentFrame->GetFirstPrincipalChild() : michael@0: nullptr; michael@0: if (next) { michael@0: // Descend into this frame, and accumulate its position. michael@0: mCurrentPosition += next->GetPosition(); michael@0: if (next->GetContent()->Tag() == nsGkAtoms::textPath) { michael@0: // Record this frame. michael@0: mTextPathFrames.AppendElement(next); michael@0: } michael@0: // Record the frame's baseline. michael@0: PushBaseline(next); michael@0: mCurrentFrame = next; michael@0: if (mCurrentFrame == mSubtree) { michael@0: // If the current frame is mSubtree, we have now moved into it. michael@0: mSubtreePosition = eWithinSubtree; michael@0: } michael@0: } else { michael@0: for (;;) { michael@0: // We want to move past the current frame. michael@0: if (mCurrentFrame == mRootFrame) { michael@0: // If we've reached the root frame, we're finished. michael@0: mCurrentFrame = nullptr; michael@0: break; michael@0: } michael@0: // Remove the current frame's position. michael@0: mCurrentPosition -= mCurrentFrame->GetPosition(); michael@0: if (mCurrentFrame->GetContent()->Tag() == nsGkAtoms::textPath) { michael@0: // Pop off the frame if this is a . michael@0: mTextPathFrames.TruncateLength(mTextPathFrames.Length() - 1); michael@0: } michael@0: // Pop off the current baseline. michael@0: PopBaseline(); michael@0: if (mCurrentFrame == mSubtree) { michael@0: // If this was mSubtree, we have now moved past it. michael@0: mSubtreePosition = eAfterSubtree; michael@0: } michael@0: next = mCurrentFrame->GetNextSibling(); michael@0: if (next) { michael@0: // Moving to the next sibling. michael@0: mCurrentPosition += next->GetPosition(); michael@0: if (next->GetContent()->Tag() == nsGkAtoms::textPath) { michael@0: // Record this frame. michael@0: mTextPathFrames.AppendElement(next); michael@0: } michael@0: // Record the frame's baseline. michael@0: PushBaseline(next); michael@0: mCurrentFrame = next; michael@0: if (mCurrentFrame == mSubtree) { michael@0: // If the current frame is mSubtree, we have now moved into it. michael@0: mSubtreePosition = eWithinSubtree; michael@0: } michael@0: break; michael@0: } michael@0: if (mCurrentFrame == mSubtree) { michael@0: // If there is no next sibling frame, and the current frame is michael@0: // mSubtree, we have now moved past it. michael@0: mSubtreePosition = eAfterSubtree; michael@0: } michael@0: // Ascend out of this frame. michael@0: mCurrentFrame = mCurrentFrame->GetParent(); michael@0: } michael@0: } michael@0: } while (mCurrentFrame && michael@0: !IsNonEmptyTextFrame(mCurrentFrame)); michael@0: } michael@0: michael@0: return Current(); michael@0: } michael@0: michael@0: void michael@0: TextFrameIterator::PushBaseline(nsIFrame* aNextFrame) michael@0: { michael@0: uint8_t baseline = aNextFrame->StyleSVGReset()->mDominantBaseline; michael@0: if (baseline == NS_STYLE_DOMINANT_BASELINE_AUTO) { michael@0: baseline = mBaselines.LastElement(); michael@0: } michael@0: mBaselines.AppendElement(baseline); michael@0: } michael@0: michael@0: void michael@0: TextFrameIterator::PopBaseline() michael@0: { michael@0: NS_ASSERTION(!mBaselines.IsEmpty(), "popped too many baselines"); michael@0: mBaselines.TruncateLength(mBaselines.Length() - 1); michael@0: } michael@0: michael@0: // ----------------------------------------------------------------------------- michael@0: // TextRenderedRunIterator michael@0: michael@0: /** michael@0: * Iterator for TextRenderedRun objects for the SVGTextFrame. michael@0: */ michael@0: class TextRenderedRunIterator michael@0: { michael@0: public: michael@0: /** michael@0: * Values for the aFilter argument of the constructor, to indicate which frames michael@0: * we should be limited to iterating TextRenderedRun objects for. michael@0: */ michael@0: enum RenderedRunFilter { michael@0: // Iterate TextRenderedRuns for all nsTextFrames. michael@0: eAllFrames, michael@0: // Iterate only TextRenderedRuns for nsTextFrames that are michael@0: // visibility:visible. michael@0: eVisibleFrames michael@0: }; michael@0: michael@0: /** michael@0: * Constructs a TextRenderedRunIterator with an optional frame subtree to michael@0: * restrict iterated rendered runs to. michael@0: * michael@0: * @param aSVGTextFrame The SVGTextFrame whose rendered runs to iterate michael@0: * through. michael@0: * @param aFilter Indicates whether to iterate rendered runs for non-visible michael@0: * nsTextFrames. michael@0: * @param aSubtree An optional frame subtree to restrict iterated rendered michael@0: * runs to. michael@0: */ michael@0: TextRenderedRunIterator(SVGTextFrame* aSVGTextFrame, michael@0: RenderedRunFilter aFilter = eAllFrames, michael@0: nsIFrame* aSubtree = nullptr) michael@0: : mFrameIterator(FrameIfAnonymousChildReflowed(aSVGTextFrame), aSubtree), michael@0: mFilter(aFilter), michael@0: mTextElementCharIndex(0), michael@0: mFrameStartTextElementCharIndex(0), michael@0: mFontSizeScaleFactor(aSVGTextFrame->mFontSizeScaleFactor), michael@0: mCurrent(First()) michael@0: { michael@0: } michael@0: michael@0: /** michael@0: * Constructs a TextRenderedRunIterator with a content subtree to restrict michael@0: * iterated rendered runs to. michael@0: * michael@0: * @param aSVGTextFrame The SVGTextFrame whose rendered runs to iterate michael@0: * through. michael@0: * @param aFilter Indicates whether to iterate rendered runs for non-visible michael@0: * nsTextFrames. michael@0: * @param aSubtree A content subtree to restrict iterated rendered runs to. michael@0: */ michael@0: TextRenderedRunIterator(SVGTextFrame* aSVGTextFrame, michael@0: RenderedRunFilter aFilter, michael@0: nsIContent* aSubtree) michael@0: : mFrameIterator(FrameIfAnonymousChildReflowed(aSVGTextFrame), aSubtree), michael@0: mFilter(aFilter), michael@0: mTextElementCharIndex(0), michael@0: mFrameStartTextElementCharIndex(0), michael@0: mFontSizeScaleFactor(aSVGTextFrame->mFontSizeScaleFactor), michael@0: mCurrent(First()) michael@0: { michael@0: } michael@0: michael@0: /** michael@0: * Returns the current TextRenderedRun. michael@0: */ michael@0: TextRenderedRun Current() const michael@0: { michael@0: return mCurrent; michael@0: } michael@0: michael@0: /** michael@0: * Advances to the next TextRenderedRun and returns it. michael@0: */ michael@0: TextRenderedRun Next(); michael@0: michael@0: private: michael@0: /** michael@0: * Returns the root SVGTextFrame this iterator is for. michael@0: */ michael@0: SVGTextFrame* Root() const michael@0: { michael@0: return mFrameIterator.Root(); michael@0: } michael@0: michael@0: /** michael@0: * Advances to the first TextRenderedRun and returns it. michael@0: */ michael@0: TextRenderedRun First(); michael@0: michael@0: /** michael@0: * The frame iterator to use. michael@0: */ michael@0: TextFrameIterator mFrameIterator; michael@0: michael@0: /** michael@0: * The filter indicating which TextRenderedRuns to return. michael@0: */ michael@0: RenderedRunFilter mFilter; michael@0: michael@0: /** michael@0: * The character index across the entire element we are currently michael@0: * up to. michael@0: */ michael@0: uint32_t mTextElementCharIndex; michael@0: michael@0: /** michael@0: * The character index across the entire for the start of the current michael@0: * frame. michael@0: */ michael@0: uint32_t mFrameStartTextElementCharIndex; michael@0: michael@0: /** michael@0: * The font-size scale factor we used when constructing the nsTextFrames. michael@0: */ michael@0: double mFontSizeScaleFactor; michael@0: michael@0: /** michael@0: * The current TextRenderedRun. michael@0: */ michael@0: TextRenderedRun mCurrent; michael@0: }; michael@0: michael@0: TextRenderedRun michael@0: TextRenderedRunIterator::Next() michael@0: { michael@0: if (!mFrameIterator.Current()) { michael@0: // If there are no more frames, then there are no more rendered runs to michael@0: // return. michael@0: mCurrent = TextRenderedRun(); michael@0: return mCurrent; michael@0: } michael@0: michael@0: // The values we will use to initialize the TextRenderedRun with. michael@0: nsTextFrame* frame; michael@0: gfxPoint pt; michael@0: double rotate; michael@0: nscoord baseline; michael@0: uint32_t offset, length; michael@0: uint32_t charIndex; michael@0: michael@0: // We loop, because we want to skip over rendered runs that either aren't michael@0: // within our subtree of interest, because they don't match the filter, michael@0: // or because they are hidden due to having fallen off the end of a michael@0: // . michael@0: for (;;) { michael@0: if (mFrameIterator.IsAfterSubtree()) { michael@0: mCurrent = TextRenderedRun(); michael@0: return mCurrent; michael@0: } michael@0: michael@0: frame = mFrameIterator.Current(); michael@0: michael@0: charIndex = mTextElementCharIndex; michael@0: michael@0: // Find the end of the rendered run, by looking through the michael@0: // SVGTextFrame's positions array until we find one that is recorded michael@0: // as a run boundary. michael@0: uint32_t runStart, runEnd; // XXX Replace runStart with mTextElementCharIndex. michael@0: runStart = mTextElementCharIndex; michael@0: runEnd = runStart + 1; michael@0: while (runEnd < Root()->mPositions.Length() && michael@0: !Root()->mPositions[runEnd].mRunBoundary) { michael@0: runEnd++; michael@0: } michael@0: michael@0: // Convert the global run start/end indexes into an offset/length into the michael@0: // current frame's nsTextNode. michael@0: offset = frame->GetContentOffset() + runStart - michael@0: mFrameStartTextElementCharIndex; michael@0: length = runEnd - runStart; michael@0: michael@0: // If the end of the frame's content comes before the run boundary we found michael@0: // in SVGTextFrame's position array, we need to shorten the rendered run. michael@0: uint32_t contentEnd = frame->GetContentEnd(); michael@0: if (offset + length > contentEnd) { michael@0: length = contentEnd - offset; michael@0: } michael@0: michael@0: NS_ASSERTION(offset >= uint32_t(frame->GetContentOffset()), "invalid offset"); michael@0: NS_ASSERTION(offset + length <= contentEnd, "invalid offset or length"); michael@0: michael@0: // Get the frame's baseline position. michael@0: frame->EnsureTextRun(nsTextFrame::eInflated); michael@0: baseline = GetBaselinePosition(frame, michael@0: frame->GetTextRun(nsTextFrame::eInflated), michael@0: mFrameIterator.DominantBaseline()); michael@0: michael@0: // Trim the offset/length to remove any leading/trailing white space. michael@0: uint32_t untrimmedOffset = offset; michael@0: uint32_t untrimmedLength = length; michael@0: nsTextFrame::TrimmedOffsets trimmedOffsets = michael@0: frame->GetTrimmedOffsets(frame->GetContent()->GetText(), true); michael@0: TrimOffsets(offset, length, trimmedOffsets); michael@0: charIndex += offset - untrimmedOffset; michael@0: michael@0: // Get the position and rotation of the character that begins this michael@0: // rendered run. michael@0: pt = Root()->mPositions[charIndex].mPosition; michael@0: rotate = Root()->mPositions[charIndex].mAngle; michael@0: michael@0: // Determine if we should skip this rendered run. michael@0: bool skip = !mFrameIterator.IsWithinSubtree() || michael@0: Root()->mPositions[mTextElementCharIndex].mHidden; michael@0: if (mFilter == eVisibleFrames) { michael@0: skip = skip || !frame->StyleVisibility()->IsVisible(); michael@0: } michael@0: michael@0: // Update our global character index to move past the characters michael@0: // corresponding to this rendered run. michael@0: mTextElementCharIndex += untrimmedLength; michael@0: michael@0: // If we have moved past the end of the current frame's content, we need to michael@0: // advance to the next frame. michael@0: if (offset + untrimmedLength >= contentEnd) { michael@0: mFrameIterator.Next(); michael@0: mTextElementCharIndex += mFrameIterator.UndisplayedCharacters(); michael@0: mFrameStartTextElementCharIndex = mTextElementCharIndex; michael@0: } michael@0: michael@0: if (!mFrameIterator.Current()) { michael@0: if (skip) { michael@0: // That was the last frame, and we skipped this rendered run. So we michael@0: // have no rendered run to return. michael@0: mCurrent = TextRenderedRun(); michael@0: return mCurrent; michael@0: } michael@0: break; michael@0: } michael@0: michael@0: if (length && !skip) { michael@0: // Only return a rendered run if it didn't get collapsed away entirely michael@0: // (due to it being all white space) and if we don't want to skip it. michael@0: break; michael@0: } michael@0: } michael@0: michael@0: mCurrent = TextRenderedRun(frame, pt, Root()->mLengthAdjustScaleFactor, michael@0: rotate, mFontSizeScaleFactor, baseline, michael@0: offset, length, charIndex); michael@0: return mCurrent; michael@0: } michael@0: michael@0: TextRenderedRun michael@0: TextRenderedRunIterator::First() michael@0: { michael@0: if (!mFrameIterator.Current()) { michael@0: return TextRenderedRun(); michael@0: } michael@0: michael@0: if (Root()->mPositions.IsEmpty()) { michael@0: mFrameIterator.Close(); michael@0: return TextRenderedRun(); michael@0: } michael@0: michael@0: // Get the character index for the start of this rendered run, by skipping michael@0: // any undisplayed characters. michael@0: mTextElementCharIndex = mFrameIterator.UndisplayedCharacters(); michael@0: mFrameStartTextElementCharIndex = mTextElementCharIndex; michael@0: michael@0: return Next(); michael@0: } michael@0: michael@0: // ----------------------------------------------------------------------------- michael@0: // CharIterator michael@0: michael@0: /** michael@0: * Iterator for characters within an SVGTextFrame. michael@0: */ michael@0: class CharIterator michael@0: { michael@0: public: michael@0: /** michael@0: * Values for the aFilter argument of the constructor, to indicate which michael@0: * characters we should be iterating over. michael@0: */ michael@0: enum CharacterFilter { michael@0: // Iterate over all original characters from the DOM that are within valid michael@0: // text content elements. michael@0: eOriginal, michael@0: // Iterate only over characters that are addressable by the positioning michael@0: // attributes x="", y="", etc. This includes all characters after michael@0: // collapsing white space as required by the value of 'white-space'. michael@0: eAddressable, michael@0: // Iterate only over characters that are the first of clusters or ligature michael@0: // groups. michael@0: eClusterAndLigatureGroupStart, michael@0: // Iterate only over characters that are part of a cluster or ligature michael@0: // group but not the first character. michael@0: eClusterOrLigatureGroupMiddle michael@0: }; michael@0: michael@0: /** michael@0: * Constructs a CharIterator. michael@0: * michael@0: * @param aSVGTextFrame The SVGTextFrame whose characters to iterate michael@0: * through. michael@0: * @param aFilter Indicates which characters to iterate over. michael@0: * @param aSubtree A content subtree to track whether the current character michael@0: * is within. michael@0: */ michael@0: CharIterator(SVGTextFrame* aSVGTextFrame, michael@0: CharacterFilter aFilter, michael@0: nsIContent* aSubtree = nullptr); michael@0: michael@0: /** michael@0: * Returns whether the iterator is finished. michael@0: */ michael@0: bool AtEnd() const michael@0: { michael@0: return !mFrameIterator.Current(); michael@0: } michael@0: michael@0: /** michael@0: * Advances to the next matching character. Returns true if there was a michael@0: * character to advance to, and false otherwise. michael@0: */ michael@0: bool Next(); michael@0: michael@0: /** michael@0: * Advances ahead aCount matching characters. Returns true if there were michael@0: * enough characters to advance past, and false otherwise. michael@0: */ michael@0: bool Next(uint32_t aCount); michael@0: michael@0: /** michael@0: * Advances ahead up to aCount matching characters. michael@0: */ michael@0: void NextWithinSubtree(uint32_t aCount); michael@0: michael@0: /** michael@0: * Advances to the character with the specified index. The index is in the michael@0: * space of original characters (i.e., all DOM characters under the michael@0: * that are within valid text content elements). michael@0: */ michael@0: bool AdvanceToCharacter(uint32_t aTextElementCharIndex); michael@0: michael@0: /** michael@0: * Advances to the first matching character after the current nsTextFrame. michael@0: */ michael@0: bool AdvancePastCurrentFrame(); michael@0: michael@0: /** michael@0: * Advances to the first matching character after the frames within michael@0: * the current . michael@0: */ michael@0: bool AdvancePastCurrentTextPathFrame(); michael@0: michael@0: /** michael@0: * Advances to the first matching character of the subtree. Returns true michael@0: * if we successfully advance to the subtree, or if we are already within michael@0: * the subtree. Returns false if we are past the subtree. michael@0: */ michael@0: bool AdvanceToSubtree(); michael@0: michael@0: /** michael@0: * Returns the nsTextFrame for the current character. michael@0: */ michael@0: nsTextFrame* TextFrame() const michael@0: { michael@0: return mFrameIterator.Current(); michael@0: } michael@0: michael@0: /** michael@0: * Returns whether the iterator is within the subtree. michael@0: */ michael@0: bool IsWithinSubtree() const michael@0: { michael@0: return mFrameIterator.IsWithinSubtree(); michael@0: } michael@0: michael@0: /** michael@0: * Returns whether the iterator is past the subtree. michael@0: */ michael@0: bool IsAfterSubtree() const michael@0: { michael@0: return mFrameIterator.IsAfterSubtree(); michael@0: } michael@0: michael@0: /** michael@0: * Returns whether the current character is a skipped character. michael@0: */ michael@0: bool IsOriginalCharSkipped() const michael@0: { michael@0: return mSkipCharsIterator.IsOriginalCharSkipped(); michael@0: } michael@0: michael@0: /** michael@0: * Returns whether the current character is the start of a cluster and michael@0: * ligature group. michael@0: */ michael@0: bool IsClusterAndLigatureGroupStart() const; michael@0: michael@0: /** michael@0: * Returns whether the current character is trimmed away when painting, michael@0: * due to it being leading/trailing white space. michael@0: */ michael@0: bool IsOriginalCharTrimmed() const; michael@0: michael@0: /** michael@0: * Returns whether the current character is unaddressable from the SVG glyph michael@0: * positioning attributes. michael@0: */ michael@0: bool IsOriginalCharUnaddressable() const michael@0: { michael@0: return IsOriginalCharSkipped() || IsOriginalCharTrimmed(); michael@0: } michael@0: michael@0: /** michael@0: * Returns the text run for the current character. michael@0: */ michael@0: gfxTextRun* TextRun() const michael@0: { michael@0: return mTextRun; michael@0: } michael@0: michael@0: /** michael@0: * Returns the current character index. michael@0: */ michael@0: uint32_t TextElementCharIndex() const michael@0: { michael@0: return mTextElementCharIndex; michael@0: } michael@0: michael@0: /** michael@0: * Returns the character index for the start of the cluster/ligature group it michael@0: * is part of. michael@0: */ michael@0: uint32_t GlyphStartTextElementCharIndex() const michael@0: { michael@0: return mGlyphStartTextElementCharIndex; michael@0: } michael@0: michael@0: /** michael@0: * Returns the number of undisplayed characters between the beginning of michael@0: * the glyph and the current character. michael@0: */ michael@0: uint32_t GlyphUndisplayedCharacters() const michael@0: { michael@0: return mGlyphUndisplayedCharacters; michael@0: } michael@0: michael@0: /** michael@0: * Gets the original character offsets within the nsTextNode for the michael@0: * cluster/ligature group the current character is a part of. michael@0: * michael@0: * @param aOriginalOffset The offset of the start of the cluster/ligature michael@0: * group (output). michael@0: * @param aOriginalLength The length of cluster/ligature group (output). michael@0: */ michael@0: void GetOriginalGlyphOffsets(uint32_t& aOriginalOffset, michael@0: uint32_t& aOriginalLength) const; michael@0: michael@0: /** michael@0: * Gets the advance, in user units, of the glyph the current character is michael@0: * part of. michael@0: * michael@0: * @param aContext The context to use for unit conversions. michael@0: */ michael@0: gfxFloat GetGlyphAdvance(nsPresContext* aContext) const; michael@0: michael@0: /** michael@0: * Gets the advance, in user units, of the current character. If the michael@0: * character is a part of ligature, then the advance returned will be michael@0: * a fraction of the ligature glyph's advance. michael@0: * michael@0: * @param aContext The context to use for unit conversions. michael@0: */ michael@0: gfxFloat GetAdvance(nsPresContext* aContext) const; michael@0: michael@0: /** michael@0: * Gets the specified partial advance of the glyph the current character is michael@0: * part of. The partial advance is measured from the first character michael@0: * corresponding to the glyph until the specified part length. michael@0: * michael@0: * The part length value does not include any undisplayed characters in the michael@0: * middle of the cluster/ligature group. For example, if you have: michael@0: * michael@0: * fxi michael@0: * michael@0: * and the "f" and "i" are ligaturized, then calling GetGlyphPartialAdvance michael@0: * with aPartLength values will have the following results: michael@0: * michael@0: * 0 => 0 michael@0: * 1 => adv("fi") / 2 michael@0: * 2 => adv("fi") michael@0: * michael@0: * @param aPartLength The number of characters in the cluster/ligature group michael@0: * to measure. michael@0: * @param aContext The context to use for unit conversions. michael@0: */ michael@0: gfxFloat GetGlyphPartialAdvance(uint32_t aPartLength, michael@0: nsPresContext* aContext) const; michael@0: michael@0: /** michael@0: * Returns the frame corresponding to the that the current michael@0: * character is within. michael@0: */ michael@0: nsIFrame* TextPathFrame() const michael@0: { michael@0: return mFrameIterator.TextPathFrame(); michael@0: } michael@0: michael@0: private: michael@0: /** michael@0: * Advances to the next character without checking it against the filter. michael@0: * Returns true if there was a next character to advance to, or false michael@0: * otherwise. michael@0: */ michael@0: bool NextCharacter(); michael@0: michael@0: /** michael@0: * Returns whether the current character matches the filter. michael@0: */ michael@0: bool MatchesFilter() const; michael@0: michael@0: /** michael@0: * If this is the start of a glyph, record it. michael@0: */ michael@0: void UpdateGlyphStartTextElementCharIndex() { michael@0: if (!IsOriginalCharSkipped() && IsClusterAndLigatureGroupStart()) { michael@0: mGlyphStartTextElementCharIndex = mTextElementCharIndex; michael@0: mGlyphUndisplayedCharacters = 0; michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * The filter to use. michael@0: */ michael@0: CharacterFilter mFilter; michael@0: michael@0: /** michael@0: * The iterator for text frames. michael@0: */ michael@0: TextFrameIterator mFrameIterator; michael@0: michael@0: /** michael@0: * A gfxSkipCharsIterator for the text frame the current character is michael@0: * a part of. michael@0: */ michael@0: gfxSkipCharsIterator mSkipCharsIterator; michael@0: michael@0: // Cache for information computed by IsOriginalCharTrimmed. michael@0: mutable nsTextFrame* mFrameForTrimCheck; michael@0: mutable uint32_t mTrimmedOffset; michael@0: mutable uint32_t mTrimmedLength; michael@0: michael@0: /** michael@0: * The text run the current character is a part of. michael@0: */ michael@0: gfxTextRun* mTextRun; michael@0: michael@0: /** michael@0: * The current character's index. michael@0: */ michael@0: uint32_t mTextElementCharIndex; michael@0: michael@0: /** michael@0: * The index of the character that starts the cluster/ligature group the michael@0: * current character is a part of. michael@0: */ michael@0: uint32_t mGlyphStartTextElementCharIndex; michael@0: michael@0: /** michael@0: * If we are iterating in mode eClusterOrLigatureGroupMiddle, then michael@0: * this tracks how many undisplayed characters were encountered michael@0: * between the start of this glyph (at mGlyphStartTextElementCharIndex) michael@0: * and the current character (at mTextElementCharIndex). michael@0: */ michael@0: uint32_t mGlyphUndisplayedCharacters; michael@0: michael@0: /** michael@0: * The scale factor to apply to glyph advances returned by michael@0: * GetGlyphAdvance etc. to take into account textLength="". michael@0: */ michael@0: float mLengthAdjustScaleFactor; michael@0: }; michael@0: michael@0: CharIterator::CharIterator(SVGTextFrame* aSVGTextFrame, michael@0: CharIterator::CharacterFilter aFilter, michael@0: nsIContent* aSubtree) michael@0: : mFilter(aFilter), michael@0: mFrameIterator(FrameIfAnonymousChildReflowed(aSVGTextFrame), aSubtree), michael@0: mFrameForTrimCheck(nullptr), michael@0: mTrimmedOffset(0), michael@0: mTrimmedLength(0), michael@0: mTextElementCharIndex(0), michael@0: mGlyphStartTextElementCharIndex(0), michael@0: mLengthAdjustScaleFactor(aSVGTextFrame->mLengthAdjustScaleFactor) michael@0: { michael@0: if (!AtEnd()) { michael@0: mSkipCharsIterator = TextFrame()->EnsureTextRun(nsTextFrame::eInflated); michael@0: mTextRun = TextFrame()->GetTextRun(nsTextFrame::eInflated); michael@0: mTextElementCharIndex = mFrameIterator.UndisplayedCharacters(); michael@0: UpdateGlyphStartTextElementCharIndex(); michael@0: if (!MatchesFilter()) { michael@0: Next(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool michael@0: CharIterator::Next() michael@0: { michael@0: while (NextCharacter()) { michael@0: if (MatchesFilter()) { michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: CharIterator::Next(uint32_t aCount) michael@0: { michael@0: if (aCount == 0 && AtEnd()) { michael@0: return false; michael@0: } michael@0: while (aCount) { michael@0: if (!Next()) { michael@0: return false; michael@0: } michael@0: aCount--; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: CharIterator::NextWithinSubtree(uint32_t aCount) michael@0: { michael@0: while (IsWithinSubtree() && aCount) { michael@0: --aCount; michael@0: if (!Next()) { michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool michael@0: CharIterator::AdvanceToCharacter(uint32_t aTextElementCharIndex) michael@0: { michael@0: while (mTextElementCharIndex < aTextElementCharIndex) { michael@0: if (!Next()) { michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CharIterator::AdvancePastCurrentFrame() michael@0: { michael@0: // XXX Can do this better than one character at a time if it matters. michael@0: nsTextFrame* currentFrame = TextFrame(); michael@0: do { michael@0: if (!Next()) { michael@0: return false; michael@0: } michael@0: } while (TextFrame() == currentFrame); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CharIterator::AdvancePastCurrentTextPathFrame() michael@0: { michael@0: nsIFrame* currentTextPathFrame = TextPathFrame(); michael@0: NS_ASSERTION(currentTextPathFrame, michael@0: "expected AdvancePastCurrentTextPathFrame to be called only " michael@0: "within a text path frame"); michael@0: do { michael@0: if (!AdvancePastCurrentFrame()) { michael@0: return false; michael@0: } michael@0: } while (TextPathFrame() == currentTextPathFrame); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CharIterator::AdvanceToSubtree() michael@0: { michael@0: while (!IsWithinSubtree()) { michael@0: if (IsAfterSubtree()) { michael@0: return false; michael@0: } michael@0: if (!AdvancePastCurrentFrame()) { michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CharIterator::IsClusterAndLigatureGroupStart() const michael@0: { michael@0: return mTextRun->IsLigatureGroupStart(mSkipCharsIterator.GetSkippedOffset()) && michael@0: mTextRun->IsClusterStart(mSkipCharsIterator.GetSkippedOffset()); michael@0: } michael@0: michael@0: bool michael@0: CharIterator::IsOriginalCharTrimmed() const michael@0: { michael@0: if (mFrameForTrimCheck != TextFrame()) { michael@0: // Since we do a lot of trim checking, we cache the trimmed offsets and michael@0: // lengths while we are in the same frame. michael@0: mFrameForTrimCheck = TextFrame(); michael@0: uint32_t offset = mFrameForTrimCheck->GetContentOffset(); michael@0: uint32_t length = mFrameForTrimCheck->GetContentLength(); michael@0: nsIContent* content = mFrameForTrimCheck->GetContent(); michael@0: nsTextFrame::TrimmedOffsets trim = michael@0: mFrameForTrimCheck->GetTrimmedOffsets(content->GetText(), true); michael@0: TrimOffsets(offset, length, trim); michael@0: mTrimmedOffset = offset; michael@0: mTrimmedLength = length; michael@0: } michael@0: michael@0: // A character is trimmed if it is outside the mTrimmedOffset/mTrimmedLength michael@0: // range and it is not a significant newline character. michael@0: uint32_t index = mSkipCharsIterator.GetOriginalOffset(); michael@0: return !((index >= mTrimmedOffset && michael@0: index < mTrimmedOffset + mTrimmedLength) || michael@0: (index >= mTrimmedOffset + mTrimmedLength && michael@0: mFrameForTrimCheck->StyleText()->NewlineIsSignificant() && michael@0: mFrameForTrimCheck->GetContent()->GetText()->CharAt(index) == '\n')); michael@0: } michael@0: michael@0: void michael@0: CharIterator::GetOriginalGlyphOffsets(uint32_t& aOriginalOffset, michael@0: uint32_t& aOriginalLength) const michael@0: { michael@0: gfxSkipCharsIterator it = TextFrame()->EnsureTextRun(nsTextFrame::eInflated); michael@0: it.SetOriginalOffset(mSkipCharsIterator.GetOriginalOffset() - michael@0: (mTextElementCharIndex - michael@0: mGlyphStartTextElementCharIndex - michael@0: mGlyphUndisplayedCharacters)); michael@0: michael@0: while (it.GetSkippedOffset() > 0 && michael@0: (!mTextRun->IsClusterStart(it.GetSkippedOffset()) || michael@0: !mTextRun->IsLigatureGroupStart(it.GetSkippedOffset()))) { michael@0: it.AdvanceSkipped(-1); michael@0: } michael@0: michael@0: aOriginalOffset = it.GetOriginalOffset(); michael@0: michael@0: // Find the end of the cluster/ligature group. michael@0: it.SetOriginalOffset(mSkipCharsIterator.GetOriginalOffset()); michael@0: do { michael@0: it.AdvanceSkipped(1); michael@0: } while (it.GetSkippedOffset() < mTextRun->GetLength() && michael@0: (!mTextRun->IsClusterStart(it.GetSkippedOffset()) || michael@0: !mTextRun->IsLigatureGroupStart(it.GetSkippedOffset()))); michael@0: michael@0: aOriginalLength = it.GetOriginalOffset() - aOriginalOffset; michael@0: } michael@0: michael@0: gfxFloat michael@0: CharIterator::GetGlyphAdvance(nsPresContext* aContext) const michael@0: { michael@0: uint32_t offset, length; michael@0: GetOriginalGlyphOffsets(offset, length); michael@0: michael@0: gfxSkipCharsIterator it = TextFrame()->EnsureTextRun(nsTextFrame::eInflated); michael@0: ConvertOriginalToSkipped(it, offset, length); michael@0: michael@0: float cssPxPerDevPx = aContext-> michael@0: AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel()); michael@0: michael@0: gfxFloat advance = mTextRun->GetAdvanceWidth(offset, length, nullptr); michael@0: return aContext->AppUnitsToGfxUnits(advance) * michael@0: mLengthAdjustScaleFactor * cssPxPerDevPx; michael@0: } michael@0: michael@0: gfxFloat michael@0: CharIterator::GetAdvance(nsPresContext* aContext) const michael@0: { michael@0: float cssPxPerDevPx = aContext-> michael@0: AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel()); michael@0: michael@0: gfxFloat advance = michael@0: mTextRun->GetAdvanceWidth(mSkipCharsIterator.GetSkippedOffset(), 1, nullptr); michael@0: return aContext->AppUnitsToGfxUnits(advance) * michael@0: mLengthAdjustScaleFactor * cssPxPerDevPx; michael@0: } michael@0: michael@0: gfxFloat michael@0: CharIterator::GetGlyphPartialAdvance(uint32_t aPartLength, michael@0: nsPresContext* aContext) const michael@0: { michael@0: uint32_t offset, length; michael@0: GetOriginalGlyphOffsets(offset, length); michael@0: michael@0: NS_ASSERTION(aPartLength <= length, "invalid aPartLength value"); michael@0: length = aPartLength; michael@0: michael@0: gfxSkipCharsIterator it = TextFrame()->EnsureTextRun(nsTextFrame::eInflated); michael@0: ConvertOriginalToSkipped(it, offset, length); michael@0: michael@0: float cssPxPerDevPx = aContext-> michael@0: AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel()); michael@0: michael@0: gfxFloat advance = mTextRun->GetAdvanceWidth(offset, length, nullptr); michael@0: return aContext->AppUnitsToGfxUnits(advance) * michael@0: mLengthAdjustScaleFactor * cssPxPerDevPx; michael@0: } michael@0: michael@0: bool michael@0: CharIterator::NextCharacter() michael@0: { michael@0: if (AtEnd()) { michael@0: return false; michael@0: } michael@0: michael@0: mTextElementCharIndex++; michael@0: michael@0: // Advance within the current text run. michael@0: mSkipCharsIterator.AdvanceOriginal(1); michael@0: if (mSkipCharsIterator.GetOriginalOffset() < TextFrame()->GetContentEnd()) { michael@0: // We're still within the part of the text run for the current text frame. michael@0: UpdateGlyphStartTextElementCharIndex(); michael@0: return true; michael@0: } michael@0: michael@0: // Advance to the next frame. michael@0: mFrameIterator.Next(); michael@0: michael@0: // Skip any undisplayed characters. michael@0: uint32_t undisplayed = mFrameIterator.UndisplayedCharacters(); michael@0: mGlyphUndisplayedCharacters += undisplayed; michael@0: mTextElementCharIndex += undisplayed; michael@0: if (!TextFrame()) { michael@0: // We're at the end. michael@0: mSkipCharsIterator = gfxSkipCharsIterator(); michael@0: return false; michael@0: } michael@0: michael@0: mSkipCharsIterator = TextFrame()->EnsureTextRun(nsTextFrame::eInflated); michael@0: mTextRun = TextFrame()->GetTextRun(nsTextFrame::eInflated); michael@0: UpdateGlyphStartTextElementCharIndex(); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CharIterator::MatchesFilter() const michael@0: { michael@0: if (mFilter == eOriginal) { michael@0: return true; michael@0: } michael@0: michael@0: if (IsOriginalCharSkipped()) { michael@0: return false; michael@0: } michael@0: michael@0: if (mFilter == eAddressable) { michael@0: return !IsOriginalCharUnaddressable(); michael@0: } michael@0: michael@0: return (mFilter == eClusterAndLigatureGroupStart) == michael@0: IsClusterAndLigatureGroupStart(); michael@0: } michael@0: michael@0: // ----------------------------------------------------------------------------- michael@0: // nsCharClipDisplayItem michael@0: michael@0: /** michael@0: * An nsCharClipDisplayItem that obtains its left and right clip edges from a michael@0: * TextRenderedRun object. michael@0: */ michael@0: class SVGCharClipDisplayItem : public nsCharClipDisplayItem { michael@0: public: michael@0: SVGCharClipDisplayItem(const TextRenderedRun& aRun) michael@0: : nsCharClipDisplayItem(aRun.mFrame) michael@0: { michael@0: aRun.GetClipEdges(mLeftEdge, mRightEdge); michael@0: } michael@0: michael@0: NS_DISPLAY_DECL_NAME("SVGText", TYPE_TEXT) michael@0: }; michael@0: michael@0: // ----------------------------------------------------------------------------- michael@0: // SVGTextDrawPathCallbacks michael@0: michael@0: /** michael@0: * Text frame draw callback class that paints the text and text decoration parts michael@0: * of an nsTextFrame using SVG painting properties, and selection backgrounds michael@0: * and decorations as they would normally. michael@0: * michael@0: * An instance of this class is passed to nsTextFrame::PaintText if painting michael@0: * cannot be done directly (e.g. if we are using an SVG pattern fill, stroking michael@0: * the text, etc.). michael@0: */ michael@0: class SVGTextDrawPathCallbacks : public nsTextFrame::DrawPathCallbacks michael@0: { michael@0: public: michael@0: /** michael@0: * Constructs an SVGTextDrawPathCallbacks. michael@0: * michael@0: * @param aContext The context to use for painting. michael@0: * @param aFrame The nsTextFrame to paint. michael@0: * @param aCanvasTM The transformation matrix to set when painting; this michael@0: * should be the FOR_OUTERSVG_TM canvas TM of the text, so that michael@0: * paint servers are painted correctly. michael@0: * @param aShouldPaintSVGGlyphs Whether SVG glyphs should be painted. michael@0: */ michael@0: SVGTextDrawPathCallbacks(nsRenderingContext* aContext, michael@0: nsTextFrame* aFrame, michael@0: const gfxMatrix& aCanvasTM, michael@0: bool aShouldPaintSVGGlyphs) michael@0: : DrawPathCallbacks(aShouldPaintSVGGlyphs), michael@0: gfx(aContext->ThebesContext()), michael@0: mRenderMode(SVGAutoRenderState::GetRenderMode(aContext)), michael@0: mFrame(aFrame), michael@0: mCanvasTM(aCanvasTM) michael@0: { michael@0: } michael@0: michael@0: void NotifyBeforeText(nscolor aColor) MOZ_OVERRIDE; michael@0: void NotifyGlyphPathEmitted() MOZ_OVERRIDE; michael@0: void NotifyBeforeSVGGlyphPainted() MOZ_OVERRIDE; michael@0: void NotifyAfterSVGGlyphPainted() MOZ_OVERRIDE; michael@0: void NotifyAfterText() MOZ_OVERRIDE; michael@0: void NotifyBeforeSelectionBackground(nscolor aColor) MOZ_OVERRIDE; michael@0: void NotifySelectionBackgroundPathEmitted() MOZ_OVERRIDE; michael@0: void NotifyBeforeDecorationLine(nscolor aColor) MOZ_OVERRIDE; michael@0: void NotifyDecorationLinePathEmitted() MOZ_OVERRIDE; michael@0: void NotifyBeforeSelectionDecorationLine(nscolor aColor) MOZ_OVERRIDE; michael@0: void NotifySelectionDecorationLinePathEmitted() MOZ_OVERRIDE; michael@0: michael@0: private: michael@0: void FillWithOpacity(); michael@0: michael@0: void SetupContext(); michael@0: michael@0: /** michael@0: * Paints a piece of text geometry. This is called when glyphs michael@0: * or text decorations have been emitted to the gfxContext. michael@0: */ michael@0: void HandleTextGeometry(); michael@0: michael@0: /** michael@0: * Sets the gfxContext paint to the appropriate color or pattern michael@0: * for filling text geometry. michael@0: */ michael@0: bool SetFillColor(); michael@0: michael@0: /** michael@0: * Fills and strokes a piece of text geometry, using group opacity michael@0: * if the selection style requires it. michael@0: */ michael@0: void FillAndStrokeGeometry(); michael@0: michael@0: /** michael@0: * Fills a piece of text geometry. michael@0: */ michael@0: void FillGeometry(); michael@0: michael@0: /** michael@0: * Strokes a piece of text geometry. michael@0: */ michael@0: void StrokeGeometry(); michael@0: michael@0: gfxContext* gfx; michael@0: uint16_t mRenderMode; michael@0: nsTextFrame* mFrame; michael@0: const gfxMatrix& mCanvasTM; michael@0: michael@0: /** michael@0: * The color that we were last told from one of the path callback functions. michael@0: * This color can be the special NS_SAME_AS_FOREGROUND_COLOR, michael@0: * NS_40PERCENT_FOREGROUND_COLOR and NS_TRANSPARENT colors when we are michael@0: * painting selections or IME decorations. michael@0: */ michael@0: nscolor mColor; michael@0: }; michael@0: michael@0: void michael@0: SVGTextDrawPathCallbacks::NotifyBeforeText(nscolor aColor) michael@0: { michael@0: mColor = aColor; michael@0: SetupContext(); michael@0: gfx->NewPath(); michael@0: } michael@0: michael@0: void michael@0: SVGTextDrawPathCallbacks::NotifyGlyphPathEmitted() michael@0: { michael@0: HandleTextGeometry(); michael@0: gfx->NewPath(); michael@0: } michael@0: michael@0: void michael@0: SVGTextDrawPathCallbacks::NotifyBeforeSVGGlyphPainted() michael@0: { michael@0: gfx->Save(); michael@0: } michael@0: michael@0: void michael@0: SVGTextDrawPathCallbacks::NotifyAfterSVGGlyphPainted() michael@0: { michael@0: gfx->Restore(); michael@0: gfx->NewPath(); michael@0: } michael@0: michael@0: void michael@0: SVGTextDrawPathCallbacks::NotifyAfterText() michael@0: { michael@0: gfx->Restore(); michael@0: } michael@0: michael@0: void michael@0: SVGTextDrawPathCallbacks::NotifyBeforeSelectionBackground(nscolor aColor) michael@0: { michael@0: if (mRenderMode != SVGAutoRenderState::NORMAL) { michael@0: // Don't paint selection backgrounds when in a clip path. michael@0: return; michael@0: } michael@0: michael@0: mColor = aColor; michael@0: gfx->Save(); michael@0: } michael@0: michael@0: void michael@0: SVGTextDrawPathCallbacks::NotifySelectionBackgroundPathEmitted() michael@0: { michael@0: if (mRenderMode != SVGAutoRenderState::NORMAL) { michael@0: // Don't paint selection backgrounds when in a clip path. michael@0: return; michael@0: } michael@0: michael@0: if (SetFillColor()) { michael@0: FillWithOpacity(); michael@0: } michael@0: gfx->Restore(); michael@0: } michael@0: michael@0: void michael@0: SVGTextDrawPathCallbacks::NotifyBeforeDecorationLine(nscolor aColor) michael@0: { michael@0: mColor = aColor; michael@0: SetupContext(); michael@0: } michael@0: michael@0: void michael@0: SVGTextDrawPathCallbacks::NotifyDecorationLinePathEmitted() michael@0: { michael@0: HandleTextGeometry(); michael@0: gfx->NewPath(); michael@0: gfx->Restore(); michael@0: } michael@0: michael@0: void michael@0: SVGTextDrawPathCallbacks::NotifyBeforeSelectionDecorationLine(nscolor aColor) michael@0: { michael@0: if (mRenderMode != SVGAutoRenderState::NORMAL) { michael@0: // Don't paint selection decorations when in a clip path. michael@0: return; michael@0: } michael@0: michael@0: mColor = aColor; michael@0: gfx->Save(); michael@0: } michael@0: michael@0: void michael@0: SVGTextDrawPathCallbacks::NotifySelectionDecorationLinePathEmitted() michael@0: { michael@0: if (mRenderMode != SVGAutoRenderState::NORMAL) { michael@0: // Don't paint selection decorations when in a clip path. michael@0: return; michael@0: } michael@0: michael@0: FillAndStrokeGeometry(); michael@0: gfx->Restore(); michael@0: } michael@0: michael@0: void michael@0: SVGTextDrawPathCallbacks::FillWithOpacity() michael@0: { michael@0: gfx->FillWithOpacity(mColor == NS_40PERCENT_FOREGROUND_COLOR ? 0.4 : 1.0); michael@0: } michael@0: michael@0: void michael@0: SVGTextDrawPathCallbacks::SetupContext() michael@0: { michael@0: gfx->Save(); michael@0: michael@0: // XXX This is copied from nsSVGGlyphFrame::Render, but cairo doesn't actually michael@0: // seem to do anything with the antialias mode. So we can perhaps remove it, michael@0: // or make SetAntialiasMode set cairo text antialiasing too. michael@0: switch (mFrame->StyleSVG()->mTextRendering) { michael@0: case NS_STYLE_TEXT_RENDERING_OPTIMIZESPEED: michael@0: gfx->SetAntialiasMode(gfxContext::MODE_ALIASED); michael@0: break; michael@0: default: michael@0: gfx->SetAntialiasMode(gfxContext::MODE_COVERAGE); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: void michael@0: SVGTextDrawPathCallbacks::HandleTextGeometry() michael@0: { michael@0: if (mRenderMode != SVGAutoRenderState::NORMAL) { michael@0: // We're in a clip path. michael@0: if (mRenderMode == SVGAutoRenderState::CLIP_MASK) { michael@0: gfx->SetColor(gfxRGBA(1.0f, 1.0f, 1.0f, 1.0f)); michael@0: gfx->Fill(); michael@0: } michael@0: } else { michael@0: // Normal painting. michael@0: gfxContextMatrixAutoSaveRestore saveMatrix(gfx); michael@0: gfx->SetMatrix(mCanvasTM); michael@0: michael@0: FillAndStrokeGeometry(); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: SVGTextDrawPathCallbacks::SetFillColor() michael@0: { michael@0: if (mColor == NS_SAME_AS_FOREGROUND_COLOR || michael@0: mColor == NS_40PERCENT_FOREGROUND_COLOR) { michael@0: return nsSVGUtils::SetupCairoFillPaint(mFrame, gfx); michael@0: } michael@0: michael@0: if (mColor == NS_TRANSPARENT) { michael@0: return false; michael@0: } michael@0: michael@0: gfx->SetColor(gfxRGBA(mColor)); michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: SVGTextDrawPathCallbacks::FillAndStrokeGeometry() michael@0: { michael@0: bool pushedGroup = false; michael@0: if (mColor == NS_40PERCENT_FOREGROUND_COLOR) { michael@0: pushedGroup = true; michael@0: gfx->PushGroup(gfxContentType::COLOR_ALPHA); michael@0: } michael@0: michael@0: uint32_t paintOrder = mFrame->StyleSVG()->mPaintOrder; michael@0: if (paintOrder == NS_STYLE_PAINT_ORDER_NORMAL) { michael@0: FillGeometry(); michael@0: StrokeGeometry(); michael@0: } else { michael@0: while (paintOrder) { michael@0: uint32_t component = michael@0: paintOrder & ((1 << NS_STYLE_PAINT_ORDER_BITWIDTH) - 1); michael@0: switch (component) { michael@0: case NS_STYLE_PAINT_ORDER_FILL: michael@0: FillGeometry(); michael@0: break; michael@0: case NS_STYLE_PAINT_ORDER_STROKE: michael@0: StrokeGeometry(); michael@0: break; michael@0: } michael@0: paintOrder >>= NS_STYLE_PAINT_ORDER_BITWIDTH; michael@0: } michael@0: } michael@0: michael@0: if (pushedGroup) { michael@0: gfx->PopGroupToSource(); michael@0: gfx->Paint(0.4); michael@0: } michael@0: } michael@0: michael@0: void michael@0: SVGTextDrawPathCallbacks::FillGeometry() michael@0: { michael@0: if (SetFillColor()) { michael@0: gfx->Fill(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: SVGTextDrawPathCallbacks::StrokeGeometry() michael@0: { michael@0: if (mColor == NS_SAME_AS_FOREGROUND_COLOR || michael@0: mColor == NS_40PERCENT_FOREGROUND_COLOR) { michael@0: // Don't paint the stroke when we are filling with a selection color. michael@0: if (nsSVGUtils::SetupCairoStroke(mFrame, gfx)) { michael@0: gfx->Stroke(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // SVGTextContextPaint methods: michael@0: michael@0: already_AddRefed michael@0: SVGTextContextPaint::GetFillPattern(float aOpacity, michael@0: const gfxMatrix& aCTM) michael@0: { michael@0: return mFillPaint.GetPattern(aOpacity, &nsStyleSVG::mFill, aCTM); michael@0: } michael@0: michael@0: already_AddRefed michael@0: SVGTextContextPaint::GetStrokePattern(float aOpacity, michael@0: const gfxMatrix& aCTM) michael@0: { michael@0: return mStrokePaint.GetPattern(aOpacity, &nsStyleSVG::mStroke, aCTM); michael@0: } michael@0: michael@0: already_AddRefed michael@0: SVGTextContextPaint::Paint::GetPattern(float aOpacity, michael@0: nsStyleSVGPaint nsStyleSVG::*aFillOrStroke, michael@0: const gfxMatrix& aCTM) michael@0: { michael@0: nsRefPtr pattern; michael@0: if (mPatternCache.Get(aOpacity, getter_AddRefs(pattern))) { michael@0: // Set the pattern matrix just in case it was messed with by a previous michael@0: // caller. We should get the same matrix each time a pattern is constructed michael@0: // so this should be fine. michael@0: pattern->SetMatrix(aCTM * mPatternMatrix); michael@0: return pattern.forget(); michael@0: } michael@0: michael@0: switch (mPaintType) { michael@0: case eStyleSVGPaintType_None: michael@0: pattern = new gfxPattern(gfxRGBA(0.0f, 0.0f, 0.0f, 0.0f)); michael@0: mPatternMatrix = gfxMatrix(); michael@0: break; michael@0: case eStyleSVGPaintType_Color: michael@0: pattern = new gfxPattern(gfxRGBA(NS_GET_R(mPaintDefinition.mColor) / 255.0, michael@0: NS_GET_G(mPaintDefinition.mColor) / 255.0, michael@0: NS_GET_B(mPaintDefinition.mColor) / 255.0, michael@0: NS_GET_A(mPaintDefinition.mColor) / 255.0 * aOpacity)); michael@0: mPatternMatrix = gfxMatrix(); michael@0: break; michael@0: case eStyleSVGPaintType_Server: michael@0: pattern = mPaintDefinition.mPaintServerFrame->GetPaintServerPattern(mFrame, michael@0: mContextMatrix, michael@0: aFillOrStroke, michael@0: aOpacity); michael@0: { michael@0: // m maps original-user-space to pattern space michael@0: gfxMatrix m = pattern->GetMatrix(); michael@0: gfxMatrix deviceToOriginalUserSpace = mContextMatrix; michael@0: deviceToOriginalUserSpace.Invert(); michael@0: // mPatternMatrix maps device space to pattern space via original user space michael@0: mPatternMatrix = deviceToOriginalUserSpace * m; michael@0: } michael@0: pattern->SetMatrix(aCTM * mPatternMatrix); michael@0: break; michael@0: case eStyleSVGPaintType_ContextFill: michael@0: pattern = mPaintDefinition.mContextPaint->GetFillPattern(aOpacity, aCTM); michael@0: // Don't cache this. mContextPaint will have cached it anyway. If we michael@0: // cache it, we'll have to compute mPatternMatrix, which is annoying. michael@0: return pattern.forget(); michael@0: case eStyleSVGPaintType_ContextStroke: michael@0: pattern = mPaintDefinition.mContextPaint->GetStrokePattern(aOpacity, aCTM); michael@0: // Don't cache this. mContextPaint will have cached it anyway. If we michael@0: // cache it, we'll have to compute mPatternMatrix, which is annoying. michael@0: return pattern.forget(); michael@0: default: michael@0: MOZ_ASSERT(false, "invalid paint type"); michael@0: return nullptr; michael@0: } michael@0: michael@0: mPatternCache.Put(aOpacity, pattern); michael@0: return pattern.forget(); michael@0: } michael@0: michael@0: } // namespace mozilla michael@0: michael@0: michael@0: // ============================================================================ michael@0: // SVGTextFrame michael@0: michael@0: // ---------------------------------------------------------------------------- michael@0: // Display list item michael@0: michael@0: class nsDisplaySVGText : public nsDisplayItem { michael@0: public: michael@0: nsDisplaySVGText(nsDisplayListBuilder* aBuilder, michael@0: SVGTextFrame* aFrame) michael@0: : nsDisplayItem(aBuilder, aFrame), michael@0: mDisableSubpixelAA(false) michael@0: { michael@0: MOZ_COUNT_CTOR(nsDisplaySVGText); michael@0: NS_ABORT_IF_FALSE(aFrame, "Must have a frame!"); michael@0: } michael@0: #ifdef NS_BUILD_REFCNT_LOGGING michael@0: virtual ~nsDisplaySVGText() { michael@0: MOZ_COUNT_DTOR(nsDisplaySVGText); michael@0: } michael@0: #endif michael@0: michael@0: NS_DISPLAY_DECL_NAME("nsDisplaySVGText", TYPE_SVG_TEXT) michael@0: michael@0: virtual void DisableComponentAlpha() MOZ_OVERRIDE { michael@0: mDisableSubpixelAA = true; michael@0: } michael@0: virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, michael@0: HitTestState* aState, michael@0: nsTArray *aOutFrames) MOZ_OVERRIDE; michael@0: virtual void Paint(nsDisplayListBuilder* aBuilder, michael@0: nsRenderingContext* aCtx) MOZ_OVERRIDE; michael@0: virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) MOZ_OVERRIDE { michael@0: bool snap; michael@0: return GetBounds(aBuilder, &snap); michael@0: } michael@0: private: michael@0: bool mDisableSubpixelAA; michael@0: }; michael@0: michael@0: void michael@0: nsDisplaySVGText::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, michael@0: HitTestState* aState, nsTArray *aOutFrames) michael@0: { michael@0: SVGTextFrame *frame = static_cast(mFrame); michael@0: nsPoint pointRelativeToReferenceFrame = aRect.Center(); michael@0: // ToReferenceFrame() includes frame->GetPosition(), our user space position. michael@0: nsPoint userSpacePt = pointRelativeToReferenceFrame - michael@0: (ToReferenceFrame() - frame->GetPosition()); michael@0: michael@0: nsIFrame* target = frame->GetFrameForPoint(userSpacePt); michael@0: if (target) { michael@0: aOutFrames->AppendElement(target); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsDisplaySVGText::Paint(nsDisplayListBuilder* aBuilder, michael@0: nsRenderingContext* aCtx) michael@0: { michael@0: gfxContextAutoDisableSubpixelAntialiasing michael@0: disable(aCtx->ThebesContext(), mDisableSubpixelAA); michael@0: michael@0: // ToReferenceFrame includes our mRect offset, but painting takes michael@0: // account of that too. To avoid double counting, we subtract that michael@0: // here. michael@0: nsPoint offset = ToReferenceFrame() - mFrame->GetPosition(); michael@0: michael@0: aCtx->PushState(); michael@0: aCtx->Translate(offset); michael@0: static_cast(mFrame)->PaintSVG(aCtx, nullptr); michael@0: aCtx->PopState(); michael@0: } michael@0: michael@0: // --------------------------------------------------------------------- michael@0: // nsQueryFrame methods michael@0: michael@0: NS_QUERYFRAME_HEAD(SVGTextFrame) michael@0: NS_QUERYFRAME_ENTRY(SVGTextFrame) michael@0: NS_QUERYFRAME_TAIL_INHERITING(SVGTextFrameBase) michael@0: michael@0: // --------------------------------------------------------------------- michael@0: // Implementation michael@0: michael@0: nsIFrame* michael@0: NS_NewSVGTextFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) michael@0: { michael@0: return new (aPresShell) SVGTextFrame(aContext); michael@0: } michael@0: michael@0: NS_IMPL_FRAMEARENA_HELPERS(SVGTextFrame) michael@0: michael@0: // --------------------------------------------------------------------- michael@0: // nsIFrame methods michael@0: michael@0: void michael@0: SVGTextFrame::Init(nsIContent* aContent, michael@0: nsIFrame* aParent, michael@0: nsIFrame* aPrevInFlow) michael@0: { michael@0: NS_ASSERTION(aContent->IsSVG(nsGkAtoms::text), "Content is not an SVG text"); michael@0: michael@0: SVGTextFrameBase::Init(aContent, aParent, aPrevInFlow); michael@0: AddStateBits((aParent->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD) | michael@0: NS_FRAME_SVG_LAYOUT | NS_FRAME_IS_SVG_TEXT); michael@0: michael@0: mMutationObserver.StartObserving(this); michael@0: } michael@0: michael@0: void michael@0: SVGTextFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, michael@0: const nsRect& aDirtyRect, michael@0: const nsDisplayListSet& aLists) michael@0: { michael@0: if (NS_SUBTREE_DIRTY(this)) { michael@0: // We can sometimes be asked to paint before reflow happens and we michael@0: // have updated mPositions, etc. In this case, we just avoid michael@0: // painting. michael@0: return; michael@0: } michael@0: aLists.Content()->AppendNewToTop( michael@0: new (aBuilder) nsDisplaySVGText(aBuilder, this)); michael@0: } michael@0: michael@0: nsresult michael@0: SVGTextFrame::AttributeChanged(int32_t aNameSpaceID, michael@0: nsIAtom* aAttribute, michael@0: int32_t aModType) michael@0: { michael@0: if (aNameSpaceID != kNameSpaceID_None) michael@0: return NS_OK; michael@0: michael@0: if (aAttribute == nsGkAtoms::transform) { michael@0: // We don't invalidate for transform changes (the layers code does that). michael@0: // Also note that SVGTransformableElement::GetAttributeChangeHint will michael@0: // return nsChangeHint_UpdateOverflow for "transform" attribute changes michael@0: // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call. michael@0: michael@0: if (!(mState & NS_FRAME_FIRST_REFLOW) && michael@0: mCanvasTM && mCanvasTM->IsSingular()) { michael@0: // We won't have calculated the glyph positions correctly. michael@0: NotifyGlyphMetricsChange(); michael@0: } michael@0: mCanvasTM = nullptr; michael@0: } else if (IsGlyphPositioningAttribute(aAttribute) || michael@0: aAttribute == nsGkAtoms::textLength || michael@0: aAttribute == nsGkAtoms::lengthAdjust) { michael@0: NotifyGlyphMetricsChange(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsIAtom * michael@0: SVGTextFrame::GetType() const michael@0: { michael@0: return nsGkAtoms::svgTextFrame; michael@0: } michael@0: michael@0: void michael@0: SVGTextFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext) michael@0: { michael@0: if (mState & NS_FRAME_IS_NONDISPLAY) { michael@0: // We need this DidSetStyleContext override to handle cases like this: michael@0: // michael@0: // michael@0: // michael@0: // michael@0: // ... michael@0: // michael@0: // michael@0: // michael@0: // michael@0: // where the is non-display, and a style change occurs on the , michael@0: // the , the , or the itself. If the style change happened michael@0: // on the parent of the , then in michael@0: // nsSVGDisplayContainerFrame::ReflowSVG, we would find the non-display michael@0: // container and then call ReflowSVGNonDisplayText on it. If we do michael@0: // not actually reflow the parent of the , then without this michael@0: // DidSetStyleContext we would (a) not cause the 's anonymous block michael@0: // child to be reflowed when it is next painted, and (b) not cause the michael@0: // to be repainted anyway since the user of the would not michael@0: // know it needs to be repainted. michael@0: ScheduleReflowSVGNonDisplayText(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: SVGTextFrame::ReflowSVGNonDisplayText() michael@0: { michael@0: MOZ_ASSERT(nsSVGUtils::AnyOuterSVGIsCallingReflowSVG(this), michael@0: "only call ReflowSVGNonDisplayText when an outer SVG frame is " michael@0: "under ReflowSVG"); michael@0: MOZ_ASSERT(mState & NS_FRAME_IS_NONDISPLAY, michael@0: "only call ReflowSVGNonDisplayText if the frame is " michael@0: "NS_FRAME_IS_NONDISPLAY"); michael@0: michael@0: // We had a style change, so we mark this frame as dirty so that the next michael@0: // time it is painted, we reflow the anonymous block frame. michael@0: AddStateBits(NS_FRAME_IS_DIRTY); michael@0: michael@0: // We also need to call InvalidateRenderingObservers, so that if the michael@0: // element is within a , say, the element referencing the will michael@0: // be updated, which will then cause this SVGTextFrame to be painted and michael@0: // in doing so cause the anonymous block frame to be reflowed. michael@0: nsSVGEffects::InvalidateRenderingObservers(this); michael@0: michael@0: // Finally, we need to actually reflow the anonymous block frame and update michael@0: // mPositions, in case we are being reflowed immediately after a DOM michael@0: // mutation that needs frame reconstruction. michael@0: MaybeReflowAnonymousBlockChild(); michael@0: UpdateGlyphPositioning(); michael@0: } michael@0: michael@0: void michael@0: SVGTextFrame::ScheduleReflowSVGNonDisplayText() michael@0: { michael@0: MOZ_ASSERT(!nsSVGUtils::OuterSVGIsCallingReflowSVG(this), michael@0: "do not call ScheduleReflowSVGNonDisplayText when the outer SVG " michael@0: "frame is under ReflowSVG"); michael@0: MOZ_ASSERT(!(mState & NS_STATE_SVG_TEXT_IN_REFLOW), michael@0: "do not call ScheduleReflowSVGNonDisplayText while reflowing the " michael@0: "anonymous block child"); michael@0: michael@0: // We need to find an ancestor frame that we can call FrameNeedsReflow michael@0: // on that will cause the document to be marked as needing relayout, michael@0: // and for that ancestor (or some further ancestor) to be marked as michael@0: // a root to reflow. We choose the closest ancestor frame that is not michael@0: // NS_FRAME_IS_NONDISPLAY and which is either an outer SVG frame or a michael@0: // non-SVG frame. (We don't consider displayed SVG frame ancestors toerh michael@0: // than nsSVGOuterSVGFrame, since calling FrameNeedsReflow on those other michael@0: // SVG frames would do a bunch of unnecessary work on the SVG frames up to michael@0: // the nsSVGOuterSVGFrame.) michael@0: michael@0: nsIFrame* f = this; michael@0: while (f) { michael@0: if (!(f->GetStateBits() & NS_FRAME_IS_NONDISPLAY)) { michael@0: if (NS_SUBTREE_DIRTY(f)) { michael@0: // This is a displayed frame, so if it is already dirty, we will be reflowed michael@0: // soon anyway. No need to call FrameNeedsReflow again, then. michael@0: return; michael@0: } michael@0: if (!f->IsFrameOfType(eSVG) || michael@0: (f->GetStateBits() & NS_STATE_IS_OUTER_SVG)) { michael@0: break; michael@0: } michael@0: f->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN); michael@0: } michael@0: f = f->GetParent(); michael@0: } michael@0: michael@0: MOZ_ASSERT(f, "should have found an ancestor frame to reflow"); michael@0: michael@0: PresContext()->PresShell()->FrameNeedsReflow( michael@0: f, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY); michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(SVGTextFrame::MutationObserver, nsIMutationObserver) michael@0: michael@0: void michael@0: SVGTextFrame::MutationObserver::ContentAppended(nsIDocument* aDocument, michael@0: nsIContent* aContainer, michael@0: nsIContent* aFirstNewContent, michael@0: int32_t aNewIndexInContainer) michael@0: { michael@0: mFrame->NotifyGlyphMetricsChange(); michael@0: } michael@0: michael@0: void michael@0: SVGTextFrame::MutationObserver::ContentInserted( michael@0: nsIDocument* aDocument, michael@0: nsIContent* aContainer, michael@0: nsIContent* aChild, michael@0: int32_t aIndexInContainer) michael@0: { michael@0: mFrame->NotifyGlyphMetricsChange(); michael@0: } michael@0: michael@0: void michael@0: SVGTextFrame::MutationObserver::ContentRemoved( michael@0: nsIDocument *aDocument, michael@0: nsIContent* aContainer, michael@0: nsIContent* aChild, michael@0: int32_t aIndexInContainer, michael@0: nsIContent* aPreviousSibling) michael@0: { michael@0: mFrame->NotifyGlyphMetricsChange(); michael@0: } michael@0: michael@0: void michael@0: SVGTextFrame::MutationObserver::CharacterDataChanged( michael@0: nsIDocument* aDocument, michael@0: nsIContent* aContent, michael@0: CharacterDataChangeInfo* aInfo) michael@0: { michael@0: mFrame->NotifyGlyphMetricsChange(); michael@0: } michael@0: michael@0: void michael@0: SVGTextFrame::MutationObserver::AttributeChanged( michael@0: nsIDocument* aDocument, michael@0: mozilla::dom::Element* aElement, michael@0: int32_t aNameSpaceID, michael@0: nsIAtom* aAttribute, michael@0: int32_t aModType) michael@0: { michael@0: if (!aElement->IsSVG()) { michael@0: return; michael@0: } michael@0: michael@0: // Attribute changes on this element are handled in michael@0: // SVGTextFrame::AttributeChanged. michael@0: if (aElement == mFrame->GetContent()) { michael@0: return; michael@0: } michael@0: michael@0: // Attributes changes on descendent elements. michael@0: if (aElement->Tag() == nsGkAtoms::textPath) { michael@0: if (aNameSpaceID == kNameSpaceID_None && michael@0: aAttribute == nsGkAtoms::startOffset) { michael@0: mFrame->NotifyGlyphMetricsChange(); michael@0: } else if (aNameSpaceID == kNameSpaceID_XLink && michael@0: aAttribute == nsGkAtoms::href) { michael@0: // Blow away our reference, if any michael@0: nsIFrame* childElementFrame = aElement->GetPrimaryFrame(); michael@0: if (childElementFrame) { michael@0: childElementFrame->Properties().Delete(nsSVGEffects::HrefProperty()); michael@0: mFrame->NotifyGlyphMetricsChange(); michael@0: } michael@0: } michael@0: } else { michael@0: if (aNameSpaceID == kNameSpaceID_None && michael@0: IsGlyphPositioningAttribute(aAttribute)) { michael@0: mFrame->NotifyGlyphMetricsChange(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: SVGTextFrame::FindCloserFrameForSelection( michael@0: nsPoint aPoint, michael@0: nsIFrame::FrameWithDistance* aCurrentBestFrame) michael@0: { michael@0: if (GetStateBits() & NS_FRAME_IS_NONDISPLAY) { michael@0: return; michael@0: } michael@0: michael@0: UpdateGlyphPositioning(); michael@0: michael@0: nsPresContext* presContext = PresContext(); michael@0: michael@0: // Find the frame that has the closest rendered run rect to aPoint. michael@0: TextRenderedRunIterator it(this); michael@0: for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) { michael@0: uint32_t flags = TextRenderedRun::eIncludeFill | michael@0: TextRenderedRun::eIncludeStroke | michael@0: TextRenderedRun::eNoHorizontalOverflow; michael@0: SVGBBox userRect = run.GetUserSpaceRect(presContext, flags); michael@0: if (!userRect.IsEmpty()) { michael@0: nsRect rect = nsSVGUtils::ToCanvasBounds(userRect.ToThebesRect(), michael@0: GetCanvasTM(FOR_HIT_TESTING), michael@0: presContext); michael@0: michael@0: if (nsLayoutUtils::PointIsCloserToRect(aPoint, rect, michael@0: aCurrentBestFrame->mXDistance, michael@0: aCurrentBestFrame->mYDistance)) { michael@0: aCurrentBestFrame->mFrame = run.mFrame; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // nsISVGChildFrame methods michael@0: michael@0: void michael@0: SVGTextFrame::NotifySVGChanged(uint32_t aFlags) michael@0: { michael@0: NS_ABORT_IF_FALSE(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED), michael@0: "Invalidation logic may need adjusting"); michael@0: michael@0: bool needNewBounds = false; michael@0: bool needGlyphMetricsUpdate = false; michael@0: bool needNewCanvasTM = false; michael@0: michael@0: if ((aFlags & COORD_CONTEXT_CHANGED) && michael@0: (mState & NS_STATE_SVG_POSITIONING_MAY_USE_PERCENTAGES)) { michael@0: needGlyphMetricsUpdate = true; michael@0: } michael@0: michael@0: if (aFlags & TRANSFORM_CHANGED) { michael@0: needNewCanvasTM = true; michael@0: if (mCanvasTM && mCanvasTM->IsSingular()) { michael@0: // We won't have calculated the glyph positions correctly. michael@0: needNewBounds = true; michael@0: needGlyphMetricsUpdate = true; michael@0: } michael@0: if (StyleSVGReset()->mVectorEffect == michael@0: NS_STYLE_VECTOR_EFFECT_NON_SCALING_STROKE) { michael@0: // Stroke currently contributes to our mRect, and our stroke depends on michael@0: // the transform to our outer- if |vector-effect:non-scaling-stroke|. michael@0: needNewBounds = true; michael@0: } michael@0: } michael@0: michael@0: // If the scale at which we computed our mFontSizeScaleFactor has changed by michael@0: // at least a factor of two, reflow the text. This avoids reflowing text michael@0: // at every tick of a transform animation, but ensures our glyph metrics michael@0: // do not get too far out of sync with the final font size on the screen. michael@0: if (needNewCanvasTM && mLastContextScale != 0.0f) { michael@0: mCanvasTM = nullptr; michael@0: // If we are a non-display frame, then we don't want to call michael@0: // GetCanvasTM(FOR_OUTERSVG_TM), since the context scale does not use it. michael@0: gfxMatrix newTM = michael@0: (mState & NS_FRAME_IS_NONDISPLAY) ? gfxMatrix() : michael@0: GetCanvasTM(FOR_OUTERSVG_TM); michael@0: // Compare the old and new context scales. michael@0: float scale = GetContextScale(newTM); michael@0: float change = scale / mLastContextScale; michael@0: if (change >= 2.0f || change <= 0.5f) { michael@0: needNewBounds = true; michael@0: needGlyphMetricsUpdate = true; michael@0: } michael@0: } michael@0: michael@0: if (needNewBounds) { michael@0: // Ancestor changes can't affect how we render from the perspective of michael@0: // any rendering observers that we may have, so we don't need to michael@0: // invalidate them. We also don't need to invalidate ourself, since our michael@0: // changed ancestor will have invalidated its entire area, which includes michael@0: // our area. michael@0: ScheduleReflowSVG(); michael@0: } michael@0: michael@0: if (needGlyphMetricsUpdate) { michael@0: // If we are positioned using percentage values we need to update our michael@0: // position whenever our viewport's dimensions change. But only do this if michael@0: // we have been reflowed once, otherwise the glyph positioning will be michael@0: // wrong. (We need to wait until bidi reordering has been done.) michael@0: if (!(mState & NS_FRAME_FIRST_REFLOW)) { michael@0: NotifyGlyphMetricsChange(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Gets the offset into a DOM node that the specified caret is positioned at. michael@0: */ michael@0: static int32_t michael@0: GetCaretOffset(nsCaret* aCaret) michael@0: { michael@0: nsCOMPtr selection = aCaret->GetCaretDOMSelection(); michael@0: if (!selection) { michael@0: return -1; michael@0: } michael@0: michael@0: int32_t offset = -1; michael@0: selection->GetAnchorOffset(&offset); michael@0: return offset; michael@0: } michael@0: michael@0: /** michael@0: * Returns whether the caret should be painted for a given TextRenderedRun michael@0: * by checking whether the caret is in the range covered by the rendered run. michael@0: * michael@0: * @param aThisRun The TextRenderedRun to be painted. michael@0: * @param aCaret The caret. michael@0: */ michael@0: static bool michael@0: ShouldPaintCaret(const TextRenderedRun& aThisRun, nsCaret* aCaret) michael@0: { michael@0: int32_t caretOffset = GetCaretOffset(aCaret); michael@0: michael@0: if (caretOffset < 0) { michael@0: return false; michael@0: } michael@0: michael@0: if (uint32_t(caretOffset) >= aThisRun.mTextFrameContentOffset && michael@0: uint32_t(caretOffset) < aThisRun.mTextFrameContentOffset + michael@0: aThisRun.mTextFrameContentLength) { michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: nsresult michael@0: SVGTextFrame::PaintSVG(nsRenderingContext* aContext, michael@0: const nsIntRect *aDirtyRect, michael@0: nsIFrame* aTransformRoot) michael@0: { michael@0: nsIFrame* kid = GetFirstPrincipalChild(); michael@0: if (!kid) michael@0: return NS_OK; michael@0: michael@0: nsPresContext* presContext = PresContext(); michael@0: michael@0: gfxContext *gfx = aContext->ThebesContext(); michael@0: gfxMatrix initialMatrix = gfx->CurrentMatrix(); michael@0: michael@0: if (mState & NS_FRAME_IS_NONDISPLAY) { michael@0: // If we are in a canvas DrawWindow call that used the michael@0: // DRAWWINDOW_DO_NOT_FLUSH flag, then we may still have out michael@0: // of date frames. Just don't paint anything if they are michael@0: // dirty. michael@0: if (presContext->PresShell()->InDrawWindowNotFlushing() && michael@0: NS_SUBTREE_DIRTY(this)) { michael@0: return NS_OK; michael@0: } michael@0: // Text frames inside , , etc. will never have had michael@0: // ReflowSVG called on them, so call UpdateGlyphPositioning to do this now. michael@0: UpdateGlyphPositioning(); michael@0: } else if (NS_SUBTREE_DIRTY(this)) { michael@0: // If we are asked to paint before reflow has recomputed mPositions etc. michael@0: // directly via PaintSVG, rather than via a display list, then we need michael@0: // to bail out here too. michael@0: return NS_OK; michael@0: } michael@0: michael@0: gfxMatrix canvasTM = GetCanvasTM(FOR_PAINTING, aTransformRoot); michael@0: if (canvasTM.IsSingular()) { michael@0: NS_WARNING("Can't render text element!"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: gfxMatrix matrixForPaintServers(canvasTM); michael@0: matrixForPaintServers.Multiply(initialMatrix); michael@0: michael@0: // Check if we need to draw anything. michael@0: if (aDirtyRect) { michael@0: NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() || michael@0: (mState & NS_FRAME_IS_NONDISPLAY), michael@0: "Display lists handle dirty rect intersection test"); michael@0: nsRect dirtyRect(aDirtyRect->x, aDirtyRect->y, michael@0: aDirtyRect->width, aDirtyRect->height); michael@0: michael@0: gfxFloat appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel(); michael@0: gfxRect frameRect(mRect.x / appUnitsPerDevPixel, michael@0: mRect.y / appUnitsPerDevPixel, michael@0: mRect.width / appUnitsPerDevPixel, michael@0: mRect.height / appUnitsPerDevPixel); michael@0: michael@0: nsRect canvasRect = nsLayoutUtils::RoundGfxRectToAppRect( michael@0: GetCanvasTM(FOR_OUTERSVG_TM).TransformBounds(frameRect), 1); michael@0: if (!canvasRect.Intersects(dirtyRect)) { michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: // SVG paints in CSS px, but normally frames paint in dev pixels. Here we michael@0: // multiply a CSS-px-to-dev-pixel factor onto canvasTM so our children paint michael@0: // correctly. michael@0: float cssPxPerDevPx = presContext-> michael@0: AppUnitsToFloatCSSPixels(presContext->AppUnitsPerDevPixel()); michael@0: gfxMatrix canvasTMForChildren = canvasTM; michael@0: canvasTMForChildren.Scale(cssPxPerDevPx, cssPxPerDevPx); michael@0: initialMatrix.Scale(1 / cssPxPerDevPx, 1 / cssPxPerDevPx); michael@0: michael@0: gfxContextAutoSaveRestore save(gfx); michael@0: gfx->NewPath(); michael@0: gfx->Multiply(canvasTMForChildren); michael@0: gfxMatrix currentMatrix = gfx->CurrentMatrix(); michael@0: michael@0: nsRefPtr caret = presContext->PresShell()->GetCaret(); michael@0: nsIFrame* caretFrame = caret->GetCaretFrame(); michael@0: michael@0: TextRenderedRunIterator it(this, TextRenderedRunIterator::eVisibleFrames); michael@0: TextRenderedRun run = it.Current(); michael@0: while (run.mFrame) { michael@0: nsTextFrame* frame = run.mFrame; michael@0: michael@0: // Determine how much of the left and right edges of the text frame we michael@0: // need to ignore. michael@0: SVGCharClipDisplayItem item(run); michael@0: michael@0: // Set up the fill and stroke so that SVG glyphs can get painted correctly michael@0: // when they use context-fill etc. michael@0: gfx->SetMatrix(initialMatrix); michael@0: gfxTextContextPaint *outerContextPaint = michael@0: (gfxTextContextPaint*)aContext->GetUserData(&gfxTextContextPaint::sUserDataKey); michael@0: michael@0: nsAutoPtr contextPaint; michael@0: DrawMode drawMode = michael@0: SetupCairoState(gfx, frame, outerContextPaint, michael@0: getter_Transfers(contextPaint)); michael@0: michael@0: // Set up the transform for painting the text frame for the substring michael@0: // indicated by the run. michael@0: gfxMatrix runTransform = michael@0: run.GetTransformFromUserSpaceForPainting(presContext, item); michael@0: runTransform.Multiply(currentMatrix); michael@0: gfx->SetMatrix(runTransform); michael@0: michael@0: if (drawMode != DrawMode(0)) { michael@0: nsRect frameRect = frame->GetVisualOverflowRect(); michael@0: bool paintSVGGlyphs; michael@0: if (ShouldRenderAsPath(aContext, frame, paintSVGGlyphs)) { michael@0: SVGTextDrawPathCallbacks callbacks(aContext, frame, michael@0: matrixForPaintServers, michael@0: paintSVGGlyphs); michael@0: frame->PaintText(aContext, nsPoint(), frameRect, item, michael@0: contextPaint, &callbacks); michael@0: } else { michael@0: frame->PaintText(aContext, nsPoint(), frameRect, item, michael@0: contextPaint, nullptr); michael@0: } michael@0: } michael@0: michael@0: if (frame == caretFrame && ShouldPaintCaret(run, caret)) { michael@0: // XXX Should we be looking at the fill/stroke colours to paint the michael@0: // caret with, rather than using the color property? michael@0: caret->PaintCaret(nullptr, aContext, frame, nsPoint()); michael@0: gfx->NewPath(); michael@0: } michael@0: michael@0: run = it.Next(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsIFrame* michael@0: SVGTextFrame::GetFrameForPoint(const nsPoint& aPoint) michael@0: { michael@0: NS_ASSERTION(GetFirstPrincipalChild(), "must have a child frame"); michael@0: michael@0: if (mState & NS_FRAME_IS_NONDISPLAY) { michael@0: // Text frames inside will never have had ReflowSVG called on michael@0: // them, so call UpdateGlyphPositioning to do this now. (Text frames michael@0: // inside and other non-display containers will never need to michael@0: // be hit tested.) michael@0: UpdateGlyphPositioning(); michael@0: } else { michael@0: NS_ASSERTION(!NS_SUBTREE_DIRTY(this), "reflow should have happened"); michael@0: } michael@0: michael@0: nsPresContext* presContext = PresContext(); michael@0: michael@0: gfxPoint pointInOuterSVGUserUnits = AppUnitsToGfxUnits(aPoint, presContext); michael@0: michael@0: TextRenderedRunIterator it(this); michael@0: nsIFrame* hit = nullptr; michael@0: for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) { michael@0: uint16_t hitTestFlags = nsSVGUtils::GetGeometryHitTestFlags(run.mFrame); michael@0: if (!(hitTestFlags & (SVG_HIT_TEST_FILL | SVG_HIT_TEST_STROKE))) { michael@0: continue; michael@0: } michael@0: michael@0: gfxMatrix m = GetCanvasTM(FOR_HIT_TESTING); michael@0: m.PreMultiply(run.GetTransformFromRunUserSpaceToUserSpace(presContext)); michael@0: m.Invert(); michael@0: michael@0: gfxPoint pointInRunUserSpace = m.Transform(pointInOuterSVGUserUnits); michael@0: gfxRect frameRect = michael@0: run.GetRunUserSpaceRect(presContext, TextRenderedRun::eIncludeFill | michael@0: TextRenderedRun::eIncludeStroke).ToThebesRect(); michael@0: michael@0: if (Inside(frameRect, pointInRunUserSpace) && michael@0: nsSVGUtils::HitTestClip(this, aPoint)) { michael@0: hit = run.mFrame; michael@0: } michael@0: } michael@0: return hit; michael@0: } michael@0: michael@0: nsRect michael@0: SVGTextFrame::GetCoveredRegion() michael@0: { michael@0: return nsSVGUtils::TransformFrameRectToOuterSVG( michael@0: mRect, GetCanvasTM(FOR_OUTERSVG_TM), PresContext()); michael@0: } michael@0: michael@0: void michael@0: SVGTextFrame::ReflowSVG() michael@0: { michael@0: NS_ASSERTION(nsSVGUtils::OuterSVGIsCallingReflowSVG(this), michael@0: "This call is probaby a wasteful mistake"); michael@0: michael@0: NS_ABORT_IF_FALSE(!(GetStateBits() & NS_FRAME_IS_NONDISPLAY), michael@0: "ReflowSVG mechanism not designed for this"); michael@0: michael@0: if (!nsSVGUtils::NeedsReflowSVG(this)) { michael@0: NS_ASSERTION(!(mState & NS_STATE_SVG_POSITIONING_DIRTY), "How did this happen?"); michael@0: return; michael@0: } michael@0: michael@0: MaybeReflowAnonymousBlockChild(); michael@0: UpdateGlyphPositioning(); michael@0: michael@0: nsPresContext* presContext = PresContext(); michael@0: michael@0: SVGBBox r; michael@0: TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames); michael@0: for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) { michael@0: uint32_t runFlags = 0; michael@0: if (run.mFrame->StyleSVG()->mFill.mType != eStyleSVGPaintType_None) { michael@0: runFlags |= TextRenderedRun::eIncludeFill | michael@0: TextRenderedRun::eIncludeTextShadow; michael@0: } michael@0: if (nsSVGUtils::HasStroke(run.mFrame)) { michael@0: runFlags |= TextRenderedRun::eIncludeFill | michael@0: TextRenderedRun::eIncludeTextShadow; michael@0: } michael@0: // Our "visual" overflow rect needs to be valid for building display lists michael@0: // for hit testing, which means that for certain values of 'pointer-events' michael@0: // it needs to include the geometry of the fill or stroke even when the fill/ michael@0: // stroke don't actually render (e.g. when stroke="none" or michael@0: // stroke-opacity="0"). GetGeometryHitTestFlags accounts for 'pointer-events'. michael@0: // The text-shadow is not part of the hit-test area. michael@0: uint16_t hitTestFlags = nsSVGUtils::GetGeometryHitTestFlags(run.mFrame); michael@0: if (hitTestFlags & SVG_HIT_TEST_FILL) { michael@0: runFlags |= TextRenderedRun::eIncludeFill; michael@0: } michael@0: if (hitTestFlags & SVG_HIT_TEST_STROKE) { michael@0: runFlags |= TextRenderedRun::eIncludeStroke; michael@0: } michael@0: michael@0: if (runFlags) { michael@0: r.UnionEdges(run.GetUserSpaceRect(presContext, runFlags)); michael@0: } michael@0: } michael@0: michael@0: if (r.IsEmpty()) { michael@0: mRect.SetEmpty(); michael@0: } else { michael@0: mRect = michael@0: nsLayoutUtils::RoundGfxRectToAppRect(r.ToThebesRect(), presContext->AppUnitsPerCSSPixel()); michael@0: michael@0: // Due to rounding issues when we have a transform applied, we sometimes michael@0: // don't include an additional row of pixels. For now, just inflate our michael@0: // covered region. michael@0: mRect.Inflate(presContext->AppUnitsPerDevPixel()); michael@0: } michael@0: michael@0: if (mState & NS_FRAME_FIRST_REFLOW) { michael@0: // Make sure we have our filter property (if any) before calling michael@0: // FinishAndStoreOverflow (subsequent filter changes are handled off michael@0: // nsChangeHint_UpdateEffects): michael@0: nsSVGEffects::UpdateEffects(this); michael@0: } michael@0: michael@0: nsRect overflow = nsRect(nsPoint(0,0), mRect.Size()); michael@0: nsOverflowAreas overflowAreas(overflow, overflow); michael@0: FinishAndStoreOverflow(overflowAreas, mRect.Size()); michael@0: michael@0: // Now unset the various reflow bits: michael@0: mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY | michael@0: NS_FRAME_HAS_DIRTY_CHILDREN); michael@0: michael@0: // XXX nsSVGContainerFrame::ReflowSVG only looks at its nsISVGChildFrame michael@0: // children, and calls ConsiderChildOverflow on them. Does it matter michael@0: // that ConsiderChildOverflow won't be called on our children? michael@0: SVGTextFrameBase::ReflowSVG(); michael@0: } michael@0: michael@0: /** michael@0: * Converts nsSVGUtils::eBBox* flags into TextRenderedRun flags appropriate michael@0: * for the specified rendered run. michael@0: */ michael@0: static uint32_t michael@0: TextRenderedRunFlagsForBBoxContribution(const TextRenderedRun& aRun, michael@0: uint32_t aBBoxFlags) michael@0: { michael@0: uint32_t flags = 0; michael@0: if ((aBBoxFlags & nsSVGUtils::eBBoxIncludeFillGeometry) || michael@0: ((aBBoxFlags & nsSVGUtils::eBBoxIncludeFill) && michael@0: aRun.mFrame->StyleSVG()->mFill.mType != eStyleSVGPaintType_None)) { michael@0: flags |= TextRenderedRun::eIncludeFill; michael@0: } michael@0: if ((aBBoxFlags & nsSVGUtils::eBBoxIncludeStrokeGeometry) || michael@0: ((aBBoxFlags & nsSVGUtils::eBBoxIncludeStroke) && michael@0: nsSVGUtils::HasStroke(aRun.mFrame))) { michael@0: flags |= TextRenderedRun::eIncludeStroke; michael@0: } michael@0: return flags; michael@0: } michael@0: michael@0: SVGBBox michael@0: SVGTextFrame::GetBBoxContribution(const gfx::Matrix &aToBBoxUserspace, michael@0: uint32_t aFlags) michael@0: { michael@0: NS_ASSERTION(GetFirstPrincipalChild(), "must have a child frame"); michael@0: michael@0: UpdateGlyphPositioning(); michael@0: michael@0: SVGBBox bbox; michael@0: nsPresContext* presContext = PresContext(); michael@0: michael@0: TextRenderedRunIterator it(this); michael@0: for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) { michael@0: uint32_t flags = TextRenderedRunFlagsForBBoxContribution(run, aFlags); michael@0: gfxMatrix m = ThebesMatrix(aToBBoxUserspace); michael@0: SVGBBox bboxForRun = michael@0: run.GetUserSpaceRect(presContext, flags, &m); michael@0: bbox.UnionEdges(bboxForRun); michael@0: } michael@0: michael@0: return bbox; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // nsSVGContainerFrame methods michael@0: michael@0: gfxMatrix michael@0: SVGTextFrame::GetCanvasTM(uint32_t aFor, nsIFrame* aTransformRoot) michael@0: { michael@0: if (!(GetStateBits() & NS_FRAME_IS_NONDISPLAY) && michael@0: !aTransformRoot) { michael@0: if ((aFor == FOR_PAINTING && NS_SVGDisplayListPaintingEnabled()) || michael@0: (aFor == FOR_HIT_TESTING && NS_SVGDisplayListHitTestingEnabled())) { michael@0: return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(this); michael@0: } michael@0: } michael@0: if (!mCanvasTM) { michael@0: NS_ASSERTION(mParent, "null parent"); michael@0: NS_ASSERTION(!(aFor == FOR_OUTERSVG_TM && michael@0: (GetStateBits() & NS_FRAME_IS_NONDISPLAY)), michael@0: "should not call GetCanvasTM(FOR_OUTERSVG_TM) when we are " michael@0: "non-display"); michael@0: michael@0: nsSVGContainerFrame *parent = static_cast(mParent); michael@0: dom::SVGTextContentElement *content = static_cast(mContent); michael@0: michael@0: gfxMatrix tm = content->PrependLocalTransformsTo( michael@0: this == aTransformRoot ? gfxMatrix() : michael@0: parent->GetCanvasTM(aFor, aTransformRoot)); michael@0: michael@0: mCanvasTM = new gfxMatrix(tm); michael@0: } michael@0: return *mCanvasTM; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // SVGTextFrame SVG DOM methods michael@0: michael@0: /** michael@0: * Returns whether the specified node has any non-empty nsTextNodes michael@0: * beneath it. michael@0: */ michael@0: static bool michael@0: HasTextContent(nsIContent* aContent) michael@0: { michael@0: NS_ASSERTION(aContent, "expected non-null aContent"); michael@0: michael@0: TextNodeIterator it(aContent); michael@0: for (nsTextNode* text = it.Current(); text; text = it.Next()) { michael@0: if (text->TextLength() != 0) { michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: /** michael@0: * Returns the number of DOM characters beneath the specified node. michael@0: */ michael@0: static uint32_t michael@0: GetTextContentLength(nsIContent* aContent) michael@0: { michael@0: NS_ASSERTION(aContent, "expected non-null aContent"); michael@0: michael@0: uint32_t length = 0; michael@0: TextNodeIterator it(aContent); michael@0: for (nsTextNode* text = it.Current(); text; text = it.Next()) { michael@0: length += text->TextLength(); michael@0: } michael@0: return length; michael@0: } michael@0: michael@0: int32_t michael@0: SVGTextFrame::ConvertTextElementCharIndexToAddressableIndex( michael@0: int32_t aIndex, michael@0: nsIContent* aContent) michael@0: { michael@0: CharIterator it(this, CharIterator::eOriginal, aContent); michael@0: if (!it.AdvanceToSubtree()) { michael@0: return -1; michael@0: } michael@0: int32_t result = 0; michael@0: int32_t textElementCharIndex; michael@0: while (!it.AtEnd() && michael@0: it.IsWithinSubtree()) { michael@0: bool addressable = !it.IsOriginalCharUnaddressable(); michael@0: textElementCharIndex = it.TextElementCharIndex(); michael@0: it.Next(); michael@0: uint32_t delta = it.TextElementCharIndex() - textElementCharIndex; michael@0: aIndex -= delta; michael@0: if (addressable) { michael@0: if (aIndex < 0) { michael@0: return result; michael@0: } michael@0: result += delta; michael@0: } michael@0: } michael@0: return -1; michael@0: } michael@0: michael@0: /** michael@0: * Implements the SVG DOM GetNumberOfChars method for the specified michael@0: * text content element. michael@0: */ michael@0: uint32_t michael@0: SVGTextFrame::GetNumberOfChars(nsIContent* aContent) michael@0: { michael@0: UpdateGlyphPositioning(); michael@0: michael@0: uint32_t n = 0; michael@0: CharIterator it(this, CharIterator::eAddressable, aContent); michael@0: if (it.AdvanceToSubtree()) { michael@0: while (!it.AtEnd() && it.IsWithinSubtree()) { michael@0: n++; michael@0: it.Next(); michael@0: } michael@0: } michael@0: return n; michael@0: } michael@0: michael@0: /** michael@0: * Implements the SVG DOM GetComputedTextLength method for the specified michael@0: * text child element. michael@0: */ michael@0: float michael@0: SVGTextFrame::GetComputedTextLength(nsIContent* aContent) michael@0: { michael@0: UpdateGlyphPositioning(); michael@0: michael@0: float cssPxPerDevPx = PresContext()-> michael@0: AppUnitsToFloatCSSPixels(PresContext()->AppUnitsPerDevPixel()); michael@0: michael@0: nscoord length = 0; michael@0: TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames, michael@0: aContent); michael@0: for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) { michael@0: length += run.GetAdvanceWidth(); michael@0: } michael@0: michael@0: return PresContext()->AppUnitsToGfxUnits(length) * michael@0: cssPxPerDevPx * mLengthAdjustScaleFactor / mFontSizeScaleFactor; michael@0: } michael@0: michael@0: /** michael@0: * Implements the SVG DOM SelectSubString method for the specified michael@0: * text content element. michael@0: */ michael@0: nsresult michael@0: SVGTextFrame::SelectSubString(nsIContent* aContent, michael@0: uint32_t charnum, uint32_t nchars) michael@0: { michael@0: UpdateGlyphPositioning(); michael@0: michael@0: // Convert charnum/nchars from addressable characters relative to michael@0: // aContent to global character indices. michael@0: CharIterator chit(this, CharIterator::eAddressable, aContent); michael@0: if (!chit.AdvanceToSubtree() || michael@0: !chit.Next(charnum) || michael@0: chit.IsAfterSubtree()) { michael@0: return NS_ERROR_DOM_INDEX_SIZE_ERR; michael@0: } michael@0: charnum = chit.TextElementCharIndex(); michael@0: nsIContent* content = chit.TextFrame()->GetContent(); michael@0: chit.NextWithinSubtree(nchars); michael@0: nchars = chit.TextElementCharIndex() - charnum; michael@0: michael@0: nsRefPtr frameSelection = GetFrameSelection(); michael@0: michael@0: frameSelection->HandleClick(content, charnum, charnum + nchars, michael@0: false, false, false); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /** michael@0: * Implements the SVG DOM GetSubStringLength method for the specified michael@0: * text content element. michael@0: */ michael@0: nsresult michael@0: SVGTextFrame::GetSubStringLength(nsIContent* aContent, michael@0: uint32_t charnum, uint32_t nchars, michael@0: float* aResult) michael@0: { michael@0: UpdateGlyphPositioning(); michael@0: michael@0: // Convert charnum/nchars from addressable characters relative to michael@0: // aContent to global character indices. michael@0: CharIterator chit(this, CharIterator::eAddressable, aContent); michael@0: if (!chit.AdvanceToSubtree() || michael@0: !chit.Next(charnum) || michael@0: chit.IsAfterSubtree()) { michael@0: return NS_ERROR_DOM_INDEX_SIZE_ERR; michael@0: } michael@0: michael@0: if (nchars == 0) { michael@0: *aResult = 0.0f; michael@0: return NS_OK; michael@0: } michael@0: michael@0: charnum = chit.TextElementCharIndex(); michael@0: chit.NextWithinSubtree(nchars); michael@0: nchars = chit.TextElementCharIndex() - charnum; michael@0: michael@0: // Find each rendered run that intersects with the range defined michael@0: // by charnum/nchars. michael@0: nscoord textLength = 0; michael@0: TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames); michael@0: TextRenderedRun run = it.Current(); michael@0: while (run.mFrame) { michael@0: // If this rendered run is past the substring we are interested in, we michael@0: // are done. michael@0: uint32_t offset = run.mTextElementCharIndex; michael@0: if (offset >= charnum + nchars) { michael@0: break; michael@0: } michael@0: michael@0: // Intersect the substring we are interested in with the range covered by michael@0: // the rendered run. michael@0: uint32_t length = run.mTextFrameContentLength; michael@0: IntersectInterval(offset, length, charnum, nchars); michael@0: michael@0: if (length != 0) { michael@0: // Convert offset into an index into the frame. michael@0: offset += run.mTextFrameContentOffset - run.mTextElementCharIndex; michael@0: michael@0: gfxSkipCharsIterator it = michael@0: run.mFrame->EnsureTextRun(nsTextFrame::eInflated); michael@0: gfxTextRun* textRun = run.mFrame->GetTextRun(nsTextFrame::eInflated); michael@0: ConvertOriginalToSkipped(it, offset, length); michael@0: michael@0: // Accumulate the advance. michael@0: textLength += textRun->GetAdvanceWidth(offset, length, nullptr); michael@0: } michael@0: michael@0: run = it.Next(); michael@0: } michael@0: michael@0: nsPresContext* presContext = PresContext(); michael@0: float cssPxPerDevPx = presContext-> michael@0: AppUnitsToFloatCSSPixels(presContext->AppUnitsPerDevPixel()); michael@0: michael@0: *aResult = presContext->AppUnitsToGfxUnits(textLength) * michael@0: cssPxPerDevPx / mFontSizeScaleFactor; michael@0: return NS_OK; michael@0: } michael@0: michael@0: /** michael@0: * Implements the SVG DOM GetCharNumAtPosition method for the specified michael@0: * text content element. michael@0: */ michael@0: int32_t michael@0: SVGTextFrame::GetCharNumAtPosition(nsIContent* aContent, michael@0: mozilla::nsISVGPoint* aPoint) michael@0: { michael@0: UpdateGlyphPositioning(); michael@0: michael@0: nsPresContext* context = PresContext(); michael@0: michael@0: gfxPoint p(aPoint->X(), aPoint->Y()); michael@0: michael@0: int32_t result = -1; michael@0: michael@0: TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames, aContent); michael@0: for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) { michael@0: // Hit test this rendered run. Later runs will override earlier ones. michael@0: int32_t index = run.GetCharNumAtPosition(context, p); michael@0: if (index != -1) { michael@0: result = index + run.mTextElementCharIndex; michael@0: } michael@0: } michael@0: michael@0: if (result == -1) { michael@0: return result; michael@0: } michael@0: michael@0: return ConvertTextElementCharIndexToAddressableIndex(result, aContent); michael@0: } michael@0: michael@0: /** michael@0: * Implements the SVG DOM GetStartPositionOfChar method for the specified michael@0: * text content element. michael@0: */ michael@0: nsresult michael@0: SVGTextFrame::GetStartPositionOfChar(nsIContent* aContent, michael@0: uint32_t aCharNum, michael@0: mozilla::nsISVGPoint** aResult) michael@0: { michael@0: UpdateGlyphPositioning(); michael@0: michael@0: CharIterator it(this, CharIterator::eAddressable, aContent); michael@0: if (!it.AdvanceToSubtree() || michael@0: !it.Next(aCharNum)) { michael@0: return NS_ERROR_DOM_INDEX_SIZE_ERR; michael@0: } michael@0: michael@0: // We need to return the start position of the whole glyph. michael@0: uint32_t startIndex = it.GlyphStartTextElementCharIndex(); michael@0: michael@0: NS_ADDREF(*aResult = michael@0: new DOMSVGPoint(ToPoint(mPositions[startIndex].mPosition))); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /** michael@0: * Implements the SVG DOM GetEndPositionOfChar method for the specified michael@0: * text content element. michael@0: */ michael@0: nsresult michael@0: SVGTextFrame::GetEndPositionOfChar(nsIContent* aContent, michael@0: uint32_t aCharNum, michael@0: mozilla::nsISVGPoint** aResult) michael@0: { michael@0: UpdateGlyphPositioning(); michael@0: michael@0: CharIterator it(this, CharIterator::eAddressable, aContent); michael@0: if (!it.AdvanceToSubtree() || michael@0: !it.Next(aCharNum)) { michael@0: return NS_ERROR_DOM_INDEX_SIZE_ERR; michael@0: } michael@0: michael@0: // We need to return the end position of the whole glyph. michael@0: uint32_t startIndex = it.GlyphStartTextElementCharIndex(); michael@0: michael@0: // Get the advance of the glyph. michael@0: gfxFloat advance = it.GetGlyphAdvance(PresContext()); michael@0: if (it.TextRun()->IsRightToLeft()) { michael@0: advance = -advance; michael@0: } michael@0: michael@0: // The end position is the start position plus the advance in the direction michael@0: // of the glyph's rotation. michael@0: Matrix m = michael@0: Matrix::Rotation(mPositions[startIndex].mAngle) * michael@0: Matrix::Translation(ToPoint(mPositions[startIndex].mPosition)); michael@0: Point p = m * Point(advance / mFontSizeScaleFactor, 0); michael@0: michael@0: NS_ADDREF(*aResult = new DOMSVGPoint(p)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /** michael@0: * Implements the SVG DOM GetExtentOfChar method for the specified michael@0: * text content element. michael@0: */ michael@0: nsresult michael@0: SVGTextFrame::GetExtentOfChar(nsIContent* aContent, michael@0: uint32_t aCharNum, michael@0: dom::SVGIRect** aResult) michael@0: { michael@0: UpdateGlyphPositioning(); michael@0: michael@0: CharIterator it(this, CharIterator::eAddressable, aContent); michael@0: if (!it.AdvanceToSubtree() || michael@0: !it.Next(aCharNum)) { michael@0: return NS_ERROR_DOM_INDEX_SIZE_ERR; michael@0: } michael@0: michael@0: nsPresContext* presContext = PresContext(); michael@0: michael@0: float cssPxPerDevPx = presContext-> michael@0: AppUnitsToFloatCSSPixels(presContext->AppUnitsPerDevPixel()); michael@0: michael@0: // We need to return the extent of the whole glyph. michael@0: uint32_t startIndex = it.GlyphStartTextElementCharIndex(); michael@0: michael@0: // The ascent and descent gives the height of the glyph. michael@0: gfxFloat ascent, descent; michael@0: GetAscentAndDescentInAppUnits(it.TextFrame(), ascent, descent); michael@0: michael@0: // Get the advance of the glyph. michael@0: gfxFloat advance = it.GetGlyphAdvance(presContext); michael@0: gfxFloat x = it.TextRun()->IsRightToLeft() ? -advance : 0.0; michael@0: michael@0: // The horizontal extent is the origin of the glyph plus the advance michael@0: // in the direction of the glyph's rotation. michael@0: gfxMatrix m; michael@0: m.Translate(mPositions[startIndex].mPosition); michael@0: m.Rotate(mPositions[startIndex].mAngle); michael@0: m.Scale(1 / mFontSizeScaleFactor, 1 / mFontSizeScaleFactor); michael@0: michael@0: gfxRect glyphRect michael@0: (x, -presContext->AppUnitsToGfxUnits(ascent) * cssPxPerDevPx, michael@0: advance, presContext->AppUnitsToGfxUnits(ascent + descent) * cssPxPerDevPx); michael@0: michael@0: // Transform the glyph's rect into user space. michael@0: gfxRect r = m.TransformBounds(glyphRect); michael@0: michael@0: NS_ADDREF(*aResult = new dom::SVGRect(aContent, r.x, r.y, r.width, r.height)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /** michael@0: * Implements the SVG DOM GetRotationOfChar method for the specified michael@0: * text content element. michael@0: */ michael@0: nsresult michael@0: SVGTextFrame::GetRotationOfChar(nsIContent* aContent, michael@0: uint32_t aCharNum, michael@0: float* aResult) michael@0: { michael@0: UpdateGlyphPositioning(); michael@0: michael@0: CharIterator it(this, CharIterator::eAddressable, aContent); michael@0: if (!it.AdvanceToSubtree() || michael@0: !it.Next(aCharNum)) { michael@0: return NS_ERROR_DOM_INDEX_SIZE_ERR; michael@0: } michael@0: michael@0: *aResult = mPositions[it.TextElementCharIndex()].mAngle * 180.0 / M_PI; michael@0: return NS_OK; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // SVGTextFrame text layout methods michael@0: michael@0: /** michael@0: * Given the character position array before values have been filled in michael@0: * to any unspecified positions, and an array of dx/dy values, returns whether michael@0: * a character at a given index should start a new rendered run. michael@0: * michael@0: * @param aPositions The array of character positions before unspecified michael@0: * positions have been filled in and dx/dy values have been added to them. michael@0: * @param aDeltas The array of dx/dy values. michael@0: * @param aIndex The character index in question. michael@0: */ michael@0: static bool michael@0: ShouldStartRunAtIndex(const nsTArray& aPositions, michael@0: const nsTArray& aDeltas, michael@0: uint32_t aIndex) michael@0: { michael@0: if (aIndex == 0) { michael@0: return true; michael@0: } michael@0: michael@0: if (aIndex < aPositions.Length()) { michael@0: // If an explicit x or y value was given, start a new run. michael@0: if (aPositions[aIndex].IsXSpecified() || michael@0: aPositions[aIndex].IsYSpecified()) { michael@0: return true; michael@0: } michael@0: michael@0: // If a non-zero rotation was given, or the previous character had a non- michael@0: // zero rotation, start a new run. michael@0: if ((aPositions[aIndex].IsAngleSpecified() && michael@0: aPositions[aIndex].mAngle != 0.0f) || michael@0: (aPositions[aIndex - 1].IsAngleSpecified() && michael@0: (aPositions[aIndex - 1].mAngle != 0.0f))) { michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: if (aIndex < aDeltas.Length()) { michael@0: // If a non-zero dx or dy value was given, start a new run. michael@0: if (aDeltas[aIndex].x != 0.0 || michael@0: aDeltas[aIndex].y != 0.0) { michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: uint32_t michael@0: SVGTextFrame::ResolvePositions(nsIContent* aContent, michael@0: uint32_t aIndex, michael@0: bool aInTextPath, michael@0: bool& aForceStartOfChunk, michael@0: nsTArray& aDeltas) michael@0: { michael@0: if (aContent->IsNodeOfType(nsINode::eTEXT)) { michael@0: // We found a text node. michael@0: uint32_t length = static_cast(aContent)->TextLength(); michael@0: if (length) { michael@0: if (aForceStartOfChunk) { michael@0: // Note this character as starting a new anchored chunk. michael@0: mPositions[aIndex].mStartOfChunk = true; michael@0: aForceStartOfChunk = false; michael@0: } michael@0: uint32_t end = aIndex + length; michael@0: while (aIndex < end) { michael@0: // Record whether each of these characters should start a new rendered michael@0: // run. That is always the case for characters on a text path. michael@0: // michael@0: // Run boundaries due to rotate="" values are handled in michael@0: // DoGlyphPositioning. michael@0: if (aInTextPath || ShouldStartRunAtIndex(mPositions, aDeltas, aIndex)) { michael@0: mPositions[aIndex].mRunBoundary = true; michael@0: } michael@0: aIndex++; michael@0: } michael@0: } michael@0: return aIndex; michael@0: } michael@0: michael@0: // Skip past elements that aren't text content elements. michael@0: if (!IsTextContentElement(aContent)) { michael@0: return aIndex; michael@0: } michael@0: michael@0: if (aContent->Tag() == nsGkAtoms::textPath) { michael@0: // elements are as if they are specified with x="0" y="0", but michael@0: // only if they actually have some text content. michael@0: if (HasTextContent(aContent)) { michael@0: mPositions[aIndex].mPosition = gfxPoint(); michael@0: mPositions[aIndex].mStartOfChunk = true; michael@0: } michael@0: } else if (aContent->Tag() != nsGkAtoms::a) { michael@0: // We have a text content element that can have x/y/dx/dy/rotate attributes. michael@0: nsSVGElement* element = static_cast(aContent); michael@0: michael@0: // Get x, y, dx, dy. michael@0: SVGUserUnitList x, y, dx, dy; michael@0: element->GetAnimatedLengthListValues(&x, &y, &dx, &dy); michael@0: michael@0: // Get rotate. michael@0: const SVGNumberList* rotate = nullptr; michael@0: SVGAnimatedNumberList* animatedRotate = michael@0: element->GetAnimatedNumberList(nsGkAtoms::rotate); michael@0: if (animatedRotate) { michael@0: rotate = &animatedRotate->GetAnimValue(); michael@0: } michael@0: michael@0: uint32_t count = GetTextContentLength(aContent); michael@0: bool percentages = false; michael@0: michael@0: // New text anchoring chunks start at each character assigned a position michael@0: // with x="" or y="", or if we forced one with aForceStartOfChunk due to michael@0: // being just after a . michael@0: uint32_t newChunkCount = std::max(x.Length(), y.Length()); michael@0: if (!newChunkCount && aForceStartOfChunk) { michael@0: newChunkCount = 1; michael@0: } michael@0: for (uint32_t i = 0, j = 0; i < newChunkCount && j < count; j++) { michael@0: if (!mPositions[aIndex + j].mUnaddressable) { michael@0: mPositions[aIndex + j].mStartOfChunk = true; michael@0: i++; michael@0: } michael@0: } michael@0: michael@0: // Copy dx="" and dy="" values into aDeltas. michael@0: if (!dx.IsEmpty() || !dy.IsEmpty()) { michael@0: // Any unspecified deltas when we grow the array just get left as 0s. michael@0: aDeltas.EnsureLengthAtLeast(aIndex + count); michael@0: for (uint32_t i = 0, j = 0; i < dx.Length() && j < count; j++) { michael@0: if (!mPositions[aIndex + j].mUnaddressable) { michael@0: aDeltas[aIndex + j].x = dx[i]; michael@0: percentages = percentages || dx.HasPercentageValueAt(i); michael@0: i++; michael@0: } michael@0: } michael@0: for (uint32_t i = 0, j = 0; i < dy.Length() && j < count; j++) { michael@0: if (!mPositions[aIndex + j].mUnaddressable) { michael@0: aDeltas[aIndex + j].y = dy[i]; michael@0: percentages = percentages || dy.HasPercentageValueAt(i); michael@0: i++; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Copy x="" and y="" values. michael@0: for (uint32_t i = 0, j = 0; i < x.Length() && j < count; j++) { michael@0: if (!mPositions[aIndex + j].mUnaddressable) { michael@0: mPositions[aIndex + j].mPosition.x = x[i]; michael@0: percentages = percentages || x.HasPercentageValueAt(i); michael@0: i++; michael@0: } michael@0: } michael@0: for (uint32_t i = 0, j = 0; i < y.Length() && j < count; j++) { michael@0: if (!mPositions[aIndex + j].mUnaddressable) { michael@0: mPositions[aIndex + j].mPosition.y = y[i]; michael@0: percentages = percentages || y.HasPercentageValueAt(i); michael@0: i++; michael@0: } michael@0: } michael@0: michael@0: // Copy rotate="" values. michael@0: if (rotate && !rotate->IsEmpty()) { michael@0: uint32_t i = 0, j = 0; michael@0: while (i < rotate->Length() && j < count) { michael@0: if (!mPositions[aIndex + j].mUnaddressable) { michael@0: mPositions[aIndex + j].mAngle = M_PI * (*rotate)[i] / 180.0; michael@0: i++; michael@0: } michael@0: j++; michael@0: } michael@0: // Propagate final rotate="" value to the end of this element. michael@0: while (j < count) { michael@0: mPositions[aIndex + j].mAngle = mPositions[aIndex + j - 1].mAngle; michael@0: j++; michael@0: } michael@0: } michael@0: michael@0: if (percentages) { michael@0: AddStateBits(NS_STATE_SVG_POSITIONING_MAY_USE_PERCENTAGES); michael@0: } michael@0: } michael@0: michael@0: // Recurse to children. michael@0: bool inTextPath = aInTextPath || aContent->Tag() == nsGkAtoms::textPath; michael@0: for (nsIContent* child = aContent->GetFirstChild(); michael@0: child; michael@0: child = child->GetNextSibling()) { michael@0: aIndex = ResolvePositions(child, aIndex, inTextPath, aForceStartOfChunk, michael@0: aDeltas); michael@0: } michael@0: michael@0: if (aContent->Tag() == nsGkAtoms::textPath) { michael@0: // Force a new anchored chunk just after a . michael@0: aForceStartOfChunk = true; michael@0: } michael@0: michael@0: return aIndex; michael@0: } michael@0: michael@0: bool michael@0: SVGTextFrame::ResolvePositions(nsTArray& aDeltas, michael@0: bool aRunPerGlyph) michael@0: { michael@0: NS_ASSERTION(mPositions.IsEmpty(), "expected mPositions to be empty"); michael@0: RemoveStateBits(NS_STATE_SVG_POSITIONING_MAY_USE_PERCENTAGES); michael@0: michael@0: CharIterator it(this, CharIterator::eOriginal); michael@0: if (it.AtEnd()) { michael@0: return false; michael@0: } michael@0: michael@0: // We assume the first character position is (0,0) unless we later see michael@0: // otherwise, and note it as unaddressable if it is. michael@0: bool firstCharUnaddressable = it.IsOriginalCharUnaddressable(); michael@0: mPositions.AppendElement(CharPosition::Unspecified(firstCharUnaddressable)); michael@0: michael@0: // Fill in unspecified positions for all remaining characters, noting michael@0: // them as unaddressable if they are. michael@0: uint32_t index = 0; michael@0: while (it.Next()) { michael@0: while (++index < it.TextElementCharIndex()) { michael@0: mPositions.AppendElement(CharPosition::Unspecified(false)); michael@0: } michael@0: mPositions.AppendElement(CharPosition::Unspecified( michael@0: it.IsOriginalCharUnaddressable())); michael@0: } michael@0: while (++index < it.TextElementCharIndex()) { michael@0: mPositions.AppendElement(CharPosition::Unspecified(false)); michael@0: } michael@0: michael@0: // Recurse over the content and fill in character positions as we go. michael@0: bool forceStartOfChunk = false; michael@0: return ResolvePositions(mContent, 0, aRunPerGlyph, michael@0: forceStartOfChunk, aDeltas) != 0; michael@0: } michael@0: michael@0: void michael@0: SVGTextFrame::DetermineCharPositions(nsTArray& aPositions) michael@0: { michael@0: NS_ASSERTION(aPositions.IsEmpty(), "expected aPositions to be empty"); michael@0: michael@0: nsPoint position, lastPosition; michael@0: michael@0: TextFrameIterator frit(this); michael@0: for (nsTextFrame* frame = frit.Current(); frame; frame = frit.Next()) { michael@0: gfxSkipCharsIterator it = frame->EnsureTextRun(nsTextFrame::eInflated); michael@0: gfxTextRun* textRun = frame->GetTextRun(nsTextFrame::eInflated); michael@0: michael@0: // Reset the position to the new frame's position. michael@0: position = frit.Position(); michael@0: if (textRun->IsRightToLeft()) { michael@0: position.x += frame->GetRect().width; michael@0: } michael@0: position.y += GetBaselinePosition(frame, textRun, frit.DominantBaseline()); michael@0: michael@0: // Any characters not in a frame, e.g. when display:none. michael@0: for (uint32_t i = 0; i < frit.UndisplayedCharacters(); i++) { michael@0: aPositions.AppendElement(position); michael@0: } michael@0: michael@0: // Any white space characters trimmed at the start of the line of text. michael@0: nsTextFrame::TrimmedOffsets trimmedOffsets = michael@0: frame->GetTrimmedOffsets(frame->GetContent()->GetText(), true); michael@0: while (it.GetOriginalOffset() < trimmedOffsets.mStart) { michael@0: aPositions.AppendElement(position); michael@0: it.AdvanceOriginal(1); michael@0: } michael@0: michael@0: // If a ligature was started in the previous frame, we should record michael@0: // the ligature's start position, not any partial position. michael@0: while (it.GetOriginalOffset() < frame->GetContentEnd() && michael@0: !it.IsOriginalCharSkipped() && michael@0: (!textRun->IsLigatureGroupStart(it.GetSkippedOffset()) || michael@0: !textRun->IsClusterStart(it.GetSkippedOffset()))) { michael@0: nscoord advance = textRun->GetAdvanceWidth(it.GetSkippedOffset(), 1, michael@0: nullptr); michael@0: position.x += textRun->IsRightToLeft() ? -advance : advance; michael@0: aPositions.AppendElement(lastPosition); michael@0: it.AdvanceOriginal(1); michael@0: } michael@0: michael@0: // The meat of the text frame. michael@0: while (it.GetOriginalOffset() < frame->GetContentEnd()) { michael@0: aPositions.AppendElement(position); michael@0: if (!it.IsOriginalCharSkipped() && michael@0: textRun->IsLigatureGroupStart(it.GetSkippedOffset()) && michael@0: textRun->IsClusterStart(it.GetSkippedOffset())) { michael@0: // A real visible character. michael@0: uint32_t length = ClusterLength(textRun, it); michael@0: nscoord advance = textRun->GetAdvanceWidth(it.GetSkippedOffset(), michael@0: length, nullptr); michael@0: position.x += textRun->IsRightToLeft() ? -advance : advance; michael@0: lastPosition = position; michael@0: } michael@0: it.AdvanceOriginal(1); michael@0: } michael@0: } michael@0: michael@0: // Finally any characters at the end that are not in a frame. michael@0: for (uint32_t i = 0; i < frit.UndisplayedCharacters(); i++) { michael@0: aPositions.AppendElement(position); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Physical text-anchor values. michael@0: */ michael@0: enum TextAnchorSide { michael@0: eAnchorLeft, michael@0: eAnchorMiddle, michael@0: eAnchorRight michael@0: }; michael@0: michael@0: /** michael@0: * Converts a logical text-anchor value to its physical value, based on whether michael@0: * it is for an RTL frame. michael@0: */ michael@0: static TextAnchorSide michael@0: ConvertLogicalTextAnchorToPhysical(uint8_t aTextAnchor, bool aIsRightToLeft) michael@0: { michael@0: NS_ASSERTION(aTextAnchor <= 3, "unexpected value for aTextAnchor"); michael@0: if (!aIsRightToLeft) michael@0: return TextAnchorSide(aTextAnchor); michael@0: return TextAnchorSide(2 - aTextAnchor); michael@0: } michael@0: michael@0: /** michael@0: * Shifts the recorded character positions for an anchored chunk. michael@0: * michael@0: * @param aCharPositions The recorded character positions. michael@0: * @param aChunkStart The character index the starts the anchored chunk. This michael@0: * character's initial position is the anchor point. michael@0: * @param aChunkEnd The character index just after the end of the anchored michael@0: * chunk. michael@0: * @param aLeftEdge The left-most edge of any of the glyphs within the michael@0: * anchored chunk. michael@0: * @param aRightEdge The right-most edge of any of the glyphs within the michael@0: * anchored chunk. michael@0: * @param aAnchorSide The direction to anchor. michael@0: */ michael@0: static void michael@0: ShiftAnchoredChunk(nsTArray& aCharPositions, michael@0: uint32_t aChunkStart, michael@0: uint32_t aChunkEnd, michael@0: gfxFloat aLeftEdge, michael@0: gfxFloat aRightEdge, michael@0: TextAnchorSide aAnchorSide) michael@0: { michael@0: NS_ASSERTION(aLeftEdge <= aRightEdge, "unexpected anchored chunk edges"); michael@0: NS_ASSERTION(aChunkStart < aChunkEnd, "unexpected values for aChunkStart and " michael@0: "aChunkEnd"); michael@0: michael@0: gfxFloat shift = aCharPositions[aChunkStart].mPosition.x; michael@0: switch (aAnchorSide) { michael@0: case eAnchorLeft: michael@0: shift -= aLeftEdge; michael@0: break; michael@0: case eAnchorMiddle: michael@0: shift -= (aLeftEdge + aRightEdge) / 2; michael@0: break; michael@0: case eAnchorRight: michael@0: shift -= aRightEdge; michael@0: break; michael@0: default: michael@0: NS_NOTREACHED("unexpected value for aAnchorSide"); michael@0: } michael@0: michael@0: if (shift != 0.0) { michael@0: for (uint32_t i = aChunkStart; i < aChunkEnd; i++) { michael@0: aCharPositions[i].mPosition.x += shift; michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: SVGTextFrame::AdjustChunksForLineBreaks() michael@0: { michael@0: nsBlockFrame* block = nsLayoutUtils::GetAsBlock(GetFirstPrincipalChild()); michael@0: NS_ASSERTION(block, "expected block frame"); michael@0: michael@0: nsBlockFrame::line_iterator line = block->begin_lines(); michael@0: michael@0: CharIterator it(this, CharIterator::eOriginal); michael@0: while (!it.AtEnd() && line != block->end_lines()) { michael@0: if (it.TextFrame() == line->mFirstChild) { michael@0: mPositions[it.TextElementCharIndex()].mStartOfChunk = true; michael@0: line++; michael@0: } michael@0: it.AdvancePastCurrentFrame(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: SVGTextFrame::AdjustPositionsForClusters() michael@0: { michael@0: nsPresContext* presContext = PresContext(); michael@0: michael@0: CharIterator it(this, CharIterator::eClusterOrLigatureGroupMiddle); michael@0: while (!it.AtEnd()) { michael@0: // Find the start of the cluster/ligature group. michael@0: uint32_t charIndex = it.TextElementCharIndex(); michael@0: uint32_t startIndex = it.GlyphStartTextElementCharIndex(); michael@0: michael@0: mPositions[charIndex].mClusterOrLigatureGroupMiddle = true; michael@0: michael@0: // Don't allow different rotations on ligature parts. michael@0: bool rotationAdjusted = false; michael@0: double angle = mPositions[startIndex].mAngle; michael@0: if (mPositions[charIndex].mAngle != angle) { michael@0: mPositions[charIndex].mAngle = angle; michael@0: rotationAdjusted = true; michael@0: } michael@0: michael@0: // Find out the partial glyph advance for this character and update michael@0: // the character position. michael@0: uint32_t partLength = michael@0: charIndex - startIndex - it.GlyphUndisplayedCharacters(); michael@0: gfxFloat advance = michael@0: it.GetGlyphPartialAdvance(partLength, presContext) / mFontSizeScaleFactor; michael@0: gfxPoint direction = gfxPoint(cos(angle), sin(angle)) * michael@0: (it.TextRun()->IsRightToLeft() ? -1.0 : 1.0); michael@0: mPositions[charIndex].mPosition = mPositions[startIndex].mPosition + michael@0: direction * advance; michael@0: michael@0: // Ensure any runs that would end in the middle of a ligature now end just michael@0: // after the ligature. michael@0: if (mPositions[charIndex].mRunBoundary) { michael@0: mPositions[charIndex].mRunBoundary = false; michael@0: if (charIndex + 1 < mPositions.Length()) { michael@0: mPositions[charIndex + 1].mRunBoundary = true; michael@0: } michael@0: } else if (rotationAdjusted) { michael@0: if (charIndex + 1 < mPositions.Length()) { michael@0: mPositions[charIndex + 1].mRunBoundary = true; michael@0: } michael@0: } michael@0: michael@0: // Ensure any anchored chunks that would begin in the middle of a ligature michael@0: // now begin just after the ligature. michael@0: if (mPositions[charIndex].mStartOfChunk) { michael@0: mPositions[charIndex].mStartOfChunk = false; michael@0: if (charIndex + 1 < mPositions.Length()) { michael@0: mPositions[charIndex + 1].mStartOfChunk = true; michael@0: } michael@0: } michael@0: michael@0: it.Next(); michael@0: } michael@0: } michael@0: michael@0: nsIFrame* michael@0: SVGTextFrame::GetTextPathPathFrame(nsIFrame* aTextPathFrame) michael@0: { michael@0: nsSVGTextPathProperty *property = static_cast michael@0: (aTextPathFrame->Properties().Get(nsSVGEffects::HrefProperty())); michael@0: michael@0: if (!property) { michael@0: nsIContent* content = aTextPathFrame->GetContent(); michael@0: dom::SVGTextPathElement* tp = static_cast(content); michael@0: nsAutoString href; michael@0: tp->mStringAttributes[dom::SVGTextPathElement::HREF].GetAnimValue(href, tp); michael@0: if (href.IsEmpty()) { michael@0: return nullptr; // no URL michael@0: } michael@0: michael@0: nsCOMPtr targetURI; michael@0: nsCOMPtr base = content->GetBaseURI(); michael@0: nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(targetURI), href, michael@0: content->GetCurrentDoc(), base); michael@0: michael@0: property = nsSVGEffects::GetTextPathProperty(targetURI, aTextPathFrame, michael@0: nsSVGEffects::HrefProperty()); michael@0: if (!property) michael@0: return nullptr; michael@0: } michael@0: michael@0: return property->GetReferencedFrame(nsGkAtoms::svgPathGeometryFrame, nullptr); michael@0: } michael@0: michael@0: TemporaryRef michael@0: SVGTextFrame::GetTextPath(nsIFrame* aTextPathFrame) michael@0: { michael@0: nsIFrame *pathFrame = GetTextPathPathFrame(aTextPathFrame); michael@0: michael@0: if (!pathFrame) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsSVGPathGeometryElement *element = michael@0: static_cast(pathFrame->GetContent()); michael@0: michael@0: RefPtr path = element->GetPathForLengthOrPositionMeasuring(); michael@0: if (!path) { michael@0: return nullptr; michael@0: } michael@0: michael@0: gfxMatrix matrix = element->PrependLocalTransformsTo(gfxMatrix()); michael@0: if (!matrix.IsIdentity()) { michael@0: RefPtr builder = michael@0: path->TransformedCopyToBuilder(ToMatrix(matrix)); michael@0: path = builder->Finish(); michael@0: } michael@0: michael@0: return path.forget(); michael@0: } michael@0: michael@0: gfxFloat michael@0: SVGTextFrame::GetOffsetScale(nsIFrame* aTextPathFrame) michael@0: { michael@0: nsIFrame *pathFrame = GetTextPathPathFrame(aTextPathFrame); michael@0: if (!pathFrame) michael@0: return 1.0; michael@0: michael@0: return static_cast(pathFrame->GetContent())-> michael@0: GetPathLengthScale(dom::SVGPathElement::eForTextPath); michael@0: } michael@0: michael@0: gfxFloat michael@0: SVGTextFrame::GetStartOffset(nsIFrame* aTextPathFrame) michael@0: { michael@0: dom::SVGTextPathElement *tp = michael@0: static_cast(aTextPathFrame->GetContent()); michael@0: nsSVGLength2 *length = michael@0: &tp->mLengthAttributes[dom::SVGTextPathElement::STARTOFFSET]; michael@0: michael@0: if (length->IsPercentage()) { michael@0: RefPtr data = GetTextPath(aTextPathFrame); michael@0: return data ? michael@0: length->GetAnimValInSpecifiedUnits() * data->ComputeLength() / 100.0 : michael@0: 0.0; michael@0: } michael@0: return length->GetAnimValue(tp) * GetOffsetScale(aTextPathFrame); michael@0: } michael@0: michael@0: void michael@0: SVGTextFrame::DoTextPathLayout() michael@0: { michael@0: nsPresContext* context = PresContext(); michael@0: michael@0: CharIterator it(this, CharIterator::eClusterAndLigatureGroupStart); michael@0: while (!it.AtEnd()) { michael@0: nsIFrame* textPathFrame = it.TextPathFrame(); michael@0: if (!textPathFrame) { michael@0: // Skip past this frame if we're not in a text path. michael@0: it.AdvancePastCurrentFrame(); michael@0: continue; michael@0: } michael@0: michael@0: // Get the path itself. michael@0: RefPtr path = GetTextPath(textPathFrame); michael@0: if (!path) { michael@0: it.AdvancePastCurrentTextPathFrame(); michael@0: continue; michael@0: } michael@0: michael@0: nsIContent* textPath = textPathFrame->GetContent(); michael@0: michael@0: gfxFloat offset = GetStartOffset(textPathFrame); michael@0: Float pathLength = path->ComputeLength(); michael@0: michael@0: // Loop for each text frame in the text path. michael@0: do { michael@0: uint32_t i = it.TextElementCharIndex(); michael@0: gfxFloat halfAdvance = michael@0: it.GetGlyphAdvance(context) / mFontSizeScaleFactor / 2.0; michael@0: gfxFloat sign = it.TextRun()->IsRightToLeft() ? -1.0 : 1.0; michael@0: gfxFloat midx = mPositions[i].mPosition.x + sign * halfAdvance + offset; michael@0: michael@0: // Hide the character if it falls off the end of the path. michael@0: mPositions[i].mHidden = midx < 0 || midx > pathLength; michael@0: michael@0: // Position the character on the path at the right angle. michael@0: Point tangent; // Unit vector tangent to the point we find. michael@0: Point pt = path->ComputePointAtLength(Float(midx), &tangent); michael@0: Float rotation = atan2f(tangent.y, tangent.x); michael@0: Point normal(-tangent.y, tangent.x); // Unit vector normal to the point. michael@0: Point offsetFromPath = normal * mPositions[i].mPosition.y; michael@0: pt += offsetFromPath; michael@0: Point direction = tangent * sign; michael@0: mPositions[i].mPosition = ThebesPoint(pt) - ThebesPoint(direction) * halfAdvance; michael@0: mPositions[i].mAngle += rotation; michael@0: michael@0: // Position any characters for a partial ligature. michael@0: for (uint32_t j = i + 1; michael@0: j < mPositions.Length() && mPositions[j].mClusterOrLigatureGroupMiddle; michael@0: j++) { michael@0: gfxPoint partialAdvance = michael@0: ThebesPoint(direction) * it.GetGlyphPartialAdvance(j - i, context) / michael@0: mFontSizeScaleFactor; michael@0: mPositions[j].mPosition = mPositions[i].mPosition + partialAdvance; michael@0: mPositions[j].mAngle = mPositions[i].mAngle; michael@0: mPositions[j].mHidden = mPositions[i].mHidden; michael@0: } michael@0: it.Next(); michael@0: } while (it.TextPathFrame() && michael@0: it.TextPathFrame()->GetContent() == textPath); michael@0: } michael@0: } michael@0: michael@0: void michael@0: SVGTextFrame::DoAnchoring() michael@0: { michael@0: nsPresContext* presContext = PresContext(); michael@0: michael@0: CharIterator it(this, CharIterator::eOriginal); michael@0: michael@0: // Don't need to worry about skipped or trimmed characters. michael@0: while (!it.AtEnd() && michael@0: (it.IsOriginalCharSkipped() || it.IsOriginalCharTrimmed())) { michael@0: it.Next(); michael@0: } michael@0: michael@0: uint32_t start = it.TextElementCharIndex(); michael@0: while (start < mPositions.Length()) { michael@0: it.AdvanceToCharacter(start); michael@0: nsTextFrame* chunkFrame = it.TextFrame(); michael@0: michael@0: // Measure characters in this chunk to find the left-most and right-most michael@0: // edges of all glyphs within the chunk. michael@0: uint32_t index = it.TextElementCharIndex(); michael@0: uint32_t end = start; michael@0: gfxFloat left = std::numeric_limits::infinity(); michael@0: gfxFloat right = -std::numeric_limits::infinity(); michael@0: do { michael@0: if (!it.IsOriginalCharSkipped() && !it.IsOriginalCharTrimmed()) { michael@0: gfxFloat advance = it.GetAdvance(presContext) / mFontSizeScaleFactor; michael@0: if (it.TextRun()->IsRightToLeft()) { michael@0: left = std::min(left, mPositions[index].mPosition.x - advance); michael@0: right = std::max(right, mPositions[index].mPosition.x); michael@0: } else { michael@0: left = std::min(left, mPositions[index].mPosition.x); michael@0: right = std::max(right, mPositions[index].mPosition.x + advance); michael@0: } michael@0: } michael@0: it.Next(); michael@0: index = end = it.TextElementCharIndex(); michael@0: } while (!it.AtEnd() && !mPositions[end].mStartOfChunk); michael@0: michael@0: if (left != std::numeric_limits::infinity()) { michael@0: bool isRTL = michael@0: chunkFrame->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL; michael@0: TextAnchorSide anchor = michael@0: ConvertLogicalTextAnchorToPhysical(chunkFrame->StyleSVG()->mTextAnchor, michael@0: isRTL); michael@0: michael@0: ShiftAnchoredChunk(mPositions, start, end, left, right, anchor); michael@0: } michael@0: michael@0: start = it.TextElementCharIndex(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: SVGTextFrame::DoGlyphPositioning() michael@0: { michael@0: mPositions.Clear(); michael@0: RemoveStateBits(NS_STATE_SVG_POSITIONING_DIRTY); michael@0: michael@0: nsIFrame* kid = GetFirstPrincipalChild(); michael@0: if (kid && NS_SUBTREE_DIRTY(kid)) { michael@0: MOZ_ASSERT(false, "should have already reflowed the kid"); michael@0: return; michael@0: } michael@0: michael@0: // Determine the positions of each character in app units. michael@0: nsTArray charPositions; michael@0: DetermineCharPositions(charPositions); michael@0: michael@0: if (charPositions.IsEmpty()) { michael@0: // No characters, so nothing to do. michael@0: return; michael@0: } michael@0: michael@0: // If the textLength="" attribute was specified, then we need ResolvePositions michael@0: // to record that a new run starts with each glyph. michael@0: SVGTextContentElement* element = static_cast(mContent); michael@0: nsSVGLength2* textLengthAttr = michael@0: element->GetAnimatedLength(nsGkAtoms::textLength); michael@0: bool adjustingTextLength = textLengthAttr->IsExplicitlySet(); michael@0: float expectedTextLength = textLengthAttr->GetAnimValue(element); michael@0: michael@0: if (adjustingTextLength && expectedTextLength < 0.0f) { michael@0: // If textLength="" is less than zero, ignore it. michael@0: adjustingTextLength = false; michael@0: } michael@0: michael@0: // Get the x, y, dx, dy, rotate values for the subtree. michael@0: nsTArray deltas; michael@0: if (!ResolvePositions(deltas, adjustingTextLength)) { michael@0: // If ResolvePositions returned false, it means that there were some michael@0: // characters in the DOM but none of them are displayed. Clear out michael@0: // mPositions so that we don't attempt to do any painting later. michael@0: mPositions.Clear(); michael@0: return; michael@0: } michael@0: michael@0: // XXX We might be able to do less work when there is at most a single michael@0: // x/y/dx/dy position. michael@0: michael@0: // Truncate the positioning arrays to the actual number of characters present. michael@0: TruncateTo(deltas, charPositions); michael@0: TruncateTo(mPositions, charPositions); michael@0: michael@0: // Fill in an unspecified character position at index 0. michael@0: if (!mPositions[0].IsXSpecified()) { michael@0: mPositions[0].mPosition.x = 0.0; michael@0: } michael@0: if (!mPositions[0].IsYSpecified()) { michael@0: mPositions[0].mPosition.y = 0.0; michael@0: } michael@0: if (!mPositions[0].IsAngleSpecified()) { michael@0: mPositions[0].mAngle = 0.0; michael@0: } michael@0: michael@0: nsPresContext* presContext = PresContext(); michael@0: michael@0: float cssPxPerDevPx = presContext-> michael@0: AppUnitsToFloatCSSPixels(presContext->AppUnitsPerDevPixel()); michael@0: double factor = cssPxPerDevPx / mFontSizeScaleFactor; michael@0: michael@0: // Determine how much to compress or expand glyph positions due to michael@0: // textLength="" and lengthAdjust="". michael@0: double adjustment = 0.0; michael@0: mLengthAdjustScaleFactor = 1.0f; michael@0: if (adjustingTextLength) { michael@0: nscoord frameWidth = GetFirstPrincipalChild()->GetRect().width; michael@0: float actualTextLength = michael@0: static_cast(presContext->AppUnitsToGfxUnits(frameWidth) * factor); michael@0: michael@0: nsRefPtr lengthAdjustEnum = element->LengthAdjust(); michael@0: uint16_t lengthAdjust = lengthAdjustEnum->AnimVal(); michael@0: switch (lengthAdjust) { michael@0: case SVG_LENGTHADJUST_SPACINGANDGLYPHS: michael@0: // Scale the glyphs and their positions. michael@0: if (actualTextLength > 0) { michael@0: mLengthAdjustScaleFactor = expectedTextLength / actualTextLength; michael@0: } michael@0: break; michael@0: michael@0: default: michael@0: MOZ_ASSERT(lengthAdjust == SVG_LENGTHADJUST_SPACING); michael@0: // Just add space between each glyph. michael@0: int32_t adjustableSpaces = 0; michael@0: for (uint32_t i = 1; i < mPositions.Length(); i++) { michael@0: if (!mPositions[i].mUnaddressable) { michael@0: adjustableSpaces++; michael@0: } michael@0: } michael@0: if (adjustableSpaces) { michael@0: adjustment = (expectedTextLength - actualTextLength) / adjustableSpaces; michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // Fill in any unspecified character positions based on the positions recorded michael@0: // in charPositions, and also add in the dx/dy values. michael@0: if (!deltas.IsEmpty()) { michael@0: mPositions[0].mPosition += deltas[0]; michael@0: } michael@0: michael@0: for (uint32_t i = 1; i < mPositions.Length(); i++) { michael@0: // Fill in unspecified x position. michael@0: if (!mPositions[i].IsXSpecified()) { michael@0: nscoord d = charPositions[i].x - charPositions[i - 1].x; michael@0: mPositions[i].mPosition.x = michael@0: mPositions[i - 1].mPosition.x + michael@0: presContext->AppUnitsToGfxUnits(d) * factor * mLengthAdjustScaleFactor; michael@0: if (!mPositions[i].mUnaddressable) { michael@0: mPositions[i].mPosition.x += adjustment; michael@0: } michael@0: } michael@0: // Fill in unspecified y position. michael@0: if (!mPositions[i].IsYSpecified()) { michael@0: nscoord d = charPositions[i].y - charPositions[i - 1].y; michael@0: mPositions[i].mPosition.y = michael@0: mPositions[i - 1].mPosition.y + michael@0: presContext->AppUnitsToGfxUnits(d) * factor; michael@0: } michael@0: // Add in dx/dy. michael@0: if (i < deltas.Length()) { michael@0: mPositions[i].mPosition += deltas[i]; michael@0: } michael@0: // Fill in unspecified rotation values. michael@0: if (!mPositions[i].IsAngleSpecified()) { michael@0: mPositions[i].mAngle = 0.0f; michael@0: } michael@0: } michael@0: michael@0: MOZ_ASSERT(mPositions.Length() == charPositions.Length()); michael@0: michael@0: AdjustChunksForLineBreaks(); michael@0: AdjustPositionsForClusters(); michael@0: DoAnchoring(); michael@0: DoTextPathLayout(); michael@0: } michael@0: michael@0: bool michael@0: SVGTextFrame::ShouldRenderAsPath(nsRenderingContext* aContext, michael@0: nsTextFrame* aFrame, michael@0: bool& aShouldPaintSVGGlyphs) michael@0: { michael@0: // Rendering to a clip path. michael@0: if (SVGAutoRenderState::GetRenderMode(aContext) != SVGAutoRenderState::NORMAL) { michael@0: aShouldPaintSVGGlyphs = false; michael@0: return true; michael@0: } michael@0: michael@0: aShouldPaintSVGGlyphs = true; michael@0: michael@0: const nsStyleSVG* style = aFrame->StyleSVG(); michael@0: michael@0: // Fill is a non-solid paint, has a non-default fill-rule or has michael@0: // non-1 opacity. michael@0: if (!(style->mFill.mType == eStyleSVGPaintType_None || michael@0: (style->mFill.mType == eStyleSVGPaintType_Color && michael@0: style->mFillOpacity == 1))) { michael@0: return true; michael@0: } michael@0: michael@0: // Text has a stroke. michael@0: if (style->HasStroke() && michael@0: SVGContentUtils::CoordToFloat(PresContext(), michael@0: static_cast(mContent), michael@0: style->mStrokeWidth) > 0) { michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: SVGTextFrame::ScheduleReflowSVG() michael@0: { michael@0: if (mState & NS_FRAME_IS_NONDISPLAY) { michael@0: ScheduleReflowSVGNonDisplayText(); michael@0: } else { michael@0: nsSVGUtils::ScheduleReflowSVG(this); michael@0: } michael@0: } michael@0: michael@0: void michael@0: SVGTextFrame::NotifyGlyphMetricsChange() michael@0: { michael@0: AddStateBits(NS_STATE_SVG_POSITIONING_DIRTY); michael@0: nsSVGEffects::InvalidateRenderingObservers(this); michael@0: ScheduleReflowSVG(); michael@0: } michael@0: michael@0: void michael@0: SVGTextFrame::UpdateGlyphPositioning() michael@0: { michael@0: nsIFrame* kid = GetFirstPrincipalChild(); michael@0: if (!kid) { michael@0: return; michael@0: } michael@0: michael@0: if (mState & NS_STATE_SVG_POSITIONING_DIRTY) { michael@0: DoGlyphPositioning(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: SVGTextFrame::MaybeReflowAnonymousBlockChild() michael@0: { michael@0: nsIFrame* kid = GetFirstPrincipalChild(); michael@0: if (!kid) michael@0: return; michael@0: michael@0: NS_ASSERTION(!(kid->GetStateBits() & NS_FRAME_IN_REFLOW), michael@0: "should not be in reflow when about to reflow again"); michael@0: michael@0: if (NS_SUBTREE_DIRTY(this)) { michael@0: if (mState & NS_FRAME_IS_DIRTY) { michael@0: // If we require a full reflow, ensure our kid is marked fully dirty. michael@0: // (Note that our anonymous nsBlockFrame is not an nsISVGChildFrame, so michael@0: // even when we are called via our ReflowSVG this will not be done for us michael@0: // by nsSVGDisplayContainerFrame::ReflowSVG.) michael@0: kid->AddStateBits(NS_FRAME_IS_DIRTY); michael@0: } michael@0: MOZ_ASSERT(nsSVGUtils::AnyOuterSVGIsCallingReflowSVG(this), michael@0: "should be under ReflowSVG"); michael@0: nsPresContext::InterruptPreventer noInterrupts(PresContext()); michael@0: DoReflow(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: SVGTextFrame::DoReflow() michael@0: { michael@0: // Since we are going to reflow the anonymous block frame, we will michael@0: // need to update mPositions. michael@0: AddStateBits(NS_STATE_SVG_POSITIONING_DIRTY); michael@0: michael@0: if (mState & NS_FRAME_IS_NONDISPLAY) { michael@0: // Normally, these dirty flags would be cleared in ReflowSVG(), but that michael@0: // doesn't get called for non-display frames. We don't want to reflow our michael@0: // descendants every time SVGTextFrame::PaintSVG makes sure that we have michael@0: // valid positions by calling UpdateGlyphPositioning(), so we need to clear michael@0: // these dirty bits. Note that this also breaks an invalidation loop where michael@0: // our descendants invalidate as they reflow, which invalidates rendering michael@0: // observers, which reschedules the frame that is currently painting by michael@0: // referencing us to paint again. See bug 839958 comment 7. Hopefully we michael@0: // will break that loop more convincingly at some point. michael@0: mState &= ~(NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN); michael@0: } michael@0: michael@0: nsPresContext *presContext = PresContext(); michael@0: nsIFrame* kid = GetFirstPrincipalChild(); michael@0: if (!kid) michael@0: return; michael@0: michael@0: nsRefPtr renderingContext = michael@0: presContext->PresShell()->CreateReferenceRenderingContext(); michael@0: michael@0: if (UpdateFontSizeScaleFactor()) { michael@0: // If the font size scale factor changed, we need the block to report michael@0: // an updated preferred width. michael@0: kid->MarkIntrinsicWidthsDirty(); michael@0: } michael@0: michael@0: mState |= NS_STATE_SVG_TEXT_IN_REFLOW; michael@0: michael@0: nscoord width = kid->GetPrefWidth(renderingContext); michael@0: nsHTMLReflowState reflowState(presContext, kid, michael@0: renderingContext, michael@0: nsSize(width, NS_UNCONSTRAINEDSIZE)); michael@0: nsHTMLReflowMetrics desiredSize(reflowState); michael@0: nsReflowStatus status; michael@0: michael@0: NS_ASSERTION(reflowState.ComputedPhysicalBorderPadding() == nsMargin(0, 0, 0, 0) && michael@0: reflowState.ComputedPhysicalMargin() == nsMargin(0, 0, 0, 0), michael@0: "style system should ensure that :-moz-svg-text " michael@0: "does not get styled"); michael@0: michael@0: kid->WillReflow(presContext); michael@0: kid->Reflow(presContext, desiredSize, reflowState, status); michael@0: kid->DidReflow(presContext, &reflowState, nsDidReflowStatus::FINISHED); michael@0: kid->SetSize(nsSize(desiredSize.Width(), desiredSize.Height())); michael@0: michael@0: mState &= ~NS_STATE_SVG_TEXT_IN_REFLOW; michael@0: michael@0: TextNodeCorrespondenceRecorder::RecordCorrespondence(this); michael@0: } michael@0: michael@0: // Usable font size range in devpixels / user-units michael@0: #define CLAMP_MIN_SIZE 8.0 michael@0: #define CLAMP_MAX_SIZE 200.0 michael@0: #define PRECISE_SIZE 200.0 michael@0: michael@0: bool michael@0: SVGTextFrame::UpdateFontSizeScaleFactor() michael@0: { michael@0: double oldFontSizeScaleFactor = mFontSizeScaleFactor; michael@0: michael@0: nsPresContext* presContext = PresContext(); michael@0: michael@0: bool geometricPrecision = false; michael@0: nscoord min = nscoord_MAX, michael@0: max = nscoord_MIN; michael@0: michael@0: // Find the minimum and maximum font sizes used over all the michael@0: // nsTextFrames. michael@0: TextFrameIterator it(this); michael@0: nsTextFrame* f = it.Current(); michael@0: while (f) { michael@0: if (!geometricPrecision) { michael@0: // Unfortunately we can't treat text-rendering:geometricPrecision michael@0: // separately for each text frame. michael@0: geometricPrecision = f->StyleSVG()->mTextRendering == michael@0: NS_STYLE_TEXT_RENDERING_GEOMETRICPRECISION; michael@0: } michael@0: nscoord size = f->StyleFont()->mFont.size; michael@0: if (size) { michael@0: min = std::min(min, size); michael@0: max = std::max(max, size); michael@0: } michael@0: f = it.Next(); michael@0: } michael@0: michael@0: if (min == nscoord_MAX) { michael@0: // No text, so no need for scaling. michael@0: mFontSizeScaleFactor = 1.0; michael@0: return mFontSizeScaleFactor != oldFontSizeScaleFactor; michael@0: } michael@0: michael@0: double minSize = presContext->AppUnitsToFloatCSSPixels(min); michael@0: michael@0: if (geometricPrecision) { michael@0: // We want to ensure minSize is scaled to PRECISE_SIZE. michael@0: mFontSizeScaleFactor = PRECISE_SIZE / minSize; michael@0: return mFontSizeScaleFactor != oldFontSizeScaleFactor; michael@0: } michael@0: michael@0: // When we are non-display, we could be painted in different coordinate michael@0: // spaces, and we don't want to have to reflow for each of these. We michael@0: // just assume that the context scale is 1.0 for them all, so we don't michael@0: // get stuck with a font size scale factor based on whichever referencing michael@0: // frame happens to reflow first. michael@0: double contextScale = 1.0; michael@0: if (!(mState & NS_FRAME_IS_NONDISPLAY)) { michael@0: gfxMatrix m(GetCanvasTM(FOR_OUTERSVG_TM)); michael@0: if (!m.IsSingular()) { michael@0: contextScale = GetContextScale(m); michael@0: } michael@0: } michael@0: mLastContextScale = contextScale; michael@0: michael@0: double maxSize = presContext->AppUnitsToFloatCSSPixels(max); michael@0: michael@0: // But we want to ignore any scaling required due to HiDPI displays, since michael@0: // regular CSS text frames will still create text runs using the font size michael@0: // in CSS pixels, and we want SVG text to have the same rendering as HTML michael@0: // text for regular font sizes. michael@0: float cssPxPerDevPx = michael@0: presContext->AppUnitsToFloatCSSPixels(presContext->AppUnitsPerDevPixel()); michael@0: contextScale *= cssPxPerDevPx; michael@0: michael@0: double minTextRunSize = minSize * contextScale; michael@0: double maxTextRunSize = maxSize * contextScale; michael@0: michael@0: if (minTextRunSize >= CLAMP_MIN_SIZE && michael@0: maxTextRunSize <= CLAMP_MAX_SIZE) { michael@0: // We are already in the ideal font size range for all text frames, michael@0: // so we only have to take into account the contextScale. michael@0: mFontSizeScaleFactor = contextScale; michael@0: } else if (maxSize / minSize > CLAMP_MAX_SIZE / CLAMP_MIN_SIZE) { michael@0: // We can't scale the font sizes so that all of the text frames lie michael@0: // within our ideal font size range, so we treat the minimum as more michael@0: // important and just scale so that minSize = CLAMP_MIN_SIZE. michael@0: mFontSizeScaleFactor = CLAMP_MIN_SIZE / minTextRunSize; michael@0: } else if (minTextRunSize < CLAMP_MIN_SIZE) { michael@0: mFontSizeScaleFactor = CLAMP_MIN_SIZE / minTextRunSize; michael@0: } else { michael@0: mFontSizeScaleFactor = CLAMP_MAX_SIZE / maxTextRunSize; michael@0: } michael@0: michael@0: return mFontSizeScaleFactor != oldFontSizeScaleFactor; michael@0: } michael@0: michael@0: double michael@0: SVGTextFrame::GetFontSizeScaleFactor() const michael@0: { michael@0: return mFontSizeScaleFactor; michael@0: } michael@0: michael@0: /** michael@0: * Take aPoint, which is in the element's user space, and convert michael@0: * it to the appropriate frame user space of aChildFrame according to michael@0: * which rendered run the point hits. michael@0: */ michael@0: gfxPoint michael@0: SVGTextFrame::TransformFramePointToTextChild(const gfxPoint& aPoint, michael@0: nsIFrame* aChildFrame) michael@0: { michael@0: NS_ASSERTION(aChildFrame && michael@0: nsLayoutUtils::GetClosestFrameOfType michael@0: (aChildFrame->GetParent(), nsGkAtoms::svgTextFrame) == this, michael@0: "aChildFrame must be a descendant of this frame"); michael@0: michael@0: UpdateGlyphPositioning(); michael@0: michael@0: nsPresContext* presContext = PresContext(); michael@0: michael@0: // Add in the mRect offset to aPoint, as that will have been taken into michael@0: // account when transforming the point from the ancestor frame down michael@0: // to this one. michael@0: float cssPxPerDevPx = presContext-> michael@0: AppUnitsToFloatCSSPixels(presContext->AppUnitsPerDevPixel()); michael@0: float factor = presContext->AppUnitsPerCSSPixel(); michael@0: gfxPoint framePosition(NSAppUnitsToFloatPixels(mRect.x, factor), michael@0: NSAppUnitsToFloatPixels(mRect.y, factor)); michael@0: gfxPoint pointInUserSpace = aPoint * cssPxPerDevPx + framePosition; michael@0: michael@0: // Find the closest rendered run for the text frames beneath aChildFrame. michael@0: TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames, michael@0: aChildFrame); michael@0: TextRenderedRun hit; michael@0: gfxPoint pointInRun; michael@0: nscoord dx = nscoord_MAX; michael@0: nscoord dy = nscoord_MAX; michael@0: for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) { michael@0: uint32_t flags = TextRenderedRun::eIncludeFill | michael@0: TextRenderedRun::eIncludeStroke | michael@0: TextRenderedRun::eNoHorizontalOverflow; michael@0: gfxRect runRect = run.GetRunUserSpaceRect(presContext, flags).ToThebesRect(); michael@0: michael@0: gfxPoint pointInRunUserSpace = michael@0: run.GetTransformFromRunUserSpaceToUserSpace(presContext).Invert(). michael@0: Transform(pointInUserSpace); michael@0: michael@0: if (Inside(runRect, pointInRunUserSpace)) { michael@0: // The point was inside the rendered run's rect, so we choose it. michael@0: dx = 0; michael@0: dy = 0; michael@0: pointInRun = pointInRunUserSpace; michael@0: hit = run; michael@0: } else if (nsLayoutUtils::PointIsCloserToRect(pointInRunUserSpace, michael@0: runRect, dx, dy)) { michael@0: // The point was closer to this rendered run's rect than any others michael@0: // we've seen so far. michael@0: pointInRun.x = clamped(pointInRunUserSpace.x, michael@0: runRect.X(), runRect.XMost()); michael@0: pointInRun.y = clamped(pointInRunUserSpace.y, michael@0: runRect.Y(), runRect.YMost()); michael@0: hit = run; michael@0: } michael@0: } michael@0: michael@0: if (!hit.mFrame) { michael@0: // We didn't find any rendered runs for the frame. michael@0: return aPoint; michael@0: } michael@0: michael@0: // Return the point in user units relative to the nsTextFrame, michael@0: // but taking into account mFontSizeScaleFactor. michael@0: gfxMatrix m = hit.GetTransformFromRunUserSpaceToFrameUserSpace(presContext); michael@0: m.Scale(mFontSizeScaleFactor, mFontSizeScaleFactor); michael@0: return m.Transform(pointInRun) / cssPxPerDevPx; michael@0: } michael@0: michael@0: /** michael@0: * For each rendered run for frames beneath aChildFrame, convert aRect michael@0: * into the run's frame user space and intersect it with the run's michael@0: * frame user space rectangle. For each of these intersections, michael@0: * then translate them up into aChildFrame's coordinate space michael@0: * and union them all together. michael@0: */ michael@0: gfxRect michael@0: SVGTextFrame::TransformFrameRectToTextChild(const gfxRect& aRect, michael@0: nsIFrame* aChildFrame) michael@0: { michael@0: NS_ASSERTION(aChildFrame && michael@0: nsLayoutUtils::GetClosestFrameOfType michael@0: (aChildFrame->GetParent(), nsGkAtoms::svgTextFrame) == this, michael@0: "aChildFrame must be a descendant of this frame"); michael@0: michael@0: UpdateGlyphPositioning(); michael@0: michael@0: nsPresContext* presContext = PresContext(); michael@0: michael@0: // Add in the mRect offset to aRect, as that will have been taken into michael@0: // account when transforming the rect from the ancestor frame down michael@0: // to this one. michael@0: float cssPxPerDevPx = presContext-> michael@0: AppUnitsToFloatCSSPixels(presContext->AppUnitsPerDevPixel()); michael@0: float factor = presContext->AppUnitsPerCSSPixel(); michael@0: gfxPoint framePosition(NSAppUnitsToFloatPixels(mRect.x, factor), michael@0: NSAppUnitsToFloatPixels(mRect.y, factor)); michael@0: gfxRect incomingRectInUserSpace(aRect.x * cssPxPerDevPx + framePosition.x, michael@0: aRect.y * cssPxPerDevPx + framePosition.y, michael@0: aRect.width * cssPxPerDevPx, michael@0: aRect.height * cssPxPerDevPx); michael@0: michael@0: // Find each rendered run for text frames beneath aChildFrame. michael@0: gfxRect result; michael@0: TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames, michael@0: aChildFrame); michael@0: for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) { michael@0: // Convert the incoming rect into frame user space. michael@0: gfxMatrix m; michael@0: m.PreMultiply(run.GetTransformFromRunUserSpaceToUserSpace(presContext).Invert()); michael@0: m.PreMultiply(run.GetTransformFromRunUserSpaceToFrameUserSpace(presContext)); michael@0: gfxRect incomingRectInFrameUserSpace = michael@0: m.TransformBounds(incomingRectInUserSpace); michael@0: michael@0: // Intersect it with this run's rectangle. michael@0: uint32_t flags = TextRenderedRun::eIncludeFill | michael@0: TextRenderedRun::eIncludeStroke; michael@0: SVGBBox runRectInFrameUserSpace = run.GetFrameUserSpaceRect(presContext, flags); michael@0: if (runRectInFrameUserSpace.IsEmpty()) { michael@0: continue; michael@0: } michael@0: gfxRect runIntersectionInFrameUserSpace = michael@0: incomingRectInFrameUserSpace.Intersect(runRectInFrameUserSpace.ToThebesRect()); michael@0: michael@0: if (!runIntersectionInFrameUserSpace.IsEmpty()) { michael@0: // Take the font size scale into account. michael@0: runIntersectionInFrameUserSpace.x *= mFontSizeScaleFactor; michael@0: runIntersectionInFrameUserSpace.y *= mFontSizeScaleFactor; michael@0: runIntersectionInFrameUserSpace.width *= mFontSizeScaleFactor; michael@0: runIntersectionInFrameUserSpace.height *= mFontSizeScaleFactor; michael@0: michael@0: // Convert it into the coordinate space of aChildFrame. michael@0: nsPoint offset = run.mFrame->GetOffsetTo(aChildFrame); michael@0: gfxRect runIntersection = michael@0: runIntersectionInFrameUserSpace + michael@0: gfxPoint(NSAppUnitsToFloatPixels(offset.x, factor), michael@0: NSAppUnitsToFloatPixels(offset.y, factor)); michael@0: michael@0: // Union it into the result. michael@0: result.UnionRect(result, runIntersection); michael@0: } michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: /** michael@0: * For each rendered run beneath aChildFrame, translate aRect from michael@0: * aChildFrame to the run's text frame, transform it then into michael@0: * the run's frame user space, intersect it with the run's michael@0: * frame user space rect, then transform it up to user space. michael@0: * The result is the union of all of these. michael@0: */ michael@0: gfxRect michael@0: SVGTextFrame::TransformFrameRectFromTextChild(const nsRect& aRect, michael@0: nsIFrame* aChildFrame) michael@0: { michael@0: NS_ASSERTION(aChildFrame && michael@0: nsLayoutUtils::GetClosestFrameOfType michael@0: (aChildFrame->GetParent(), nsGkAtoms::svgTextFrame) == this, michael@0: "aChildFrame must be a descendant of this frame"); michael@0: michael@0: UpdateGlyphPositioning(); michael@0: michael@0: nsPresContext* presContext = PresContext(); michael@0: michael@0: gfxRect result; michael@0: TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames, michael@0: aChildFrame); michael@0: for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) { michael@0: // First, translate aRect from aChildFrame to this run's frame. michael@0: nsRect rectInTextFrame = aRect + aChildFrame->GetOffsetTo(run.mFrame); michael@0: michael@0: // Scale it into frame user space. michael@0: gfxRect rectInFrameUserSpace = michael@0: AppUnitsToFloatCSSPixels(gfxRect(rectInTextFrame.x, michael@0: rectInTextFrame.y, michael@0: rectInTextFrame.width, michael@0: rectInTextFrame.height), presContext); michael@0: michael@0: // Intersect it with the run. michael@0: uint32_t flags = TextRenderedRun::eIncludeFill | michael@0: TextRenderedRun::eIncludeStroke; michael@0: rectInFrameUserSpace.IntersectRect michael@0: (rectInFrameUserSpace, run.GetFrameUserSpaceRect(presContext, flags).ToThebesRect()); michael@0: michael@0: if (!rectInFrameUserSpace.IsEmpty()) { michael@0: // Transform it up to user space of the , also taking into michael@0: // account the font size scale. michael@0: gfxMatrix m = run.GetTransformFromRunUserSpaceToUserSpace(presContext); michael@0: m.Scale(mFontSizeScaleFactor, mFontSizeScaleFactor); michael@0: gfxRect rectInUserSpace = m.Transform(rectInFrameUserSpace); michael@0: michael@0: // Union it into the result. michael@0: result.UnionRect(result, rectInUserSpace); michael@0: } michael@0: } michael@0: michael@0: // Subtract the mRect offset from the result, as our user space for michael@0: // this frame is relative to the top-left of mRect. michael@0: float factor = presContext->AppUnitsPerCSSPixel(); michael@0: gfxPoint framePosition(NSAppUnitsToFloatPixels(mRect.x, factor), michael@0: NSAppUnitsToFloatPixels(mRect.y, factor)); michael@0: michael@0: return result - framePosition; michael@0: } michael@0: michael@0: DrawMode michael@0: SVGTextFrame::SetupCairoState(gfxContext* aContext, michael@0: nsIFrame* aFrame, michael@0: gfxTextContextPaint* aOuterContextPaint, michael@0: gfxTextContextPaint** aThisContextPaint) michael@0: { michael@0: DrawMode toDraw = DrawMode(0); michael@0: SVGTextContextPaint *thisContextPaint = new SVGTextContextPaint(); michael@0: michael@0: if (SetupCairoStroke(aContext, aFrame, aOuterContextPaint, thisContextPaint)) { michael@0: toDraw = DrawMode(int(toDraw) | int(DrawMode::GLYPH_STROKE)); michael@0: } michael@0: michael@0: if (SetupCairoFill(aContext, aFrame, aOuterContextPaint, thisContextPaint)) { michael@0: toDraw = DrawMode(int(toDraw) | int(DrawMode::GLYPH_FILL)); michael@0: } michael@0: michael@0: *aThisContextPaint = thisContextPaint; michael@0: michael@0: return toDraw; michael@0: } michael@0: michael@0: bool michael@0: SVGTextFrame::SetupCairoStroke(gfxContext* aContext, michael@0: nsIFrame* aFrame, michael@0: gfxTextContextPaint* aOuterContextPaint, michael@0: SVGTextContextPaint* aThisContextPaint) michael@0: { michael@0: const nsStyleSVG *style = aFrame->StyleSVG(); michael@0: if (style->mStroke.mType == eStyleSVGPaintType_None) { michael@0: aThisContextPaint->SetStrokeOpacity(0.0f); michael@0: return false; michael@0: } michael@0: michael@0: nsSVGUtils::SetupCairoStrokeGeometry(aFrame, aContext, aOuterContextPaint); michael@0: float opacity = nsSVGUtils::GetOpacity(style->mStrokeOpacitySource, michael@0: style->mStrokeOpacity, michael@0: aOuterContextPaint); michael@0: michael@0: SetupInheritablePaint(aContext, aFrame, opacity, aOuterContextPaint, michael@0: aThisContextPaint->mStrokePaint, &nsStyleSVG::mStroke, michael@0: nsSVGEffects::StrokeProperty()); michael@0: michael@0: aThisContextPaint->SetStrokeOpacity(opacity); michael@0: michael@0: return opacity != 0.0f; michael@0: } michael@0: michael@0: bool michael@0: SVGTextFrame::SetupCairoFill(gfxContext* aContext, michael@0: nsIFrame* aFrame, michael@0: gfxTextContextPaint* aOuterContextPaint, michael@0: SVGTextContextPaint* aThisContextPaint) michael@0: { michael@0: const nsStyleSVG *style = aFrame->StyleSVG(); michael@0: if (style->mFill.mType == eStyleSVGPaintType_None) { michael@0: aThisContextPaint->SetFillOpacity(0.0f); michael@0: return false; michael@0: } michael@0: michael@0: float opacity = nsSVGUtils::GetOpacity(style->mFillOpacitySource, michael@0: style->mFillOpacity, michael@0: aOuterContextPaint); michael@0: michael@0: SetupInheritablePaint(aContext, aFrame, opacity, aOuterContextPaint, michael@0: aThisContextPaint->mFillPaint, &nsStyleSVG::mFill, michael@0: nsSVGEffects::FillProperty()); michael@0: michael@0: aThisContextPaint->SetFillOpacity(opacity); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: SVGTextFrame::SetupInheritablePaint(gfxContext* aContext, michael@0: nsIFrame* aFrame, michael@0: float& aOpacity, michael@0: gfxTextContextPaint* aOuterContextPaint, michael@0: SVGTextContextPaint::Paint& aTargetPaint, michael@0: nsStyleSVGPaint nsStyleSVG::*aFillOrStroke, michael@0: const FramePropertyDescriptor* aProperty) michael@0: { michael@0: const nsStyleSVG *style = aFrame->StyleSVG(); michael@0: nsSVGPaintServerFrame *ps = michael@0: nsSVGEffects::GetPaintServer(aFrame, &(style->*aFillOrStroke), aProperty); michael@0: michael@0: if (ps && ps->SetupPaintServer(aContext, aFrame, aFillOrStroke, aOpacity)) { michael@0: aTargetPaint.SetPaintServer(aFrame, aContext->CurrentMatrix(), ps); michael@0: } else if (nsSVGUtils::SetupContextPaint(aContext, aOuterContextPaint, michael@0: style->*aFillOrStroke, michael@0: aOpacity)) { michael@0: aTargetPaint.SetContextPaint(aOuterContextPaint, (style->*aFillOrStroke).mType); michael@0: } else { michael@0: nscolor color = nsSVGUtils::GetFallbackOrPaintColor(aContext, michael@0: aFrame->StyleContext(), michael@0: aFillOrStroke); michael@0: aTargetPaint.SetColor(color); michael@0: michael@0: nsRefPtr pattern = michael@0: new gfxPattern(gfxRGBA(NS_GET_R(color) / 255.0, michael@0: NS_GET_G(color) / 255.0, michael@0: NS_GET_B(color) / 255.0, michael@0: NS_GET_A(color) / 255.0 * aOpacity)); michael@0: aContext->SetPattern(pattern); michael@0: } michael@0: }