1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
15 * The Original Code is the Mozilla SVG project.
17 * The Initial Developer of the Original Code is
19 * Portions created by the Initial Developer are Copyright (C) 2004
20 * the Initial Developer. All Rights Reserved.
23 * Scooter Morris <scootermorris@comcast.net>
24 * Jonathan Watt <jwatt@jwatt.org>
26 * Alternatively, the contents of this file may be used under the terms of
27 * either the GNU General Public License Version 2 or later (the "GPL"), or
28 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
38 * ***** END LICENSE BLOCK ***** */
40 #include "nsIDOMSVGAnimatedNumber.h"
41 #include "nsIDOMSVGAnimTransformList.h"
42 #include "nsSVGTransformList.h"
43 #include "nsSVGMatrix.h"
44 #include "nsSVGEffects.h"
45 #include "nsIDOMSVGStopElement.h"
46 #include "nsSVGGradientElement.h"
47 #include "nsSVGGeometryFrame.h"
48 #include "nsSVGGradientFrame.h"
49 #include "gfxContext.h"
50 #include "gfxPattern.h"
52 //----------------------------------------------------------------------
55 nsSVGGradientFrame::nsSVGGradientFrame(nsStyleContext* aContext) :
56 nsSVGGradientFrameBase(aContext),
62 NS_IMPL_FRAMEARENA_HELPERS(nsSVGGradientFrame)
64 //----------------------------------------------------------------------
68 nsSVGGradientFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
70 nsSVGEffects::InvalidateRenderingObservers(this);
71 nsSVGGradientFrameBase::DidSetStyleContext(aOldStyleContext);
75 nsSVGGradientFrame::AttributeChanged(PRInt32 aNameSpaceID,
79 if (aNameSpaceID == kNameSpaceID_None &&
80 (aAttribute == nsGkAtoms::gradientUnits ||
81 aAttribute == nsGkAtoms::gradientTransform ||
82 aAttribute == nsGkAtoms::spreadMethod)) {
83 nsSVGEffects::InvalidateRenderingObservers(this);
84 } else if (aNameSpaceID == kNameSpaceID_XLink &&
85 aAttribute == nsGkAtoms::href) {
86 // Blow away our reference, if any
87 Properties().Delete(nsSVGEffects::HrefProperty());
88 mNoHRefURI = PR_FALSE;
89 // And update whoever references us
90 nsSVGEffects::InvalidateRenderingObservers(this);
93 return nsSVGGradientFrameBase::AttributeChanged(aNameSpaceID,
94 aAttribute, aModType);
97 //----------------------------------------------------------------------
100 nsSVGGradientFrame::GetStopCount()
102 return GetStopFrame(-1, nsnull);
106 nsSVGGradientFrame::GetStopInformation(PRInt32 aIndex,
112 *aStopColor = NS_RGBA(0, 0, 0, 0);
113 *aStopOpacity = 1.0f;
115 nsIFrame *stopFrame = nsnull;
116 GetStopFrame(aIndex, &stopFrame);
117 nsCOMPtr<nsIDOMSVGStopElement> stopElement =
118 do_QueryInterface(stopFrame->GetContent());
121 nsCOMPtr<nsIDOMSVGAnimatedNumber> aNum;
122 stopElement->GetOffset(getter_AddRefs(aNum));
124 aNum->GetAnimVal(aOffset);
127 else if (*aOffset > 1.0f)
132 *aStopColor = stopFrame->GetStyleSVGReset()->mStopColor;
133 *aStopOpacity = stopFrame->GetStyleSVGReset()->mStopOpacity;
136 // One way or another we have an implementation problem if we get here
137 else if (stopElement) {
138 NS_WARNING("We *do* have a stop but can't use it because it doesn't have "
139 "a frame - we need frame free gradients and stops!");
142 NS_ERROR("Don't call me with an invalid stop index!");
148 nsSVGGradientFrame::GetGradientTransform(nsIFrame *aSource,
149 const gfxRect *aOverrideBounds)
151 gfxMatrix bboxMatrix;
153 PRUint16 gradientUnits = GetGradientUnits();
154 if (gradientUnits == nsIDOMSVGUnitTypes::SVG_UNIT_TYPE_USERSPACEONUSE) {
155 // If this gradient is applied to text, our caller
156 // will be the glyph, which is not a container, so we
157 // need to get the parent
158 if (aSource->GetContent()->IsNodeOfType(nsINode::eTEXT))
159 mSource = aSource->GetParent();
163 NS_ASSERTION(gradientUnits == nsIDOMSVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX,
164 "Unknown gradientUnits type");
165 // objectBoundingBox is the default anyway
167 nsIFrame *frame = aSource->GetContent()->IsNodeOfType(nsINode::eTEXT) ?
168 aSource->GetParent() : aSource;
169 gfxRect bbox = aOverrideBounds ? *aOverrideBounds : nsSVGUtils::GetBBox(frame);
170 bboxMatrix = gfxMatrix(bbox.Width(), 0, 0, bbox.Height(), bbox.X(), bbox.Y());
173 nsSVGGradientElement *element =
174 GetGradientWithAttr(nsGkAtoms::gradientTransform, mContent);
176 if (!element->mGradientTransform)
179 nsCOMPtr<nsIDOMSVGTransformList> trans;
180 element->mGradientTransform->GetAnimVal(getter_AddRefs(trans));
181 nsCOMPtr<nsIDOMSVGMatrix> gradientTransform =
182 nsSVGTransformList::GetConsolidationMatrix(trans);
184 if (!gradientTransform)
187 return bboxMatrix.PreMultiply(nsSVGUtils::ConvertSVGMatrixToThebes(gradientTransform));
191 nsSVGGradientFrame::GetSpreadMethod()
193 nsSVGGradientElement *element =
194 GetGradientWithAttr(nsGkAtoms::spreadMethod, mContent);
196 return element->mEnumAttributes[nsSVGGradientElement::SPREADMETHOD].GetAnimValue();
199 //----------------------------------------------------------------------
200 // nsSVGPaintServerFrame methods:
202 already_AddRefed<gfxPattern>
203 nsSVGGradientFrame::GetPaintServerPattern(nsIFrame *aSource,
204 float aGraphicOpacity,
205 const gfxRect *aOverrideBounds)
207 // Get the transform list (if there is one)
208 gfxMatrix patternMatrix = GetGradientTransform(aSource, aOverrideBounds);
210 if (patternMatrix.IsSingular())
213 PRUint32 nStops = GetStopCount();
215 // SVG specification says that no stops should be treated like
216 // the corresponding fill or stroke had "none" specified.
218 nsRefPtr<gfxPattern> pattern = new gfxPattern(gfxRGBA(0, 0, 0, 0));
219 return pattern.forget();
222 patternMatrix.Invert();
224 nsRefPtr<gfxPattern> gradient = CreateGradient();
225 if (!gradient || gradient->CairoStatus())
228 PRUint16 aSpread = GetSpreadMethod();
229 if (aSpread == nsIDOMSVGGradientElement::SVG_SPREADMETHOD_PAD)
230 gradient->SetExtend(gfxPattern::EXTEND_PAD);
231 else if (aSpread == nsIDOMSVGGradientElement::SVG_SPREADMETHOD_REFLECT)
232 gradient->SetExtend(gfxPattern::EXTEND_REFLECT);
233 else if (aSpread == nsIDOMSVGGradientElement::SVG_SPREADMETHOD_REPEAT)
234 gradient->SetExtend(gfxPattern::EXTEND_REPEAT);
236 gradient->SetMatrix(patternMatrix);
239 float lastOffset = 0.0f;
241 for (PRUint32 i = 0; i < nStops; i++) {
242 float offset, stopOpacity;
245 GetStopInformation(i, &offset, &stopColor, &stopOpacity);
247 if (offset < lastOffset)
252 gradient->AddColorStop(offset,
253 gfxRGBA(NS_GET_R(stopColor)/255.0,
254 NS_GET_G(stopColor)/255.0,
255 NS_GET_B(stopColor)/255.0,
256 NS_GET_A(stopColor)/255.0 *
257 stopOpacity * aGraphicOpacity));
260 return gradient.forget();
263 // Private (helper) methods
266 nsSVGGradientFrame::GetReferencedGradient()
271 nsSVGPaintingProperty *property = static_cast<nsSVGPaintingProperty*>
272 (Properties().Get(nsSVGEffects::HrefProperty()));
275 // Fetch our gradient element's xlink:href attribute
276 nsSVGGradientElement *grad = static_cast<nsSVGGradientElement *>(mContent);
278 grad->mStringAttributes[nsSVGGradientElement::HREF].GetAnimValue(href, grad);
279 if (href.IsEmpty()) {
280 mNoHRefURI = PR_TRUE;
281 return nsnull; // no URL
284 // Convert href to an nsIURI
285 nsCOMPtr<nsIURI> targetURI;
286 nsCOMPtr<nsIURI> base = mContent->GetBaseURI();
287 nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(targetURI), href,
288 mContent->GetCurrentDoc(), base);
291 nsSVGEffects::GetPaintingProperty(targetURI, this, nsSVGEffects::HrefProperty());
296 nsIFrame *result = property->GetReferencedFrame();
300 nsIAtom* frameType = result->GetType();
301 if (frameType != nsGkAtoms::svgLinearGradientFrame &&
302 frameType != nsGkAtoms::svgRadialGradientFrame)
305 return static_cast<nsSVGGradientFrame*>(result);
308 nsSVGGradientElement *
309 nsSVGGradientFrame::GetGradientWithAttr(nsIAtom *aAttrName, nsIContent *aDefault)
311 if (mContent->HasAttr(kNameSpaceID_None, aAttrName))
312 return static_cast<nsSVGGradientElement *>(mContent);
314 nsSVGGradientElement *grad = static_cast<nsSVGGradientElement *>(aDefault);
316 nsSVGGradientFrame *next = GetReferencedGradient();
320 // Set mLoopFlag before checking mNextGrad->mLoopFlag in case we are mNextGrad
322 // XXXjwatt: we should really send an error to the JavaScript Console here:
323 NS_WARN_IF_FALSE(!next->mLoopFlag, "gradient reference loop detected "
324 "while inheriting attribute!");
325 if (!next->mLoopFlag)
326 grad = next->GetGradientWithAttr(aAttrName, aDefault);
327 mLoopFlag = PR_FALSE;
332 nsSVGGradientElement *
333 nsSVGGradientFrame::GetGradientWithAttr(nsIAtom *aAttrName, nsIAtom *aGradType,
334 nsIContent *aDefault)
336 if (GetType() == aGradType && mContent->HasAttr(kNameSpaceID_None, aAttrName))
337 return static_cast<nsSVGGradientElement *>(mContent);
339 nsSVGGradientElement *grad = static_cast<nsSVGGradientElement *>(aDefault);
341 nsSVGGradientFrame *next = GetReferencedGradient();
345 // Set mLoopFlag before checking mNextGrad->mLoopFlag in case we are mNextGrad
347 // XXXjwatt: we should really send an error to the JavaScript Console here:
348 NS_WARN_IF_FALSE(!next->mLoopFlag, "gradient reference loop detected "
349 "while inheriting attribute!");
350 if (!next->mLoopFlag)
351 grad = next->GetGradientWithAttr(aAttrName, aGradType, aDefault);
352 mLoopFlag = PR_FALSE;
358 nsSVGGradientFrame::GetStopFrame(PRInt32 aIndex, nsIFrame * *aStopFrame)
360 PRInt32 stopCount = 0;
361 nsIFrame *stopFrame = nsnull;
362 for (stopFrame = mFrames.FirstChild(); stopFrame;
363 stopFrame = stopFrame->GetNextSibling()) {
364 if (stopFrame->GetType() == nsGkAtoms::svgStopFrame) {
365 // Is this the one we're looking for?
366 if (stopCount++ == aIndex)
367 break; // Yes, break out of the loop
372 *aStopFrame = stopFrame;
376 // Our gradient element doesn't have stops - try to "inherit" them
378 nsSVGGradientFrame *next = GetReferencedGradient();
381 *aStopFrame = nsnull;
385 // Set mLoopFlag before checking mNextGrad->mLoopFlag in case we are mNextGrad
387 // XXXjwatt: we should really send an error to the JavaScript Console here:
388 NS_WARN_IF_FALSE(!next->mLoopFlag, "gradient reference loop detected "
389 "while inheriting stop!");
390 if (!next->mLoopFlag)
391 stopCount = next->GetStopFrame(aIndex, aStopFrame);
392 mLoopFlag = PR_FALSE;
398 nsSVGGradientFrame::GetGradientUnits()
400 // This getter is called every time the others are called - maybe cache it?
402 nsSVGGradientElement *element =
403 GetGradientWithAttr(nsGkAtoms::gradientUnits, mContent);
404 return element->mEnumAttributes[nsSVGGradientElement::GRADIENTUNITS].GetAnimValue();
407 // -------------------------------------------------------------------------
409 // -------------------------------------------------------------------------
413 nsSVGLinearGradientFrame::Init(nsIContent* aContent,
415 nsIFrame* aPrevInFlow)
417 nsCOMPtr<nsIDOMSVGLinearGradientElement> grad = do_QueryInterface(aContent);
418 NS_ASSERTION(grad, "Content is not an SVG linearGradient");
420 return nsSVGLinearGradientFrameBase::Init(aContent, aParent, aPrevInFlow);
425 nsSVGLinearGradientFrame::GetType() const
427 return nsGkAtoms::svgLinearGradientFrame;
431 nsSVGLinearGradientFrame::AttributeChanged(PRInt32 aNameSpaceID,
435 if (aNameSpaceID == kNameSpaceID_None &&
436 (aAttribute == nsGkAtoms::x1 ||
437 aAttribute == nsGkAtoms::y1 ||
438 aAttribute == nsGkAtoms::x2 ||
439 aAttribute == nsGkAtoms::y2)) {
440 nsSVGEffects::InvalidateRenderingObservers(this);
443 return nsSVGGradientFrame::AttributeChanged(aNameSpaceID,
444 aAttribute, aModType);
447 //----------------------------------------------------------------------
450 nsSVGLinearGradientFrame::GradientLookupAttribute(nsIAtom *aAtomName,
453 nsSVGLinearGradientElement *element =
454 GetLinearGradientWithAttr(aAtomName, mContent);
456 // Object bounding box units are handled by setting the appropriate
457 // transform in GetGradientTransform, but we need to handle user
458 // space units as part of the individual Get* routines. Fixes 323669.
460 PRUint16 gradientUnits = GetGradientUnits();
461 if (gradientUnits == nsIDOMSVGUnitTypes::SVG_UNIT_TYPE_USERSPACEONUSE) {
462 return nsSVGUtils::UserSpace(mSource,
463 &element->mLengthAttributes[aEnumName]);
466 NS_ASSERTION(gradientUnits == nsIDOMSVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX,
467 "Unknown gradientUnits type");
469 return element->mLengthAttributes[aEnumName].
470 GetAnimValue(static_cast<nsSVGSVGElement*>(nsnull));
473 already_AddRefed<gfxPattern>
474 nsSVGLinearGradientFrame::CreateGradient()
476 float x1, y1, x2, y2;
478 x1 = GradientLookupAttribute(nsGkAtoms::x1, nsSVGLinearGradientElement::X1);
479 y1 = GradientLookupAttribute(nsGkAtoms::y1, nsSVGLinearGradientElement::Y1);
480 x2 = GradientLookupAttribute(nsGkAtoms::x2, nsSVGLinearGradientElement::X2);
481 y2 = GradientLookupAttribute(nsGkAtoms::y2, nsSVGLinearGradientElement::Y2);
483 gfxPattern *pattern = new gfxPattern(x1, y1, x2, y2);
484 NS_IF_ADDREF(pattern);
488 // -------------------------------------------------------------------------
490 // -------------------------------------------------------------------------
494 nsSVGRadialGradientFrame::Init(nsIContent* aContent,
496 nsIFrame* aPrevInFlow)
498 nsCOMPtr<nsIDOMSVGRadialGradientElement> grad = do_QueryInterface(aContent);
499 NS_ASSERTION(grad, "Content is not an SVG radialGradient");
501 return nsSVGRadialGradientFrameBase::Init(aContent, aParent, aPrevInFlow);
506 nsSVGRadialGradientFrame::GetType() const
508 return nsGkAtoms::svgRadialGradientFrame;
512 nsSVGRadialGradientFrame::AttributeChanged(PRInt32 aNameSpaceID,
516 if (aNameSpaceID == kNameSpaceID_None &&
517 (aAttribute == nsGkAtoms::r ||
518 aAttribute == nsGkAtoms::cx ||
519 aAttribute == nsGkAtoms::cy ||
520 aAttribute == nsGkAtoms::fx ||
521 aAttribute == nsGkAtoms::fy)) {
522 nsSVGEffects::InvalidateRenderingObservers(this);
525 return nsSVGGradientFrame::AttributeChanged(aNameSpaceID,
526 aAttribute, aModType);
529 //----------------------------------------------------------------------
532 nsSVGRadialGradientFrame::GradientLookupAttribute(nsIAtom *aAtomName,
534 nsSVGRadialGradientElement *aElement)
536 nsSVGRadialGradientElement *element;
541 element = GetRadialGradientWithAttr(aAtomName, mContent);
544 // Object bounding box units are handled by setting the appropriate
545 // transform in GetGradientTransform, but we need to handle user
546 // space units as part of the individual Get* routines. Fixes 323669.
548 PRUint16 gradientUnits = GetGradientUnits();
549 if (gradientUnits == nsIDOMSVGUnitTypes::SVG_UNIT_TYPE_USERSPACEONUSE) {
550 return nsSVGUtils::UserSpace(mSource,
551 &element->mLengthAttributes[aEnumName]);
554 NS_ASSERTION(gradientUnits == nsIDOMSVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX,
555 "Unknown gradientUnits type");
557 return element->mLengthAttributes[aEnumName].
558 GetAnimValue(static_cast<nsSVGSVGElement*>(nsnull));
561 already_AddRefed<gfxPattern>
562 nsSVGRadialGradientFrame::CreateGradient()
564 float cx, cy, r, fx, fy;
566 cx = GradientLookupAttribute(nsGkAtoms::cx, nsSVGRadialGradientElement::CX);
567 cy = GradientLookupAttribute(nsGkAtoms::cy, nsSVGRadialGradientElement::CY);
568 r = GradientLookupAttribute(nsGkAtoms::r, nsSVGRadialGradientElement::R);
570 nsSVGRadialGradientElement *gradient;
572 if (!(gradient = GetRadialGradientWithAttr(nsGkAtoms::fx, nsnull)))
573 fx = cx; // if fx isn't set, we must use cx
575 fx = GradientLookupAttribute(nsGkAtoms::fx, nsSVGRadialGradientElement::FX, gradient);
577 if (!(gradient = GetRadialGradientWithAttr(nsGkAtoms::fy, nsnull)))
578 fy = cy; // if fy isn't set, we must use cy
580 fy = GradientLookupAttribute(nsGkAtoms::fy, nsSVGRadialGradientElement::FY, gradient);
582 if (fx != cx || fy != cy) {
583 // The focal point (fFx and fFy) must be clamped to be *inside* - not on -
584 // the circumference of the gradient or we'll get rendering anomalies. We
585 // calculate the distance from the focal point to the gradient center and
586 // make sure it is *less* than the gradient radius.
587 // 1/128 is the limit of the fractional part of cairo's 24.8 fixed point
588 // representation divided by 2 to ensure that we get different cairo
590 double dMax = NS_MAX(0.0, r - 1.0/128);
593 double d = sqrt((dx * dx) + (dy * dy));
595 double angle = atan2(dy, dx);
596 fx = (float)(dMax * cos(angle)) + cx;
597 fy = (float)(dMax * sin(angle)) + cy;
601 gfxPattern *pattern = new gfxPattern(fx, fy, 0, cx, cy, r);
602 NS_IF_ADDREF(pattern);
606 // -------------------------------------------------------------------------
608 // -------------------------------------------------------------------------
611 NS_NewSVGLinearGradientFrame(nsIPresShell* aPresShell,
612 nsStyleContext* aContext)
614 return new (aPresShell) nsSVGLinearGradientFrame(aContext);
617 NS_IMPL_FRAMEARENA_HELPERS(nsSVGLinearGradientFrame)
620 NS_NewSVGRadialGradientFrame(nsIPresShell* aPresShell,
621 nsStyleContext* aContext)
623 return new (aPresShell) nsSVGRadialGradientFrame(aContext);
626 NS_IMPL_FRAMEARENA_HELPERS(nsSVGRadialGradientFrame)