Line data Source code
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 <stdint.h>
8 : #include "mozilla/ArrayUtils.h"
9 : #include "mozilla/ContentEvents.h"
10 : #include "mozilla/EventDispatcher.h"
11 : #include "mozilla/Likely.h"
12 : #include "mozilla/dom/SVGMatrix.h"
13 : #include "mozilla/dom/SVGViewportElement.h"
14 : #include "mozilla/dom/SVGViewElement.h"
15 :
16 : #include "DOMSVGLength.h"
17 : #include "DOMSVGPoint.h"
18 : #include "nsCOMPtr.h"
19 : #include "nsContentUtils.h"
20 : #include "nsFrameSelection.h"
21 : #include "nsError.h"
22 : #include "nsGkAtoms.h"
23 : #include "nsIDocument.h"
24 : #include "nsIFrame.h"
25 : #include "nsIPresShell.h"
26 : #include "nsISVGSVGFrame.h" //XXX
27 : #include "nsLayoutUtils.h"
28 : #include "nsStyleUtil.h"
29 : #include "nsSMILTypes.h"
30 : #include "SVGContentUtils.h"
31 :
32 : #include <algorithm>
33 : #include "prtime.h"
34 :
35 : using namespace mozilla::gfx;
36 :
37 : namespace mozilla {
38 : namespace dom {
39 :
40 : nsSVGElement::LengthInfo SVGViewportElement::sLengthInfo[4] =
41 : {
42 : { &nsGkAtoms::x, 0, nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER, SVGContentUtils::X },
43 : { &nsGkAtoms::y, 0, nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER, SVGContentUtils::Y },
44 : { &nsGkAtoms::width, 100, nsIDOMSVGLength::SVG_LENGTHTYPE_PERCENTAGE, SVGContentUtils::X },
45 : { &nsGkAtoms::height, 100, nsIDOMSVGLength::SVG_LENGTHTYPE_PERCENTAGE, SVGContentUtils::Y },
46 : };
47 :
48 : //----------------------------------------------------------------------
49 : // Implementation
50 :
51 22 : SVGViewportElement::SVGViewportElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
52 : : SVGGraphicsElement(aNodeInfo),
53 : mViewportWidth(0),
54 : mViewportHeight(0),
55 22 : mHasChildrenOnlyTransform(false)
56 : {
57 22 : }
58 :
59 0 : SVGViewportElement::~SVGViewportElement()
60 : {
61 0 : }
62 :
63 : //----------------------------------------------------------------------
64 :
65 : already_AddRefed<SVGAnimatedRect>
66 0 : SVGViewportElement::ViewBox()
67 : {
68 0 : return mViewBox.ToSVGAnimatedRect(this);
69 : }
70 :
71 : already_AddRefed<DOMSVGAnimatedPreserveAspectRatio>
72 0 : SVGViewportElement::PreserveAspectRatio()
73 : {
74 0 : return mPreserveAspectRatio.ToDOMAnimatedPreserveAspectRatio(this);
75 : }
76 :
77 : //----------------------------------------------------------------------
78 : // nsIContent methods
79 :
80 : NS_IMETHODIMP_(bool)
81 189 : SVGViewportElement::IsAttributeMapped(const nsIAtom* name) const
82 : {
83 : // We want to map the 'width' and 'height' attributes into style for
84 : // outer-<svg>, except when the attributes aren't set (since their default
85 : // values of '100%' can cause unexpected and undesirable behaviour for SVG
86 : // inline in HTML). We rely on nsSVGElement::UpdateContentStyleRule() to
87 : // prevent mapping of the default values into style (it only maps attributes
88 : // that are set). We also rely on a check in nsSVGElement::
89 : // UpdateContentStyleRule() to prevent us mapping the attributes when they're
90 : // given a <length> value that is not currently recognized by the SVG
91 : // specification.
92 :
93 189 : if (!IsInner() && (name == nsGkAtoms::width || name == nsGkAtoms::height)) {
94 123 : return true;
95 : }
96 :
97 : static const MappedAttributeEntry* const map[] = {
98 : sColorMap,
99 : sFEFloodMap,
100 : sFillStrokeMap,
101 : sFiltersMap,
102 : sFontSpecificationMap,
103 : sGradientStopMap,
104 : sGraphicsMap,
105 : sLightingEffectsMap,
106 : sMarkersMap,
107 : sTextContentElementsMap,
108 : sViewportsMap
109 : };
110 :
111 129 : return FindAttributeDependence(name, map) ||
112 129 : SVGGraphicsElement::IsAttributeMapped(name);
113 : }
114 :
115 : //----------------------------------------------------------------------
116 : // nsSVGElement overrides
117 :
118 : // Helper for GetViewBoxTransform on root <svg> node
119 : // * aLength: internal value for our <svg> width or height attribute.
120 : // * aViewportLength: length of the corresponding dimension of the viewport.
121 : // * aSelf: the outermost <svg> node itself.
122 : // NOTE: aSelf is not an ancestor viewport element, so it can't be used to
123 : // resolve percentage lengths. (It can only be used to resolve
124 : // 'em'/'ex'-valued units).
125 : inline float
126 236 : ComputeSynthesizedViewBoxDimension(const nsSVGLength2& aLength,
127 : float aViewportLength,
128 : const SVGViewportElement* aSelf)
129 : {
130 236 : if (aLength.IsPercentage()) {
131 0 : return aViewportLength * aLength.GetAnimValInSpecifiedUnits() / 100.0f;
132 : }
133 :
134 236 : return aLength.GetAnimValue(const_cast<SVGViewportElement*>(aSelf));
135 : }
136 :
137 : //----------------------------------------------------------------------
138 : // public helpers:
139 :
140 : void
141 40 : SVGViewportElement::UpdateHasChildrenOnlyTransform()
142 : {
143 : bool hasChildrenOnlyTransform =
144 80 : HasViewBoxOrSyntheticViewBox() ||
145 41 : (IsRoot() && (GetCurrentTranslate() != SVGPoint(0.0f, 0.0f) ||
146 80 : GetCurrentScale() != 1.0f));
147 40 : mHasChildrenOnlyTransform = hasChildrenOnlyTransform;
148 40 : }
149 :
150 : void
151 18 : SVGViewportElement::ChildrenOnlyTransformChanged(uint32_t aFlags)
152 : {
153 : // Avoid wasteful calls:
154 18 : MOZ_ASSERT(!(GetPrimaryFrame()->GetStateBits() & NS_FRAME_IS_NONDISPLAY),
155 : "Non-display SVG frames don't maintain overflow rects");
156 :
157 : nsChangeHint changeHint;
158 :
159 18 : bool hadChildrenOnlyTransform = mHasChildrenOnlyTransform;
160 :
161 18 : UpdateHasChildrenOnlyTransform();
162 :
163 18 : if (hadChildrenOnlyTransform != mHasChildrenOnlyTransform) {
164 : // Reconstruct the frame tree to handle stacking context changes:
165 : // XXXjwatt don't do this for root-<svg> or even outer-<svg>?
166 0 : changeHint = nsChangeHint_ReconstructFrame;
167 : } else {
168 : // We just assume the old and new transforms are different.
169 : changeHint = nsChangeHint(nsChangeHint_UpdateOverflow |
170 18 : nsChangeHint_ChildrenOnlyTransform);
171 : }
172 :
173 : // If we're not reconstructing the frame tree, then we only call
174 : // PostRestyleEvent if we're not being called under reflow to avoid recursing
175 : // to death. See bug 767056 comments 10 and 12. Since our nsSVGOuterSVGFrame
176 : // is being reflowed we're going to invalidate and repaint its entire area
177 : // anyway (which will include our children).
178 36 : if ((changeHint & nsChangeHint_ReconstructFrame) ||
179 18 : !(aFlags & eDuringReflow)) {
180 0 : nsLayoutUtils::PostRestyleEvent(this, nsRestyleHint(0), changeHint);
181 : }
182 18 : }
183 :
184 : gfx::Matrix
185 770 : SVGViewportElement::GetViewBoxTransform() const
186 : {
187 : float viewportWidth, viewportHeight;
188 770 : if (IsInner()) {
189 0 : SVGViewportElement *ctx = GetCtx();
190 0 : viewportWidth = mLengthAttributes[ATTR_WIDTH].GetAnimValue(ctx);
191 0 : viewportHeight = mLengthAttributes[ATTR_HEIGHT].GetAnimValue(ctx);
192 : } else {
193 770 : viewportWidth = mViewportWidth;
194 770 : viewportHeight = mViewportHeight;
195 : }
196 :
197 770 : if (viewportWidth <= 0.0f || viewportHeight <= 0.0f) {
198 231 : return gfx::Matrix(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); // singular
199 : }
200 :
201 : nsSVGViewBoxRect viewBox =
202 539 : GetViewBoxWithSynthesis(viewportWidth, viewportHeight);
203 :
204 539 : if (viewBox.width <= 0.0f || viewBox.height <= 0.0f) {
205 0 : return gfx::Matrix(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); // singular
206 : }
207 :
208 : return SVGContentUtils::GetViewBoxTransform(viewportWidth, viewportHeight,
209 : viewBox.x, viewBox.y,
210 : viewBox.width, viewBox.height,
211 539 : GetPreserveAspectRatioWithOverride());
212 : }
213 : //----------------------------------------------------------------------
214 : // SVGViewportElement
215 :
216 : float
217 84 : SVGViewportElement::GetLength(uint8_t aCtxType)
218 : {
219 : const nsSVGViewBoxRect* viewbox =
220 84 : GetViewBoxInternal().HasRect() ? &GetViewBoxInternal().GetAnimValue()
221 84 : : nullptr;
222 :
223 : float h, w;
224 84 : if (viewbox) {
225 84 : w = viewbox->width;
226 84 : h = viewbox->height;
227 0 : } else if (IsInner()) {
228 0 : SVGViewportElement *ctx = GetCtx();
229 0 : w = mLengthAttributes[ATTR_WIDTH].GetAnimValue(ctx);
230 0 : h = mLengthAttributes[ATTR_HEIGHT].GetAnimValue(ctx);
231 0 : } else if (ShouldSynthesizeViewBox()) {
232 0 : w = ComputeSynthesizedViewBoxDimension(mLengthAttributes[ATTR_WIDTH],
233 : mViewportWidth, this);
234 0 : h = ComputeSynthesizedViewBoxDimension(mLengthAttributes[ATTR_HEIGHT],
235 : mViewportHeight, this);
236 : } else {
237 0 : w = mViewportWidth;
238 0 : h = mViewportHeight;
239 : }
240 :
241 84 : w = std::max(w, 0.0f);
242 84 : h = std::max(h, 0.0f);
243 :
244 84 : switch (aCtxType) {
245 : case SVGContentUtils::X:
246 42 : return w;
247 : case SVGContentUtils::Y:
248 42 : return h;
249 : case SVGContentUtils::XY:
250 0 : return float(SVGContentUtils::ComputeNormalizedHypotenuse(w, h));
251 : }
252 0 : return 0;
253 : }
254 :
255 : //----------------------------------------------------------------------
256 : // nsSVGElement methods
257 :
258 : /* virtual */ gfxMatrix
259 770 : SVGViewportElement::PrependLocalTransformsTo(const gfxMatrix& aMatrix,
260 : SVGTransformTypes aWhich) const
261 : {
262 : // 'transform' attribute (or an override from a fragment identifier):
263 770 : gfxMatrix userToParent;
264 :
265 770 : if (aWhich == eUserSpaceToParent || aWhich == eAllTransforms) {
266 : userToParent = GetUserToParentTransform(mAnimateMotionTransform,
267 0 : GetTransformInternal());
268 0 : if (aWhich == eUserSpaceToParent) {
269 0 : return userToParent * aMatrix;
270 : }
271 : }
272 :
273 770 : gfxMatrix childToUser;
274 :
275 770 : if (IsInner()) {
276 : float x, y;
277 0 : const_cast<SVGViewportElement*>(this)->GetAnimatedLengthValues(&x, &y, nullptr);
278 0 : childToUser = ThebesMatrix(GetViewBoxTransform().PostTranslate(x, y));
279 770 : } else if (IsRoot()) {
280 770 : SVGPoint translate = GetCurrentTranslate();
281 770 : float scale = GetCurrentScale();
282 1540 : childToUser = ThebesMatrix(GetViewBoxTransform()
283 770 : .PostScale(scale, scale)
284 : .PostTranslate(translate.GetX(),
285 1540 : translate.GetY()));
286 : } else {
287 : // outer-<svg>, but inline in some other content:
288 0 : childToUser = ThebesMatrix(GetViewBoxTransform());
289 : }
290 :
291 770 : if (aWhich == eAllTransforms) {
292 0 : return childToUser * userToParent * aMatrix;
293 : }
294 :
295 770 : MOZ_ASSERT(aWhich == eChildToUserSpace, "Unknown TransformTypes");
296 :
297 : // The following may look broken because pre-multiplying our eChildToUserSpace
298 : // transform with another matrix without including our eUserSpaceToParent
299 : // transform between the two wouldn't make sense. We don't expect that to
300 : // ever happen though. We get here either when the identity matrix has been
301 : // passed because our caller just wants our eChildToUserSpace transform, or
302 : // when our eUserSpaceToParent transform has already been multiplied into the
303 : // matrix that our caller passes (such as when we're called from PaintSVG).
304 770 : return childToUser * aMatrix;
305 : }
306 :
307 : /* virtual */ bool
308 18 : SVGViewportElement::HasValidDimensions() const
309 : {
310 36 : return !IsInner() ||
311 0 : ((!mLengthAttributes[ATTR_WIDTH].IsExplicitlySet() ||
312 0 : mLengthAttributes[ATTR_WIDTH].GetAnimValInSpecifiedUnits() > 0) &&
313 0 : (!mLengthAttributes[ATTR_HEIGHT].IsExplicitlySet() ||
314 18 : mLengthAttributes[ATTR_HEIGHT].GetAnimValInSpecifiedUnits() > 0));
315 : }
316 :
317 :
318 :
319 : nsSVGViewBox*
320 38 : SVGViewportElement::GetViewBox()
321 : {
322 38 : return &mViewBox;
323 : }
324 :
325 : SVGAnimatedPreserveAspectRatio *
326 25 : SVGViewportElement::GetPreserveAspectRatio()
327 : {
328 25 : return &mPreserveAspectRatio;
329 : }
330 :
331 : bool
332 264 : SVGViewportElement::ShouldSynthesizeViewBox() const
333 : {
334 264 : MOZ_ASSERT(!HasViewBoxRect(), "Should only be called if we lack a viewBox");
335 :
336 264 : return IsRoot() && OwnerDoc()->IsBeingUsedAsImage();
337 : }
338 :
339 : //----------------------------------------------------------------------
340 : // implementation helpers
341 :
342 : nsSVGViewBoxRect
343 539 : SVGViewportElement::GetViewBoxWithSynthesis(
344 : float aViewportWidth, float aViewportHeight) const
345 : {
346 539 : if (GetViewBoxInternal().HasRect()) {
347 421 : return GetViewBoxInternal().GetAnimValue();
348 : }
349 :
350 118 : if (ShouldSynthesizeViewBox()) {
351 : // Special case -- fake a viewBox, using height & width attrs.
352 : // (Use |this| as context, since if we get here, we're outermost <svg>.)
353 : return nsSVGViewBoxRect(0, 0,
354 : ComputeSynthesizedViewBoxDimension(mLengthAttributes[ATTR_WIDTH],
355 118 : mViewportWidth, this),
356 : ComputeSynthesizedViewBoxDimension(mLengthAttributes[ATTR_HEIGHT],
357 236 : mViewportHeight, this));
358 :
359 : }
360 :
361 : // No viewBox attribute, so we shouldn't auto-scale. This is equivalent
362 : // to having a viewBox that exactly matches our viewport size.
363 0 : return nsSVGViewBoxRect(0, 0, aViewportWidth, aViewportHeight);
364 : }
365 :
366 : nsSVGElement::LengthAttributesInfo
367 124 : SVGViewportElement::GetLengthInfo()
368 : {
369 : return LengthAttributesInfo(mLengthAttributes, sLengthInfo,
370 124 : ArrayLength(sLengthInfo));
371 : }
372 :
373 : } // namespace dom
374 : } // namespace mozilla
|