Bumping manifests a=b2g-bump
[gecko.git] / layout / svg / nsSVGPathGeometryFrame.cpp
blob9fa123dbce42fcb10fab090e395ff60048c6f43c
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/. */
6 // Main header first:
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"
14 #include "gfxUtils.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 //----------------------------------------------------------------------
36 // Implementation
38 nsIFrame*
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 //----------------------------------------------------------------------
56 // Display list item:
58 class nsDisplaySVGPathGeometry : public nsDisplayItem {
59 public:
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);
71 #endif
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;
81 void
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);
98 void
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
106 // here.
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 //----------------------------------------------------------------------
118 // nsIFrame methods
120 void
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);
129 nsresult
130 nsSVGPathGeometryFrame::AttributeChanged(int32_t aNameSpaceID,
131 nsIAtom* aAttribute,
132 int32_t aModType)
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);
147 return NS_OK;
150 /* virtual */ void
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.
162 InvalidateFrame();
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();
184 } else {
185 if (StyleSVG()->mFillRule !=
186 aOldStyleContext->PeekStyleSVG()->mFillRule) {
187 // Moz2D Path objects are fill-rule specific.
188 element->ClearAnyCachedPath();
195 nsIAtom *
196 nsSVGPathGeometryFrame::GetType() const
198 return nsGkAtoms::svgPathGeometryFrame;
201 bool
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();
209 if (parent &&
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()) {
220 if (aOwnTransform) {
221 *aOwnTransform = gfx::ToMatrix(content->PrependLocalTransformsTo(gfxMatrix(),
222 nsSVGElement::eUserSpaceToParent));
224 foundTransform = true;
226 return foundTransform;
229 void
230 nsSVGPathGeometryFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
231 const nsRect& aDirtyRect,
232 const nsDisplayListSet& aLists)
234 if (!static_cast<const nsSVGElement*>(mContent)->HasValidDimensions()) {
235 return;
237 aLists.Content()->AppendNewToTop(
238 new (aBuilder) nsDisplaySVGPathGeometry(aBuilder, this));
241 //----------------------------------------------------------------------
242 // nsISVGChildFrame methods
244 nsresult
245 nsSVGPathGeometryFrame::PaintSVG(gfxContext& aContext,
246 const gfxMatrix& aTransform,
247 const nsIntRect* aDirtyRect)
249 if (!StyleVisibility()->IsVisible())
250 return NS_OK;
252 // Matrix to the geometry's user space:
253 gfxMatrix newMatrix =
254 aContext.CurrentMatrix().PreMultiply(aTransform).NudgeToIntegers();
255 if (newMatrix.IsSingular()) {
256 return NS_OK;
259 uint32_t paintOrder = StyleSVG()->mPaintOrder;
260 if (paintOrder == NS_STYLE_PAINT_ORDER_NORMAL) {
261 Render(&aContext, eRenderFill | eRenderStroke, newMatrix);
262 PaintMarkers(aContext, aTransform);
263 } else {
264 while (paintOrder) {
265 uint32_t component =
266 paintOrder & ((1 << NS_STYLE_PAINT_ORDER_BITWIDTH) - 1);
267 switch (component) {
268 case NS_STYLE_PAINT_ORDER_FILL:
269 Render(&aContext, eRenderFill, newMatrix);
270 break;
271 case NS_STYLE_PAINT_ORDER_STROKE:
272 Render(&aContext, eRenderStroke, newMatrix);
273 break;
274 case NS_STYLE_PAINT_ORDER_MARKERS:
275 PaintMarkers(aContext, aTransform);
276 break;
278 paintOrder >>= NS_STYLE_PAINT_ORDER_BITWIDTH;
282 return NS_OK;
285 nsIFrame*
286 nsSVGPathGeometryFrame::GetFrameForPoint(const gfxPoint& aPoint)
288 FillRule fillRule;
289 uint16_t hitTestFlags;
290 if (GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD) {
291 hitTestFlags = SVG_HIT_TEST_FILL;
292 fillRule = nsSVGUtils::ToFillRule(StyleSVG()->mClipRule);
293 } else {
294 hitTestFlags = GetHitTestFlags();
295 if (!hitTestFlags) {
296 return nullptr;
298 if (hitTestFlags & SVG_HIT_TEST_CHECK_MRECT) {
299 gfxRect rect =
300 nsLayoutUtils::RectToGfxRect(mRect, PresContext()->AppUnitsPerCSSPixel());
301 if (!rect.Contains(aPoint)) {
302 return nullptr;
305 fillRule = nsSVGUtils::ToFillRule(StyleSVG()->mFillRule);
308 bool isHit = false;
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);
319 if (!path) {
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))
345 return this;
347 return nullptr;
350 nsRect
351 nsSVGPathGeometryFrame::GetCoveredRegion()
353 return nsSVGUtils::TransformFrameRectToOuterSVG(
354 mRect, GetCanvasTM(), PresContext());
357 void
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)) {
367 return;
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)) {
407 InvalidateFrame();
411 void
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
435 // mRect.
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);
450 SVGBBox
451 nsSVGPathGeometryFrame::GetBBoxContribution(const Matrix &aToBBoxUserspace,
452 uint32_t aFlags)
454 SVGBBox bbox;
456 if (aToBBoxUserspace.IsSingular()) {
457 // XXX ReportToConsole
458 return bbox;
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;
475 Rect simpleBounds;
476 gotSimpleBounds = element->GetGeometryBounds(&simpleBounds, strokeWidth,
477 aToBBoxUserspace);
478 if (gotSimpleBounds) {
479 bbox = simpleBounds;
483 if (!gotSimpleBounds) {
484 // Get the bounds using a Moz2D Path object (more expensive):
485 RefPtr<DrawTarget> tmpDT;
486 #ifdef XP_WIN
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));
496 #else
497 tmpDT = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
498 #endif
500 FillRule fillRule = nsSVGUtils::ToFillRule(StyleSVG()->mFillRule);
501 RefPtr<Path> pathInUserSpace = element->GetOrBuildPath(*tmpDT, fillRule);
502 if (!pathInUserSpace) {
503 return bbox;
505 RefPtr<Path> pathInBBoxSpace;
506 if (aToBBoxUserspace.IsIdentity()) {
507 pathInBBoxSpace = pathInUserSpace;
508 } else {
509 RefPtr<PathBuilder> builder =
510 pathInUserSpace->TransformedCopyToBuilder(aToBBoxUserspace, fillRule);
511 pathInBBoxSpace = builder->Finish();
512 if (!pathInBBoxSpace) {
513 return bbox;
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.
533 return bbox;
536 // Account for fill:
537 if (getFill) {
538 bbox = pathBBoxExtents;
541 // Account for stroke:
542 if (getStroke) {
543 #if 0
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();
572 strokeBBoxExtents =
573 pathInOuterSVGSpace->GetStrokedBounds(strokeOptions, outerSVGToBBox);
574 } else {
575 strokeBBoxExtents =
576 pathInUserSpace->GetStrokedBounds(strokeOptions, aToBBoxUserspace);
578 MOZ_ASSERT(strokeBBoxExtents.IsFinite(), "bbox is about to go bad");
579 bbox.UnionEdges(strokeBBoxExtents);
580 #else
581 // For now we just use nsSVGUtils::PathExtentsToMaxStrokeExtents:
582 gfxRect strokeBBoxExtents =
583 nsSVGUtils::PathExtentsToMaxStrokeExtents(ThebesRect(pathBBoxExtents),
584 this,
585 ThebesMatrix(aToBBoxUserspace));
586 MOZ_ASSERT(ToRect(strokeBBoxExtents).IsFinite(), "bbox is about to go bad");
587 bbox.UnionEdges(strokeBBoxExtents);
588 #endif
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];
615 if (frame) {
616 SVGBBox mbbox =
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);
626 return bbox;
629 //----------------------------------------------------------------------
630 // nsSVGPathGeometryFrame methods:
632 gfxMatrix
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());
653 result.mMarkerMid =
654 nsSVGEffects::GetMarkerProperty(style->mMarkerMid, aFrame,
655 nsSVGEffects::MarkerMiddleProperty());
656 result.mMarkerEnd =
657 nsSVGEffects::GetMarkerProperty(style->mMarkerEnd, aFrame,
658 nsSVGEffects::MarkerEndProperty());
659 return result;
662 nsSVGMarkerFrame *
663 nsSVGPathGeometryFrame::MarkerProperties::GetMarkerStartFrame()
665 if (!mMarkerStart)
666 return nullptr;
667 return static_cast<nsSVGMarkerFrame *>
668 (mMarkerStart->GetReferencedFrame(nsGkAtoms::svgMarkerFrame, nullptr));
671 nsSVGMarkerFrame *
672 nsSVGPathGeometryFrame::MarkerProperties::GetMarkerMidFrame()
674 if (!mMarkerMid)
675 return nullptr;
676 return static_cast<nsSVGMarkerFrame *>
677 (mMarkerMid->GetReferencedFrame(nsGkAtoms::svgMarkerFrame, nullptr));
680 nsSVGMarkerFrame *
681 nsSVGPathGeometryFrame::MarkerProperties::GetMarkerEndFrame()
683 if (!mMarkerEnd)
684 return nullptr;
685 return static_cast<nsSVGMarkerFrame *>
686 (mMarkerEnd->GetReferencedFrame(nsGkAtoms::svgMarkerFrame, nullptr));
689 void
690 nsSVGPathGeometryFrame::Render(gfxContext* aContext,
691 uint32_t aRenderComponents,
692 const gfxMatrix& aNewTransform)
694 MOZ_ASSERT(!aNewTransform.IsSingular());
696 DrawTarget* drawTarget = aContext->GetDrawTarget();
698 FillRule fillRule =
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
712 // some backends).
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);
720 if (path) {
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));
725 return;
728 nsSVGPathGeometryElement::SimplePath simplePath;
729 RefPtr<Path> path;
731 element->GetAsSimplePath(&simplePath);
732 if (!simplePath.IsPath()) {
733 path = element->GetOrBuildPath(*drawTarget, fillRule);
734 if (!path) {
735 return;
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);
750 } else if (path) {
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:
763 if (!path) {
764 path = element->GetOrBuildPath(*drawTarget, fillRule);
765 if (!path) {
766 return;
768 simplePath.Reset();
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) {
789 return;
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);
798 } else {
799 drawTarget->Stroke(path, strokePattern, strokeOptions, drawOptions);
805 void
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();
823 if (num) {
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];
835 if (frame) {
836 frame->PaintMark(aContext, aTransform, this, &mark, strokeWidth);
844 uint16_t
845 nsSVGPathGeometryFrame::GetHitTestFlags()
847 return nsSVGUtils::GetGeometryHitTestFlags(this);