Bug 528332 - Implement non-scaling-stroke vector-effect. Part 2 - SVG changes r=dholbert

This commit is contained in:
Robert Longson 2012-05-18 09:34:25 +01:00
parent 3ca6774243
commit 38cb4c47d9
25 changed files with 226 additions and 13 deletions

View File

@ -11275,7 +11275,7 @@ define("examples/textview/textStyler", ['orion/textview/annotations'], function(
"target", "target-name", "target-new", "target-position", "text-align", "text-align-last", "text-decoration", "text-emphasis",
"text-height", "text-indent", "text-justify", "text-outline", "text-shadow", "text-transform", "text-wrap", "top", "transform",
"transform-origin", "transform-style", "transition", "transition-delay", "transition-duration", "transition-property",
"transition-timing-function", "unicode-bidi", "vertical-align", "visibility", "voice-balance", "voice-duration", "voice-family",
"transition-timing-function", "unicode-bidi", "vector-effect", "vertical-align", "visibility", "voice-balance", "voice-duration", "voice-family",
"voice-pitch", "voice-pitch-range", "voice-rate", "voice-stress", "voice-volume", "volume", "white-space", "white-space-collapse",
"widows", "width", "word-break", "word-spacing", "word-wrap", "z-index"
];

View File

@ -1378,6 +1378,7 @@ GK_ATOM(y2, "y2")
GK_ATOM(yChannelSelector, "yChannelSelector")
GK_ATOM(z, "z")
GK_ATOM(zoomAndPan, "zoomAndPan")
GK_ATOM(vector_effect, "vector-effect")
GK_ATOM(accumulate, "accumulate")
GK_ATOM(additive, "additive")

View File

@ -628,6 +628,7 @@ nsIAtom** const kAttributesSVG[] = {
// v-ideographic
// v-mathematical
&nsGkAtoms::values, // values
&nsGkAtoms::vector_effect, // vector-effect
// vert-adv-y
// vert-origin-x
// vert-origin-y

View File

@ -281,6 +281,7 @@ nsSMILCSSProperty::IsPropertyAnimatable(nsCSSProperty aPropID)
case eCSSProperty_text_decoration:
case eCSSProperty_text_decoration_line:
case eCSSProperty_text_rendering:
case eCSSProperty_vector_effect:
case eCSSProperty_visibility:
case eCSSProperty_word_spacing:
return true;

View File

@ -463,6 +463,9 @@ var gFromToBundles = [
new TestcaseBundle(gPropList.unicode_bidi, [
new AnimTestcaseFromTo("embed", "bidi-override"),
]),
new TestcaseBundle(gPropList.vector_effect, [
new AnimTestcaseFromTo("none", "non-scaling-stroke"),
]),
new TestcaseBundle(gPropList.visibility, [
new AnimTestcaseFromTo("visible", "hidden"),
new AnimTestcaseFromTo("hidden", "collapse"),

View File

@ -116,6 +116,7 @@ var gPropList =
text_decoration: new NonAdditiveAttribute("text-decoration", "CSS", "text"),
text_rendering: new NonAdditiveAttribute("text-rendering", "CSS", "text"),
unicode_bidi: new NonAnimatableAttribute("unicode-bidi", "CSS", "text"),
vector_effect: new NonAdditiveAttribute("vector-effect", "CSS", "rect"),
visibility: new NonAdditiveAttribute("visibility", "CSS", "rect"),
word_spacing: new AdditiveAttribute("word-spacing", "CSS", "text"),
writing_mode:

View File

@ -931,6 +931,7 @@ nsSVGElement::sFillStrokeMap[] = {
{ &nsGkAtoms::stroke_miterlimit },
{ &nsGkAtoms::stroke_opacity },
{ &nsGkAtoms::stroke_width },
{ &nsGkAtoms::vector_effect },
{ nsnull }
};

View File

@ -20,6 +20,7 @@ text { font: 20px monospace; }
<g transform="scale(2)">
<rect id="rect3" x="25" y="80" width="50" height="50" fill="green"/>
<rect id="rect3a" x="25" y="80" width="50" height="50" fill="none" stroke-width="4" stroke="blue"/>
<rect id="rect3b" vector-effect="non-scaling-stroke" x="100" y="100" width="25" height="25" fill="orange" stroke-width="4" stroke="yellow"/>
</g>
<g transform="scale(2) rotate(45 175 75)">
<rect id="rect4" x="150" y="50" width="50" height="50" fill="yellow"/>

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -93,6 +93,7 @@ function runTest()
var rect1aBounds = doc.getElementById("rect1a").getBoundingClientRect();
var rect2aBounds = doc.getElementById("rect2a").getBoundingClientRect();
var rect3aBounds = doc.getElementById("rect3a").getBoundingClientRect();
var rect3bBounds = doc.getElementById("rect3b").getBoundingClientRect();
var rect4aBounds = doc.getElementById("rect4a").getBoundingClientRect();
is(rect1aBounds.left, 48, "rect1a.getBoundingClientRect().left");
@ -111,6 +112,11 @@ function runTest()
is(rect3aBounds.width, 108, "rect3a.getBoundingClientRect().width");
is(rect3aBounds.height, 108, "rect3a.getBoundingClientRect().height");
is(rect3bBounds.left, 198, "rect3b.getBoundingClientRect().left");
is(rect3bBounds.top, 198, "rect3b.getBoundingClientRect().top");
is(rect3bBounds.width, 54, "rect3b.getBoundingClientRect().width");
is(rect3bBounds.height, 54, "rect3b.getBoundingClientRect().height");
rect = new Rect(350 - 108 * sin45, 150 - 108 * sin45, 108 * sin45 * 2, 108 * sin45 * 2);
isWithAbsTolerance(rect4aBounds.left, rect.left, 0.1, "rect4a.getBoundingClientRect().left");
isWithAbsTolerance(rect4aBounds.top, rect.top, 0.1, "rect4a.getBoundingClientRect().top");

View File

@ -0,0 +1,33 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<style type="text/css" >
rect {
stroke-width: 15px;
}
</style>
<defs>
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0" stop-color="blue"/>
<stop offset="1" stop-color="yellow"/>
</linearGradient>
<linearGradient id="grad2" x1="180" y1="250" x2="280" y2="300" gradientUnits="userSpaceOnUse" gradientTransform="scale(0.25,1)">
<stop offset="0" stop-color="blue"/>
<stop offset="1" stop-color="yellow"/>
</linearGradient>
<pattern id="pattern" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse" patternTransform="scale(1,0.5), skewX(45)">
<rect x="0" y="0" width="10" height="10" fill="red"/>
<rect x="10" y="0" width="10" height="10" fill="green"/>
<rect x="0" y="10" width="10" height="10" fill="blue"/>
<rect x="10" y="10" width="10" height="10" fill="yellow"/>
</pattern>
<rect id="rect" width="100" height="50" fill="none"/>
</defs>
<rect x="20" y="20" width="100" height="50" fill="none" stroke="url(#grad1)"/>
<rect x="20" y="100" width="100" height="50" fill="none" stroke="url(#grad2)" />
<use xlink:href="#rect" transform="translate(20, 180)" stroke="url(#pattern)"/>
<use xlink:href="#rect" x="20" y="260" stroke="green"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,36 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<style type="text/css" >
rect {
stroke-width: 15px;
vector-effect: non-scaling-stroke;
}
</style>
<defs>
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0" stop-color="blue"/>
<stop offset="1" stop-color="yellow"/>
</linearGradient>
<linearGradient id="grad2" x1="100" y1="150" x2="200" y2="200" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="blue"/>
<stop offset="1" stop-color="yellow"/>
</linearGradient>
<pattern id="pattern" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse" patternTransform="scale(4,0.5), skewX(45)">
<rect x="0" y="0" width="10" height="10" fill="red"/>
<rect x="10" y="0" width="10" height="10" fill="green"/>
<rect x="0" y="10" width="10" height="10" fill="blue"/>
<rect x="10" y="10" width="10" height="10" fill="yellow"/>
</pattern>
<rect id="rect" width="400" height="50" fill="none"/>
</defs>
<g transform="translate(20,20)">
<rect width="400" height="50" fill="none" stroke="url(#grad1)" transform="scale(0.25,1)"/>
</g>
<rect width="400" height="50" fill="none" stroke="url(#grad2)" transform="translate(20,100) scale(0.25,1)"/>
<use xlink:href="#rect" transform="translate(20, 180) scale(0.25,1)" stroke="url(#pattern)"/>
<use xlink:href="#rect" x="40" y="80" transform="translate(10, 180) scale(0.25,1)" stroke="green"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,33 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" overflow="hidden">
<style type="text/css" >
rect {
stroke-width: 30px;
}
</style>
<defs>
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0" stop-color="blue"/>
<stop offset="1" stop-color="yellow"/>
</linearGradient>
<linearGradient id="grad2" x1="360" y1="500" x2="560" y2="600" gradientUnits="userSpaceOnUse" gradientTransform="scale(0.25,1)">
<stop offset="0" stop-color="blue"/>
<stop offset="1" stop-color="yellow"/>
</linearGradient>
<pattern id="pattern" x="0" y="0" width="40" height="40" patternUnits="userSpaceOnUse" patternTransform="scale(1,0.5), skewX(45)">
<rect x="0" y="0" width="20" height="20" fill="red"/>
<rect x="20" y="0" width="20" height="20" fill="green"/>
<rect x="0" y="20" width="20" height="20" fill="blue"/>
<rect x="20" y="20" width="20" height="20" fill="yellow"/>
</pattern>
<rect id="rect" width="200" height="100" fill="none"/>
</defs>
<rect x="40" y="40" width="200" height="100" fill="none" stroke="url(#grad1)"/>
<rect x="40" y="200" width="200" height="100" fill="none" stroke="url(#grad2)" />
<use xlink:href="#rect" transform="translate(40, 360)" stroke="url(#pattern)"/>
<use xlink:href="#rect" x="40" y="520" stroke="green"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,36 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" reftest-zoom="2" overflow="hidden">
<style type="text/css" >
rect {
stroke-width: 15px;
vector-effect: non-scaling-stroke;
}
</style>
<defs>
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0" stop-color="blue"/>
<stop offset="1" stop-color="yellow"/>
</linearGradient>
<linearGradient id="grad2" x1="100" y1="150" x2="200" y2="200" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="blue"/>
<stop offset="1" stop-color="yellow"/>
</linearGradient>
<pattern id="pattern" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse" patternTransform="scale(4,0.5), skewX(45)">
<rect x="0" y="0" width="10" height="10" fill="red"/>
<rect x="10" y="0" width="10" height="10" fill="green"/>
<rect x="0" y="10" width="10" height="10" fill="blue"/>
<rect x="10" y="10" width="10" height="10" fill="yellow"/>
</pattern>
<rect id="rect" width="400" height="50" fill="none"/>
</defs>
<g transform="translate(20,20)">
<rect width="400" height="50" fill="none" stroke="url(#grad1)" transform="scale(0.25,1)"/>
</g>
<rect width="400" height="50" fill="none" stroke="url(#grad2)" transform="translate(20,100) scale(0.25,1)"/>
<use xlink:href="#rect" transform="translate(20, 180) scale(0.25,1)" stroke="url(#pattern)"/>
<use xlink:href="#rect" x="40" y="80" transform="translate(10, 180) scale(0.25,1)" stroke="green"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -174,6 +174,8 @@ fails == inline-in-xul-basic-01.xul pass.svg
== mask-transformed-01.svg mask-transformed-01-ref.svg
== nested-viewBox-01.svg pass.svg
== nesting-invalid-01.svg nesting-invalid-01-ref.svg
== non-scaling-stroke-01.svg non-scaling-stroke-01-ref.svg
== non-scaling-stroke-02.svg non-scaling-stroke-02-ref.svg
== objectBoundingBox-and-clipPath.svg pass.svg
# Bug 588684
random-if(gtk2Widget) == objectBoundingBox-and-fePointLight-01.svg objectBoundingBox-and-fePointLight-01-ref.svg

View File

@ -200,7 +200,7 @@ nsSVGGeometryFrame::SetupCairoFill(gfxContext *aContext)
nsSVGPaintServerFrame *ps =
GetPaintServer(&style->mFill, nsSVGEffects::FillProperty());
if (ps && ps->SetupPaintServer(aContext, this, opacity))
if (ps && ps->SetupPaintServer(aContext, this, &nsStyleSVG::mFill, opacity))
return true;
// On failure, use the fallback colour in case we have an
@ -229,6 +229,9 @@ nsSVGGeometryFrame::SetupCairoStrokeGeometry(gfxContext *aContext)
return;
aContext->SetLineWidth(width);
// Apply any stroke-specific transform
aContext->Multiply(nsSVGUtils::GetStrokeTransform(this));
const nsStyleSVG* style = GetStyleSVG();
switch (style->mStrokeLinecap) {
@ -283,7 +286,7 @@ nsSVGGeometryFrame::SetupCairoStroke(gfxContext *aContext)
nsSVGPaintServerFrame *ps =
GetPaintServer(&style->mStroke, nsSVGEffects::StrokeProperty());
if (ps && ps->SetupPaintServer(aContext, this, opacity))
if (ps && ps->SetupPaintServer(aContext, this, &nsStyleSVG::mStroke, opacity))
return true;
// On failure, use the fallback colour in case we have an

View File

@ -909,7 +909,7 @@ nsSVGGlyphFrame::SetupCairoState(gfxContext *aContext, gfxPattern **aStrokePatte
if (ps) {
// Gradient or Pattern: can get pattern directly from frame
strokePattern = ps->GetPaintServerPattern(this, opacity);
strokePattern = ps->GetPaintServerPattern(this, &nsStyleSVG::mStroke, opacity);
}
if (!strokePattern) {

View File

@ -272,8 +272,9 @@ nsSVGGradientFrame::GetRadialGradientWithLength(PRUint32 aIndex,
already_AddRefed<gfxPattern>
nsSVGGradientFrame::GetPaintServerPattern(nsIFrame *aSource,
float aGraphicOpacity,
const gfxRect *aOverrideBounds)
nsStyleSVGPaint nsStyleSVG::*aFillOrStroke,
float aGraphicOpacity,
const gfxRect *aOverrideBounds)
{
// Get the transform list (if there is one)
gfxMatrix patternMatrix = GetGradientTransform(aSource, aOverrideBounds);
@ -290,6 +291,11 @@ nsSVGGradientFrame::GetPaintServerPattern(nsIFrame *aSource,
return pattern.forget();
}
// revert the vector effect transform so that the gradient appears unchanged
if (aFillOrStroke == &nsStyleSVG::mStroke) {
patternMatrix.Multiply(nsSVGUtils::GetStrokeTransform(aSource).Invert());
}
patternMatrix.Invert();
nsRefPtr<gfxPattern> gradient = CreateGradient();

View File

@ -77,6 +77,7 @@ public:
// nsSVGPaintServerFrame methods:
virtual already_AddRefed<gfxPattern>
GetPaintServerPattern(nsIFrame *aSource,
nsStyleSVGPaint nsStyleSVG::*aFillOrStroke,
float aGraphicOpacity,
const gfxRect *aOverrideBounds);

View File

@ -513,7 +513,7 @@ DrawableFromPaintServer(nsIFrame* aFrame,
aPaintServerSize.width, aPaintServerSize.height);
overrideBounds.ScaleInverse(aFrame->PresContext()->AppUnitsPerDevPixel());
nsRefPtr<gfxPattern> pattern =
server->GetPaintServerPattern(aTarget, 1.0, &overrideBounds);
server->GetPaintServerPattern(aTarget, &nsStyleSVG::mFill, 1.0, &overrideBounds);
if (!pattern)
return nsnull;

View File

@ -38,6 +38,7 @@
#include "nsSVGPaintServerFrame.h"
// Keep others in (case-insensitive) order:
#include "nsSVGElement.h"
#include "nsSVGGeometryFrame.h"
NS_IMPL_FRAMEARENA_HELPERS(nsSVGPaintServerFrame)
@ -45,9 +46,10 @@ NS_IMPL_FRAMEARENA_HELPERS(nsSVGPaintServerFrame)
bool
nsSVGPaintServerFrame::SetupPaintServer(gfxContext *aContext,
nsSVGGeometryFrame *aSource,
nsStyleSVGPaint nsStyleSVG::*aFillOrStroke,
float aOpacity)
{
nsRefPtr<gfxPattern> pattern = GetPaintServerPattern(aSource, aOpacity);
nsRefPtr<gfxPattern> pattern = GetPaintServerPattern(aSource, aFillOrStroke, aOpacity);
if (!pattern)
return false;

View File

@ -70,6 +70,7 @@ public:
*/
virtual already_AddRefed<gfxPattern>
GetPaintServerPattern(nsIFrame *aSource,
nsStyleSVGPaint nsStyleSVG::*aFillOrStroke,
float aOpacity,
const gfxRect *aOverrideBounds = nsnull) = 0;
@ -78,8 +79,9 @@ public:
* @return false to skip rendering
*/
virtual bool SetupPaintServer(gfxContext *aContext,
nsSVGGeometryFrame *aSource,
float aOpacity);
nsSVGGeometryFrame *aSource,
nsStyleSVGPaint nsStyleSVG::*aFillOrStroke,
float aOpacity);
virtual bool IsFrameOfType(PRUint32 aFlags) const
{

View File

@ -178,6 +178,7 @@ nsresult
nsSVGPatternFrame::PaintPattern(gfxASurface** surface,
gfxMatrix* patternMatrix,
nsIFrame *aSource,
nsStyleSVGPaint nsStyleSVG::*aFillOrStroke,
float aGraphicOpacity,
const gfxRect *aOverrideBounds)
{
@ -252,6 +253,11 @@ nsSVGPatternFrame::PaintPattern(gfxASurface** surface,
// Get the pattern transform
gfxMatrix patternTransform = GetPatternTransform();
// revert the vector effect transform so that the pattern appears unchanged
if (aFillOrStroke == &nsStyleSVG::mStroke) {
patternTransform.Multiply(nsSVGUtils::GetStrokeTransform(aSource).Invert());
}
// Get the transformation matrix that we will hand to the renderer's pattern
// routine.
*patternMatrix = GetPatternMatrix(patternTransform,
@ -683,6 +689,7 @@ nsSVGPatternFrame::GetTargetGeometry(gfxMatrix *aCTM,
already_AddRefed<gfxPattern>
nsSVGPatternFrame::GetPaintServerPattern(nsIFrame *aSource,
nsStyleSVGPaint nsStyleSVG::*aFillOrStroke,
float aGraphicOpacity,
const gfxRect *aOverrideBounds)
{
@ -695,7 +702,7 @@ nsSVGPatternFrame::GetPaintServerPattern(nsIFrame *aSource,
nsRefPtr<gfxASurface> surface;
gfxMatrix pMatrix;
nsresult rv = PaintPattern(getter_AddRefs(surface), &pMatrix,
aSource, aGraphicOpacity, aOverrideBounds);
aSource, aFillOrStroke, aGraphicOpacity, aOverrideBounds);
if (NS_FAILED(rv)) {
return nsnull;

View File

@ -73,6 +73,7 @@ public:
// nsSVGPaintServerFrame methods:
virtual already_AddRefed<gfxPattern>
GetPaintServerPattern(nsIFrame *aSource,
nsStyleSVGPaint nsStyleSVG::*aFillOrStroke,
float aOpacity,
const gfxRect *aOverrideBounds);
@ -141,6 +142,7 @@ protected:
nsresult PaintPattern(gfxASurface **surface,
gfxMatrix *patternMatrix,
nsIFrame *aSource,
nsStyleSVGPaint nsStyleSVG::*aFillOrStroke,
float aGraphicOpacity,
const gfxRect *aOverrideBounds);
nsIFrame* GetPatternFirstChild();

View File

@ -1709,6 +1709,32 @@ nsSVGUtils::WritePPM(const char *fname, gfxImageSurface *aSurface)
}
#endif
gfxMatrix
nsSVGUtils::GetStrokeTransform(nsIFrame *aFrame)
{
if (aFrame->GetStyleSVGReset()->mVectorEffect ==
NS_STYLE_VECTOR_EFFECT_NON_SCALING_STROKE) {
if (aFrame->GetContent()->IsNodeOfType(nsINode::eTEXT)) {
aFrame = aFrame->GetParent();
}
nsIContent *content = aFrame->GetContent();
NS_ABORT_IF_FALSE(content->IsSVG(), "bad cast");
// a non-scaling stroke is in the screen co-ordinate
// space rather so we need to invert the transform
// to the screen co-ordinate space to get there.
// See http://www.w3.org/TR/SVGTiny12/painting.html#NonScalingStroke
gfxMatrix transform = nsSVGUtils::GetCTM(
static_cast<nsSVGElement*>(content), true);
if (!transform.IsSingular()) {
return transform.Invert();
}
}
return gfxMatrix();
}
// The logic here comes from _cairo_stroke_style_max_distance_from_path
static gfxRect
PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents,
@ -1719,8 +1745,11 @@ PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents,
double style_expansion =
styleExpansionFactor * aFrame->GetStrokeWidth();
double dx = style_expansion * (fabs(aMatrix.xx) + fabs(aMatrix.xy));
double dy = style_expansion * (fabs(aMatrix.yy) + fabs(aMatrix.yx));
gfxMatrix matrix = aMatrix;
matrix.Multiply(nsSVGUtils::GetStrokeTransform(aFrame));
double dx = style_expansion * (fabs(matrix.xx) + fabs(matrix.xy));
double dy = style_expansion * (fabs(matrix.yy) + fabs(matrix.yx));
gfxRect strokeExtents = aPathExtents;
strokeExtents.Inflate(dx, dy);

View File

@ -631,6 +631,12 @@ public:
static bool OuterSVGIsCallingUpdateBounds(nsIFrame *aFrame);
#endif
/*
* Get any additional transforms that apply only to stroking
* e.g. non-scaling-stroke
*/
static gfxMatrix GetStrokeTransform(nsIFrame *aFrame);
/**
* Compute the maximum possible device space stroke extents of a path given
* the path's device space path extents, its stroke style and its ctm.