Line data Source code
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/. */
5 :
6 : // Main header first:
7 : #include "SVGGeometryFrame.h"
8 :
9 : // Keep others in (case-insensitive) order:
10 : #include "gfx2DGlue.h"
11 : #include "gfxContext.h"
12 : #include "gfxPlatform.h"
13 : #include "gfxUtils.h"
14 : #include "mozilla/gfx/2D.h"
15 : #include "mozilla/gfx/Helpers.h"
16 : #include "mozilla/RefPtr.h"
17 : #include "mozilla/SVGContextPaint.h"
18 : #include "nsDisplayList.h"
19 : #include "nsGkAtoms.h"
20 : #include "nsLayoutUtils.h"
21 : #include "nsSVGEffects.h"
22 : #include "nsSVGIntegrationUtils.h"
23 : #include "nsSVGMarkerFrame.h"
24 : #include "SVGGeometryElement.h"
25 : #include "nsSVGUtils.h"
26 : #include "mozilla/ArrayUtils.h"
27 : #include "SVGAnimatedTransformList.h"
28 : #include "SVGContentUtils.h"
29 : #include "SVGGraphicsElement.h"
30 :
31 : using namespace mozilla;
32 : using namespace mozilla::dom;
33 : using namespace mozilla::gfx;
34 : using namespace mozilla::image;
35 :
36 : //----------------------------------------------------------------------
37 : // Implementation
38 :
39 : nsIFrame*
40 73 : NS_NewSVGGeometryFrame(nsIPresShell* aPresShell,
41 : nsStyleContext* aContext)
42 : {
43 73 : return new (aPresShell) SVGGeometryFrame(aContext);
44 : }
45 :
46 73 : NS_IMPL_FRAMEARENA_HELPERS(SVGGeometryFrame)
47 :
48 : //----------------------------------------------------------------------
49 : // nsQueryFrame methods
50 :
51 230 : NS_QUERYFRAME_HEAD(SVGGeometryFrame)
52 138 : NS_QUERYFRAME_ENTRY(nsSVGDisplayableFrame)
53 2 : NS_QUERYFRAME_ENTRY(SVGGeometryFrame)
54 90 : NS_QUERYFRAME_TAIL_INHERITING(nsFrame)
55 :
56 : //----------------------------------------------------------------------
57 : // Display list item:
58 :
59 : class nsDisplaySVGGeometry : public nsDisplayItem {
60 : typedef mozilla::image::imgDrawingParams imgDrawingParams;
61 :
62 : public:
63 34 : nsDisplaySVGGeometry(nsDisplayListBuilder* aBuilder,
64 : SVGGeometryFrame* aFrame)
65 34 : : nsDisplayItem(aBuilder, aFrame)
66 : {
67 34 : MOZ_COUNT_CTOR(nsDisplaySVGGeometry);
68 34 : MOZ_ASSERT(aFrame, "Must have a frame!");
69 34 : }
70 : #ifdef NS_BUILD_REFCNT_LOGGING
71 68 : virtual ~nsDisplaySVGGeometry() {
72 34 : MOZ_COUNT_DTOR(nsDisplaySVGGeometry);
73 34 : }
74 : #endif
75 :
76 314 : NS_DISPLAY_DECL_NAME("nsDisplaySVGGeometry", TYPE_SVG_GEOMETRY)
77 :
78 : virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
79 : HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) override;
80 : virtual void Paint(nsDisplayListBuilder* aBuilder,
81 : gfxContext* aCtx) override;
82 :
83 0 : nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override
84 : {
85 0 : return new nsDisplayItemGenericImageGeometry(this, aBuilder);
86 : }
87 :
88 : void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
89 : const nsDisplayItemGeometry* aGeometry,
90 : nsRegion *aInvalidRegion) override;
91 : };
92 :
93 : void
94 0 : nsDisplaySVGGeometry::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
95 : HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames)
96 : {
97 0 : SVGGeometryFrame *frame = static_cast<SVGGeometryFrame*>(mFrame);
98 0 : nsPoint pointRelativeToReferenceFrame = aRect.Center();
99 : // ToReferenceFrame() includes frame->GetPosition(), our user space position.
100 : nsPoint userSpacePtInAppUnits = pointRelativeToReferenceFrame -
101 0 : (ToReferenceFrame() - frame->GetPosition());
102 : gfxPoint userSpacePt =
103 0 : gfxPoint(userSpacePtInAppUnits.x, userSpacePtInAppUnits.y) /
104 0 : frame->PresContext()->AppUnitsPerCSSPixel();
105 0 : if (frame->GetFrameForPoint(userSpacePt)) {
106 0 : aOutFrames->AppendElement(frame);
107 : }
108 0 : }
109 :
110 : void
111 34 : nsDisplaySVGGeometry::Paint(nsDisplayListBuilder* aBuilder,
112 : gfxContext* aCtx)
113 : {
114 34 : uint32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
115 :
116 : // ToReferenceFrame includes our mRect offset, but painting takes
117 : // account of that too. To avoid double counting, we subtract that
118 : // here.
119 34 : nsPoint offset = ToReferenceFrame() - mFrame->GetPosition();
120 :
121 : gfxPoint devPixelOffset =
122 34 : nsLayoutUtils::PointToGfxPoint(offset, appUnitsPerDevPixel);
123 :
124 68 : gfxMatrix tm = nsSVGUtils::GetCSSPxToDevPxMatrix(mFrame) *
125 102 : gfxMatrix::Translation(devPixelOffset);
126 34 : imgDrawingParams imgParams(aBuilder->ShouldSyncDecodeImages()
127 : ? imgIContainer::FLAG_SYNC_DECODE
128 34 : : imgIContainer::FLAG_SYNC_DECODE_IF_FAST);
129 :
130 68 : static_cast<SVGGeometryFrame*>(mFrame)->PaintSVG(*aCtx,
131 68 : tm, imgParams);
132 :
133 34 : nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, imgParams.result);
134 34 : }
135 :
136 : void
137 0 : nsDisplaySVGGeometry::ComputeInvalidationRegion(
138 : nsDisplayListBuilder* aBuilder,
139 : const nsDisplayItemGeometry* aGeometry,
140 : nsRegion* aInvalidRegion)
141 : {
142 : auto geometry =
143 0 : static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry);
144 :
145 0 : if (aBuilder->ShouldSyncDecodeImages() &&
146 0 : geometry->ShouldInvalidateToSyncDecodeImages()) {
147 : bool snap;
148 0 : aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
149 : }
150 :
151 0 : nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion);
152 0 : }
153 :
154 : namespace mozilla {
155 :
156 : //----------------------------------------------------------------------
157 : // nsIFrame methods
158 :
159 : void
160 73 : SVGGeometryFrame::Init(nsIContent* aContent,
161 : nsContainerFrame* aParent,
162 : nsIFrame* aPrevInFlow)
163 : {
164 73 : AddStateBits(aParent->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD);
165 73 : nsFrame::Init(aContent, aParent, aPrevInFlow);
166 73 : }
167 :
168 : nsresult
169 0 : SVGGeometryFrame::AttributeChanged(int32_t aNameSpaceID,
170 : nsIAtom* aAttribute,
171 : int32_t aModType)
172 : {
173 : // We don't invalidate for transform changes (the layers code does that).
174 : // Also note that SVGTransformableElement::GetAttributeChangeHint will
175 : // return nsChangeHint_UpdateOverflow for "transform" attribute changes
176 : // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call.
177 :
178 0 : if (aNameSpaceID == kNameSpaceID_None &&
179 : (static_cast<SVGGeometryElement*>
180 0 : (mContent)->AttributeDefinesGeometry(aAttribute))) {
181 0 : nsLayoutUtils::PostRestyleEvent(
182 0 : mContent->AsElement(), nsRestyleHint(0),
183 0 : nsChangeHint_InvalidateRenderingObservers);
184 0 : nsSVGUtils::ScheduleReflowSVG(this);
185 : }
186 0 : return NS_OK;
187 : }
188 :
189 : /* virtual */ void
190 89 : SVGGeometryFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
191 : {
192 89 : nsFrame::DidSetStyleContext(aOldStyleContext);
193 :
194 89 : if (aOldStyleContext) {
195 16 : auto oldStyleEffects = aOldStyleContext->PeekStyleEffects();
196 30 : if (oldStyleEffects &&
197 16 : StyleEffects()->mOpacity != oldStyleEffects->mOpacity &&
198 0 : nsSVGUtils::CanOptimizeOpacity(this)) {
199 : // nsIFrame::BuildDisplayListForStackingContext() is not going to create an
200 : // nsDisplayOpacity display list item, so DLBI won't invalidate for us.
201 0 : InvalidateFrame();
202 : }
203 :
204 : SVGGeometryElement* element =
205 16 : static_cast<SVGGeometryElement*>(mContent);
206 :
207 16 : auto oldStyleSVG = aOldStyleContext->PeekStyleSVG();
208 16 : if (oldStyleSVG && !SVGContentUtils::ShapeTypeHasNoCorners(mContent)) {
209 2 : if (StyleSVG()->mStrokeLinecap != oldStyleSVG->mStrokeLinecap &&
210 0 : element->IsSVGElement(nsGkAtoms::path)) {
211 : // If the stroke-linecap changes to or from "butt" then our element
212 : // needs to update its cached Moz2D Path, since SVGPathData::BuildPath
213 : // decides whether or not to insert little lines into the path for zero
214 : // length subpaths base on that property.
215 0 : element->ClearAnyCachedPath();
216 2 : } else if (GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD) {
217 2 : if (StyleSVG()->mClipRule != oldStyleSVG->mClipRule) {
218 : // Moz2D Path objects are fill-rule specific.
219 : // For clipPath we use clip-rule as the path's fill-rule.
220 0 : element->ClearAnyCachedPath();
221 : }
222 : } else {
223 0 : if (StyleSVG()->mFillRule != oldStyleSVG->mFillRule) {
224 : // Moz2D Path objects are fill-rule specific.
225 0 : element->ClearAnyCachedPath();
226 : }
227 : }
228 : }
229 : }
230 89 : }
231 :
232 : bool
233 150 : SVGGeometryFrame::IsSVGTransformed(gfx::Matrix *aOwnTransform,
234 : gfx::Matrix *aFromParentTransform) const
235 : {
236 150 : bool foundTransform = false;
237 :
238 : // Check if our parent has children-only transforms:
239 150 : nsIFrame *parent = GetParent();
240 300 : if (parent &&
241 150 : parent->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer)) {
242 : foundTransform = static_cast<nsSVGContainerFrame*>(parent)->
243 150 : HasChildrenOnlyTransform(aFromParentTransform);
244 : }
245 :
246 150 : nsSVGElement *content = static_cast<nsSVGElement*>(mContent);
247 : nsSVGAnimatedTransformList* transformList =
248 150 : content->GetAnimatedTransformList();
249 300 : if ((transformList && transformList->HasTransform()) ||
250 150 : content->GetAnimateMotionTransform()) {
251 0 : if (aOwnTransform) {
252 : *aOwnTransform = gfx::ToMatrix(
253 0 : content->PrependLocalTransformsTo(
254 0 : gfxMatrix(),
255 0 : eUserSpaceToParent));
256 : }
257 0 : foundTransform = true;
258 : }
259 150 : return foundTransform;
260 : }
261 :
262 : void
263 34 : SVGGeometryFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
264 : const nsRect& aDirtyRect,
265 : const nsDisplayListSet& aLists)
266 : {
267 68 : if (!static_cast<const nsSVGElement*>(mContent)->HasValidDimensions() ||
268 34 : (!IsVisibleForPainting(aBuilder) && aBuilder->IsForPainting())) {
269 0 : return;
270 : }
271 34 : DisplayOutline(aBuilder, aLists);
272 34 : aLists.Content()->AppendNewToTop(
273 68 : new (aBuilder) nsDisplaySVGGeometry(aBuilder, this));
274 : }
275 :
276 : //----------------------------------------------------------------------
277 : // nsSVGDisplayableFrame methods
278 :
279 : void
280 34 : SVGGeometryFrame::PaintSVG(gfxContext& aContext,
281 : const gfxMatrix& aTransform,
282 : imgDrawingParams& aImgParams,
283 : const nsIntRect* aDirtyRect)
284 : {
285 34 : if (!StyleVisibility()->IsVisible())
286 0 : return;
287 :
288 : // Matrix to the geometry's user space:
289 : gfxMatrix newMatrix =
290 34 : aContext.CurrentMatrix().PreMultiply(aTransform).NudgeToIntegers();
291 34 : if (newMatrix.IsSingular()) {
292 0 : return;
293 : }
294 :
295 34 : uint32_t paintOrder = StyleSVG()->mPaintOrder;
296 :
297 34 : if (paintOrder == NS_STYLE_PAINT_ORDER_NORMAL) {
298 34 : Render(&aContext, eRenderFill | eRenderStroke, newMatrix, aImgParams);
299 34 : PaintMarkers(aContext, aTransform, aImgParams);
300 : } else {
301 0 : while (paintOrder) {
302 : uint32_t component =
303 0 : paintOrder & ((1 << NS_STYLE_PAINT_ORDER_BITWIDTH) - 1);
304 0 : switch (component) {
305 : case NS_STYLE_PAINT_ORDER_FILL:
306 0 : Render(&aContext, eRenderFill, newMatrix, aImgParams);
307 0 : break;
308 : case NS_STYLE_PAINT_ORDER_STROKE:
309 0 : Render(&aContext, eRenderStroke, newMatrix, aImgParams);
310 0 : break;
311 : case NS_STYLE_PAINT_ORDER_MARKERS:
312 0 : PaintMarkers(aContext, aTransform, aImgParams);
313 0 : break;
314 : }
315 0 : paintOrder >>= NS_STYLE_PAINT_ORDER_BITWIDTH;
316 : }
317 : }
318 : }
319 :
320 : nsIFrame*
321 0 : SVGGeometryFrame::GetFrameForPoint(const gfxPoint& aPoint)
322 : {
323 : FillRule fillRule;
324 : uint16_t hitTestFlags;
325 0 : if (GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD) {
326 0 : hitTestFlags = SVG_HIT_TEST_FILL;
327 0 : fillRule = nsSVGUtils::ToFillRule(StyleSVG()->mClipRule);
328 : } else {
329 0 : hitTestFlags = GetHitTestFlags();
330 0 : if (!hitTestFlags) {
331 0 : return nullptr;
332 : }
333 0 : if (hitTestFlags & SVG_HIT_TEST_CHECK_MRECT) {
334 : gfxRect rect =
335 0 : nsLayoutUtils::RectToGfxRect(mRect, PresContext()->AppUnitsPerCSSPixel());
336 0 : if (!rect.Contains(aPoint)) {
337 0 : return nullptr;
338 : }
339 : }
340 0 : fillRule = nsSVGUtils::ToFillRule(StyleSVG()->mFillRule);
341 : }
342 :
343 0 : bool isHit = false;
344 :
345 : SVGGeometryElement* content =
346 0 : static_cast<SVGGeometryElement*>(mContent);
347 :
348 : // Using ScreenReferenceDrawTarget() opens us to Moz2D backend specific hit-
349 : // testing bugs. Maybe we should use a BackendType::CAIRO DT for hit-testing
350 : // so that we get more consistent/backwards compatible results?
351 : RefPtr<DrawTarget> drawTarget =
352 0 : gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
353 0 : RefPtr<Path> path = content->GetOrBuildPath(*drawTarget, fillRule);
354 0 : if (!path) {
355 0 : return nullptr; // no path, so we don't paint anything that can be hit
356 : }
357 :
358 0 : if (hitTestFlags & SVG_HIT_TEST_FILL) {
359 0 : isHit = path->ContainsPoint(ToPoint(aPoint), Matrix());
360 : }
361 0 : if (!isHit && (hitTestFlags & SVG_HIT_TEST_STROKE)) {
362 0 : Point point = ToPoint(aPoint);
363 0 : SVGContentUtils::AutoStrokeOptions stroke;
364 0 : SVGContentUtils::GetStrokeOptions(&stroke, content, StyleContext(), nullptr);
365 0 : gfxMatrix userToOuterSVG;
366 0 : if (nsSVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) {
367 : // We need to transform the path back into the appropriate ancestor
368 : // coordinate system in order for non-scaled stroke to be correct.
369 : // Naturally we also need to transform the point into the same
370 : // coordinate system in order to hit-test against the path.
371 0 : point = ToMatrix(userToOuterSVG).TransformPoint(point);
372 : RefPtr<PathBuilder> builder =
373 0 : path->TransformedCopyToBuilder(ToMatrix(userToOuterSVG), fillRule);
374 0 : path = builder->Finish();
375 : }
376 0 : isHit = path->StrokeContainsPoint(stroke, point, Matrix());
377 : }
378 :
379 0 : if (isHit && nsSVGUtils::HitTestClip(this, aPoint))
380 0 : return this;
381 :
382 0 : return nullptr;
383 : }
384 :
385 : void
386 82 : SVGGeometryFrame::ReflowSVG()
387 : {
388 82 : NS_ASSERTION(nsSVGUtils::OuterSVGIsCallingReflowSVG(this),
389 : "This call is probably a wasteful mistake");
390 :
391 82 : MOZ_ASSERT(!(GetStateBits() & NS_FRAME_IS_NONDISPLAY),
392 : "ReflowSVG mechanism not designed for this");
393 :
394 82 : if (!nsSVGUtils::NeedsReflowSVG(this)) {
395 0 : return;
396 : }
397 :
398 : uint32_t flags = nsSVGUtils::eBBoxIncludeFill |
399 : nsSVGUtils::eBBoxIncludeStroke |
400 82 : nsSVGUtils::eBBoxIncludeMarkers;
401 : // Our "visual" overflow rect needs to be valid for building display lists
402 : // for hit testing, which means that for certain values of 'pointer-events'
403 : // it needs to include the geometry of the fill or stroke even when the fill/
404 : // stroke don't actually render (e.g. when stroke="none" or
405 : // stroke-opacity="0"). GetHitTestFlags() accounts for 'pointer-events'.
406 82 : uint16_t hitTestFlags = GetHitTestFlags();
407 82 : if ((hitTestFlags & SVG_HIT_TEST_FILL)) {
408 78 : flags |= nsSVGUtils::eBBoxIncludeFillGeometry;
409 : }
410 82 : if ((hitTestFlags & SVG_HIT_TEST_STROKE)) {
411 4 : flags |= nsSVGUtils::eBBoxIncludeStrokeGeometry;
412 : }
413 :
414 82 : gfxRect extent = GetBBoxContribution(Matrix(), flags).ToThebesRect();
415 82 : mRect = nsLayoutUtils::RoundGfxRectToAppRect(extent,
416 82 : PresContext()->AppUnitsPerCSSPixel());
417 :
418 82 : if (mState & NS_FRAME_FIRST_REFLOW) {
419 : // Make sure we have our filter property (if any) before calling
420 : // FinishAndStoreOverflow (subsequent filter changes are handled off
421 : // nsChangeHint_UpdateEffects):
422 48 : nsSVGEffects::UpdateEffects(this);
423 : }
424 :
425 164 : nsRect overflow = nsRect(nsPoint(0,0), mRect.Size());
426 164 : nsOverflowAreas overflowAreas(overflow, overflow);
427 82 : FinishAndStoreOverflow(overflowAreas, mRect.Size());
428 :
429 : mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
430 82 : NS_FRAME_HAS_DIRTY_CHILDREN);
431 :
432 : // Invalidate, but only if this is not our first reflow (since if it is our
433 : // first reflow then we haven't had our first paint yet).
434 82 : if (!(GetParent()->GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
435 34 : InvalidateFrame();
436 : }
437 : }
438 :
439 : void
440 50 : SVGGeometryFrame::NotifySVGChanged(uint32_t aFlags)
441 : {
442 50 : MOZ_ASSERT(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED),
443 : "Invalidation logic may need adjusting");
444 :
445 : // Changes to our ancestors may affect how we render when we are rendered as
446 : // part of our ancestor (specifically, if our coordinate context changes size
447 : // and we have percentage lengths defining our geometry, then we need to be
448 : // reflowed). However, ancestor changes cannot affect how we render when we
449 : // are rendered as part of any rendering observers that we may have.
450 : // Therefore no need to notify rendering observers here.
451 :
452 : // Don't try to be too smart trying to avoid the ScheduleReflowSVG calls
453 : // for the stroke properties examined below. Checking HasStroke() is not
454 : // enough, since what we care about is whether we include the stroke in our
455 : // overflow rects or not, and we sometimes deliberately include stroke
456 : // when it's not visible. See the complexities of GetBBoxContribution.
457 :
458 50 : if (aFlags & COORD_CONTEXT_CHANGED) {
459 : // Stroke currently contributes to our mRect, which is why we have to take
460 : // account of stroke-width here. Note that we do not need to take account
461 : // of stroke-dashoffset since, although that can have a percentage value
462 : // that is resolved against our coordinate context, it does not affect our
463 : // mRect.
464 34 : if (static_cast<SVGGeometryElement*>(mContent)->GeometryDependsOnCoordCtx() ||
465 17 : StyleSVG()->mStrokeWidth.HasPercent()) {
466 0 : static_cast<SVGGeometryElement*>(mContent)->ClearAnyCachedPath();
467 0 : nsSVGUtils::ScheduleReflowSVG(this);
468 : }
469 : }
470 :
471 50 : if ((aFlags & TRANSFORM_CHANGED) && StyleSVGReset()->HasNonScalingStroke()) {
472 : // Stroke currently contributes to our mRect, and our stroke depends on
473 : // the transform to our outer-<svg> if |vector-effect:non-scaling-stroke|.
474 0 : nsSVGUtils::ScheduleReflowSVG(this);
475 : }
476 50 : }
477 :
478 : SVGBBox
479 82 : SVGGeometryFrame::GetBBoxContribution(const Matrix &aToBBoxUserspace,
480 : uint32_t aFlags)
481 : {
482 82 : SVGBBox bbox;
483 :
484 82 : if (aToBBoxUserspace.IsSingular()) {
485 : // XXX ReportToConsole
486 0 : return bbox;
487 : }
488 :
489 82 : if ((aFlags & nsSVGUtils::eForGetClientRects) &&
490 0 : aToBBoxUserspace.PreservesAxisAlignedRectangles()) {
491 0 : Rect rect = NSRectToRect(mRect, PresContext()->AppUnitsPerCSSPixel());
492 0 : bbox = aToBBoxUserspace.TransformBounds(rect);
493 0 : return bbox;
494 : }
495 :
496 : SVGGeometryElement* element =
497 82 : static_cast<SVGGeometryElement*>(mContent);
498 :
499 164 : bool getFill = (aFlags & nsSVGUtils::eBBoxIncludeFillGeometry) ||
500 8 : ((aFlags & nsSVGUtils::eBBoxIncludeFill) &&
501 86 : StyleSVG()->mFill.Type() != eStyleSVGPaintType_None);
502 :
503 164 : bool getStroke = (aFlags & nsSVGUtils::eBBoxIncludeStrokeGeometry) ||
504 156 : ((aFlags & nsSVGUtils::eBBoxIncludeStroke) &&
505 160 : nsSVGUtils::HasStroke(this));
506 :
507 164 : SVGContentUtils::AutoStrokeOptions strokeOptions;
508 82 : if (getStroke) {
509 4 : SVGContentUtils::GetStrokeOptions(&strokeOptions, element,
510 : StyleContext(), nullptr,
511 4 : SVGContentUtils::eIgnoreStrokeDashing);
512 : } else {
513 : // Override the default line width of 1.f so that when we call
514 : // GetGeometryBounds below the result doesn't include stroke bounds.
515 78 : strokeOptions.mLineWidth = 0.f;
516 : }
517 :
518 82 : Rect simpleBounds;
519 82 : bool gotSimpleBounds = false;
520 82 : gfxMatrix userToOuterSVG;
521 86 : if (getStroke &&
522 4 : nsSVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) {
523 0 : Matrix moz2dUserToOuterSVG = ToMatrix(userToOuterSVG);
524 0 : if (moz2dUserToOuterSVG.IsSingular()) {
525 0 : return bbox;
526 : }
527 : gotSimpleBounds = element->GetGeometryBounds(&simpleBounds,
528 : strokeOptions,
529 : aToBBoxUserspace,
530 0 : &moz2dUserToOuterSVG);
531 : } else {
532 : gotSimpleBounds = element->GetGeometryBounds(&simpleBounds,
533 : strokeOptions,
534 82 : aToBBoxUserspace);
535 : }
536 :
537 82 : if (gotSimpleBounds) {
538 42 : bbox = simpleBounds;
539 : } else {
540 : // Get the bounds using a Moz2D Path object (more expensive):
541 80 : RefPtr<DrawTarget> tmpDT;
542 : #ifdef XP_WIN
543 : // Unfortunately D2D backed DrawTarget produces bounds with rounding errors
544 : // when whole number results are expected, even in the case of trivial
545 : // calculations. To avoid that and meet the expectations of web content we
546 : // have to use a CAIRO DrawTarget. The most efficient way to do that is to
547 : // wrap the cached cairo_surface_t from ScreenReferenceSurface():
548 : RefPtr<gfxASurface> refSurf =
549 : gfxPlatform::GetPlatform()->ScreenReferenceSurface();
550 : tmpDT = gfxPlatform::GetPlatform()->
551 : CreateDrawTargetForSurface(refSurf, IntSize(1, 1));
552 : #else
553 40 : tmpDT = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
554 : #endif
555 :
556 40 : FillRule fillRule = nsSVGUtils::ToFillRule(StyleSVG()->mFillRule);
557 80 : RefPtr<Path> pathInUserSpace = element->GetOrBuildPath(*tmpDT, fillRule);
558 40 : if (!pathInUserSpace) {
559 0 : return bbox;
560 : }
561 80 : RefPtr<Path> pathInBBoxSpace;
562 40 : if (aToBBoxUserspace.IsIdentity()) {
563 40 : pathInBBoxSpace = pathInUserSpace;
564 : } else {
565 : RefPtr<PathBuilder> builder =
566 0 : pathInUserSpace->TransformedCopyToBuilder(aToBBoxUserspace, fillRule);
567 0 : pathInBBoxSpace = builder->Finish();
568 0 : if (!pathInBBoxSpace) {
569 0 : return bbox;
570 : }
571 : }
572 :
573 : // Be careful when replacing the following logic to get the fill and stroke
574 : // extents independently (instead of computing the stroke extents from the
575 : // path extents). You may think that you can just use the stroke extents if
576 : // there is both a fill and a stroke. In reality it's necessary to
577 : // calculate both the fill and stroke extents, and take the union of the
578 : // two. There are two reasons for this:
579 : //
580 : // # Due to stroke dashing, in certain cases the fill extents could
581 : // actually extend outside the stroke extents.
582 : // # If the stroke is very thin, cairo won't paint any stroke, and so the
583 : // stroke bounds that it will return will be empty.
584 :
585 40 : Rect pathBBoxExtents = pathInBBoxSpace->GetBounds();
586 40 : if (!pathBBoxExtents.IsFinite()) {
587 : // This can happen in the case that we only have a move-to command in the
588 : // path commands, in which case we know nothing gets rendered.
589 0 : return bbox;
590 : }
591 :
592 : // Account for fill:
593 40 : if (getFill) {
594 40 : bbox = pathBBoxExtents;
595 : }
596 :
597 : // Account for stroke:
598 40 : if (getStroke) {
599 : #if 0
600 : // This disabled code is how we would calculate the stroke bounds using
601 : // Moz2D Path::GetStrokedBounds(). Unfortunately at the time of writing
602 : // it there are two problems that prevent us from using it.
603 : //
604 : // First, it seems that some of the Moz2D backends are really dumb. Not
605 : // only do some GetStrokeOptions() implementations sometimes
606 : // significantly overestimate the stroke bounds, but if an argument is
607 : // passed for the aTransform parameter then they just return bounds-of-
608 : // transformed-bounds. These two things combined can lead the bounds to
609 : // be unacceptably oversized, leading to massive over-invalidation.
610 : //
611 : // Second, the way we account for non-scaling-stroke by transforming the
612 : // path using the transform to the outer-<svg> element is not compatible
613 : // with the way that SVGGeometryFrame::Reflow() inserts a scale
614 : // into aToBBoxUserspace and then scales the bounds that we return.
615 : SVGContentUtils::AutoStrokeOptions strokeOptions;
616 : SVGContentUtils::GetStrokeOptions(&strokeOptions, element,
617 : StyleContext(), nullptr,
618 : SVGContentUtils::eIgnoreStrokeDashing);
619 : Rect strokeBBoxExtents;
620 : gfxMatrix userToOuterSVG;
621 : if (nsSVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) {
622 : Matrix outerSVGToUser = ToMatrix(userToOuterSVG);
623 : outerSVGToUser.Invert();
624 : Matrix outerSVGToBBox = aToBBoxUserspace * outerSVGToUser;
625 : RefPtr<PathBuilder> builder =
626 : pathInUserSpace->TransformedCopyToBuilder(ToMatrix(userToOuterSVG));
627 : RefPtr<Path> pathInOuterSVGSpace = builder->Finish();
628 : strokeBBoxExtents =
629 : pathInOuterSVGSpace->GetStrokedBounds(strokeOptions, outerSVGToBBox);
630 : } else {
631 : strokeBBoxExtents =
632 : pathInUserSpace->GetStrokedBounds(strokeOptions, aToBBoxUserspace);
633 : }
634 : MOZ_ASSERT(strokeBBoxExtents.IsFinite(), "bbox is about to go bad");
635 : bbox.UnionEdges(strokeBBoxExtents);
636 : #else
637 : // For now we just use nsSVGUtils::PathExtentsToMaxStrokeExtents:
638 : gfxRect strokeBBoxExtents =
639 0 : nsSVGUtils::PathExtentsToMaxStrokeExtents(ThebesRect(pathBBoxExtents),
640 : this,
641 0 : ThebesMatrix(aToBBoxUserspace));
642 0 : MOZ_ASSERT(ToRect(strokeBBoxExtents).IsFinite(), "bbox is about to go bad");
643 0 : bbox.UnionEdges(strokeBBoxExtents);
644 : #endif
645 : }
646 : }
647 :
648 : // Account for markers:
649 164 : if ((aFlags & nsSVGUtils::eBBoxIncludeMarkers) != 0 &&
650 82 : static_cast<SVGGeometryElement*>(mContent)->IsMarkable()) {
651 :
652 64 : float strokeWidth = nsSVGUtils::GetStrokeWidth(this);
653 64 : MarkerProperties properties = GetMarkerProperties(this);
654 :
655 64 : if (properties.MarkersExist()) {
656 0 : nsTArray<nsSVGMark> marks;
657 0 : static_cast<SVGGeometryElement*>(mContent)->GetMarkPoints(&marks);
658 0 : uint32_t num = marks.Length();
659 :
660 : // These are in the same order as the nsSVGMark::Type constants.
661 : nsSVGMarkerFrame* markerFrames[] = {
662 0 : properties.GetMarkerStartFrame(),
663 0 : properties.GetMarkerMidFrame(),
664 0 : properties.GetMarkerEndFrame(),
665 0 : };
666 : static_assert(MOZ_ARRAY_LENGTH(markerFrames) == nsSVGMark::eTypeCount,
667 : "Number of Marker frames should be equal to eTypeCount");
668 :
669 0 : for (uint32_t i = 0; i < num; i++) {
670 0 : const nsSVGMark& mark = marks[i];
671 0 : nsSVGMarkerFrame* frame = markerFrames[mark.type];
672 0 : if (frame) {
673 : SVGBBox mbbox =
674 : frame->GetMarkBBoxContribution(aToBBoxUserspace, aFlags, this,
675 0 : mark, strokeWidth);
676 0 : MOZ_ASSERT(mbbox.IsFinite(), "bbox is about to go bad");
677 0 : bbox.UnionEdges(mbbox);
678 : }
679 : }
680 : }
681 : }
682 :
683 82 : return bbox;
684 : }
685 :
686 : //----------------------------------------------------------------------
687 : // SVGGeometryFrame methods:
688 :
689 : gfxMatrix
690 0 : SVGGeometryFrame::GetCanvasTM()
691 : {
692 0 : NS_ASSERTION(GetParent(), "null parent");
693 :
694 0 : nsSVGContainerFrame *parent = static_cast<nsSVGContainerFrame*>(GetParent());
695 0 : SVGGraphicsElement *content = static_cast<SVGGraphicsElement*>(mContent);
696 :
697 0 : return content->PrependLocalTransformsTo(parent->GetCanvasTM());
698 : }
699 :
700 : SVGGeometryFrame::MarkerProperties
701 90 : SVGGeometryFrame::GetMarkerProperties(SVGGeometryFrame *aFrame)
702 : {
703 90 : NS_ASSERTION(!aFrame->GetPrevContinuation(), "aFrame should be first continuation");
704 :
705 : MarkerProperties result;
706 : nsCOMPtr<nsIURI> markerURL =
707 180 : nsSVGEffects::GetMarkerURI(aFrame, &nsStyleSVG::mMarkerStart);
708 90 : result.mMarkerStart =
709 90 : nsSVGEffects::GetMarkerProperty(markerURL, aFrame,
710 : nsSVGEffects::MarkerBeginProperty());
711 :
712 90 : markerURL = nsSVGEffects::GetMarkerURI(aFrame, &nsStyleSVG::mMarkerMid);
713 90 : result.mMarkerMid =
714 90 : nsSVGEffects::GetMarkerProperty(markerURL, aFrame,
715 : nsSVGEffects::MarkerMiddleProperty());
716 :
717 90 : markerURL = nsSVGEffects::GetMarkerURI(aFrame, &nsStyleSVG::mMarkerEnd);
718 90 : result.mMarkerEnd =
719 90 : nsSVGEffects::GetMarkerProperty(markerURL, aFrame,
720 : nsSVGEffects::MarkerEndProperty());
721 180 : return result;
722 : }
723 :
724 : nsSVGMarkerFrame *
725 0 : SVGGeometryFrame::MarkerProperties::GetMarkerStartFrame()
726 : {
727 0 : if (!mMarkerStart)
728 0 : return nullptr;
729 : return static_cast<nsSVGMarkerFrame*>(
730 0 : mMarkerStart->GetReferencedFrame(LayoutFrameType::SVGMarker, nullptr));
731 : }
732 :
733 : nsSVGMarkerFrame *
734 0 : SVGGeometryFrame::MarkerProperties::GetMarkerMidFrame()
735 : {
736 0 : if (!mMarkerMid)
737 0 : return nullptr;
738 : return static_cast<nsSVGMarkerFrame*>(
739 0 : mMarkerMid->GetReferencedFrame(LayoutFrameType::SVGMarker, nullptr));
740 : }
741 :
742 : nsSVGMarkerFrame *
743 0 : SVGGeometryFrame::MarkerProperties::GetMarkerEndFrame()
744 : {
745 0 : if (!mMarkerEnd)
746 0 : return nullptr;
747 : return static_cast<nsSVGMarkerFrame*>(
748 0 : mMarkerEnd->GetReferencedFrame(LayoutFrameType::SVGMarker, nullptr));
749 : }
750 :
751 : void
752 34 : SVGGeometryFrame::Render(gfxContext* aContext,
753 : uint32_t aRenderComponents,
754 : const gfxMatrix& aNewTransform,
755 : imgDrawingParams& aImgParams)
756 : {
757 34 : MOZ_ASSERT(!aNewTransform.IsSingular());
758 :
759 34 : DrawTarget* drawTarget = aContext->GetDrawTarget();
760 :
761 : FillRule fillRule =
762 68 : nsSVGUtils::ToFillRule((GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD) ?
763 68 : StyleSVG()->mClipRule : StyleSVG()->mFillRule);
764 :
765 : SVGGeometryElement* element =
766 34 : static_cast<SVGGeometryElement*>(mContent);
767 :
768 : AntialiasMode aaMode =
769 68 : (StyleSVG()->mShapeRendering == NS_STYLE_SHAPE_RENDERING_OPTIMIZESPEED ||
770 34 : StyleSVG()->mShapeRendering == NS_STYLE_SHAPE_RENDERING_CRISPEDGES) ?
771 34 : AntialiasMode::NONE : AntialiasMode::SUBPIXEL;
772 :
773 : // We wait as late as possible before setting the transform so that we don't
774 : // set it unnecessarily if we return early (it's an expensive operation for
775 : // some backends).
776 68 : gfxContextMatrixAutoSaveRestore autoRestoreTransform(aContext);
777 34 : aContext->SetMatrix(aNewTransform);
778 :
779 34 : if (GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD) {
780 : // We don't complicate this code with GetAsSimplePath since the cost of
781 : // masking will dwarf Path creation overhead anyway.
782 0 : RefPtr<Path> path = element->GetOrBuildPath(*drawTarget, fillRule);
783 0 : if (path) {
784 0 : ColorPattern white(ToDeviceColor(Color(1.0f, 1.0f, 1.0f, 1.0f)));
785 : drawTarget->Fill(path, white,
786 0 : DrawOptions(1.0f, CompositionOp::OP_OVER, aaMode));
787 : }
788 0 : return;
789 : }
790 :
791 34 : SVGGeometryElement::SimplePath simplePath;
792 68 : RefPtr<Path> path;
793 :
794 34 : element->GetAsSimplePath(&simplePath);
795 34 : if (!simplePath.IsPath()) {
796 30 : path = element->GetOrBuildPath(*drawTarget, fillRule);
797 30 : if (!path) {
798 0 : return;
799 : }
800 : }
801 :
802 34 : SVGContextPaint* contextPaint = SVGContextPaint::GetContextPaint(mContent);
803 :
804 34 : if (aRenderComponents & eRenderFill) {
805 68 : GeneralPattern fillPattern;
806 34 : nsSVGUtils::MakeFillPatternFor(this, aContext, &fillPattern, aImgParams,
807 34 : contextPaint);
808 :
809 34 : if (fillPattern.GetPattern()) {
810 32 : DrawOptions drawOptions(1.0f, CompositionOp::OP_OVER, aaMode);
811 32 : if (simplePath.IsRect()) {
812 4 : drawTarget->FillRect(simplePath.AsRect(), fillPattern, drawOptions);
813 28 : } else if (path) {
814 28 : drawTarget->Fill(path, fillPattern, drawOptions);
815 : }
816 : }
817 : }
818 :
819 68 : if ((aRenderComponents & eRenderStroke) &&
820 34 : nsSVGUtils::HasStroke(this, contextPaint)) {
821 : // Account for vector-effect:non-scaling-stroke:
822 2 : gfxMatrix userToOuterSVG;
823 2 : if (nsSVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) {
824 : // A simple Rect can't be transformed with rotate/skew, so let's switch
825 : // to using a real path:
826 0 : if (!path) {
827 0 : path = element->GetOrBuildPath(*drawTarget, fillRule);
828 0 : if (!path) {
829 0 : return;
830 : }
831 0 : simplePath.Reset();
832 : }
833 : // We need to transform the path back into the appropriate ancestor
834 : // coordinate system, and paint it it that coordinate system, in order
835 : // for non-scaled stroke to paint correctly.
836 0 : gfxMatrix outerSVGToUser = userToOuterSVG;
837 0 : outerSVGToUser.Invert();
838 0 : aContext->Multiply(outerSVGToUser);
839 : RefPtr<PathBuilder> builder =
840 0 : path->TransformedCopyToBuilder(ToMatrix(userToOuterSVG), fillRule);
841 0 : path = builder->Finish();
842 : }
843 4 : GeneralPattern strokePattern;
844 2 : nsSVGUtils::MakeStrokePatternFor(this, aContext, &strokePattern,
845 2 : aImgParams, contextPaint);
846 :
847 2 : if (strokePattern.GetPattern()) {
848 4 : SVGContentUtils::AutoStrokeOptions strokeOptions;
849 2 : SVGContentUtils::GetStrokeOptions(&strokeOptions,
850 2 : static_cast<nsSVGElement*>(mContent),
851 2 : StyleContext(), contextPaint);
852 : // GetStrokeOptions may set the line width to zero as an optimization
853 2 : if (strokeOptions.mLineWidth <= 0) {
854 0 : return;
855 : }
856 2 : DrawOptions drawOptions(1.0f, CompositionOp::OP_OVER, aaMode);
857 2 : if (simplePath.IsRect()) {
858 0 : drawTarget->StrokeRect(simplePath.AsRect(), strokePattern,
859 0 : strokeOptions, drawOptions);
860 2 : } else if (simplePath.IsLine()) {
861 0 : drawTarget->StrokeLine(simplePath.Point1(), simplePath.Point2(),
862 0 : strokePattern, strokeOptions, drawOptions);
863 : } else {
864 2 : drawTarget->Stroke(path, strokePattern, strokeOptions, drawOptions);
865 : }
866 : }
867 : }
868 : }
869 :
870 : void
871 34 : SVGGeometryFrame::PaintMarkers(gfxContext& aContext,
872 : const gfxMatrix& aTransform,
873 : imgDrawingParams& aImgParams)
874 : {
875 34 : SVGContextPaint* contextPaint = SVGContextPaint::GetContextPaint(mContent);
876 34 : if (static_cast<SVGGeometryElement*>(mContent)->IsMarkable()) {
877 26 : MarkerProperties properties = GetMarkerProperties(this);
878 :
879 26 : if (properties.MarkersExist()) {
880 0 : float strokeWidth = nsSVGUtils::GetStrokeWidth(this, contextPaint);
881 :
882 0 : nsTArray<nsSVGMark> marks;
883 : static_cast<SVGGeometryElement*>
884 0 : (mContent)->GetMarkPoints(&marks);
885 :
886 0 : uint32_t num = marks.Length();
887 0 : if (num) {
888 : // These are in the same order as the nsSVGMark::Type constants.
889 : nsSVGMarkerFrame* markerFrames[] = {
890 0 : properties.GetMarkerStartFrame(),
891 0 : properties.GetMarkerMidFrame(),
892 0 : properties.GetMarkerEndFrame(),
893 0 : };
894 : static_assert(MOZ_ARRAY_LENGTH(markerFrames) == nsSVGMark::eTypeCount,
895 : "Number of Marker frames should be equal to eTypeCount");
896 :
897 0 : for (uint32_t i = 0; i < num; i++) {
898 0 : const nsSVGMark& mark = marks[i];
899 0 : nsSVGMarkerFrame* frame = markerFrames[mark.type];
900 0 : if (frame) {
901 : frame->PaintMark(aContext, aTransform, this, mark, strokeWidth,
902 0 : aImgParams);
903 : }
904 : }
905 : }
906 : }
907 : }
908 34 : }
909 :
910 : uint16_t
911 82 : SVGGeometryFrame::GetHitTestFlags()
912 : {
913 82 : return nsSVGUtils::GetGeometryHitTestFlags(this);
914 : }
915 : } // namespace mozilla
|