1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsSVGPathGeometryFrame.h"
9 // Keep others in (case-insensitive) order:
10 #include "gfx2DGlue.h"
11 #include "gfxContext.h"
12 #include "gfxPlatform.h"
13 #include "gfxSVGGlyphs.h"
15 #include "mozilla/gfx/2D.h"
16 #include "mozilla/gfx/Helpers.h"
17 #include "mozilla/RefPtr.h"
18 #include "nsDisplayList.h"
19 #include "nsGkAtoms.h"
20 #include "nsLayoutUtils.h"
21 #include "nsRenderingContext.h"
22 #include "nsSVGEffects.h"
23 #include "nsSVGIntegrationUtils.h"
24 #include "nsSVGMarkerFrame.h"
25 #include "nsSVGPathGeometryElement.h"
26 #include "nsSVGUtils.h"
27 #include "mozilla/ArrayUtils.h"
28 #include "SVGAnimatedTransformList.h"
29 #include "SVGContentUtils.h"
30 #include "SVGGraphicsElement.h"
32 using namespace mozilla
;
33 using namespace mozilla::gfx
;
35 //----------------------------------------------------------------------
39 NS_NewSVGPathGeometryFrame(nsIPresShell
* aPresShell
,
40 nsStyleContext
* aContext
)
42 return new (aPresShell
) nsSVGPathGeometryFrame(aContext
);
45 NS_IMPL_FRAMEARENA_HELPERS(nsSVGPathGeometryFrame
)
47 //----------------------------------------------------------------------
48 // nsQueryFrame methods
50 NS_QUERYFRAME_HEAD(nsSVGPathGeometryFrame
)
51 NS_QUERYFRAME_ENTRY(nsISVGChildFrame
)
52 NS_QUERYFRAME_ENTRY(nsSVGPathGeometryFrame
)
53 NS_QUERYFRAME_TAIL_INHERITING(nsSVGPathGeometryFrameBase
)
55 //----------------------------------------------------------------------
58 class nsDisplaySVGPathGeometry
: public nsDisplayItem
{
60 nsDisplaySVGPathGeometry(nsDisplayListBuilder
* aBuilder
,
61 nsSVGPathGeometryFrame
* aFrame
)
62 : nsDisplayItem(aBuilder
, aFrame
)
64 MOZ_COUNT_CTOR(nsDisplaySVGPathGeometry
);
65 NS_ABORT_IF_FALSE(aFrame
, "Must have a frame!");
67 #ifdef NS_BUILD_REFCNT_LOGGING
68 virtual ~nsDisplaySVGPathGeometry() {
69 MOZ_COUNT_DTOR(nsDisplaySVGPathGeometry
);
73 NS_DISPLAY_DECL_NAME("nsDisplaySVGPathGeometry", TYPE_SVG_PATH_GEOMETRY
)
75 virtual void HitTest(nsDisplayListBuilder
* aBuilder
, const nsRect
& aRect
,
76 HitTestState
* aState
, nsTArray
<nsIFrame
*> *aOutFrames
) MOZ_OVERRIDE
;
77 virtual void Paint(nsDisplayListBuilder
* aBuilder
,
78 nsRenderingContext
* aCtx
) MOZ_OVERRIDE
;
82 nsDisplaySVGPathGeometry::HitTest(nsDisplayListBuilder
* aBuilder
, const nsRect
& aRect
,
83 HitTestState
* aState
, nsTArray
<nsIFrame
*> *aOutFrames
)
85 nsSVGPathGeometryFrame
*frame
= static_cast<nsSVGPathGeometryFrame
*>(mFrame
);
86 nsPoint pointRelativeToReferenceFrame
= aRect
.Center();
87 // ToReferenceFrame() includes frame->GetPosition(), our user space position.
88 nsPoint userSpacePtInAppUnits
= pointRelativeToReferenceFrame
-
89 (ToReferenceFrame() - frame
->GetPosition());
90 gfxPoint userSpacePt
=
91 gfxPoint(userSpacePtInAppUnits
.x
, userSpacePtInAppUnits
.y
) /
92 frame
->PresContext()->AppUnitsPerCSSPixel();
93 if (frame
->GetFrameForPoint(userSpacePt
)) {
94 aOutFrames
->AppendElement(frame
);
99 nsDisplaySVGPathGeometry::Paint(nsDisplayListBuilder
* aBuilder
,
100 nsRenderingContext
* aCtx
)
102 uint32_t appUnitsPerDevPixel
= mFrame
->PresContext()->AppUnitsPerDevPixel();
104 // ToReferenceFrame includes our mRect offset, but painting takes
105 // account of that too. To avoid double counting, we subtract that
107 nsPoint offset
= ToReferenceFrame() - mFrame
->GetPosition();
109 gfxPoint devPixelOffset
=
110 nsLayoutUtils::PointToGfxPoint(offset
, appUnitsPerDevPixel
);
112 gfxMatrix tm
= nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(mFrame
) *
113 gfxMatrix::Translation(devPixelOffset
);
114 static_cast<nsSVGPathGeometryFrame
*>(mFrame
)->PaintSVG(*aCtx
->ThebesContext(), tm
);
117 //----------------------------------------------------------------------
121 nsSVGPathGeometryFrame::Init(nsIContent
* aContent
,
122 nsContainerFrame
* aParent
,
123 nsIFrame
* aPrevInFlow
)
125 AddStateBits(aParent
->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD
);
126 nsSVGPathGeometryFrameBase::Init(aContent
, aParent
, aPrevInFlow
);
130 nsSVGPathGeometryFrame::AttributeChanged(int32_t aNameSpaceID
,
134 // We don't invalidate for transform changes (the layers code does that).
135 // Also note that SVGTransformableElement::GetAttributeChangeHint will
136 // return nsChangeHint_UpdateOverflow for "transform" attribute changes
137 // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call.
139 if (aNameSpaceID
== kNameSpaceID_None
&&
140 (static_cast<nsSVGPathGeometryElement
*>
141 (mContent
)->AttributeDefinesGeometry(aAttribute
))) {
142 nsLayoutUtils::PostRestyleEvent(
143 mContent
->AsElement(), nsRestyleHint(0),
144 nsChangeHint_InvalidateRenderingObservers
);
145 nsSVGUtils::ScheduleReflowSVG(this);
151 nsSVGPathGeometryFrame::DidSetStyleContext(nsStyleContext
* aOldStyleContext
)
153 nsSVGPathGeometryFrameBase::DidSetStyleContext(aOldStyleContext
);
155 if (aOldStyleContext
) {
156 float oldOpacity
= aOldStyleContext
->PeekStyleDisplay()->mOpacity
;
157 float newOpacity
= StyleDisplay()->mOpacity
;
158 if (newOpacity
!= oldOpacity
&&
159 nsSVGUtils::CanOptimizeOpacity(this)) {
160 // nsIFrame::BuildDisplayListForStackingContext() is not going to create an
161 // nsDisplayOpacity display list item, so DLBI won't invalidate for us.
165 nsSVGPathGeometryElement
* element
=
166 static_cast<nsSVGPathGeometryElement
*>(mContent
);
168 if (aOldStyleContext
->PeekStyleSVG()) {
169 if ((StyleSVG()->mStrokeLinecap
!=
170 aOldStyleContext
->PeekStyleSVG()->mStrokeLinecap
) &&
171 element
->Tag() == nsGkAtoms::path
) {
172 // If the stroke-linecap changes to or from "butt" then our element
173 // needs to update its cached Moz2D Path, since SVGPathData::BuildPath
174 // decides whether or not to insert little lines into the path for zero
175 // length subpaths base on that property.
176 element
->ClearAnyCachedPath();
177 } else if (GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD
) {
178 if (StyleSVG()->mClipRule
!=
179 aOldStyleContext
->PeekStyleSVG()->mClipRule
) {
180 // Moz2D Path objects are fill-rule specific.
181 // For clipPath we use clip-rule as the path's fill-rule.
182 element
->ClearAnyCachedPath();
185 if (StyleSVG()->mFillRule
!=
186 aOldStyleContext
->PeekStyleSVG()->mFillRule
) {
187 // Moz2D Path objects are fill-rule specific.
188 element
->ClearAnyCachedPath();
196 nsSVGPathGeometryFrame::GetType() const
198 return nsGkAtoms::svgPathGeometryFrame
;
202 nsSVGPathGeometryFrame::IsSVGTransformed(gfx::Matrix
*aOwnTransform
,
203 gfx::Matrix
*aFromParentTransform
) const
205 bool foundTransform
= false;
207 // Check if our parent has children-only transforms:
208 nsIFrame
*parent
= GetParent();
210 parent
->IsFrameOfType(nsIFrame::eSVG
| nsIFrame::eSVGContainer
)) {
211 foundTransform
= static_cast<nsSVGContainerFrame
*>(parent
)->
212 HasChildrenOnlyTransform(aFromParentTransform
);
215 nsSVGElement
*content
= static_cast<nsSVGElement
*>(mContent
);
216 nsSVGAnimatedTransformList
* transformList
=
217 content
->GetAnimatedTransformList();
218 if ((transformList
&& transformList
->HasTransform()) ||
219 content
->GetAnimateMotionTransform()) {
221 *aOwnTransform
= gfx::ToMatrix(content
->PrependLocalTransformsTo(gfxMatrix(),
222 nsSVGElement::eUserSpaceToParent
));
224 foundTransform
= true;
226 return foundTransform
;
230 nsSVGPathGeometryFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
231 const nsRect
& aDirtyRect
,
232 const nsDisplayListSet
& aLists
)
234 if (!static_cast<const nsSVGElement
*>(mContent
)->HasValidDimensions()) {
237 aLists
.Content()->AppendNewToTop(
238 new (aBuilder
) nsDisplaySVGPathGeometry(aBuilder
, this));
241 //----------------------------------------------------------------------
242 // nsISVGChildFrame methods
245 nsSVGPathGeometryFrame::PaintSVG(gfxContext
& aContext
,
246 const gfxMatrix
& aTransform
,
247 const nsIntRect
* aDirtyRect
)
249 if (!StyleVisibility()->IsVisible())
252 // Matrix to the geometry's user space:
253 gfxMatrix newMatrix
=
254 aContext
.CurrentMatrix().PreMultiply(aTransform
).NudgeToIntegers();
255 if (newMatrix
.IsSingular()) {
259 uint32_t paintOrder
= StyleSVG()->mPaintOrder
;
260 if (paintOrder
== NS_STYLE_PAINT_ORDER_NORMAL
) {
261 Render(&aContext
, eRenderFill
| eRenderStroke
, newMatrix
);
262 PaintMarkers(aContext
, aTransform
);
266 paintOrder
& ((1 << NS_STYLE_PAINT_ORDER_BITWIDTH
) - 1);
268 case NS_STYLE_PAINT_ORDER_FILL
:
269 Render(&aContext
, eRenderFill
, newMatrix
);
271 case NS_STYLE_PAINT_ORDER_STROKE
:
272 Render(&aContext
, eRenderStroke
, newMatrix
);
274 case NS_STYLE_PAINT_ORDER_MARKERS
:
275 PaintMarkers(aContext
, aTransform
);
278 paintOrder
>>= NS_STYLE_PAINT_ORDER_BITWIDTH
;
286 nsSVGPathGeometryFrame::GetFrameForPoint(const gfxPoint
& aPoint
)
289 uint16_t hitTestFlags
;
290 if (GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD
) {
291 hitTestFlags
= SVG_HIT_TEST_FILL
;
292 fillRule
= nsSVGUtils::ToFillRule(StyleSVG()->mClipRule
);
294 hitTestFlags
= GetHitTestFlags();
298 if (hitTestFlags
& SVG_HIT_TEST_CHECK_MRECT
) {
300 nsLayoutUtils::RectToGfxRect(mRect
, PresContext()->AppUnitsPerCSSPixel());
301 if (!rect
.Contains(aPoint
)) {
305 fillRule
= nsSVGUtils::ToFillRule(StyleSVG()->mFillRule
);
310 nsSVGPathGeometryElement
* content
=
311 static_cast<nsSVGPathGeometryElement
*>(mContent
);
313 // Using ScreenReferenceDrawTarget() opens us to Moz2D backend specific hit-
314 // testing bugs. Maybe we should use a BackendType::CAIRO DT for hit-testing
315 // so that we get more consistent/backwards compatible results?
316 RefPtr
<DrawTarget
> drawTarget
=
317 gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
318 RefPtr
<Path
> path
= content
->GetOrBuildPath(*drawTarget
, fillRule
);
320 return nullptr; // no path, so we don't paint anything that can be hit
323 if (hitTestFlags
& SVG_HIT_TEST_FILL
) {
324 isHit
= path
->ContainsPoint(ToPoint(aPoint
), Matrix());
326 if (!isHit
&& (hitTestFlags
& SVG_HIT_TEST_STROKE
)) {
327 Point point
= ToPoint(aPoint
);
328 SVGContentUtils::AutoStrokeOptions stroke
;
329 SVGContentUtils::GetStrokeOptions(&stroke
, content
, StyleContext(), nullptr);
330 gfxMatrix userToOuterSVG
;
331 if (nsSVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG
)) {
332 // We need to transform the path back into the appropriate ancestor
333 // coordinate system in order for non-scaled stroke to be correct.
334 // Naturally we also need to transform the point into the same
335 // coordinate system in order to hit-test against the path.
336 point
= ToMatrix(userToOuterSVG
) * point
;
337 RefPtr
<PathBuilder
> builder
=
338 path
->TransformedCopyToBuilder(ToMatrix(userToOuterSVG
), fillRule
);
339 path
= builder
->Finish();
341 isHit
= path
->StrokeContainsPoint(stroke
, point
, Matrix());
344 if (isHit
&& nsSVGUtils::HitTestClip(this, aPoint
))
351 nsSVGPathGeometryFrame::GetCoveredRegion()
353 return nsSVGUtils::TransformFrameRectToOuterSVG(
354 mRect
, GetCanvasTM(), PresContext());
358 nsSVGPathGeometryFrame::ReflowSVG()
360 NS_ASSERTION(nsSVGUtils::OuterSVGIsCallingReflowSVG(this),
361 "This call is probably a wasteful mistake");
363 NS_ABORT_IF_FALSE(!(GetStateBits() & NS_FRAME_IS_NONDISPLAY
),
364 "ReflowSVG mechanism not designed for this");
366 if (!nsSVGUtils::NeedsReflowSVG(this)) {
370 uint32_t flags
= nsSVGUtils::eBBoxIncludeFill
|
371 nsSVGUtils::eBBoxIncludeStroke
|
372 nsSVGUtils::eBBoxIncludeMarkers
;
373 // Our "visual" overflow rect needs to be valid for building display lists
374 // for hit testing, which means that for certain values of 'pointer-events'
375 // it needs to include the geometry of the fill or stroke even when the fill/
376 // stroke don't actually render (e.g. when stroke="none" or
377 // stroke-opacity="0"). GetHitTestFlags() accounts for 'pointer-events'.
378 uint16_t hitTestFlags
= GetHitTestFlags();
379 if ((hitTestFlags
& SVG_HIT_TEST_FILL
)) {
380 flags
|= nsSVGUtils::eBBoxIncludeFillGeometry
;
382 if ((hitTestFlags
& SVG_HIT_TEST_STROKE
)) {
383 flags
|= nsSVGUtils::eBBoxIncludeStrokeGeometry
;
386 gfxRect extent
= GetBBoxContribution(Matrix(), flags
).ToThebesRect();
387 mRect
= nsLayoutUtils::RoundGfxRectToAppRect(extent
,
388 PresContext()->AppUnitsPerCSSPixel());
390 if (mState
& NS_FRAME_FIRST_REFLOW
) {
391 // Make sure we have our filter property (if any) before calling
392 // FinishAndStoreOverflow (subsequent filter changes are handled off
393 // nsChangeHint_UpdateEffects):
394 nsSVGEffects::UpdateEffects(this);
397 nsRect overflow
= nsRect(nsPoint(0,0), mRect
.Size());
398 nsOverflowAreas
overflowAreas(overflow
, overflow
);
399 FinishAndStoreOverflow(overflowAreas
, mRect
.Size());
401 mState
&= ~(NS_FRAME_FIRST_REFLOW
| NS_FRAME_IS_DIRTY
|
402 NS_FRAME_HAS_DIRTY_CHILDREN
);
404 // Invalidate, but only if this is not our first reflow (since if it is our
405 // first reflow then we haven't had our first paint yet).
406 if (!(GetParent()->GetStateBits() & NS_FRAME_FIRST_REFLOW
)) {
412 nsSVGPathGeometryFrame::NotifySVGChanged(uint32_t aFlags
)
414 NS_ABORT_IF_FALSE(aFlags
& (TRANSFORM_CHANGED
| COORD_CONTEXT_CHANGED
),
415 "Invalidation logic may need adjusting");
417 // Changes to our ancestors may affect how we render when we are rendered as
418 // part of our ancestor (specifically, if our coordinate context changes size
419 // and we have percentage lengths defining our geometry, then we need to be
420 // reflowed). However, ancestor changes cannot affect how we render when we
421 // are rendered as part of any rendering observers that we may have.
422 // Therefore no need to notify rendering observers here.
424 // Don't try to be too smart trying to avoid the ScheduleReflowSVG calls
425 // for the stroke properties examined below. Checking HasStroke() is not
426 // enough, since what we care about is whether we include the stroke in our
427 // overflow rects or not, and we sometimes deliberately include stroke
428 // when it's not visible. See the complexities of GetBBoxContribution.
430 if (aFlags
& COORD_CONTEXT_CHANGED
) {
431 // Stroke currently contributes to our mRect, which is why we have to take
432 // account of stroke-width here. Note that we do not need to take account
433 // of stroke-dashoffset since, although that can have a percentage value
434 // that is resolved against our coordinate context, it does not affect our
436 if (static_cast<nsSVGPathGeometryElement
*>(mContent
)->GeometryDependsOnCoordCtx() ||
437 StyleSVG()->mStrokeWidth
.HasPercent()) {
438 static_cast<nsSVGPathGeometryElement
*>(mContent
)->ClearAnyCachedPath();
439 nsSVGUtils::ScheduleReflowSVG(this);
443 if ((aFlags
& TRANSFORM_CHANGED
) && StyleSVGReset()->HasNonScalingStroke()) {
444 // Stroke currently contributes to our mRect, and our stroke depends on
445 // the transform to our outer-<svg> if |vector-effect:non-scaling-stroke|.
446 nsSVGUtils::ScheduleReflowSVG(this);
451 nsSVGPathGeometryFrame::GetBBoxContribution(const Matrix
&aToBBoxUserspace
,
456 if (aToBBoxUserspace
.IsSingular()) {
457 // XXX ReportToConsole
461 nsSVGPathGeometryElement
* element
=
462 static_cast<nsSVGPathGeometryElement
*>(mContent
);
464 bool getFill
= (aFlags
& nsSVGUtils::eBBoxIncludeFillGeometry
) ||
465 ((aFlags
& nsSVGUtils::eBBoxIncludeFill
) &&
466 StyleSVG()->mFill
.mType
!= eStyleSVGPaintType_None
);
468 bool getStroke
= (aFlags
& nsSVGUtils::eBBoxIncludeStrokeGeometry
) ||
469 ((aFlags
& nsSVGUtils::eBBoxIncludeStroke
) &&
470 nsSVGUtils::HasStroke(this));
472 bool gotSimpleBounds
= false;
473 if (!StyleSVGReset()->HasNonScalingStroke()) {
474 Float strokeWidth
= getStroke
? nsSVGUtils::GetStrokeWidth(this) : 0.f
;
476 gotSimpleBounds
= element
->GetGeometryBounds(&simpleBounds
, strokeWidth
,
478 if (gotSimpleBounds
) {
483 if (!gotSimpleBounds
) {
484 // Get the bounds using a Moz2D Path object (more expensive):
485 RefPtr
<DrawTarget
> tmpDT
;
487 // Unfortunately D2D backed DrawTarget produces bounds with rounding errors
488 // when whole number results are expected, even in the case of trivial
489 // calculations. To avoid that and meet the expectations of web content we
490 // have to use a CAIRO DrawTarget. The most efficient way to do that is to
491 // wrap the cached cairo_surface_t from ScreenReferenceSurface():
492 nsRefPtr
<gfxASurface
> refSurf
=
493 gfxPlatform::GetPlatform()->ScreenReferenceSurface();
494 tmpDT
= gfxPlatform::GetPlatform()->
495 CreateDrawTargetForSurface(refSurf
, IntSize(1, 1));
497 tmpDT
= gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
500 FillRule fillRule
= nsSVGUtils::ToFillRule(StyleSVG()->mFillRule
);
501 RefPtr
<Path
> pathInUserSpace
= element
->GetOrBuildPath(*tmpDT
, fillRule
);
502 if (!pathInUserSpace
) {
505 RefPtr
<Path
> pathInBBoxSpace
;
506 if (aToBBoxUserspace
.IsIdentity()) {
507 pathInBBoxSpace
= pathInUserSpace
;
509 RefPtr
<PathBuilder
> builder
=
510 pathInUserSpace
->TransformedCopyToBuilder(aToBBoxUserspace
, fillRule
);
511 pathInBBoxSpace
= builder
->Finish();
512 if (!pathInBBoxSpace
) {
517 // Be careful when replacing the following logic to get the fill and stroke
518 // extents independently (instead of computing the stroke extents from the
519 // path extents). You may think that you can just use the stroke extents if
520 // there is both a fill and a stroke. In reality it's necessary to
521 // calculate both the fill and stroke extents, and take the union of the
522 // two. There are two reasons for this:
524 // # Due to stroke dashing, in certain cases the fill extents could
525 // actually extend outside the stroke extents.
526 // # If the stroke is very thin, cairo won't paint any stroke, and so the
527 // stroke bounds that it will return will be empty.
529 Rect pathBBoxExtents
= pathInBBoxSpace
->GetBounds();
530 if (!pathBBoxExtents
.IsFinite()) {
531 // This can happen in the case that we only have a move-to command in the
532 // path commands, in which case we know nothing gets rendered.
538 bbox
= pathBBoxExtents
;
541 // Account for stroke:
544 // This disabled code is how we would calculate the stroke bounds using
545 // Moz2D Path::GetStrokedBounds(). Unfortunately at the time of writing
546 // it there are two problems that prevent us from using it.
548 // First, it seems that some of the Moz2D backends are really dumb. Not
549 // only do some GetStrokeOptions() implementations sometimes
550 // significantly overestimate the stroke bounds, but if an argument is
551 // passed for the aTransform parameter then they just return bounds-of-
552 // transformed-bounds. These two things combined can lead the bounds to
553 // be unacceptably oversized, leading to massive over-invalidation.
555 // Second, the way we account for non-scaling-stroke by transforming the
556 // path using the transform to the outer-<svg> element is not compatible
557 // with the way that nsSVGPathGeometryFrame::Reflow() inserts a scale
558 // into aToBBoxUserspace and then scales the bounds that we return.
559 SVGContentUtils::AutoStrokeOptions strokeOptions
;
560 SVGContentUtils::GetStrokeOptions(&strokeOptions
, element
,
561 StyleContext(), nullptr,
562 SVGContentUtils::eIgnoreStrokeDashing
);
563 Rect strokeBBoxExtents
;
564 gfxMatrix userToOuterSVG
;
565 if (nsSVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG
)) {
566 Matrix outerSVGToUser
= ToMatrix(userToOuterSVG
);
567 outerSVGToUser
.Invert();
568 Matrix outerSVGToBBox
= aToBBoxUserspace
* outerSVGToUser
;
569 RefPtr
<PathBuilder
> builder
=
570 pathInUserSpace
->TransformedCopyToBuilder(ToMatrix(userToOuterSVG
));
571 RefPtr
<Path
> pathInOuterSVGSpace
= builder
->Finish();
573 pathInOuterSVGSpace
->GetStrokedBounds(strokeOptions
, outerSVGToBBox
);
576 pathInUserSpace
->GetStrokedBounds(strokeOptions
, aToBBoxUserspace
);
578 MOZ_ASSERT(strokeBBoxExtents
.IsFinite(), "bbox is about to go bad");
579 bbox
.UnionEdges(strokeBBoxExtents
);
581 // For now we just use nsSVGUtils::PathExtentsToMaxStrokeExtents:
582 gfxRect strokeBBoxExtents
=
583 nsSVGUtils::PathExtentsToMaxStrokeExtents(ThebesRect(pathBBoxExtents
),
585 ThebesMatrix(aToBBoxUserspace
));
586 MOZ_ASSERT(ToRect(strokeBBoxExtents
).IsFinite(), "bbox is about to go bad");
587 bbox
.UnionEdges(strokeBBoxExtents
);
592 // Account for markers:
593 if ((aFlags
& nsSVGUtils::eBBoxIncludeMarkers
) != 0 &&
594 static_cast<nsSVGPathGeometryElement
*>(mContent
)->IsMarkable()) {
596 float strokeWidth
= nsSVGUtils::GetStrokeWidth(this);
597 MarkerProperties properties
= GetMarkerProperties(this);
599 if (properties
.MarkersExist()) {
600 nsTArray
<nsSVGMark
> marks
;
601 static_cast<nsSVGPathGeometryElement
*>(mContent
)->GetMarkPoints(&marks
);
602 uint32_t num
= marks
.Length();
604 // These are in the same order as the nsSVGMark::Type constants.
605 nsSVGMarkerFrame
* markerFrames
[] = {
606 properties
.GetMarkerStartFrame(),
607 properties
.GetMarkerMidFrame(),
608 properties
.GetMarkerEndFrame(),
610 PR_STATIC_ASSERT(MOZ_ARRAY_LENGTH(markerFrames
) == nsSVGMark::eTypeCount
);
612 for (uint32_t i
= 0; i
< num
; i
++) {
613 nsSVGMark
& mark
= marks
[i
];
614 nsSVGMarkerFrame
* frame
= markerFrames
[mark
.type
];
617 frame
->GetMarkBBoxContribution(aToBBoxUserspace
, aFlags
, this,
618 &marks
[i
], strokeWidth
);
619 MOZ_ASSERT(mbbox
.IsFinite(), "bbox is about to go bad");
620 bbox
.UnionEdges(mbbox
);
629 //----------------------------------------------------------------------
630 // nsSVGPathGeometryFrame methods:
633 nsSVGPathGeometryFrame::GetCanvasTM()
635 NS_ASSERTION(GetParent(), "null parent");
637 nsSVGContainerFrame
*parent
= static_cast<nsSVGContainerFrame
*>(GetParent());
638 dom::SVGGraphicsElement
*content
= static_cast<dom::SVGGraphicsElement
*>(mContent
);
640 return content
->PrependLocalTransformsTo(parent
->GetCanvasTM());
643 nsSVGPathGeometryFrame::MarkerProperties
644 nsSVGPathGeometryFrame::GetMarkerProperties(nsSVGPathGeometryFrame
*aFrame
)
646 NS_ASSERTION(!aFrame
->GetPrevContinuation(), "aFrame should be first continuation");
648 MarkerProperties result
;
649 const nsStyleSVG
*style
= aFrame
->StyleSVG();
650 result
.mMarkerStart
=
651 nsSVGEffects::GetMarkerProperty(style
->mMarkerStart
, aFrame
,
652 nsSVGEffects::MarkerBeginProperty());
654 nsSVGEffects::GetMarkerProperty(style
->mMarkerMid
, aFrame
,
655 nsSVGEffects::MarkerMiddleProperty());
657 nsSVGEffects::GetMarkerProperty(style
->mMarkerEnd
, aFrame
,
658 nsSVGEffects::MarkerEndProperty());
663 nsSVGPathGeometryFrame::MarkerProperties::GetMarkerStartFrame()
667 return static_cast<nsSVGMarkerFrame
*>
668 (mMarkerStart
->GetReferencedFrame(nsGkAtoms::svgMarkerFrame
, nullptr));
672 nsSVGPathGeometryFrame::MarkerProperties::GetMarkerMidFrame()
676 return static_cast<nsSVGMarkerFrame
*>
677 (mMarkerMid
->GetReferencedFrame(nsGkAtoms::svgMarkerFrame
, nullptr));
681 nsSVGPathGeometryFrame::MarkerProperties::GetMarkerEndFrame()
685 return static_cast<nsSVGMarkerFrame
*>
686 (mMarkerEnd
->GetReferencedFrame(nsGkAtoms::svgMarkerFrame
, nullptr));
690 nsSVGPathGeometryFrame::Render(gfxContext
* aContext
,
691 uint32_t aRenderComponents
,
692 const gfxMatrix
& aNewTransform
)
694 MOZ_ASSERT(!aNewTransform
.IsSingular());
696 DrawTarget
* drawTarget
= aContext
->GetDrawTarget();
699 nsSVGUtils::ToFillRule((GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD
) ?
700 StyleSVG()->mClipRule
: StyleSVG()->mFillRule
);
702 nsSVGPathGeometryElement
* element
=
703 static_cast<nsSVGPathGeometryElement
*>(mContent
);
705 AntialiasMode aaMode
=
706 (StyleSVG()->mShapeRendering
== NS_STYLE_SHAPE_RENDERING_OPTIMIZESPEED
||
707 StyleSVG()->mShapeRendering
== NS_STYLE_SHAPE_RENDERING_CRISPEDGES
) ?
708 AntialiasMode::NONE
: AntialiasMode::SUBPIXEL
;
710 // We wait as late as possible before setting the transform so that we don't
711 // set it unnecessarily if we return early (it's an expensive operation for
713 gfxContextMatrixAutoSaveRestore
autoRestoreTransform(aContext
);
714 aContext
->SetMatrix(aNewTransform
);
716 if (GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD
) {
717 // We don't complicate this code with GetAsSimplePath since the cost of
718 // masking will dwarf Path creation overhead anyway.
719 RefPtr
<Path
> path
= element
->GetOrBuildPath(*drawTarget
, fillRule
);
721 ColorPattern
white(ToDeviceColor(Color(1.0f
, 1.0f
, 1.0f
, 1.0f
)));
722 drawTarget
->Fill(path
, white
,
723 DrawOptions(1.0f
, CompositionOp::OP_OVER
, aaMode
));
728 nsSVGPathGeometryElement::SimplePath simplePath
;
731 element
->GetAsSimplePath(&simplePath
);
732 if (!simplePath
.IsPath()) {
733 path
= element
->GetOrBuildPath(*drawTarget
, fillRule
);
739 gfxTextContextPaint
*contextPaint
=
740 (gfxTextContextPaint
*)drawTarget
->
741 GetUserData(&gfxTextContextPaint::sUserDataKey
);
743 if (aRenderComponents
& eRenderFill
) {
744 GeneralPattern fillPattern
;
745 nsSVGUtils::MakeFillPatternFor(this, aContext
, &fillPattern
, contextPaint
);
746 if (fillPattern
.GetPattern()) {
747 DrawOptions
drawOptions(1.0f
, CompositionOp::OP_OVER
, aaMode
);
748 if (simplePath
.IsRect()) {
749 drawTarget
->FillRect(simplePath
.AsRect(), fillPattern
, drawOptions
);
751 drawTarget
->Fill(path
, fillPattern
, drawOptions
);
756 if ((aRenderComponents
& eRenderStroke
) &&
757 nsSVGUtils::HasStroke(this, contextPaint
)) {
758 // Account for vector-effect:non-scaling-stroke:
759 gfxMatrix userToOuterSVG
;
760 if (nsSVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG
)) {
761 // A simple Rect can't be transformed with rotate/skew, so let's switch
762 // to using a real path:
764 path
= element
->GetOrBuildPath(*drawTarget
, fillRule
);
770 // We need to transform the path back into the appropriate ancestor
771 // coordinate system, and paint it it that coordinate system, in order
772 // for non-scaled stroke to paint correctly.
773 gfxMatrix outerSVGToUser
= userToOuterSVG
;
774 outerSVGToUser
.Invert();
775 aContext
->Multiply(outerSVGToUser
);
776 RefPtr
<PathBuilder
> builder
=
777 path
->TransformedCopyToBuilder(ToMatrix(userToOuterSVG
), fillRule
);
778 path
= builder
->Finish();
780 GeneralPattern strokePattern
;
781 nsSVGUtils::MakeStrokePatternFor(this, aContext
, &strokePattern
, contextPaint
);
782 if (strokePattern
.GetPattern()) {
783 SVGContentUtils::AutoStrokeOptions strokeOptions
;
784 SVGContentUtils::GetStrokeOptions(&strokeOptions
,
785 static_cast<nsSVGElement
*>(mContent
),
786 StyleContext(), contextPaint
);
787 // GetStrokeOptions may set the line width to zero as an optimization
788 if (strokeOptions
.mLineWidth
<= 0) {
791 DrawOptions
drawOptions(1.0f
, CompositionOp::OP_OVER
, aaMode
);
792 if (simplePath
.IsRect()) {
793 drawTarget
->StrokeRect(simplePath
.AsRect(), strokePattern
,
794 strokeOptions
, drawOptions
);
795 } else if (simplePath
.IsLine()) {
796 drawTarget
->StrokeLine(simplePath
.Point1(), simplePath
.Point2(),
797 strokePattern
, strokeOptions
, drawOptions
);
799 drawTarget
->Stroke(path
, strokePattern
, strokeOptions
, drawOptions
);
806 nsSVGPathGeometryFrame::PaintMarkers(gfxContext
& aContext
,
807 const gfxMatrix
& aTransform
)
809 gfxTextContextPaint
*contextPaint
=
810 (gfxTextContextPaint
*)aContext
.GetDrawTarget()->GetUserData(&gfxTextContextPaint::sUserDataKey
);
812 if (static_cast<nsSVGPathGeometryElement
*>(mContent
)->IsMarkable()) {
813 MarkerProperties properties
= GetMarkerProperties(this);
815 if (properties
.MarkersExist()) {
816 float strokeWidth
= nsSVGUtils::GetStrokeWidth(this, contextPaint
);
818 nsTArray
<nsSVGMark
> marks
;
819 static_cast<nsSVGPathGeometryElement
*>
820 (mContent
)->GetMarkPoints(&marks
);
822 uint32_t num
= marks
.Length();
824 // These are in the same order as the nsSVGMark::Type constants.
825 nsSVGMarkerFrame
* markerFrames
[] = {
826 properties
.GetMarkerStartFrame(),
827 properties
.GetMarkerMidFrame(),
828 properties
.GetMarkerEndFrame(),
830 PR_STATIC_ASSERT(MOZ_ARRAY_LENGTH(markerFrames
) == nsSVGMark::eTypeCount
);
832 for (uint32_t i
= 0; i
< num
; i
++) {
833 nsSVGMark
& mark
= marks
[i
];
834 nsSVGMarkerFrame
* frame
= markerFrames
[mark
.type
];
836 frame
->PaintMark(aContext
, aTransform
, this, &mark
, strokeWidth
);
845 nsSVGPathGeometryFrame::GetHitTestFlags()
847 return nsSVGUtils::GetGeometryHitTestFlags(this);