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 #include "SVGTransformableElement.h"
8
9 #include "DOMSVGAnimatedTransformList.h"
10 #include "gfx2DGlue.h"
11 #include "mozilla/dom/MutationEventBinding.h"
12 #include "mozilla/dom/SVGGraphicsElementBinding.h"
13 #include "mozilla/dom/SVGMatrix.h"
14 #include "mozilla/dom/SVGRect.h"
15 #include "mozilla/dom/SVGSVGElement.h"
16 #include "nsContentUtils.h"
17 #include "nsIFrame.h"
18 #include "SVGTextFrame.h"
19 #include "SVGContentUtils.h"
20 #include "nsSVGDisplayableFrame.h"
21 #include "nsSVGUtils.h"
22
23 using namespace mozilla::gfx;
24
25 namespace mozilla {
26 namespace dom {
27
28 already_AddRefed<DOMSVGAnimatedTransformList>
Transform()29 SVGTransformableElement::Transform() {
30 // We're creating a DOM wrapper, so we must tell GetAnimatedTransformList
31 // to allocate the DOMSVGAnimatedTransformList if it hasn't already done so:
32 return DOMSVGAnimatedTransformList::GetDOMWrapper(
33 GetAnimatedTransformList(DO_ALLOCATE), this);
34 }
35
36 //----------------------------------------------------------------------
37 // nsIContent methods
38
NS_IMETHODIMP_(bool)39 NS_IMETHODIMP_(bool)
40 SVGTransformableElement::IsAttributeMapped(const nsAtom* name) const {
41 static const MappedAttributeEntry* const map[] = {sColorMap, sFillStrokeMap,
42 sGraphicsMap};
43
44 return FindAttributeDependence(name, map) ||
45 SVGElement::IsAttributeMapped(name);
46 }
47
GetAttributeChangeHint(const nsAtom * aAttribute,int32_t aModType) const48 nsChangeHint SVGTransformableElement::GetAttributeChangeHint(
49 const nsAtom* aAttribute, int32_t aModType) const {
50 nsChangeHint retval =
51 SVGElement::GetAttributeChangeHint(aAttribute, aModType);
52 if (aAttribute == nsGkAtoms::transform ||
53 aAttribute == nsGkAtoms::mozAnimateMotionDummyAttr) {
54 nsIFrame* frame =
55 const_cast<SVGTransformableElement*>(this)->GetPrimaryFrame();
56 retval |= nsChangeHint_InvalidateRenderingObservers;
57 if (!frame || (frame->GetStateBits() & NS_FRAME_IS_NONDISPLAY)) {
58 return retval;
59 }
60
61 bool isAdditionOrRemoval = false;
62 if (aModType == MutationEvent_Binding::ADDITION ||
63 aModType == MutationEvent_Binding::REMOVAL) {
64 isAdditionOrRemoval = true;
65 } else {
66 MOZ_ASSERT(aModType == MutationEvent_Binding::MODIFICATION,
67 "Unknown modification type.");
68 if (!mTransforms || !mTransforms->HasTransform()) {
69 // New value is empty, treat as removal.
70 // FIXME: Should we just rely on CreatedOrRemovedOnLastChange?
71 isAdditionOrRemoval = true;
72 } else if (mTransforms->CreatedOrRemovedOnLastChange()) {
73 // Old value was empty, treat as addition.
74 isAdditionOrRemoval = true;
75 }
76 }
77
78 if (isAdditionOrRemoval) {
79 retval |= nsChangeHint_ComprehensiveAddOrRemoveTransform;
80 } else {
81 // We just assume the old and new transforms are different.
82 retval |= nsChangeHint_UpdatePostTransformOverflow |
83 nsChangeHint_UpdateTransformLayer;
84 }
85 }
86 return retval;
87 }
88
IsEventAttributeNameInternal(nsAtom * aName)89 bool SVGTransformableElement::IsEventAttributeNameInternal(nsAtom* aName) {
90 return nsContentUtils::IsEventAttributeName(aName, EventNameType_SVGGraphic);
91 }
92
93 //----------------------------------------------------------------------
94 // SVGElement overrides
95
PrependLocalTransformsTo(const gfxMatrix & aMatrix,SVGTransformTypes aWhich) const96 gfxMatrix SVGTransformableElement::PrependLocalTransformsTo(
97 const gfxMatrix& aMatrix, SVGTransformTypes aWhich) const {
98 if (aWhich == eChildToUserSpace) {
99 // We don't have any eUserSpaceToParent transforms. (Sub-classes that do
100 // must override this function and handle that themselves.)
101 return aMatrix;
102 }
103 return GetUserToParentTransform(mAnimateMotionTransform.get(),
104 mTransforms.get()) *
105 aMatrix;
106 }
107
GetAnimateMotionTransform() const108 const gfx::Matrix* SVGTransformableElement::GetAnimateMotionTransform() const {
109 return mAnimateMotionTransform.get();
110 }
111
SetAnimateMotionTransform(const gfx::Matrix * aMatrix)112 void SVGTransformableElement::SetAnimateMotionTransform(
113 const gfx::Matrix* aMatrix) {
114 if ((!aMatrix && !mAnimateMotionTransform) ||
115 (aMatrix && mAnimateMotionTransform &&
116 aMatrix->FuzzyEquals(*mAnimateMotionTransform))) {
117 return;
118 }
119 bool transformSet = mTransforms && mTransforms->IsExplicitlySet();
120 bool prevSet = mAnimateMotionTransform || transformSet;
121 mAnimateMotionTransform =
122 aMatrix ? MakeUnique<gfx::Matrix>(*aMatrix) : nullptr;
123 bool nowSet = mAnimateMotionTransform || transformSet;
124 int32_t modType;
125 if (prevSet && !nowSet) {
126 modType = MutationEvent_Binding::REMOVAL;
127 } else if (!prevSet && nowSet) {
128 modType = MutationEvent_Binding::ADDITION;
129 } else {
130 modType = MutationEvent_Binding::MODIFICATION;
131 }
132 DidAnimateTransformList(modType);
133 nsIFrame* frame = GetPrimaryFrame();
134 if (frame) {
135 // If the result of this transform and any other transforms on this frame
136 // is the identity matrix, then DoApplyRenderingChangeToTree won't handle
137 // our nsChangeHint_UpdateTransformLayer hint since aFrame->IsTransformed()
138 // will return false. That's fine, but we still need to schedule a repaint,
139 // and that won't otherwise happen. Since it's cheap to call SchedulePaint,
140 // we don't bother to check IsTransformed().
141 frame->SchedulePaint();
142 }
143 }
144
GetAnimatedTransformList(uint32_t aFlags)145 SVGAnimatedTransformList* SVGTransformableElement::GetAnimatedTransformList(
146 uint32_t aFlags) {
147 if (!mTransforms && (aFlags & DO_ALLOCATE)) {
148 mTransforms = MakeUnique<SVGAnimatedTransformList>();
149 }
150 return mTransforms.get();
151 }
152
GetNearestViewportElement()153 SVGElement* SVGTransformableElement::GetNearestViewportElement() {
154 return SVGContentUtils::GetNearestViewportElement(this);
155 }
156
GetFarthestViewportElement()157 SVGElement* SVGTransformableElement::GetFarthestViewportElement() {
158 return SVGContentUtils::GetOuterSVGElement(this);
159 }
160
ZeroBBox(SVGTransformableElement & aOwner)161 static already_AddRefed<SVGRect> ZeroBBox(SVGTransformableElement& aOwner) {
162 return MakeAndAddRef<SVGRect>(&aOwner, Rect{0, 0, 0, 0});
163 }
164
GetBBox(const SVGBoundingBoxOptions & aOptions)165 already_AddRefed<SVGRect> SVGTransformableElement::GetBBox(
166 const SVGBoundingBoxOptions& aOptions) {
167 nsIFrame* frame = GetPrimaryFrame(FlushType::Layout);
168
169 if (!frame || (frame->GetStateBits() & NS_FRAME_IS_NONDISPLAY)) {
170 return ZeroBBox(*this);
171 }
172 nsSVGDisplayableFrame* svgframe = do_QueryFrame(frame);
173
174 if (!svgframe) {
175 if (!nsSVGUtils::IsInSVGTextSubtree(frame)) {
176 return ZeroBBox(*this);
177 }
178
179 // For <tspan>, <textPath>, the frame is an nsInlineFrame or
180 // nsBlockFrame, |svgframe| will be a nullptr.
181 // We implement their getBBox directly here instead of in
182 // nsSVGUtils::GetBBox, because nsSVGUtils::GetBBox is more
183 // or less used for other purpose elsewhere. e.g. gradient
184 // code assumes GetBBox of <tspan> returns the bbox of the
185 // outer <text>.
186 // TODO: cleanup this sort of usecase of nsSVGUtils::GetBBox,
187 // then move this code nsSVGUtils::GetBBox.
188 SVGTextFrame* text =
189 static_cast<SVGTextFrame*>(nsLayoutUtils::GetClosestFrameOfType(
190 frame->GetParent(), LayoutFrameType::SVGText));
191
192 if (text->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
193 return ZeroBBox(*this);
194 }
195
196 gfxRect rec = text->TransformFrameRectFromTextChild(
197 frame->GetRectRelativeToSelf(), frame);
198
199 // Should also add the |x|, |y| of the SVGTextFrame itself, since
200 // the result obtained by TransformFrameRectFromTextChild doesn't
201 // include them.
202 rec.x += float(text->GetPosition().x) / AppUnitsPerCSSPixel();
203 rec.y += float(text->GetPosition().y) / AppUnitsPerCSSPixel();
204
205 return do_AddRef(new SVGRect(this, ToRect(rec)));
206 }
207
208 if (!NS_SVGNewGetBBoxEnabled()) {
209 return do_AddRef(new SVGRect(
210 this, ToRect(nsSVGUtils::GetBBox(
211 frame, nsSVGUtils::eBBoxIncludeFillGeometry |
212 nsSVGUtils::eUseUserSpaceOfUseElement))));
213 }
214 uint32_t flags = 0;
215 if (aOptions.mFill) {
216 flags |= nsSVGUtils::eBBoxIncludeFill;
217 }
218 if (aOptions.mStroke) {
219 flags |= nsSVGUtils::eBBoxIncludeStroke;
220 }
221 if (aOptions.mMarkers) {
222 flags |= nsSVGUtils::eBBoxIncludeMarkers;
223 }
224 if (aOptions.mClipped) {
225 flags |= nsSVGUtils::eBBoxIncludeClipped;
226 }
227 if (flags == 0) {
228 return do_AddRef(new SVGRect(this, gfx::Rect()));
229 }
230 if (flags == nsSVGUtils::eBBoxIncludeMarkers ||
231 flags == nsSVGUtils::eBBoxIncludeClipped) {
232 flags |= nsSVGUtils::eBBoxIncludeFill;
233 }
234 flags |= nsSVGUtils::eUseUserSpaceOfUseElement;
235 return do_AddRef(
236 new SVGRect(this, ToRect(nsSVGUtils::GetBBox(frame, flags))));
237 }
238
GetCTM()239 already_AddRefed<SVGMatrix> SVGTransformableElement::GetCTM() {
240 Document* currentDoc = GetComposedDoc();
241 if (currentDoc) {
242 // Flush all pending notifications so that our frames are up to date
243 currentDoc->FlushPendingNotifications(FlushType::Layout);
244 }
245 gfx::Matrix m = SVGContentUtils::GetCTM(this, false);
246 RefPtr<SVGMatrix> mat =
247 m.IsSingular() ? nullptr : new SVGMatrix(ThebesMatrix(m));
248 return mat.forget();
249 }
250
GetScreenCTM()251 already_AddRefed<SVGMatrix> SVGTransformableElement::GetScreenCTM() {
252 Document* currentDoc = GetComposedDoc();
253 if (currentDoc) {
254 // Flush all pending notifications so that our frames are up to date
255 currentDoc->FlushPendingNotifications(FlushType::Layout);
256 }
257 gfx::Matrix m = SVGContentUtils::GetCTM(this, true);
258 RefPtr<SVGMatrix> mat =
259 m.IsSingular() ? nullptr : new SVGMatrix(ThebesMatrix(m));
260 return mat.forget();
261 }
262
GetTransformToElement(SVGGraphicsElement & aElement,ErrorResult & rv)263 already_AddRefed<SVGMatrix> SVGTransformableElement::GetTransformToElement(
264 SVGGraphicsElement& aElement, ErrorResult& rv) {
265 // the easiest way to do this (if likely to increase rounding error):
266 RefPtr<SVGMatrix> ourScreenCTM = GetScreenCTM();
267 RefPtr<SVGMatrix> targetScreenCTM = aElement.GetScreenCTM();
268 if (!ourScreenCTM || !targetScreenCTM) {
269 rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
270 return nullptr;
271 }
272 RefPtr<SVGMatrix> tmp = targetScreenCTM->Inverse(rv);
273 if (rv.Failed()) return nullptr;
274
275 RefPtr<SVGMatrix> mat = tmp->Multiply(*ourScreenCTM);
276 return mat.forget();
277 }
278
279 /* static */
GetUserToParentTransform(const gfx::Matrix * aAnimateMotionTransform,const SVGAnimatedTransformList * aTransforms)280 gfxMatrix SVGTransformableElement::GetUserToParentTransform(
281 const gfx::Matrix* aAnimateMotionTransform,
282 const SVGAnimatedTransformList* aTransforms) {
283 gfxMatrix result;
284
285 if (aAnimateMotionTransform) {
286 result.PreMultiply(ThebesMatrix(*aAnimateMotionTransform));
287 }
288
289 if (aTransforms) {
290 result.PreMultiply(aTransforms->GetAnimValue().GetConsolidationMatrix());
291 }
292
293 return result;
294 }
295
296 } // namespace dom
297 } // namespace mozilla
298