mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-11-09 04:39:03 +02:00
gfx::Color is currently misused in many places. The DrawTargets expect the color space to be in device space, e.g. what we are actually going to draw using. Everything sitting above generally deals with sRGB, as specified in CSS. Sometimes we missed the conversion from sRGB to device space when issuing draw calls, and similarly sometimes we converted the color to device space twice. This patch splits the type in two. sRGBColor and DeviceColor now represent sRGB and device color spaces respectively. DrawTarget only accepts DeviceColor, and one can get a DeviceColor from an sRGBColor via the ToDeviceColor helper API. The reftests now pass with color management enabled for everything (e.g. CSS) instead of just tagged raster images. There will be a follow up patch to enable color management everywhere by default on all supported platforms. Differential Revision: https://phabricator.services.mozilla.com/D64771 --HG-- extra : moz-landing-system : lando
612 lines
23 KiB
C++
612 lines
23 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
// Main header first:
|
|
#include "nsSVGGradientFrame.h"
|
|
#include <algorithm>
|
|
|
|
// Keep others in (case-insensitive) order:
|
|
#include "AutoReferenceChainGuard.h"
|
|
#include "gfxPattern.h"
|
|
#include "mozilla/PresShell.h"
|
|
#include "mozilla/dom/SVGGradientElement.h"
|
|
#include "mozilla/dom/SVGGradientElementBinding.h"
|
|
#include "mozilla/dom/SVGStopElement.h"
|
|
#include "mozilla/dom/SVGUnitTypesBinding.h"
|
|
#include "nsContentUtils.h"
|
|
#include "SVGObserverUtils.h"
|
|
#include "SVGAnimatedTransformList.h"
|
|
|
|
// XXX Tight coupling with content classes ahead!
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::dom;
|
|
using namespace mozilla::dom::SVGGradientElement_Binding;
|
|
using namespace mozilla::dom::SVGUnitTypes_Binding;
|
|
using namespace mozilla::gfx;
|
|
|
|
//----------------------------------------------------------------------
|
|
// Implementation
|
|
|
|
nsSVGGradientFrame::nsSVGGradientFrame(ComputedStyle* aStyle,
|
|
nsPresContext* aPresContext, ClassID aID)
|
|
: nsSVGPaintServerFrame(aStyle, aPresContext, aID),
|
|
mSource(nullptr),
|
|
mLoopFlag(false),
|
|
mNoHRefURI(false) {}
|
|
|
|
//----------------------------------------------------------------------
|
|
// nsIFrame methods:
|
|
|
|
nsresult nsSVGGradientFrame::AttributeChanged(int32_t aNameSpaceID,
|
|
nsAtom* aAttribute,
|
|
int32_t aModType) {
|
|
if (aNameSpaceID == kNameSpaceID_None &&
|
|
(aAttribute == nsGkAtoms::gradientUnits ||
|
|
aAttribute == nsGkAtoms::gradientTransform ||
|
|
aAttribute == nsGkAtoms::spreadMethod)) {
|
|
SVGObserverUtils::InvalidateDirectRenderingObservers(this);
|
|
} else if ((aNameSpaceID == kNameSpaceID_XLink ||
|
|
aNameSpaceID == kNameSpaceID_None) &&
|
|
aAttribute == nsGkAtoms::href) {
|
|
// Blow away our reference, if any
|
|
SVGObserverUtils::RemoveTemplateObserver(this);
|
|
mNoHRefURI = false;
|
|
// And update whoever references us
|
|
SVGObserverUtils::InvalidateDirectRenderingObservers(this);
|
|
}
|
|
|
|
return nsSVGPaintServerFrame::AttributeChanged(aNameSpaceID, aAttribute,
|
|
aModType);
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
uint16_t nsSVGGradientFrame::GetEnumValue(uint32_t aIndex,
|
|
nsIContent* aDefault) {
|
|
const SVGAnimatedEnumeration& thisEnum =
|
|
static_cast<dom::SVGGradientElement*>(GetContent())
|
|
->mEnumAttributes[aIndex];
|
|
|
|
if (thisEnum.IsExplicitlySet()) {
|
|
return thisEnum.GetAnimValue();
|
|
}
|
|
|
|
// Before we recurse, make sure we'll break reference loops and over long
|
|
// reference chains:
|
|
static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
|
|
AutoReferenceChainGuard refChainGuard(this, &mLoopFlag,
|
|
&sRefChainLengthCounter);
|
|
if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
|
|
// Break reference chain
|
|
return static_cast<dom::SVGGradientElement*>(aDefault)
|
|
->mEnumAttributes[aIndex]
|
|
.GetAnimValue();
|
|
}
|
|
|
|
nsSVGGradientFrame* next = GetReferencedGradient();
|
|
|
|
return next ? next->GetEnumValue(aIndex, aDefault)
|
|
: static_cast<dom::SVGGradientElement*>(aDefault)
|
|
->mEnumAttributes[aIndex]
|
|
.GetAnimValue();
|
|
}
|
|
|
|
uint16_t nsSVGGradientFrame::GetGradientUnits() {
|
|
// This getter is called every time the others are called - maybe cache it?
|
|
return GetEnumValue(dom::SVGGradientElement::GRADIENTUNITS);
|
|
}
|
|
|
|
uint16_t nsSVGGradientFrame::GetSpreadMethod() {
|
|
return GetEnumValue(dom::SVGGradientElement::SPREADMETHOD);
|
|
}
|
|
|
|
const SVGAnimatedTransformList* nsSVGGradientFrame::GetGradientTransformList(
|
|
nsIContent* aDefault) {
|
|
SVGAnimatedTransformList* thisTransformList =
|
|
static_cast<dom::SVGGradientElement*>(GetContent())
|
|
->GetAnimatedTransformList();
|
|
|
|
if (thisTransformList && thisTransformList->IsExplicitlySet())
|
|
return thisTransformList;
|
|
|
|
// Before we recurse, make sure we'll break reference loops and over long
|
|
// reference chains:
|
|
static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
|
|
AutoReferenceChainGuard refChainGuard(this, &mLoopFlag,
|
|
&sRefChainLengthCounter);
|
|
if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
|
|
// Break reference chain
|
|
return static_cast<const dom::SVGGradientElement*>(aDefault)
|
|
->mGradientTransform.get();
|
|
}
|
|
|
|
nsSVGGradientFrame* next = GetReferencedGradient();
|
|
|
|
return next ? next->GetGradientTransformList(aDefault)
|
|
: static_cast<const dom::SVGGradientElement*>(aDefault)
|
|
->mGradientTransform.get();
|
|
}
|
|
|
|
gfxMatrix nsSVGGradientFrame::GetGradientTransform(
|
|
nsIFrame* aSource, const gfxRect* aOverrideBounds) {
|
|
gfxMatrix bboxMatrix;
|
|
|
|
uint16_t gradientUnits = GetGradientUnits();
|
|
if (gradientUnits != SVG_UNIT_TYPE_USERSPACEONUSE) {
|
|
NS_ASSERTION(gradientUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX,
|
|
"Unknown gradientUnits type");
|
|
// objectBoundingBox is the default anyway
|
|
|
|
gfxRect bbox = aOverrideBounds
|
|
? *aOverrideBounds
|
|
: nsSVGUtils::GetBBox(
|
|
aSource, nsSVGUtils::eUseFrameBoundsForOuterSVG |
|
|
nsSVGUtils::eBBoxIncludeFillGeometry);
|
|
bboxMatrix =
|
|
gfxMatrix(bbox.Width(), 0, 0, bbox.Height(), bbox.X(), bbox.Y());
|
|
}
|
|
|
|
const SVGAnimatedTransformList* animTransformList =
|
|
GetGradientTransformList(GetContent());
|
|
if (!animTransformList) {
|
|
return bboxMatrix;
|
|
}
|
|
|
|
gfxMatrix gradientTransform =
|
|
animTransformList->GetAnimValue().GetConsolidationMatrix();
|
|
return bboxMatrix.PreMultiply(gradientTransform);
|
|
}
|
|
|
|
dom::SVGLinearGradientElement* nsSVGGradientFrame::GetLinearGradientWithLength(
|
|
uint32_t aIndex, dom::SVGLinearGradientElement* aDefault) {
|
|
// If this was a linear gradient with the required length, we would have
|
|
// already found it in nsSVGLinearGradientFrame::GetLinearGradientWithLength.
|
|
// Since we didn't find the length, continue looking down the chain.
|
|
|
|
// Before we recurse, make sure we'll break reference loops and over long
|
|
// reference chains:
|
|
static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
|
|
AutoReferenceChainGuard refChainGuard(this, &mLoopFlag,
|
|
&sRefChainLengthCounter);
|
|
if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
|
|
// Break reference chain
|
|
return aDefault;
|
|
}
|
|
|
|
nsSVGGradientFrame* next = GetReferencedGradient();
|
|
return next ? next->GetLinearGradientWithLength(aIndex, aDefault) : aDefault;
|
|
}
|
|
|
|
dom::SVGRadialGradientElement* nsSVGGradientFrame::GetRadialGradientWithLength(
|
|
uint32_t aIndex, dom::SVGRadialGradientElement* aDefault) {
|
|
// If this was a radial gradient with the required length, we would have
|
|
// already found it in nsSVGRadialGradientFrame::GetRadialGradientWithLength.
|
|
// Since we didn't find the length, continue looking down the chain.
|
|
|
|
// Before we recurse, make sure we'll break reference loops and over long
|
|
// reference chains:
|
|
static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
|
|
AutoReferenceChainGuard refChainGuard(this, &mLoopFlag,
|
|
&sRefChainLengthCounter);
|
|
if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
|
|
// Break reference chain
|
|
return aDefault;
|
|
}
|
|
|
|
nsSVGGradientFrame* next = GetReferencedGradient();
|
|
return next ? next->GetRadialGradientWithLength(aIndex, aDefault) : aDefault;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// nsSVGPaintServerFrame methods:
|
|
|
|
// helper
|
|
static void GetStopInformation(nsIFrame* aStopFrame, float* aOffset,
|
|
nscolor* aStopColor, float* aStopOpacity) {
|
|
nsIContent* stopContent = aStopFrame->GetContent();
|
|
MOZ_ASSERT(stopContent && stopContent->IsSVGElement(nsGkAtoms::stop));
|
|
|
|
static_cast<SVGStopElement*>(stopContent)
|
|
->GetAnimatedNumberValues(aOffset, nullptr);
|
|
|
|
const nsStyleSVGReset* styleSVGReset = aStopFrame->StyleSVGReset();
|
|
*aOffset = mozilla::clamped(*aOffset, 0.0f, 1.0f);
|
|
*aStopColor = styleSVGReset->mStopColor.CalcColor(aStopFrame);
|
|
*aStopOpacity = styleSVGReset->mStopOpacity;
|
|
}
|
|
|
|
already_AddRefed<gfxPattern> nsSVGGradientFrame::GetPaintServerPattern(
|
|
nsIFrame* aSource, const DrawTarget* aDrawTarget,
|
|
const gfxMatrix& aContextMatrix, StyleSVGPaint nsStyleSVG::*aFillOrStroke,
|
|
float aGraphicOpacity, imgDrawingParams& aImgParams,
|
|
const gfxRect* aOverrideBounds) {
|
|
uint16_t gradientUnits = GetGradientUnits();
|
|
MOZ_ASSERT(gradientUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX ||
|
|
gradientUnits == SVG_UNIT_TYPE_USERSPACEONUSE);
|
|
if (gradientUnits == SVG_UNIT_TYPE_USERSPACEONUSE) {
|
|
// Set mSource for this consumer.
|
|
// If this gradient is applied to text, our caller will be the glyph, which
|
|
// is not an element, so we need to get the parent
|
|
mSource = aSource->GetContent()->IsText() ? aSource->GetParent() : aSource;
|
|
}
|
|
|
|
AutoTArray<nsIFrame*, 8> stopFrames;
|
|
GetStopFrames(&stopFrames);
|
|
|
|
uint32_t nStops = stopFrames.Length();
|
|
|
|
// SVG specification says that no stops should be treated like
|
|
// the corresponding fill or stroke had "none" specified.
|
|
if (nStops == 0) {
|
|
RefPtr<gfxPattern> pattern = new gfxPattern(DeviceColor());
|
|
return do_AddRef(new gfxPattern(DeviceColor()));
|
|
}
|
|
|
|
if (nStops == 1 || GradientVectorLengthIsZero()) {
|
|
auto lastStopFrame = stopFrames[nStops - 1];
|
|
auto svgReset = lastStopFrame->StyleSVGReset();
|
|
// The gradient paints a single colour, using the stop-color of the last
|
|
// gradient step if there are more than one.
|
|
float stopOpacity = svgReset->mStopOpacity;
|
|
nscolor stopColor = svgReset->mStopColor.CalcColor(lastStopFrame);
|
|
|
|
sRGBColor stopColor2 = sRGBColor::FromABGR(stopColor);
|
|
stopColor2.a *= stopOpacity * aGraphicOpacity;
|
|
return do_AddRef(new gfxPattern(ToDeviceColor(stopColor2)));
|
|
}
|
|
|
|
// Get the transform list (if there is one). We do this after the returns
|
|
// above since this call can be expensive when "gradientUnits" is set to
|
|
// "objectBoundingBox" (since that requiring a GetBBox() call).
|
|
gfxMatrix patternMatrix = GetGradientTransform(aSource, aOverrideBounds);
|
|
|
|
if (patternMatrix.IsSingular()) {
|
|
return nullptr;
|
|
}
|
|
|
|
// revert any vector effect transform so that the gradient appears unchanged
|
|
if (aFillOrStroke == &nsStyleSVG::mStroke) {
|
|
gfxMatrix userToOuterSVG;
|
|
if (nsSVGUtils::GetNonScalingStrokeTransform(aSource, &userToOuterSVG)) {
|
|
patternMatrix *= userToOuterSVG;
|
|
}
|
|
}
|
|
|
|
if (!patternMatrix.Invert()) {
|
|
return nullptr;
|
|
}
|
|
|
|
RefPtr<gfxPattern> gradient = CreateGradient();
|
|
if (!gradient) {
|
|
return nullptr;
|
|
}
|
|
|
|
uint16_t aSpread = GetSpreadMethod();
|
|
if (aSpread == SVG_SPREADMETHOD_PAD)
|
|
gradient->SetExtend(ExtendMode::CLAMP);
|
|
else if (aSpread == SVG_SPREADMETHOD_REFLECT)
|
|
gradient->SetExtend(ExtendMode::REFLECT);
|
|
else if (aSpread == SVG_SPREADMETHOD_REPEAT)
|
|
gradient->SetExtend(ExtendMode::REPEAT);
|
|
|
|
gradient->SetMatrix(patternMatrix);
|
|
|
|
// setup stops
|
|
float lastOffset = 0.0f;
|
|
|
|
for (uint32_t i = 0; i < nStops; i++) {
|
|
float offset, stopOpacity;
|
|
nscolor stopColor;
|
|
|
|
GetStopInformation(stopFrames[i], &offset, &stopColor, &stopOpacity);
|
|
|
|
if (offset < lastOffset)
|
|
offset = lastOffset;
|
|
else
|
|
lastOffset = offset;
|
|
|
|
sRGBColor stopColor2 = sRGBColor::FromABGR(stopColor);
|
|
stopColor2.a *= stopOpacity * aGraphicOpacity;
|
|
gradient->AddColorStop(offset, ToDeviceColor(stopColor2));
|
|
}
|
|
|
|
return gradient.forget();
|
|
}
|
|
|
|
// Private (helper) methods
|
|
|
|
nsSVGGradientFrame* nsSVGGradientFrame::GetReferencedGradient() {
|
|
if (mNoHRefURI) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto GetHref = [this](nsAString& aHref) {
|
|
dom::SVGGradientElement* grad =
|
|
static_cast<dom::SVGGradientElement*>(this->GetContent());
|
|
if (grad->mStringAttributes[dom::SVGGradientElement::HREF]
|
|
.IsExplicitlySet()) {
|
|
grad->mStringAttributes[dom::SVGGradientElement::HREF].GetAnimValue(aHref,
|
|
grad);
|
|
} else {
|
|
grad->mStringAttributes[dom::SVGGradientElement::XLINK_HREF].GetAnimValue(
|
|
aHref, grad);
|
|
}
|
|
this->mNoHRefURI = aHref.IsEmpty();
|
|
};
|
|
|
|
nsIFrame* tframe = SVGObserverUtils::GetAndObserveTemplate(this, GetHref);
|
|
if (tframe) {
|
|
LayoutFrameType frameType = tframe->Type();
|
|
if (frameType == LayoutFrameType::SVGLinearGradient ||
|
|
frameType == LayoutFrameType::SVGRadialGradient) {
|
|
return static_cast<nsSVGGradientFrame*>(tframe);
|
|
}
|
|
// We don't call SVGObserverUtils::RemoveTemplateObserver and set
|
|
// `mNoHRefURI = false` here since we want to be invalidated if the ID
|
|
// specified by our href starts resolving to a different/valid element.
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void nsSVGGradientFrame::GetStopFrames(nsTArray<nsIFrame*>* aStopFrames) {
|
|
nsIFrame* stopFrame = nullptr;
|
|
for (stopFrame = mFrames.FirstChild(); stopFrame;
|
|
stopFrame = stopFrame->GetNextSibling()) {
|
|
if (stopFrame->IsSVGStopFrame()) {
|
|
aStopFrames->AppendElement(stopFrame);
|
|
}
|
|
}
|
|
if (aStopFrames->Length() > 0) {
|
|
return;
|
|
}
|
|
|
|
// Our gradient element doesn't have stops - try to "inherit" them
|
|
|
|
// Before we recurse, make sure we'll break reference loops and over long
|
|
// reference chains:
|
|
static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
|
|
AutoReferenceChainGuard refChainGuard(this, &mLoopFlag,
|
|
&sRefChainLengthCounter);
|
|
if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
|
|
// Break reference chain
|
|
return;
|
|
}
|
|
|
|
nsSVGGradientFrame* next = GetReferencedGradient();
|
|
if (next) {
|
|
next->GetStopFrames(aStopFrames);
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Linear Gradients
|
|
// -------------------------------------------------------------------------
|
|
|
|
#ifdef DEBUG
|
|
void nsSVGLinearGradientFrame::Init(nsIContent* aContent,
|
|
nsContainerFrame* aParent,
|
|
nsIFrame* aPrevInFlow) {
|
|
NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::linearGradient),
|
|
"Content is not an SVG linearGradient");
|
|
|
|
nsSVGGradientFrame::Init(aContent, aParent, aPrevInFlow);
|
|
}
|
|
#endif /* DEBUG */
|
|
|
|
nsresult nsSVGLinearGradientFrame::AttributeChanged(int32_t aNameSpaceID,
|
|
nsAtom* aAttribute,
|
|
int32_t aModType) {
|
|
if (aNameSpaceID == kNameSpaceID_None &&
|
|
(aAttribute == nsGkAtoms::x1 || aAttribute == nsGkAtoms::y1 ||
|
|
aAttribute == nsGkAtoms::x2 || aAttribute == nsGkAtoms::y2)) {
|
|
SVGObserverUtils::InvalidateDirectRenderingObservers(this);
|
|
}
|
|
|
|
return nsSVGGradientFrame::AttributeChanged(aNameSpaceID, aAttribute,
|
|
aModType);
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
float nsSVGLinearGradientFrame::GetLengthValue(uint32_t aIndex) {
|
|
dom::SVGLinearGradientElement* lengthElement = GetLinearGradientWithLength(
|
|
aIndex, static_cast<dom::SVGLinearGradientElement*>(GetContent()));
|
|
// We passed in mContent as a fallback, so, assuming mContent is non-null, the
|
|
// return value should also be non-null.
|
|
MOZ_ASSERT(lengthElement,
|
|
"Got unexpected null element from GetLinearGradientWithLength");
|
|
const SVGAnimatedLength& length = lengthElement->mLengthAttributes[aIndex];
|
|
|
|
// Object bounding box units are handled by setting the appropriate
|
|
// transform in GetGradientTransform, but we need to handle user
|
|
// space units as part of the individual Get* routines. Fixes 323669.
|
|
|
|
uint16_t gradientUnits = GetGradientUnits();
|
|
if (gradientUnits == SVG_UNIT_TYPE_USERSPACEONUSE) {
|
|
return nsSVGUtils::UserSpace(mSource, &length);
|
|
}
|
|
|
|
NS_ASSERTION(gradientUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX,
|
|
"Unknown gradientUnits type");
|
|
|
|
return length.GetAnimValue(static_cast<SVGViewportElement*>(nullptr));
|
|
}
|
|
|
|
dom::SVGLinearGradientElement*
|
|
nsSVGLinearGradientFrame::GetLinearGradientWithLength(
|
|
uint32_t aIndex, dom::SVGLinearGradientElement* aDefault) {
|
|
dom::SVGLinearGradientElement* thisElement =
|
|
static_cast<dom::SVGLinearGradientElement*>(GetContent());
|
|
const SVGAnimatedLength& length = thisElement->mLengthAttributes[aIndex];
|
|
|
|
if (length.IsExplicitlySet()) {
|
|
return thisElement;
|
|
}
|
|
|
|
return nsSVGGradientFrame::GetLinearGradientWithLength(aIndex, aDefault);
|
|
}
|
|
|
|
bool nsSVGLinearGradientFrame::GradientVectorLengthIsZero() {
|
|
return GetLengthValue(dom::SVGLinearGradientElement::ATTR_X1) ==
|
|
GetLengthValue(dom::SVGLinearGradientElement::ATTR_X2) &&
|
|
GetLengthValue(dom::SVGLinearGradientElement::ATTR_Y1) ==
|
|
GetLengthValue(dom::SVGLinearGradientElement::ATTR_Y2);
|
|
}
|
|
|
|
already_AddRefed<gfxPattern> nsSVGLinearGradientFrame::CreateGradient() {
|
|
float x1, y1, x2, y2;
|
|
|
|
x1 = GetLengthValue(dom::SVGLinearGradientElement::ATTR_X1);
|
|
y1 = GetLengthValue(dom::SVGLinearGradientElement::ATTR_Y1);
|
|
x2 = GetLengthValue(dom::SVGLinearGradientElement::ATTR_X2);
|
|
y2 = GetLengthValue(dom::SVGLinearGradientElement::ATTR_Y2);
|
|
|
|
RefPtr<gfxPattern> pattern = new gfxPattern(x1, y1, x2, y2);
|
|
return pattern.forget();
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Radial Gradients
|
|
// -------------------------------------------------------------------------
|
|
|
|
#ifdef DEBUG
|
|
void nsSVGRadialGradientFrame::Init(nsIContent* aContent,
|
|
nsContainerFrame* aParent,
|
|
nsIFrame* aPrevInFlow) {
|
|
NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::radialGradient),
|
|
"Content is not an SVG radialGradient");
|
|
|
|
nsSVGGradientFrame::Init(aContent, aParent, aPrevInFlow);
|
|
}
|
|
#endif /* DEBUG */
|
|
|
|
nsresult nsSVGRadialGradientFrame::AttributeChanged(int32_t aNameSpaceID,
|
|
nsAtom* aAttribute,
|
|
int32_t aModType) {
|
|
if (aNameSpaceID == kNameSpaceID_None &&
|
|
(aAttribute == nsGkAtoms::r || aAttribute == nsGkAtoms::cx ||
|
|
aAttribute == nsGkAtoms::cy || aAttribute == nsGkAtoms::fx ||
|
|
aAttribute == nsGkAtoms::fy)) {
|
|
SVGObserverUtils::InvalidateDirectRenderingObservers(this);
|
|
}
|
|
|
|
return nsSVGGradientFrame::AttributeChanged(aNameSpaceID, aAttribute,
|
|
aModType);
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
float nsSVGRadialGradientFrame::GetLengthValue(uint32_t aIndex) {
|
|
dom::SVGRadialGradientElement* lengthElement = GetRadialGradientWithLength(
|
|
aIndex, static_cast<dom::SVGRadialGradientElement*>(GetContent()));
|
|
// We passed in mContent as a fallback, so, assuming mContent is non-null,
|
|
// the return value should also be non-null.
|
|
MOZ_ASSERT(lengthElement,
|
|
"Got unexpected null element from GetRadialGradientWithLength");
|
|
return GetLengthValueFromElement(aIndex, *lengthElement);
|
|
}
|
|
|
|
float nsSVGRadialGradientFrame::GetLengthValue(uint32_t aIndex,
|
|
float aDefaultValue) {
|
|
dom::SVGRadialGradientElement* lengthElement =
|
|
GetRadialGradientWithLength(aIndex, nullptr);
|
|
|
|
return lengthElement ? GetLengthValueFromElement(aIndex, *lengthElement)
|
|
: aDefaultValue;
|
|
}
|
|
|
|
float nsSVGRadialGradientFrame::GetLengthValueFromElement(
|
|
uint32_t aIndex, dom::SVGRadialGradientElement& aElement) {
|
|
const SVGAnimatedLength& length = aElement.mLengthAttributes[aIndex];
|
|
|
|
// Object bounding box units are handled by setting the appropriate
|
|
// transform in GetGradientTransform, but we need to handle user
|
|
// space units as part of the individual Get* routines. Fixes 323669.
|
|
|
|
uint16_t gradientUnits = GetGradientUnits();
|
|
if (gradientUnits == SVG_UNIT_TYPE_USERSPACEONUSE) {
|
|
return nsSVGUtils::UserSpace(mSource, &length);
|
|
}
|
|
|
|
NS_ASSERTION(gradientUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX,
|
|
"Unknown gradientUnits type");
|
|
|
|
return length.GetAnimValue(static_cast<SVGViewportElement*>(nullptr));
|
|
}
|
|
|
|
dom::SVGRadialGradientElement*
|
|
nsSVGRadialGradientFrame::GetRadialGradientWithLength(
|
|
uint32_t aIndex, dom::SVGRadialGradientElement* aDefault) {
|
|
dom::SVGRadialGradientElement* thisElement =
|
|
static_cast<dom::SVGRadialGradientElement*>(GetContent());
|
|
const SVGAnimatedLength& length = thisElement->mLengthAttributes[aIndex];
|
|
|
|
if (length.IsExplicitlySet()) {
|
|
return thisElement;
|
|
}
|
|
|
|
return nsSVGGradientFrame::GetRadialGradientWithLength(aIndex, aDefault);
|
|
}
|
|
|
|
bool nsSVGRadialGradientFrame::GradientVectorLengthIsZero() {
|
|
return GetLengthValue(dom::SVGRadialGradientElement::ATTR_R) == 0;
|
|
}
|
|
|
|
already_AddRefed<gfxPattern> nsSVGRadialGradientFrame::CreateGradient() {
|
|
float cx, cy, r, fx, fy, fr;
|
|
|
|
cx = GetLengthValue(dom::SVGRadialGradientElement::ATTR_CX);
|
|
cy = GetLengthValue(dom::SVGRadialGradientElement::ATTR_CY);
|
|
r = GetLengthValue(dom::SVGRadialGradientElement::ATTR_R);
|
|
// If fx or fy are not set, use cx/cy instead
|
|
fx = GetLengthValue(dom::SVGRadialGradientElement::ATTR_FX, cx);
|
|
fy = GetLengthValue(dom::SVGRadialGradientElement::ATTR_FY, cy);
|
|
fr = GetLengthValue(dom::SVGRadialGradientElement::ATTR_FR);
|
|
|
|
if (fx != cx || fy != cy) {
|
|
// The focal point (fFx and fFy) must be clamped to be *inside* - not on -
|
|
// the circumference of the gradient or we'll get rendering anomalies. We
|
|
// calculate the distance from the focal point to the gradient center and
|
|
// make sure it is *less* than the gradient radius.
|
|
// 1/128 is the limit of the fractional part of cairo's 24.8 fixed point
|
|
// representation divided by 2 to ensure that we get different cairo
|
|
// fractions
|
|
double dMax = std::max(0.0, r - 1.0 / 128);
|
|
double dx = fx - cx;
|
|
double dy = fy - cy;
|
|
double d = std::sqrt((dx * dx) + (dy * dy));
|
|
if (d > dMax) {
|
|
double angle = std::atan2(dy, dx);
|
|
fx = float(dMax * std::cos(angle)) + cx;
|
|
fy = float(dMax * std::sin(angle)) + cy;
|
|
}
|
|
}
|
|
|
|
RefPtr<gfxPattern> pattern = new gfxPattern(fx, fy, fr, cx, cy, r);
|
|
return pattern.forget();
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Public functions
|
|
// -------------------------------------------------------------------------
|
|
|
|
nsIFrame* NS_NewSVGLinearGradientFrame(mozilla::PresShell* aPresShell,
|
|
ComputedStyle* aStyle) {
|
|
return new (aPresShell)
|
|
nsSVGLinearGradientFrame(aStyle, aPresShell->GetPresContext());
|
|
}
|
|
|
|
NS_IMPL_FRAMEARENA_HELPERS(nsSVGLinearGradientFrame)
|
|
|
|
nsIFrame* NS_NewSVGRadialGradientFrame(mozilla::PresShell* aPresShell,
|
|
ComputedStyle* aStyle) {
|
|
return new (aPresShell)
|
|
nsSVGRadialGradientFrame(aStyle, aPresShell->GetPresContext());
|
|
}
|
|
|
|
NS_IMPL_FRAMEARENA_HELPERS(nsSVGRadialGradientFrame)
|