From 56be0350f9e814bf51d92276ddba11b600e58a88 Mon Sep 17 00:00:00 2001 From: Cameron McCormack Date: Mon, 11 Feb 2013 17:22:18 +1100 Subject: [PATCH] Bug 655877 - Part 41b: Make SVG text selectable with the mouse. r=roc,jwatt --- layout/base/nsLayoutUtils.cpp | 64 ++++-- layout/base/nsLayoutUtils.h | 15 +- layout/generic/nsFrame.cpp | 49 +++-- layout/generic/nsIFrame.h | 30 +++ layout/generic/nsTextFrame.h | 2 +- layout/generic/nsTextFrameThebes.cpp | 2 +- layout/svg/nsSVGTextFrame2.cpp | 304 ++++++++++++++++++++++++++- layout/svg/nsSVGTextFrame2.h | 34 +++ 8 files changed, 463 insertions(+), 37 deletions(-) diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index 19023343f1a6..de3e5d32f1ad 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -1312,15 +1312,14 @@ nsLayoutUtils::GetEventCoordinatesRelativeTo(nsIWidget* aWidget, /* If we encountered a transform, we can't do simple arithmetic to figure * out how to convert back to aFrame's coordinates and must use the CTM. */ - if (transformFound) { + if (transformFound || aFrame->IsSVGText()) { return TransformRootPointToFrame(aFrame, widgetToView); } /* Otherwise, all coordinate systems are translations of one another, - * so we can just subtract out the different. + * so we can just subtract out the difference. */ - nsPoint offset = aFrame->GetOffsetToCrossDoc(rootFrame); - return widgetToView - offset; + return widgetToView - aFrame->GetOffsetToCrossDoc(rootFrame); } nsIFrame* @@ -1538,15 +1537,35 @@ TransformGfxRectToAncestor(nsIFrame *aFrame, return ctm.TransformBounds(aRect); } -nsPoint -nsLayoutUtils::TransformRootPointToFrame(nsIFrame *aFrame, - const nsPoint &aPoint) +static nsSVGTextFrame2* +GetContainingSVGTextFrame(nsIFrame* aFrame) { + if (!aFrame->IsSVGText()) { + return nullptr; + } + + return static_cast + (nsLayoutUtils::GetClosestFrameOfType(aFrame->GetParent(), + nsGkAtoms::svgTextFrame2)); +} + +nsPoint +nsLayoutUtils::TransformAncestorPointToFrame(nsIFrame* aFrame, + const nsPoint& aPoint, + nsIFrame* aAncestor) +{ + nsSVGTextFrame2* text = GetContainingSVGTextFrame(aFrame); + float factor = aFrame->PresContext()->AppUnitsPerDevPixel(); gfxPoint result(NSAppUnitsToFloatPixels(aPoint.x, factor), NSAppUnitsToFloatPixels(aPoint.y, factor)); - result = TransformGfxPointFromAncestor(aFrame, result, nullptr); + if (text) { + result = TransformGfxPointFromAncestor(text, result, aAncestor); + result = text->TransformFramePointToTextChild(result, aFrame); + } else { + result = TransformGfxPointFromAncestor(aFrame, result, nullptr); + } return nsPoint(NSFloatPixelsToAppUnits(float(result.x), factor), NSFloatPixelsToAppUnits(float(result.y), factor)); @@ -1557,13 +1576,20 @@ nsLayoutUtils::TransformAncestorRectToFrame(nsIFrame* aFrame, const nsRect &aRect, const nsIFrame* aAncestor) { + nsSVGTextFrame2* text = GetContainingSVGTextFrame(aFrame); + float srcAppUnitsPerDevPixel = aAncestor->PresContext()->AppUnitsPerDevPixel(); gfxRect result(NSAppUnitsToFloatPixels(aRect.x, srcAppUnitsPerDevPixel), NSAppUnitsToFloatPixels(aRect.y, srcAppUnitsPerDevPixel), NSAppUnitsToFloatPixels(aRect.width, srcAppUnitsPerDevPixel), NSAppUnitsToFloatPixels(aRect.height, srcAppUnitsPerDevPixel)); - result = TransformGfxRectFromAncestor(aFrame, result, aAncestor); + if (text) { + result = TransformGfxRectFromAncestor(text, result, aAncestor); + result = text->TransformFrameRectToTextChild(result, aFrame); + } else { + result = TransformGfxRectFromAncestor(aFrame, result, aAncestor); + } float destAppUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel(); return nsRect(NSFloatPixelsToAppUnits(float(result.x), destAppUnitsPerDevPixel), @@ -1577,13 +1603,21 @@ nsLayoutUtils::TransformFrameRectToAncestor(nsIFrame* aFrame, const nsRect& aRect, const nsIFrame* aAncestor) { - float srcAppUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel(); - gfxRect result(NSAppUnitsToFloatPixels(aRect.x, srcAppUnitsPerDevPixel), - NSAppUnitsToFloatPixels(aRect.y, srcAppUnitsPerDevPixel), - NSAppUnitsToFloatPixels(aRect.width, srcAppUnitsPerDevPixel), - NSAppUnitsToFloatPixels(aRect.height, srcAppUnitsPerDevPixel)); + nsSVGTextFrame2* text = GetContainingSVGTextFrame(aFrame); - result = TransformGfxRectToAncestor(aFrame, result, aAncestor); + float srcAppUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel(); + gfxRect result; + + if (text) { + result = text->TransformFrameRectFromTextChild(aRect, aFrame); + result = TransformGfxRectToAncestor(text, result, aAncestor); + } else { + result = gfxRect(NSAppUnitsToFloatPixels(aRect.x, srcAppUnitsPerDevPixel), + NSAppUnitsToFloatPixels(aRect.y, srcAppUnitsPerDevPixel), + NSAppUnitsToFloatPixels(aRect.width, srcAppUnitsPerDevPixel), + NSAppUnitsToFloatPixels(aRect.height, srcAppUnitsPerDevPixel)); + result = TransformGfxRectToAncestor(aFrame, result, aAncestor); + } float destAppUnitsPerDevPixel = aAncestor->PresContext()->AppUnitsPerDevPixel(); return nsRect(NSFloatPixelsToAppUnits(float(result.x), destAppUnitsPerDevPixel), diff --git a/layout/base/nsLayoutUtils.h b/layout/base/nsLayoutUtils.h index a6c1f8332c52..47289c8a5d79 100644 --- a/layout/base/nsLayoutUtils.h +++ b/layout/base/nsLayoutUtils.h @@ -473,7 +473,7 @@ public: static nsIFrame* GetPopupFrameForEventCoordinates(nsPresContext* aPresContext, const nsEvent* aEvent); -/** + /** * Translate from widget coordinates to the view's coordinates * @param aPresContext the PresContext for the view * @param aWidget the widget @@ -588,7 +588,18 @@ public: * @return aPoint, expressed in aFrame's canonical coordinate space. */ static nsPoint TransformRootPointToFrame(nsIFrame* aFrame, - const nsPoint &aPt); + const nsPoint &aPoint) + { + return TransformAncestorPointToFrame(aFrame, aPoint, nullptr); + } + + /** + * Transform aPoint relative to aAncestor down to the coordinate system of + * aFrame. + */ + static nsPoint TransformAncestorPointToFrame(nsIFrame* aFrame, + const nsPoint& aPoint, + nsIFrame* aAncestor); /** * Helper function that, given a rectangle and a matrix, returns the smallest diff --git a/layout/generic/nsFrame.cpp b/layout/generic/nsFrame.cpp index 01a934faa53d..b581679332d5 100644 --- a/layout/generic/nsFrame.cpp +++ b/layout/generic/nsFrame.cpp @@ -335,6 +335,18 @@ nsIFrame::IsVisibleConsideringAncestors(uint32_t aFlags) const return true; } +void +nsIFrame::FindCloserFrameForSelection( + nsPoint aPoint, + nsIFrame::FrameWithDistance* aCurrentBestFrame) +{ + if (nsLayoutUtils::PointIsCloserToRect(aPoint, mRect, + aCurrentBestFrame->mXDistance, + aCurrentBestFrame->mYDistance)) { + aCurrentBestFrame->mFrame = this; + } +} + static bool ApplyOverflowClipping(nsDisplayListBuilder* aBuilder, const nsIFrame* aFrame, const nsStyleDisplay* aDisp, @@ -3522,24 +3534,18 @@ static FrameTarget GetSelectionClosestFrame(nsIFrame* aFrame, nsPoint aPoint, if (kid) { // Go through all the child frames to find the closest one - - // Large number to force the comparison to succeed - const nscoord HUGE_DISTANCE = nscoord_MAX; - nscoord closestXDistance = HUGE_DISTANCE; - nscoord closestYDistance = HUGE_DISTANCE; - nsIFrame *closestFrame = nullptr; - + nsIFrame::FrameWithDistance closest = { nullptr, nscoord_MAX, nscoord_MAX }; for (; kid; kid = kid->GetNextSibling()) { if (!SelfIsSelectable(kid, aFlags) || kid->IsEmpty()) continue; - if (nsLayoutUtils::PointIsCloserToRect(aPoint, kid->GetRect(), - closestXDistance, - closestYDistance)) - closestFrame = kid; + kid->FindCloserFrameForSelection(aPoint, &closest); + } + if (closest.mFrame) { + if (closest.mFrame->IsSVGText()) + return FrameTarget(closest.mFrame, false, false); + return GetSelectionClosestFrameForChild(closest.mFrame, aPoint, aFlags); } - if (closestFrame) - return GetSelectionClosestFrameForChild(closestFrame, aPoint, aFlags); } return FrameTarget(aFrame, false, false); } @@ -3616,8 +3622,8 @@ nsIFrame::ContentOffsets nsIFrame::GetContentOffsetsFromPoint(nsPoint aPoint, // should lead to the whole frame being selected if (adjustedFrame && adjustedFrame->GetStyleUIReset()->mUserSelect == NS_STYLE_USER_SELECT_ALL) { - return OffsetsForSingleFrame(adjustedFrame, aPoint + - this->GetOffsetTo(adjustedFrame)); + nsPoint adjustedPoint = aPoint + this->GetOffsetTo(adjustedFrame); + return OffsetsForSingleFrame(adjustedFrame, adjustedPoint); } // For other cases, try to find a closest frame starting from the parent of @@ -3656,7 +3662,18 @@ nsIFrame::ContentOffsets nsIFrame::GetContentOffsetsFromPoint(nsPoint aPoint, offsets.associateWithNext = (offsets.offset == range.start); return offsets; } - nsPoint pt = aPoint - closest.frame->GetOffsetTo(this); + + nsPoint pt; + if (closest.frame != this) { + if (closest.frame->IsSVGText()) { + pt = nsLayoutUtils::TransformAncestorPointToFrame(closest.frame, + aPoint, this); + } else { + pt = aPoint - closest.frame->GetOffsetTo(this); + } + } else { + pt = aPoint; + } return static_cast(closest.frame)->CalcContentOffsetsFromFramePoint(pt); // XXX should I add some kind of offset standardization? diff --git a/layout/generic/nsIFrame.h b/layout/generic/nsIFrame.h index 2f950500b2ec..e9f42d904737 100644 --- a/layout/generic/nsIFrame.h +++ b/layout/generic/nsIFrame.h @@ -2900,6 +2900,36 @@ NS_PTR_TO_INT32(frame->Properties().Get(nsIFrame::ParagraphDepthProperty())) }; bool IsVisibleConsideringAncestors(uint32_t aFlags = 0) const; + struct FrameWithDistance + { + nsIFrame* mFrame; + nscoord mXDistance; + nscoord mYDistance; + }; + + /** + * Finds a frame that is closer to a specified point than a current + * distance. Distance is measured as for text selection -- a closer x + * distance beats a closer y distance. + * + * Normally, this function will only check the distance between this + * frame's rectangle and the specified point. nsSVGTextFrame2 overrides + * this so that it can manage all of its descendant frames and take + * into account any SVG text layout. + * + * If aPoint is closer to this frame's rectangle than aCurrentBestFrame + * indicates, then aCurrentBestFrame is updated with the distance between + * aPoint and this frame's rectangle, and with a pointer to this frame. + * If aPoint is not closer, then aCurrentBestFrame is left unchanged. + * + * @param aPoint The point to check for its distance to this frame. + * @param aCurrentBestFrame Pointer to a struct that will be updated with + * a pointer to this frame and its distance to aPoint, if this frame + * is indeed closer than the current distance in aCurrentBestFrame. + */ + virtual void FindCloserFrameForSelection(nsPoint aPoint, + FrameWithDistance* aCurrentBestFrame); + inline bool IsBlockInside() const; inline bool IsBlockOutside() const; inline bool IsInlineOutside() const; diff --git a/layout/generic/nsTextFrame.h b/layout/generic/nsTextFrame.h index 617c5cf381b8..8c26bb68c4cf 100644 --- a/layout/generic/nsTextFrame.h +++ b/layout/generic/nsTextFrame.h @@ -674,7 +674,7 @@ protected: bool CombineSelectionUnderlineRect(nsPresContext* aPresContext, nsRect& aRect); - ContentOffsets GetCharacterOffsetAtFramePointInternal(const nsPoint &aPoint, + ContentOffsets GetCharacterOffsetAtFramePointInternal(nsPoint aPoint, bool aForInsertionPoint); void ClearFrameOffsetCache(); diff --git a/layout/generic/nsTextFrameThebes.cpp b/layout/generic/nsTextFrameThebes.cpp index 9d56c04587b5..8e409b2810a7 100644 --- a/layout/generic/nsTextFrameThebes.cpp +++ b/layout/generic/nsTextFrameThebes.cpp @@ -6207,7 +6207,7 @@ nsTextFrame::GetCharacterOffsetAtFramePoint(const nsPoint &aPoint) } nsIFrame::ContentOffsets -nsTextFrame::GetCharacterOffsetAtFramePointInternal(const nsPoint &aPoint, +nsTextFrame::GetCharacterOffsetAtFramePointInternal(nsPoint aPoint, bool aForInsertionPoint) { ContentOffsets offsets; diff --git a/layout/svg/nsSVGTextFrame2.cpp b/layout/svg/nsSVGTextFrame2.cpp index 0bf1cb267696..2074be161849 100644 --- a/layout/svg/nsSVGTextFrame2.cpp +++ b/layout/svg/nsSVGTextFrame2.cpp @@ -12,6 +12,7 @@ #include "gfxSkipChars.h" #include "gfxTypes.h" #include "LookAndFeel.h" +#include "nsAlgorithm.h" #include "nsBlockFrame.h" #include "nsCaret.h" #include "nsContentUtils.h" @@ -124,6 +125,18 @@ ScaleAround(gfxRect& aRect, const gfxPoint& aPoint, double aScale) aRect.height *= aScale; } +/** + * Returns whether a gfxPoint lies within a gfxRect. + */ +static bool +Inside(const gfxRect& aRect, const gfxPoint& aPoint) +{ + return aPoint.x >= aRect.x && + aPoint.x < aRect.XMost() && + aPoint.y >= aRect.y && + aPoint.y < aRect.YMost(); +} + /** * Gets the measured ascent and descent of the text in the given nsTextFrame * in app units. @@ -2731,8 +2744,10 @@ nsDisplaySVGText::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, // ToReferenceFrame() includes frame->GetPosition(), our user space position. nsPoint userSpacePt = pointRelativeToReferenceFrame - (ToReferenceFrame() - frame->GetPosition()); - if (frame->GetFrameForPoint(userSpacePt)) { - aOutFrames->AppendElement(frame); + + nsIFrame* target = frame->GetFrameForPoint(userSpacePt); + if (target) { + aOutFrames->AppendElement(target); } } @@ -2938,6 +2953,35 @@ nsSVGTextFrame2::InvalidateInternal(const nsRect& aDamageRect, (this, nsSVGUtils::OuterSVGIsCallingReflowSVG(this), nullptr, aFlags); } +void +nsSVGTextFrame2::FindCloserFrameForSelection( + nsPoint aPoint, + nsIFrame::FrameWithDistance* aCurrentBestFrame) +{ + UpdateGlyphPositioning(true); + + nsPresContext* presContext = PresContext(); + + // Find the frame that has the closest rendered run rect to aPoint. + TextRenderedRunIterator it(this); + for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) { + uint32_t flags = TextRenderedRun::eIncludeFill | + TextRenderedRun::eIncludeStroke | + TextRenderedRun::eNoHorizontalOverflow; + gfxRect userRect = run.GetUserSpaceRect(presContext, flags); + + nsRect rect = nsSVGUtils::ToCanvasBounds(userRect, + GetCanvasTM(FOR_HIT_TESTING), + presContext); + + if (nsLayoutUtils::PointIsCloserToRect(aPoint, rect, + aCurrentBestFrame->mXDistance, + aCurrentBestFrame->mYDistance)) { + aCurrentBestFrame->mFrame = run.mFrame; + } + } +} + //---------------------------------------------------------------------- // nsISVGChildFrame methods @@ -3138,6 +3182,52 @@ nsSVGTextFrame2::PaintSVG(nsRenderingContext* aContext, return NS_OK; } +NS_IMETHODIMP_(nsIFrame*) +nsSVGTextFrame2::GetFrameForPoint(const nsPoint& aPoint) +{ + NS_ASSERTION(GetFirstPrincipalChild(), "must have a child frame"); + + AutoCanvasTMForMarker autoCanvasTMFor(this, FOR_HIT_TESTING); + + if (mState & NS_STATE_SVG_NONDISPLAY_CHILD) { + // Text frames inside will never have had ReflowSVG called on + // them, so call UpdateGlyphPositioning to do this now. (Text frames + // inside and other non-display containers will never need to + // be hit tested.) + UpdateGlyphPositioning(true); + } else { + NS_ASSERTION(!NS_SUBTREE_DIRTY(this), "reflow should have happened"); + } + + nsPresContext* presContext = PresContext(); + + gfxPoint pointInOuterSVGUserUnits = AppUnitsToGfxUnits(aPoint, presContext); + + TextRenderedRunIterator it(this); + nsIFrame* hit = nullptr; + for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) { + uint16_t hitTestFlags = nsSVGUtils::GetGeometryHitTestFlags(run.mFrame); + if (!(hitTestFlags & (SVG_HIT_TEST_FILL | SVG_HIT_TEST_STROKE))) { + continue; + } + + gfxMatrix m = GetCanvasTM(FOR_HIT_TESTING); + m.PreMultiply(run.GetTransformFromRunUserSpaceToUserSpace(presContext)); + m.Invert(); + + gfxPoint pointInRunUserSpace = m.Transform(pointInOuterSVGUserUnits); + gfxRect frameRect = + run.GetRunUserSpaceRect(presContext, TextRenderedRun::eIncludeFill | + TextRenderedRun::eIncludeStroke); + + if (Inside(frameRect, pointInRunUserSpace) && + nsSVGUtils::HitTestClip(this, aPoint)) { + hit = run.mFrame; + } + } + return hit; +} + NS_IMETHODIMP_(nsRect) nsSVGTextFrame2::GetCoveredRegion() { @@ -4589,3 +4679,213 @@ nsSVGTextFrame2::GetFontSizeScaleFactor() const { return mFontSizeScaleFactor; } + +/** + * Take aPoint, which is in the element's user space, and convert + * it to the appropriate frame user space of aChildFrame according to + * which rendered run the point hits. + */ +gfxPoint +nsSVGTextFrame2::TransformFramePointToTextChild(const gfxPoint& aPoint, + nsIFrame* aChildFrame) +{ + NS_ASSERTION(aChildFrame && + nsLayoutUtils::GetClosestFrameOfType + (aChildFrame->GetParent(), nsGkAtoms::svgTextFrame2) == this, + "aChildFrame must be a descendant of this frame"); + + UpdateGlyphPositioning(true); + + nsPresContext* presContext = PresContext(); + + // Add in the mRect offset to aPoint, as that will have been taken into + // account when transforming the point from the ancestor frame down + // to this one. + float cssPxPerDevPx = presContext-> + AppUnitsToFloatCSSPixels(presContext->AppUnitsPerDevPixel()); + float factor = presContext->AppUnitsPerCSSPixel(); + gfxPoint framePosition(NSAppUnitsToFloatPixels(mRect.x, factor), + NSAppUnitsToFloatPixels(mRect.y, factor)); + gfxPoint pointInUserSpace = aPoint * cssPxPerDevPx + framePosition; + + // Find the closest rendered run for the text frames beneath aChildFrame. + TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames, + aChildFrame); + TextRenderedRun hit; + gfxPoint pointInRun; + nscoord dx = nscoord_MAX; + nscoord dy = nscoord_MAX; + for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) { + uint32_t flags = TextRenderedRun::eIncludeFill | + TextRenderedRun::eIncludeStroke | + TextRenderedRun::eNoHorizontalOverflow; + gfxRect runRect = run.GetRunUserSpaceRect(presContext, flags); + + gfxPoint pointInRunUserSpace = + run.GetTransformFromRunUserSpaceToUserSpace(presContext).Invert(). + Transform(pointInUserSpace); + + if (Inside(runRect, pointInRunUserSpace)) { + // The point was inside the rendered run's rect, so we choose it. + dx = 0; + dy = 0; + pointInRun = pointInRunUserSpace; + hit = run; + } else if (nsLayoutUtils::PointIsCloserToRect(pointInRunUserSpace, + runRect, dx, dy)) { + // The point was closer to this rendered run's rect than any others + // we've seen so far. + pointInRun.x = clamped(pointInRunUserSpace.x, + runRect.X(), runRect.XMost()); + pointInRun.y = clamped(pointInRunUserSpace.y, + runRect.Y(), runRect.YMost()); + hit = run; + } + } + + if (!hit.mFrame) { + // We didn't find any rendered runs for the frame. + return aPoint; + } + + // Return the point in user units relative to the nsTextFrame, + // but taking into account mFontSizeScaleFactor. + gfxMatrix m = hit.GetTransformFromRunUserSpaceToFrameUserSpace(presContext); + m.Scale(mFontSizeScaleFactor, mFontSizeScaleFactor); + return m.Transform(pointInRun) / cssPxPerDevPx; +} + +/** + * For each rendered run for frames beneath aChildFrame, convert aRect + * into the run's frame user space and intersect it with the run's + * frame user space rectangle. For each of these intersections, + * then translate them up into aChildFrame's coordinate space + * and union them all together. + */ +gfxRect +nsSVGTextFrame2::TransformFrameRectToTextChild(const gfxRect& aRect, + nsIFrame* aChildFrame) +{ + NS_ASSERTION(aChildFrame && + nsLayoutUtils::GetClosestFrameOfType + (aChildFrame->GetParent(), nsGkAtoms::svgTextFrame2) == this, + "aChildFrame must be a descendant of this frame"); + + UpdateGlyphPositioning(true); + + nsPresContext* presContext = PresContext(); + + // Add in the mRect offset to aRect, as that will have been taken into + // account when transforming the rect from the ancestor frame down + // to this one. + float cssPxPerDevPx = presContext-> + AppUnitsToFloatCSSPixels(presContext->AppUnitsPerDevPixel()); + float factor = presContext->AppUnitsPerCSSPixel(); + gfxPoint framePosition(NSAppUnitsToFloatPixels(mRect.x, factor), + NSAppUnitsToFloatPixels(mRect.y, factor)); + gfxRect incomingRectInUserSpace(aRect.x * cssPxPerDevPx + framePosition.x, + aRect.y * cssPxPerDevPx + framePosition.y, + aRect.width * cssPxPerDevPx, + aRect.height * cssPxPerDevPx); + + // Find each rendered run for text frames beneath aChildFrame. + gfxRect result; + TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames, + aChildFrame); + for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) { + // Convert the incoming rect into frame user space. + gfxMatrix m; + m.PreMultiply(run.GetTransformFromRunUserSpaceToUserSpace(presContext).Invert()); + m.PreMultiply(run.GetTransformFromRunUserSpaceToFrameUserSpace(presContext)); + gfxRect incomingRectInFrameUserSpace = + m.TransformBounds(incomingRectInUserSpace); + + // Intersect it with this run's rectangle. + uint32_t flags = TextRenderedRun::eIncludeFill | + TextRenderedRun::eIncludeStroke; + gfxRect runRectInFrameUserSpace = run.GetFrameUserSpaceRect(presContext, flags); + gfxRect runIntersectionInFrameUserSpace = + incomingRectInFrameUserSpace.Intersect(runRectInFrameUserSpace); + + if (!runIntersectionInFrameUserSpace.IsEmpty()) { + // Take the font size scale into account. + runIntersectionInFrameUserSpace.x *= mFontSizeScaleFactor; + runIntersectionInFrameUserSpace.y *= mFontSizeScaleFactor; + runIntersectionInFrameUserSpace.width *= mFontSizeScaleFactor; + runIntersectionInFrameUserSpace.height *= mFontSizeScaleFactor; + + // Convert it into the coordinate space of aChildFrame. + nsPoint offset = run.mFrame->GetOffsetTo(aChildFrame); + gfxRect runIntersection = + runIntersectionInFrameUserSpace + + gfxPoint(NSAppUnitsToFloatPixels(offset.x, factor), + NSAppUnitsToFloatPixels(offset.y, factor)); + + // Union it into the result. + result.UnionRect(result, runIntersection); + } + } + + return result; +} + +/** + * For each rendered run beneath aChildFrame, translate aRect from + * aChildFrame to the run's text frame, transform it then into + * the run's frame user space, intersect it with the run's + * frame user space rect, then transform it up to user space. + * The result is the union of all of these. + */ +gfxRect +nsSVGTextFrame2::TransformFrameRectFromTextChild(const nsRect& aRect, + nsIFrame* aChildFrame) +{ + NS_ASSERTION(aChildFrame && + nsLayoutUtils::GetClosestFrameOfType + (aChildFrame->GetParent(), nsGkAtoms::svgTextFrame2) == this, + "aChildFrame must be a descendant of this frame"); + + UpdateGlyphPositioning(true); + + nsPresContext* presContext = PresContext(); + + gfxRect result; + TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames, + aChildFrame); + for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) { + // First, translate aRect from aChildFrame to this run's frame. + nsRect rectInTextFrame = aRect + aChildFrame->GetOffsetTo(run.mFrame); + + // Scale it into frame user space. + gfxRect rectInFrameUserSpace = + AppUnitsToFloatCSSPixels(gfxRect(rectInTextFrame.x, + rectInTextFrame.y, + rectInTextFrame.width, + rectInTextFrame.height), presContext); + + // Intersect it with the run. + uint32_t flags = TextRenderedRun::eIncludeFill | + TextRenderedRun::eIncludeStroke; + rectInFrameUserSpace.IntersectRect + (rectInFrameUserSpace, run.GetFrameUserSpaceRect(presContext, flags)); + + if (!rectInFrameUserSpace.IsEmpty()) { + // Transform it up to user space of the , also taking into + // account the font size scale. + gfxMatrix m = run.GetTransformFromRunUserSpaceToUserSpace(presContext); + m.Scale(mFontSizeScaleFactor, mFontSizeScaleFactor); + gfxRect rectInUserSpace = m.Transform(rectInFrameUserSpace); + + // Union it into the result. + result.UnionRect(result, rectInUserSpace); + } + } + + // Subtract the mRect offset from the result, as our user space for + // this frame is relative to the top-left of mRect. + float factor = presContext->AppUnitsPerCSSPixel(); + gfxPoint framePosition(NSAppUnitsToFloatPixels(mRect.x, factor), + NSAppUnitsToFloatPixels(mRect.y, factor)); + + return result - framePosition; +} diff --git a/layout/svg/nsSVGTextFrame2.h b/layout/svg/nsSVGTextFrame2.h index 3c6a8850f549..c4aba12b6002 100644 --- a/layout/svg/nsSVGTextFrame2.h +++ b/layout/svg/nsSVGTextFrame2.h @@ -215,10 +215,18 @@ public: nscoord aX, nscoord aY, nsIFrame* aForChild, uint32_t aFlags); + /** + * Finds the nsTextFrame for the closest rendered run to the specified point. + */ + virtual void FindCloserFrameForSelection(nsPoint aPoint, + FrameWithDistance* aCurrentBestFrame); + + // nsISVGChildFrame interface: virtual void NotifySVGChanged(uint32_t aFlags); NS_IMETHOD PaintSVG(nsRenderingContext* aContext, const nsIntRect* aDirtyRect); + NS_IMETHOD_(nsIFrame*) GetFrameForPoint(const nsPoint& aPoint); virtual void ReflowSVG(); NS_IMETHOD_(nsRect) GetCoveredRegion(); virtual SVGBBox GetBBoxContribution(const gfxMatrix& aToBBoxUserspace, @@ -258,6 +266,32 @@ public: double GetFontSizeScaleFactor() const; + /** + * Takes a point from the element's user space and + * converts it to the appropriate frame user space of aChildFrame, + * according to which rendered run the point hits. + */ + gfxPoint TransformFramePointToTextChild(const gfxPoint& aPoint, + nsIFrame* aChildFrame); + + /** + * Takes a rectangle, aRect, in the element's user space, and + * returns a rectangle in aChildFrame's frame user space that + * covers intersections of aRect with each rendered run for text frames + * within aChildFrame. + */ + gfxRect TransformFrameRectToTextChild(const gfxRect& aRect, + nsIFrame* aChildFrame); + + /** + * Takes an app unit rectangle in the coordinate space of a given descendant + * frame of this frame, and returns a rectangle in the element's user + * space that covers all parts of rendered runs that intersect with the + * rectangle. + */ + gfxRect TransformFrameRectFromTextChild(const nsRect& aRect, + nsIFrame* aChildFrame); + private: /** * This class exists purely because it would be too messy to pass the "for"