Visitar URL original
Make sure that attributes are inherited down all nodes with children,… · flutter/packages@b64e124 · GitHub
Skip to content

Commit b64e124

Browse files
authored
Make sure that attributes are inherited down all nodes with children, and handle "none" correctly. (#191)
1 parent 04f70aa commit b64e124

5 files changed

Lines changed: 143 additions & 33 deletions

File tree

‎packages/vector_graphics_compiler/lib/src/svg/node.dart‎

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,6 @@ class ClipNode extends TransformableNode {
321321
required this.child,
322322
required this.clipId,
323323
required AffineMatrix transform,
324-
String? id,
325324
}) : super(transform);
326325

327326
/// Called by visitors to resolve [clipId] to a list of paths.
@@ -346,6 +345,16 @@ class ClipNode extends TransformableNode {
346345
S accept<S, V>(Visitor<S, V> visitor, V data) {
347346
return visitor.visitClipNode(this, data);
348347
}
348+
349+
@override
350+
Node applyAttributes(SvgAttributes newAttributes, {bool replace = false}) {
351+
return ClipNode(
352+
resolver: resolver,
353+
clipId: clipId,
354+
transform: transform,
355+
child: child.applyAttributes(newAttributes, replace: replace),
356+
);
357+
}
349358
}
350359

351360
/// A parent node that applies a mask to its child.
@@ -380,6 +389,17 @@ class MaskNode extends TransformableNode {
380389
S accept<S, V>(Visitor<S, V> visitor, V data) {
381390
return visitor.visitMaskNode(this, data);
382391
}
392+
393+
@override
394+
Node applyAttributes(SvgAttributes newAttributes, {bool replace = false}) {
395+
return MaskNode(
396+
resolver: resolver,
397+
maskId: maskId,
398+
blendMode: blendMode,
399+
transform: transform,
400+
child: child.applyAttributes(newAttributes, replace: replace),
401+
);
402+
}
383403
}
384404

385405
/// A leaf node in the graphics tree.
@@ -607,4 +627,14 @@ class PatternNode extends TransformableNode {
607627
S accept<S, V>(Visitor<S, V> visitor, V data) {
608628
return visitor.visitPatternNode(this, data);
609629
}
630+
631+
@override
632+
Node applyAttributes(SvgAttributes newAttributes, {bool replace = false}) {
633+
return PatternNode(
634+
resolver: resolver,
635+
patternId: patternId,
636+
transform: transform,
637+
child: child.applyAttributes(newAttributes, replace: replace),
638+
);
639+
}
610640
}

‎packages/vector_graphics_compiler/lib/src/svg/parser.dart‎

