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 "nsSVGMarkerFrame.h"
8 
9 // Keep others in (case-insensitive) order:
10 #include "gfxContext.h"
11 #include "nsSVGEffects.h"
12 #include "mozilla/dom/SVGMarkerElement.h"
13 #include "nsSVGPathGeometryElement.h"
14 #include "nsSVGPathGeometryFrame.h"
15 
16 using namespace mozilla::dom;
17 using namespace mozilla::gfx;
18 
19 nsContainerFrame*
NS_NewSVGMarkerFrame(nsIPresShell * aPresShell,nsStyleContext * aContext)20 NS_NewSVGMarkerFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
21 {
22   return new (aPresShell) nsSVGMarkerFrame(aContext);
23 }
24 
NS_IMPL_FRAMEARENA_HELPERS(nsSVGMarkerFrame)25 NS_IMPL_FRAMEARENA_HELPERS(nsSVGMarkerFrame)
26 
27 //----------------------------------------------------------------------
28 // nsIFrame methods:
29 
30 nsresult
31 nsSVGMarkerFrame::AttributeChanged(int32_t  aNameSpaceID,
32                                    nsIAtom* aAttribute,
33                                    int32_t  aModType)
34 {
35   if (aNameSpaceID == kNameSpaceID_None &&
36       (aAttribute == nsGkAtoms::markerUnits ||
37        aAttribute == nsGkAtoms::refX ||
38        aAttribute == nsGkAtoms::refY ||
39        aAttribute == nsGkAtoms::markerWidth ||
40        aAttribute == nsGkAtoms::markerHeight ||
41        aAttribute == nsGkAtoms::orient ||
42        aAttribute == nsGkAtoms::preserveAspectRatio ||
43        aAttribute == nsGkAtoms::viewBox)) {
44     nsSVGEffects::InvalidateDirectRenderingObservers(this);
45   }
46 
47   return nsSVGContainerFrame::AttributeChanged(aNameSpaceID,
48                                                aAttribute, aModType);
49 }
50 
51 #ifdef DEBUG
52 void
Init(nsIContent * aContent,nsContainerFrame * aParent,nsIFrame * aPrevInFlow)53 nsSVGMarkerFrame::Init(nsIContent*       aContent,
54                        nsContainerFrame* aParent,
55                        nsIFrame*         aPrevInFlow)
56 {
57   NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::marker), "Content is not an SVG marker");
58 
59   nsSVGContainerFrame::Init(aContent, aParent, aPrevInFlow);
60 }
61 #endif /* DEBUG */
62 
63 nsIAtom *
GetType() const64 nsSVGMarkerFrame::GetType() const
65 {
66   return nsGkAtoms::svgMarkerFrame;
67 }
68 
69 //----------------------------------------------------------------------
70 // nsSVGContainerFrame methods:
71 
72 gfxMatrix
GetCanvasTM()73 nsSVGMarkerFrame::GetCanvasTM()
74 {
75   NS_ASSERTION(mMarkedFrame, "null nsSVGPathGeometry frame");
76 
77   if (mInUse2) {
78     // We're going to be bailing drawing the marker, so return an identity.
79     return gfxMatrix();
80   }
81 
82   SVGMarkerElement *content = static_cast<SVGMarkerElement*>(mContent);
83 
84   mInUse2 = true;
85   gfxMatrix markedTM = mMarkedFrame->GetCanvasTM();
86   mInUse2 = false;
87 
88   Matrix markerTM = content->GetMarkerTransform(mStrokeWidth, mX, mY,
89                                                 mAutoAngle, mIsStart);
90   Matrix viewBoxTM = content->GetViewBoxTransform();
91 
92   return ThebesMatrix(viewBoxTM * markerTM) * markedTM;
93 }
94 
95 static nsIFrame*
GetAnonymousChildFrame(nsIFrame * aFrame)96 GetAnonymousChildFrame(nsIFrame* aFrame)
97 {
98   nsIFrame* kid = aFrame->PrincipalChildList().FirstChild();
99   MOZ_ASSERT(kid && kid->GetType() == nsGkAtoms::svgMarkerAnonChildFrame,
100              "expected to find anonymous child of marker frame");
101   return kid;
102 }
103 
104 nsresult
PaintMark(gfxContext & aContext,const gfxMatrix & aToMarkedFrameUserSpace,nsSVGPathGeometryFrame * aMarkedFrame,nsSVGMark * aMark,float aStrokeWidth)105 nsSVGMarkerFrame::PaintMark(gfxContext& aContext,
106                             const gfxMatrix& aToMarkedFrameUserSpace,
107                             nsSVGPathGeometryFrame *aMarkedFrame,
108                             nsSVGMark *aMark, float aStrokeWidth)
109 {
110   // If the flag is set when we get here, it means this marker frame
111   // has already been used painting the current mark, and the document
112   // has a marker reference loop.
113   if (mInUse)
114     return NS_OK;
115 
116   AutoMarkerReferencer markerRef(this, aMarkedFrame);
117 
118   SVGMarkerElement *marker = static_cast<SVGMarkerElement*>(mContent);
119   if (!marker->HasValidDimensions()) {
120     return NS_OK;
121   }
122 
123   const nsSVGViewBoxRect viewBox = marker->GetViewBoxRect();
124 
125   if (viewBox.width <= 0.0f || viewBox.height <= 0.0f) {
126     // We must disable rendering if the viewBox width or height are zero.
127     return NS_OK;
128   }
129 
130   mStrokeWidth = aStrokeWidth;
131   mX = aMark->x;
132   mY = aMark->y;
133   mAutoAngle = aMark->angle;
134   mIsStart = aMark->type == nsSVGMark::eStart;
135 
136   Matrix viewBoxTM = marker->GetViewBoxTransform();
137 
138   Matrix markerTM = marker->GetMarkerTransform(mStrokeWidth, mX, mY,
139                                                mAutoAngle, mIsStart);
140 
141   gfxMatrix markTM = ThebesMatrix(viewBoxTM) * ThebesMatrix(markerTM) *
142                      aToMarkedFrameUserSpace;
143 
144   if (StyleDisplay()->IsScrollableOverflow()) {
145     aContext.Save();
146     gfxRect clipRect =
147       nsSVGUtils::GetClipRectForFrame(this, viewBox.x, viewBox.y,
148                                       viewBox.width, viewBox.height);
149     nsSVGUtils::SetClipRect(&aContext, markTM, clipRect);
150   }
151 
152 
153   nsIFrame* kid = GetAnonymousChildFrame(this);
154   nsISVGChildFrame* SVGFrame = do_QueryFrame(kid);
155   // The CTM of each frame referencing us may be different.
156   SVGFrame->NotifySVGChanged(nsISVGChildFrame::TRANSFORM_CHANGED);
157   DrawResult result = nsSVGUtils::PaintFrameWithEffects(kid, aContext, markTM);
158 
159   if (StyleDisplay()->IsScrollableOverflow())
160     aContext.Restore();
161 
162   return (result == DrawResult::SUCCESS) ? NS_OK : NS_ERROR_FAILURE;
163 }
164 
165 SVGBBox
GetMarkBBoxContribution(const Matrix & aToBBoxUserspace,uint32_t aFlags,nsSVGPathGeometryFrame * aMarkedFrame,const nsSVGMark * aMark,float aStrokeWidth)166 nsSVGMarkerFrame::GetMarkBBoxContribution(const Matrix &aToBBoxUserspace,
167                                           uint32_t aFlags,
168                                           nsSVGPathGeometryFrame *aMarkedFrame,
169                                           const nsSVGMark *aMark,
170                                           float aStrokeWidth)
171 {
172   SVGBBox bbox;
173 
174   // If the flag is set when we get here, it means this marker frame
175   // has already been used in calculating the current mark bbox, and
176   // the document has a marker reference loop.
177   if (mInUse)
178     return bbox;
179 
180   AutoMarkerReferencer markerRef(this, aMarkedFrame);
181 
182   SVGMarkerElement *content = static_cast<SVGMarkerElement*>(mContent);
183   if (!content->HasValidDimensions()) {
184     return bbox;
185   }
186 
187   const nsSVGViewBoxRect viewBox = content->GetViewBoxRect();
188 
189   if (viewBox.width <= 0.0f || viewBox.height <= 0.0f) {
190     return bbox;
191   }
192 
193   mStrokeWidth = aStrokeWidth;
194   mX = aMark->x;
195   mY = aMark->y;
196   mAutoAngle = aMark->angle;
197   mIsStart = aMark->type == nsSVGMark::eStart;
198 
199   Matrix markerTM =
200     content->GetMarkerTransform(mStrokeWidth, mX, mY, mAutoAngle, mIsStart);
201   Matrix viewBoxTM = content->GetViewBoxTransform();
202 
203   Matrix tm = viewBoxTM * markerTM * aToBBoxUserspace;
204 
205   nsISVGChildFrame* child = do_QueryFrame(GetAnonymousChildFrame(this));
206   // When we're being called to obtain the invalidation area, we need to
207   // pass down all the flags so that stroke is included. However, once DOM
208   // getBBox() accepts flags, maybe we should strip some of those here?
209 
210   // We need to include zero width/height vertical/horizontal lines, so we have
211   // to use UnionEdges.
212   bbox.UnionEdges(child->GetBBoxContribution(tm, aFlags));
213 
214   return bbox;
215 }
216 
217 void
SetParentCoordCtxProvider(SVGSVGElement * aContext)218 nsSVGMarkerFrame::SetParentCoordCtxProvider(SVGSVGElement *aContext)
219 {
220   SVGMarkerElement *marker = static_cast<SVGMarkerElement*>(mContent);
221   marker->SetParentCoordCtxProvider(aContext);
222 }
223 
224 //----------------------------------------------------------------------
225 // helper class
226 
AutoMarkerReferencer(nsSVGMarkerFrame * aFrame,nsSVGPathGeometryFrame * aMarkedFrame MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)227 nsSVGMarkerFrame::AutoMarkerReferencer::AutoMarkerReferencer(
228     nsSVGMarkerFrame *aFrame,
229     nsSVGPathGeometryFrame *aMarkedFrame
230     MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
231       : mFrame(aFrame)
232 {
233   MOZ_GUARD_OBJECT_NOTIFIER_INIT;
234   mFrame->mInUse = true;
235   mFrame->mMarkedFrame = aMarkedFrame;
236 
237   SVGSVGElement *ctx =
238     static_cast<nsSVGElement*>(aMarkedFrame->GetContent())->GetCtx();
239   mFrame->SetParentCoordCtxProvider(ctx);
240 }
241 
~AutoMarkerReferencer()242 nsSVGMarkerFrame::AutoMarkerReferencer::~AutoMarkerReferencer()
243 {
244   mFrame->SetParentCoordCtxProvider(nullptr);
245 
246   mFrame->mMarkedFrame = nullptr;
247   mFrame->mInUse = false;
248 }
249 
250 //----------------------------------------------------------------------
251 // Implementation of nsSVGMarkerAnonChildFrame
252 
253 nsContainerFrame*
NS_NewSVGMarkerAnonChildFrame(nsIPresShell * aPresShell,nsStyleContext * aContext)254 NS_NewSVGMarkerAnonChildFrame(nsIPresShell* aPresShell,
255                               nsStyleContext* aContext)
256 {
257   return new (aPresShell) nsSVGMarkerAnonChildFrame(aContext);
258 }
259 
NS_IMPL_FRAMEARENA_HELPERS(nsSVGMarkerAnonChildFrame)260 NS_IMPL_FRAMEARENA_HELPERS(nsSVGMarkerAnonChildFrame)
261 
262 #ifdef DEBUG
263 void
264 nsSVGMarkerAnonChildFrame::Init(nsIContent*       aContent,
265                                 nsContainerFrame* aParent,
266                                 nsIFrame*         aPrevInFlow)
267 {
268   MOZ_ASSERT(aParent->GetType() == nsGkAtoms::svgMarkerFrame,
269              "Unexpected parent");
270   nsSVGDisplayContainerFrame::Init(aContent, aParent, aPrevInFlow);
271 }
272 #endif
273 
274 nsIAtom *
GetType() const275 nsSVGMarkerAnonChildFrame::GetType() const
276 {
277   return nsGkAtoms::svgMarkerAnonChildFrame;
278 }
279