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