Lines changed: 78 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1492,9 +1492,6 @@ class SvgParser {
14921492
String? id,
14931493
) {
14941494
final String? rawStroke = attributeMap['stroke'];
1495-
if (rawStroke == 'none') {
1496-
return SvgStrokeAttributes.none;
1497-
}
14981495

14991496
final String? rawStrokeOpacity = attributeMap['stroke-opacity'];
15001497
double? opacity;
@@ -1546,7 +1543,9 @@ class SvgParser {
15461543
return SvgStrokeAttributes._(
15471544
_definitions,
15481545
shaderId: shaderId,
1549-
color: color,
1546+
color: rawStroke == 'none'
1547+
? const ColorOrNone.none()
1548+
: ColorOrNone.color(color),
15501549
cap: _parseCap(rawStrokeCap, null),
15511550
join: _parseJoin(rawLineJoin, null),
15521551
miterLimit: parseDouble(rawMiterLimit),
@@ -1566,10 +1565,6 @@ class SvgParser {
15661565
) {
15671566
final String rawFill = attributeMap['fill'] ?? '';
15681567

1569-
if (rawFill == 'none') {
1570-
return SvgFillAttributes.none;
1571-
}
1572-
15731568
final String? rawFillOpacity = attributeMap['fill-opacity'];
15741569
double? opacity;
15751570
if (rawFillOpacity != null) {
@@ -1589,7 +1584,7 @@ class SvgParser {
15891584
}
15901585
return SvgFillAttributes._(
15911586
_definitions,
1592-
color: const Color(0xFFFFFFFF),
1587+
color: const ColorOrNone.color(Color(0xFFFFFFFF)),
15931588
shaderId: rawFill,
15941589
hasPattern: hasPattern,
15951590
opacity: opacity,
@@ -1605,7 +1600,9 @@ class SvgParser {
16051600

16061601
return SvgFillAttributes._(
16071602
_definitions,
1608-
color: fillColor,
1603+
color: rawFill == 'none'
1604+
? const ColorOrNone.none()
1605+
: ColorOrNone.color(fillColor),
16091606
opacity: opacity,
16101607
);
16111608
}
@@ -1640,7 +1637,9 @@ class SvgParser {
16401637
dx: DoubleOrPercentage.fromString(rawDx),
16411638
dy: DoubleOrPercentage.fromString(rawDy),
16421639
href: attributeMap['href'],
1643-
color: color,
1640+
color: attributeMap['color']?.toLowerCase() == 'none'
1641+
? const ColorOrNone.none()
1642+
: ColorOrNone.color(color),
16441643
stroke: _parseStrokeAttributes(
16451644
attributeMap,
16461645
opacity,
@@ -1822,7 +1821,7 @@ class SvgAttributes {
18221821
this.id,
18231822
this.href,
18241823
this.transform = AffineMatrix.identity,
1825-
this.color,
1824+
this.color = const ColorOrNone.color(),
18261825
this.stroke,
18271826
this.fill,
18281827
this.fillRule = PathFillType.nonZero,
@@ -1935,7 +1934,7 @@ class SvgAttributes {
19351934
/// [stroke].
19361935
///
19371936
/// https://www.w3.org/TR/SVG11/color.html#ColorProperty
1938-
final Color? color;
1937+
final ColorOrNone color;
19391938

19401939
/// The stroking properties of this element.
19411940
final SvgStrokeAttributes? stroke;
@@ -2052,7 +2051,7 @@ class SvgAttributes {
20522051
id: newRaw['id'],
20532052
href: newRaw['href'],
20542053
transform: transformOverride ?? transform,
2055-
color: color ?? parent.color,
2054+
color: color._applyParent(parent.color),
20562055
stroke: stroke?.applyParent(parent.stroke) ?? parent.stroke,
20572056
fill: fill?.applyParent(parent.fill) ?? parent.fill,
20582057
fillRule: fillRule,
@@ -2123,7 +2122,7 @@ class DoubleOrPercentage {
21232122
class SvgStrokeAttributes {
21242123
const SvgStrokeAttributes._(
21252124
this._definitions, {
2126-
this.color,
2125+
this.color = const ColorOrNone.color(),
21272126
this.shaderId,
21282127
this.join,
21292128
this.cap,
@@ -2135,15 +2134,11 @@ class SvgStrokeAttributes {
21352134
this.opacity,
21362135
});
21372136

2138-
/// Specifies that strokes should not be drawn, even if they otherwise would
2139-
/// be.
2140-
static const SvgStrokeAttributes none = SvgStrokeAttributes._(null);
2141-
21422137
final _Resolver? _definitions;
21432138

21442139
/// The color to use for stroking. Does _not_ include the [opacity] value. Only
21452140
/// opacity is used if the [shaderId] is not null.
2146-
final Color? color;
2141+
final ColorOrNone color;
21472142

21482143
/// The literal reference to a shader defined elsewhere.
21492144
final String? shaderId;
@@ -2195,7 +2190,7 @@ class SvgStrokeAttributes {
21952190
SvgStrokeAttributes applyParent(SvgStrokeAttributes? parent) {
21962191
return SvgStrokeAttributes._(
21972192
_definitions,
2198-
color: color ?? parent?.color,
2193+
color: color._applyParent(parent?.color),
21992194
shaderId: shaderId ?? parent?.shaderId,
22002195
join: join ?? parent?.join,
22012196
cap: cap ?? parent?.cap,
@@ -2214,7 +2209,9 @@ class SvgStrokeAttributes {
22142209
/// Returns null if this is [none].
22152210
Stroke? toStroke(Rect shaderBounds, AffineMatrix transform) {
22162211
// A zero width stroke is a hairline in Flutter, but a nop in SVG.
2217-
if (color == null && hasPattern == null && shaderId == null || width == 0) {
2212+
if (color.isNone ||
2213+
(color.color == null && hasPattern == null && shaderId == null ||
2214+
width == 0)) {
22182215
return null;
22192216
}
22202217

@@ -2242,7 +2239,7 @@ class SvgStrokeAttributes {
22422239
}
22432240

22442241
return Stroke(
2245-
color: color!.withOpacity(opacity ?? 1.0),
2242+
color: color.color!.withOpacity(opacity ?? 1.0),
22462243
shader: shader,
22472244
join: join,
22482245
cap: cap,
@@ -2257,20 +2254,17 @@ class SvgFillAttributes {
22572254
/// Create a new [SvgFillAttributes];
22582255
const SvgFillAttributes._(
22592256
this._definitions, {
2260-
this.color,
2257+
this.color = const ColorOrNone.color(),
22612258
this.shaderId,
22622259
this.hasPattern,
22632260
this.opacity,
22642261
});
22652262

2266-
/// Specifies that fills should not be drawn, even if they otherwise would be.
2267-
static const SvgFillAttributes none = SvgFillAttributes._(null);
2268-
22692263
final _Resolver? _definitions;
22702264

22712265
/// The color to use for filling. Does _not_ include the [opacity] value. Only
22722266
/// opacity is used if the [shaderId] is not null.
2273-
final Color? color;
2267+
final ColorOrNone color;
22742268

22752269
/// The opacity to apply to a default color, if [color] is null.
22762270
final double? opacity;
@@ -2298,7 +2292,7 @@ class SvgFillAttributes {
22982292
SvgFillAttributes applyParent(SvgFillAttributes? parent) {
22992293
return SvgFillAttributes._(
23002294
_definitions,
2301-
color: color ?? parent?.color,
2295+
color: color._applyParent(parent?.color),
23022296
shaderId: shaderId ?? parent?.shaderId,
23032297
hasPattern: hasPattern ?? parent?.hasPattern,
23042298
opacity: opacity ?? parent?.opacity,
@@ -2314,7 +2308,10 @@ class SvgFillAttributes {
23142308
AffineMatrix transform, {
23152309
Color? defaultColor,
23162310
}) {
2317-
final Color? resolvedColor = color?.withOpacity(opacity ?? 1.0) ??
2311+
if (color.isNone) {
2312+
return null;
2313+
}
2314+
final Color? resolvedColor = color.color?.withOpacity(opacity ?? 1.0) ??
23182315
defaultColor?.withOpacity(opacity ?? 1.0);
23192316
if (resolvedColor == null) {
23202317
return null;
@@ -2338,4 +2335,55 @@ class SvgFillAttributes {
23382335

23392336
return Fill(color: resolvedColor, shader: shader);
23402337
}
2338+
2339+
@override
2340+
String toString() {
2341+
return 'SvgFillAttributes('
2342+
'definitions: $_definitions, '
2343+
'color: $color, '
2344+
'shaderId: $shaderId, '
2345+
'hasPattern: $hasPattern, '
2346+
'oapctiy: $opacity)';
2347+
}
2348+
}
2349+
2350+
/// Represents a color for filling or stroking.
2351+
///
2352+
/// If the [color] is not null, [isNone] is false.
2353+
///
2354+
/// If the [color] is null and [isNone] is false, color should be inherited from
2355+
/// the parent or defaulted as per the SVG specification.
2356+
///
2357+
/// If the [color] is null and [isNone] is true, inheritence should be cleared
2358+
/// and no painting should happen.
2359+
class ColorOrNone {
2360+
/// See [ColorOrNone].
2361+
const ColorOrNone.none()
2362+
: isNone = true,
2363+
color = null;
2364+
2365+
/// See [ColorOrNone].
2366+
const ColorOrNone.color([this.color]) : isNone = false;
2367+
2368+
/// Whether to paint anything.
2369+
final bool isNone;
2370+
2371+
/// The color to use when painting. If null and [isNone] is false, inherit
2372+
/// from parent.
2373+
final Color? color;
2374+
2375+
ColorOrNone _applyParent(ColorOrNone? parent) {
2376+
if (parent == null || isNone) {
2377+
return this;
2378+
}
2379+
2380+
if (parent.isNone && color == null) {
2381+
return const ColorOrNone.none();
2382+
}
2383+
2384+
return ColorOrNone.color(color ?? parent.color);
2385+
}
2386+
2387+
@override
2388+
String toString() => isNone ? '"none"' : (color?.toString() ?? 'null');
23412389
}

‎packages/vector_graphics_compiler/lib/src/svg/resolver.dart‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ class ResolvingVisitor extends Visitor<Node, AffineMatrix> {
5757
final AffineMatrix nextTransform = parentNode.concatTransform(data);
5858

5959
final Paint? saveLayerPaint = parentNode.createLayerPaint();
60+
6061
final Node result;
6162
if (saveLayerPaint == null) {
6263
result = ParentNode(

‎packages/vector_graphics_compiler/test/masking_optimizer_test.dart‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,9 @@ void main() {
9393

9494
test("Don't resolve MaskNode if intersection of Mask and Path is empty", () {
9595
final Node node = parseAndResolve(
96-
'''<svg width="24px" height="24px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
96+
'''<svg width="24px" height="24px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
9797
<mask id="a">
98-
<path d="M58.645 16.232L46.953 28.72l2.024 1.895 11.691-12.486"/>
98+
<path d="M58.645 16.232L46.953 28.72l2.024 1.895 11.691-12.486"/>
9999
</mask>
100100
<path mask="url(#a)" d="M0 0 z"/>
101101
</svg>

‎packages/vector_graphics_compiler/test/parser_test.dart‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,37 @@ import 'package:vector_graphics_compiler/vector_graphics_compiler.dart';
44
import 'test_svg_strings.dart';
55

66
void main() {
7+
test('None on fill', () {
8+
const String svg = '''
9+
<svg xmlns="http://www.w3.org/2000/svg" width="384" height="384" fill="none"
10+
style="-webkit-print-color-adjust:exact">
11+
<defs>
12+
<clipPath id="a" class="frame-clip">
13+
<rect width="384" height="384" rx="40" ry="40" style="opacity:1" />
14+
</clipPath>
15+
</defs>
16+
<g clip-path="url(#a)">
17+
<rect width="384" height="384" class="frame-background" rx="40" ry="40" style="opacity:1" />
18+
<g class="frame-children">
19+
<rect width="290" height="70" x="31" y="32" rx="30" ry="30"
20+
style="fill:#22c55e;fill-opacity:1" />
21+
<rect width="290" height="70" x="31" y="282" rx="30" ry="30"
22+
style="fill:#22c55e;fill-opacity:1" />
23+
<rect width="290" height="70" x="95" y="157" rx="30" ry="30"
24+
style="fill:#f59e0b;fill-opacity:1" />
25+
</g>
26+
</g>
27+
</svg>
28+
''';
29+
30+
final VectorInstructions instructions = parseWithoutOptimizers(svg);
31+
// Should _not_ contain a paint with an opaque black fill for the rect with class "frame-background".
32+
expect(instructions.paints, const <Paint>[
33+
Paint(blendMode: BlendMode.srcOver, fill: Fill(color: Color(0xff22c55e))),
34+
Paint(blendMode: BlendMode.srcOver, fill: Fill(color: Color(0xfff59e0b))),
35+
]);
36+
});
37+
738
test('text spacing', () {
839
const String svg = '''
940
<svg width="185" height="43" viewBox="0 0 185 43" xmlns="http://www.w3.org/2000/svg">

0 commit comments

Comments
 (0)