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/. */
7 #include "SVGTransformableElement.h"
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 "mozilla/ISVGDisplayableFrame.h"
17 #include "mozilla/SVGContentUtils.h"
18 #include "mozilla/SVGTextFrame.h"
19 #include "mozilla/SVGUtils.h"
20 #include "nsContentUtils.h"
22 #include "nsLayoutUtils.h"
24 using namespace mozilla::gfx
;
29 already_AddRefed
<DOMSVGAnimatedTransformList
>
30 SVGTransformableElement::Transform() {
31 // We're creating a DOM wrapper, so we must tell GetAnimatedTransformList
32 // to allocate the DOMSVGAnimatedTransformList if it hasn't already done so:
33 return DOMSVGAnimatedTransformList::GetDOMWrapper(
34 GetAnimatedTransformList(DO_ALLOCATE
), this);
37 //----------------------------------------------------------------------
41 SVGTransformableElement::IsAttributeMapped(const nsAtom
* name
) const {
42 static const MappedAttributeEntry
* const map
[] = {sColorMap
, sFillStrokeMap
,
45 return FindAttributeDependence(name
, map
) ||
46 SVGElement::IsAttributeMapped(name
);
49 nsChangeHint
SVGTransformableElement::GetAttributeChangeHint(
50 const nsAtom
* aAttribute
, int32_t aModType
) const {
52 SVGElement::GetAttributeChangeHint(aAttribute
, aModType
);
53 if (aAttribute
== nsGkAtoms::transform
||
54 aAttribute
== nsGkAtoms::mozAnimateMotionDummyAttr
) {
56 const_cast<SVGTransformableElement
*>(this)->GetPrimaryFrame();
57 retval
|= nsChangeHint_InvalidateRenderingObservers
;
58 if (!frame
|| (frame
->GetStateBits() & NS_FRAME_IS_NONDISPLAY
)) {
62 bool isAdditionOrRemoval
= false;
63 if (aModType
== MutationEvent_Binding::ADDITION
||
64 aModType
== MutationEvent_Binding::REMOVAL
) {
65 isAdditionOrRemoval
= true;
67 MOZ_ASSERT(aModType
== MutationEvent_Binding::MODIFICATION
,
68 "Unknown modification type.");
69 if (!mTransforms
|| !mTransforms
->HasTransform()) {
70 // New value is empty, treat as removal.
71 // FIXME: Should we just rely on CreatedOrRemovedOnLastChange?
72 isAdditionOrRemoval
= true;
73 } else if (mTransforms
->CreatedOrRemovedOnLastChange()) {
74 // Old value was empty, treat as addition.
75 isAdditionOrRemoval
= true;
79 if (isAdditionOrRemoval
) {
80 retval
|= nsChangeHint_ComprehensiveAddOrRemoveTransform
;
82 // We just assume the old and new transforms are different.
83 retval
|= nsChangeHint_UpdatePostTransformOverflow
|
84 nsChangeHint_UpdateTransformLayer
;
90 bool SVGTransformableElement::IsEventAttributeNameInternal(nsAtom
* aName
) {
91 return nsContentUtils::IsEventAttributeName(aName
, EventNameType_SVGGraphic
);
94 //----------------------------------------------------------------------
95 // SVGElement overrides
97 gfxMatrix
SVGTransformableElement::PrependLocalTransformsTo(
98 const gfxMatrix
& aMatrix
, SVGTransformTypes aWhich
) const {
99 if (aWhich
== eChildToUserSpace
) {
100 // We don't have any eUserSpaceToParent transforms. (Sub-classes that do
101 // must override this function and handle that themselves.)
104 return GetUserToParentTransform(mAnimateMotionTransform
.get(),
109 const gfx::Matrix
* SVGTransformableElement::GetAnimateMotionTransform() const {
110 return mAnimateMotionTransform
.get();
113 void SVGTransformableElement::SetAnimateMotionTransform(
114 const gfx::Matrix
* aMatrix
) {
115 if ((!aMatrix
&& !mAnimateMotionTransform
) ||
116 (aMatrix
&& mAnimateMotionTransform
&&
117 aMatrix
->FuzzyEquals(*mAnimateMotionTransform
))) {
120 bool transformSet
= mTransforms
&& mTransforms
->IsExplicitlySet();
121 bool prevSet
= mAnimateMotionTransform
|| transformSet
;
122 mAnimateMotionTransform
=
123 aMatrix
? MakeUnique
<gfx::Matrix
>(*aMatrix
) : nullptr;
124 bool nowSet
= mAnimateMotionTransform
|| transformSet
;
126 if (prevSet
&& !nowSet
) {
127 modType
= MutationEvent_Binding::REMOVAL
;
128 } else if (!prevSet
&& nowSet
) {
129 modType
= MutationEvent_Binding::ADDITION
;
131 modType
= MutationEvent_Binding::MODIFICATION
;
133 DidAnimateTransformList(modType
);
134 nsIFrame
* frame
= GetPrimaryFrame();
136 // If the result of this transform and any other transforms on this frame
137 // is the identity matrix, then DoApplyRenderingChangeToTree won't handle
138 // our nsChangeHint_UpdateTransformLayer hint since aFrame->IsTransformed()
139 // will return false. That's fine, but we still need to schedule a repaint,
140 // and that won't otherwise happen. Since it's cheap to call SchedulePaint,
141 // we don't bother to check IsTransformed().
142 frame
->SchedulePaint();
146 SVGAnimatedTransformList
* SVGTransformableElement::GetAnimatedTransformList(
148 if (!mTransforms
&& (aFlags
& DO_ALLOCATE
)) {
149 mTransforms
= MakeUnique
<SVGAnimatedTransformList
>();
151 return mTransforms
.get();
154 SVGElement
* SVGTransformableElement::GetNearestViewportElement() {
155 return SVGContentUtils::GetNearestViewportElement(this);
158 SVGElement
* SVGTransformableElement::GetFarthestViewportElement() {
159 return SVGContentUtils::GetOuterSVGElement(this);
162 static already_AddRefed
<SVGRect
> ZeroBBox(SVGTransformableElement
& aOwner
) {
163 return MakeAndAddRef
<SVGRect
>(&aOwner
, Rect
{0, 0, 0, 0});
166 already_AddRefed
<SVGRect
> SVGTransformableElement::GetBBox(
167 const SVGBoundingBoxOptions
& aOptions
) {
168 nsIFrame
* frame
= GetPrimaryFrame(FlushType::Layout
);
170 if (!frame
|| (frame
->GetStateBits() & NS_FRAME_IS_NONDISPLAY
)) {
171 return ZeroBBox(*this);
173 ISVGDisplayableFrame
* svgframe
= do_QueryFrame(frame
);
176 if (!SVGUtils::IsInSVGTextSubtree(frame
)) {
177 return ZeroBBox(*this);
180 // For <tspan>, <textPath>, the frame is an nsInlineFrame or
181 // nsBlockFrame, |svgframe| will be a nullptr.
182 // We implement their getBBox directly here instead of in
183 // SVGUtils::GetBBox, because SVGUtils::GetBBox is more
184 // or less used for other purpose elsewhere. e.g. gradient
185 // code assumes GetBBox of <tspan> returns the bbox of the
187 // TODO: cleanup this sort of usecase of SVGUtils::GetBBox,
188 // then move this code SVGUtils::GetBBox.
190 static_cast<SVGTextFrame
*>(nsLayoutUtils::GetClosestFrameOfType(
191 frame
->GetParent(), LayoutFrameType::SVGText
));
193 if (text
->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY
)) {
194 return ZeroBBox(*this);
197 gfxRect rec
= text
->TransformFrameRectFromTextChild(
198 frame
->GetRectRelativeToSelf(), frame
);
200 // Should also add the |x|, |y| of the SVGTextFrame itself, since
201 // the result obtained by TransformFrameRectFromTextChild doesn't
203 rec
.x
+= float(text
->GetPosition().x
) / AppUnitsPerCSSPixel();
204 rec
.y
+= float(text
->GetPosition().y
) / AppUnitsPerCSSPixel();
206 return do_AddRef(new SVGRect(this, ToRect(rec
)));
209 if (!NS_SVGNewGetBBoxEnabled()) {
210 return do_AddRef(new SVGRect(
211 this, ToRect(SVGUtils::GetBBox(
212 frame
, SVGUtils::eBBoxIncludeFillGeometry
|
213 SVGUtils::eUseUserSpaceOfUseElement
))));
216 if (aOptions
.mFill
) {
217 flags
|= SVGUtils::eBBoxIncludeFill
;
219 if (aOptions
.mStroke
) {
220 flags
|= SVGUtils::eBBoxIncludeStroke
;
222 if (aOptions
.mMarkers
) {
223 flags
|= SVGUtils::eBBoxIncludeMarkers
;
225 if (aOptions
.mClipped
) {
226 flags
|= SVGUtils::eBBoxIncludeClipped
;
229 return do_AddRef(new SVGRect(this, gfx::Rect()));
231 if (flags
== SVGUtils::eBBoxIncludeMarkers
||
232 flags
== SVGUtils::eBBoxIncludeClipped
) {
233 flags
|= SVGUtils::eBBoxIncludeFill
;
235 flags
|= SVGUtils::eUseUserSpaceOfUseElement
;
236 return do_AddRef(new SVGRect(this, ToRect(SVGUtils::GetBBox(frame
, flags
))));
239 already_AddRefed
<SVGMatrix
> SVGTransformableElement::GetCTM() {
240 Document
* currentDoc
= GetComposedDoc();
242 // Flush all pending notifications so that our frames are up to date
243 currentDoc
->FlushPendingNotifications(FlushType::Layout
);
245 gfx::Matrix m
= SVGContentUtils::GetCTM(this, false);
246 RefPtr
<SVGMatrix
> mat
=
247 m
.IsSingular() ? nullptr : new SVGMatrix(ThebesMatrix(m
));
251 already_AddRefed
<SVGMatrix
> SVGTransformableElement::GetScreenCTM() {
252 Document
* currentDoc
= GetComposedDoc();
254 // Flush all pending notifications so that our frames are up to date
255 currentDoc
->FlushPendingNotifications(FlushType::Layout
);
257 gfx::Matrix m
= SVGContentUtils::GetCTM(this, true);
258 RefPtr
<SVGMatrix
> mat
=
259 m
.IsSingular() ? nullptr : new SVGMatrix(ThebesMatrix(m
));
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
);
272 RefPtr
<SVGMatrix
> tmp
= targetScreenCTM
->Inverse(rv
);
273 if (rv
.Failed()) return nullptr;
275 RefPtr
<SVGMatrix
> mat
= tmp
->Multiply(*ourScreenCTM
);
280 gfxMatrix
SVGTransformableElement::GetUserToParentTransform(
281 const gfx::Matrix
* aAnimateMotionTransform
,
282 const SVGAnimatedTransformList
* aTransforms
) {
285 if (aAnimateMotionTransform
) {
286 result
.PreMultiply(ThebesMatrix(*aAnimateMotionTransform
));
290 result
.PreMultiply(aTransforms
->GetAnimValue().GetConsolidationMatrix());
297 } // namespace mozilla