prawn-svg 0.30.0 → 0.31.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +6 -6
- data/README.md +3 -3
- data/lib/prawn/svg/elements.rb +1 -0
- data/lib/prawn/svg/elements/base.rb +4 -0
- data/lib/prawn/svg/elements/gradient.rb +81 -24
- data/lib/prawn/svg/elements/image.rb +2 -2
- data/lib/prawn/svg/elements/path.rb +41 -29
- data/lib/prawn/svg/elements/root.rb +4 -1
- data/lib/prawn/svg/elements/text_component.rb +4 -2
- data/lib/prawn/svg/elements/use.rb +23 -7
- data/lib/prawn/svg/interface.rb +10 -1
- data/lib/prawn/svg/properties.rb +1 -0
- data/lib/prawn/svg/version.rb +1 -1
- data/prawn-svg.gemspec +1 -1
- data/spec/prawn/svg/elements/gradient_spec.rb +53 -2
- data/spec/prawn/svg/elements/path_spec.rb +29 -17
- data/spec/prawn/svg/elements/text_spec.rb +15 -0
- data/spec/sample_svg/radgrad01-bounding.svg +26 -0
- data/spec/sample_svg/radgrad01.svg +26 -0
- data/spec/sample_svg/svg_fill.svg +5 -0
- data/spec/sample_svg/text-decoration.svg +4 -0
- data/spec/sample_svg/use_disordered.svg +17 -0
- metadata +18 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9ed78a64dc94709fc5e53f1d6f521f93e3bf41196c29dde616c6139743c9c889
|
4
|
+
data.tar.gz: 7a0a5683370d3478e8b9717069e0f4012337aaf6250106065dfd5ad9b4458a91
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a58fd3f4508d486b3632f2e337f5e2fe97ef8c2baeeeda9b4f53f816ff18eb10b3e8879c98ea10c645becb29c8351a7addc6867c22abfeda77a7edf654607f44
|
7
|
+
data.tar.gz: 3bdbbc06f25405d3f51de30e81c39406f7ccf535e507bb36b43df3939d1a4931b763cb5308bde72674ab3f2ffcb55171b958f93a3b603bf5cf17a1bbd24d44b4
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -61,7 +61,7 @@ prawn-svg supports most but not all of the full SVG 1.1 specification. It curre
|
|
61
61
|
implementation of elliptical arc is a bit rough at the moment.
|
62
62
|
|
63
63
|
- `<text>`, `<tspan>` and `<tref>` with attributes `x`, `y`, `dx`, `dy`, `rotate`, 'textLength', 'lengthAdjust', and with extra properties
|
64
|
-
`text-anchor`, `font-size`, `font-family`, `font-weight`, `font-style`, `letter-spacing`
|
64
|
+
`text-anchor`, `text-decoration` (underline only), `font-size`, `font-family`, `font-weight`, `font-style`, `letter-spacing`
|
65
65
|
|
66
66
|
- <tt><svg></tt>, <tt><g></tt> and <tt><symbol></tt>
|
67
67
|
|
@@ -76,7 +76,7 @@ prawn-svg supports most but not all of the full SVG 1.1 specification. It curre
|
|
76
76
|
|
77
77
|
- `<marker>`
|
78
78
|
|
79
|
-
- `<linearGradient>`
|
79
|
+
- `<linearGradient>` and `<radialGradient>` are implemented on Prawn 2.2.0+ with attributes `gradientUnits` and `gradientTransform` (spreadMethod and stop-opacity are unimplemented.)
|
80
80
|
|
81
81
|
- `<switch>` and `<foreignObject>`, although prawn-svg cannot handle any data that is not SVG so `<foreignObject>`
|
82
82
|
tags are always ignored.
|
@@ -116,7 +116,7 @@ implemented, but `!important` is not.
|
|
116
116
|
|
117
117
|
## Not supported
|
118
118
|
|
119
|
-
prawn-svg does not support
|
119
|
+
prawn-svg does not support hyperlinks, patterns or filters.
|
120
120
|
|
121
121
|
It does not support text in the clip area, but you can clip shapes and text by any shape.
|
122
122
|
|
data/lib/prawn/svg/elements.rb
CHANGED
@@ -28,6 +28,7 @@ module Prawn::SVG::Elements
|
|
28
28
|
use: Prawn::SVG::Elements::Use,
|
29
29
|
image: Prawn::SVG::Elements::Image,
|
30
30
|
linearGradient: Prawn::SVG::Elements::Gradient,
|
31
|
+
radialGradient: Prawn::SVG::Elements::Gradient,
|
31
32
|
marker: Prawn::SVG::Elements::Marker,
|
32
33
|
style: Prawn::SVG::Elements::Ignored, # because it is pre-parsed by Document
|
33
34
|
title: Prawn::SVG::Elements::Ignored,
|
@@ -267,4 +267,8 @@ class Prawn::SVG::Elements::Base
|
|
267
267
|
element = document.elements_by_id[matches[1]] if matches
|
268
268
|
element if element && (expected_type.nil? || element.name == expected_type)
|
269
269
|
end
|
270
|
+
|
271
|
+
def href_attribute
|
272
|
+
attributes['xlink:href'] || attributes['href']
|
273
|
+
end
|
270
274
|
end
|
@@ -1,10 +1,18 @@
|
|
1
1
|
class Prawn::SVG::Elements::Gradient < Prawn::SVG::Elements::Base
|
2
|
-
|
2
|
+
attr_reader :parent_gradient
|
3
|
+
attr_reader :x1, :y1, :x2, :y2, :cx, :cy, :fx, :fy, :radius, :units, :stops, :transform_matrix
|
4
|
+
|
5
|
+
TAG_NAME_TO_TYPE = {
|
6
|
+
"linearGradient" => :linear,
|
7
|
+
"radialGradient" => :radial
|
8
|
+
}
|
3
9
|
|
4
10
|
def parse
|
5
11
|
# A gradient tag without an ID is inaccessible and can never be used
|
6
12
|
raise SkipElementQuietly if attributes['id'].nil?
|
7
13
|
|
14
|
+
@parent_gradient = document.gradients[href_attribute[1..-1]] if href_attribute && href_attribute[0] == '#'
|
15
|
+
|
8
16
|
assert_compatible_prawn_version
|
9
17
|
load_gradient_configuration
|
10
18
|
load_coordinates
|
@@ -16,31 +24,57 @@ class Prawn::SVG::Elements::Gradient < Prawn::SVG::Elements::Base
|
|
16
24
|
end
|
17
25
|
|
18
26
|
def gradient_arguments(element)
|
19
|
-
case @units
|
20
|
-
when :bounding_box
|
21
|
-
x1, y1, x2, y2 = element.bounding_box
|
22
|
-
return if y2.nil?
|
23
|
-
|
24
|
-
width = x2 - x1
|
25
|
-
height = y1 - y2
|
26
|
-
|
27
|
-
from = [x1 + width * @x1, y1 - height * @y1]
|
28
|
-
to = [x1 + width * @x2, y1 - height * @y2]
|
29
|
-
|
30
|
-
when :user_space
|
31
|
-
from = [@x1, @y1]
|
32
|
-
to = [@x2, @y2]
|
33
|
-
end
|
34
|
-
|
35
27
|
# Passing in a transformation matrix to the apply_transformations option is supported
|
36
28
|
# by a monkey patch installed by prawn-svg. Prawn only sees this as a truthy variable.
|
37
29
|
#
|
38
30
|
# See Prawn::SVG::Extensions::AdditionalGradientTransforms for details.
|
39
|
-
|
31
|
+
base_arguments = {stops: stops, apply_transformations: transform_matrix || true}
|
32
|
+
|
33
|
+
arguments = specific_gradient_arguments(element)
|
34
|
+
arguments.merge(base_arguments) if arguments
|
40
35
|
end
|
41
36
|
|
42
37
|
private
|
43
38
|
|
39
|
+
def specific_gradient_arguments(element)
|
40
|
+
if units == :bounding_box
|
41
|
+
bounding_x1, bounding_y1, bounding_x2, bounding_y2 = element.bounding_box
|
42
|
+
return if bounding_y2.nil?
|
43
|
+
|
44
|
+
width = bounding_x2 - bounding_x1
|
45
|
+
height = bounding_y1 - bounding_y2
|
46
|
+
end
|
47
|
+
|
48
|
+
case [type, units]
|
49
|
+
when [:linear, :bounding_box]
|
50
|
+
from = [bounding_x1 + width * x1, bounding_y1 - height * y1]
|
51
|
+
to = [bounding_x1 + width * x2, bounding_y1 - height * y2]
|
52
|
+
|
53
|
+
{from: from, to: to}
|
54
|
+
|
55
|
+
when [:linear, :user_space]
|
56
|
+
{from: [x1, y1], to: [x2, y2]}
|
57
|
+
|
58
|
+
when [:radial, :bounding_box]
|
59
|
+
center = [bounding_x1 + width * cx, bounding_y1 - height * cy]
|
60
|
+
focus = [bounding_x1 + width * fx, bounding_y1 - height * fy]
|
61
|
+
|
62
|
+
# Note: Chrome, at least, implements radial bounding box radiuses as
|
63
|
+
# having separate X and Y components, so in bounding box mode their
|
64
|
+
# gradients come out as ovals instead of circles. PDF radial shading
|
65
|
+
# doesn't have the option to do this, and it's confusing why the
|
66
|
+
# Chrome user space gradients don't apply the same logic anyway.
|
67
|
+
hypot = Math.sqrt(width * width + height * height)
|
68
|
+
{from: focus, r1: 0, to: center, r2: radius * hypot}
|
69
|
+
|
70
|
+
when [:radial, :user_space]
|
71
|
+
{from: [fx, fy], r1: 0, to: [cx, cy], r2: radius}
|
72
|
+
|
73
|
+
else
|
74
|
+
raise "unexpected type/unit system"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
44
78
|
def type
|
45
79
|
TAG_NAME_TO_TYPE.fetch(name)
|
46
80
|
end
|
@@ -64,18 +98,35 @@ class Prawn::SVG::Elements::Gradient < Prawn::SVG::Elements::Base
|
|
64
98
|
end
|
65
99
|
|
66
100
|
def load_coordinates
|
67
|
-
case
|
68
|
-
when :bounding_box
|
101
|
+
case [type, units]
|
102
|
+
when [:linear, :bounding_box]
|
69
103
|
@x1 = parse_zero_to_one(attributes["x1"], 0)
|
70
104
|
@y1 = parse_zero_to_one(attributes["y1"], 0)
|
71
105
|
@x2 = parse_zero_to_one(attributes["x2"], 1)
|
72
106
|
@y2 = parse_zero_to_one(attributes["y2"], 0)
|
73
107
|
|
74
|
-
when :user_space
|
108
|
+
when [:linear, :user_space]
|
75
109
|
@x1 = x(attributes["x1"])
|
76
110
|
@y1 = y(attributes["y1"])
|
77
111
|
@x2 = x(attributes["x2"])
|
78
112
|
@y2 = y(attributes["y2"])
|
113
|
+
|
114
|
+
when [:radial, :bounding_box]
|
115
|
+
@cx = parse_zero_to_one(attributes["cx"], 0.5)
|
116
|
+
@cy = parse_zero_to_one(attributes["cy"], 0.5)
|
117
|
+
@fx = parse_zero_to_one(attributes["fx"], cx)
|
118
|
+
@fy = parse_zero_to_one(attributes["fy"], cy)
|
119
|
+
@radius = parse_zero_to_one(attributes["r"], 0.5)
|
120
|
+
|
121
|
+
when [:radial, :user_space]
|
122
|
+
@cx = x(attributes["cx"] || '50%')
|
123
|
+
@cy = y(attributes["cy"] || '50%')
|
124
|
+
@fx = x(attributes["fx"] || attributes["cx"])
|
125
|
+
@fy = y(attributes["fy"] || attributes["cy"])
|
126
|
+
@radius = pixels(attributes["r"] || '50%')
|
127
|
+
|
128
|
+
else
|
129
|
+
raise "unexpected type/unit system"
|
79
130
|
end
|
80
131
|
end
|
81
132
|
|
@@ -101,10 +152,16 @@ class Prawn::SVG::Elements::Gradient < Prawn::SVG::Elements::Base
|
|
101
152
|
end
|
102
153
|
end
|
103
154
|
|
104
|
-
|
155
|
+
if stops.empty?
|
156
|
+
if parent_gradient.nil? || parent_gradient.stops.empty?
|
157
|
+
raise SkipElementError, "gradient does not have any valid stops"
|
158
|
+
end
|
105
159
|
|
106
|
-
|
107
|
-
|
160
|
+
@stops = parent_gradient.stops
|
161
|
+
else
|
162
|
+
stops.unshift([0, stops.first.last]) if stops.first.first > 0
|
163
|
+
stops.push([1, stops.last.last]) if stops.last.first < 1
|
164
|
+
end
|
108
165
|
end
|
109
166
|
|
110
167
|
def parse_zero_to_one(string, default = 0)
|
@@ -15,9 +15,9 @@ class Prawn::SVG::Elements::Image < Prawn::SVG::Elements::Base
|
|
15
15
|
|
16
16
|
raise SkipElementQuietly if state.computed_properties.display == "none"
|
17
17
|
|
18
|
-
@url =
|
18
|
+
@url = href_attribute
|
19
19
|
if @url.nil?
|
20
|
-
raise SkipElementError, "image tag must have an xlink:href"
|
20
|
+
raise SkipElementError, "image tag must have an href or xlink:href"
|
21
21
|
end
|
22
22
|
|
23
23
|
x = x(attributes['x'] || 0)
|
@@ -4,10 +4,31 @@ class Prawn::SVG::Elements::Path < Prawn::SVG::Elements::Base
|
|
4
4
|
|
5
5
|
INSIDE_SPACE_REGEXP = /[ \t\r\n,]*/
|
6
6
|
OUTSIDE_SPACE_REGEXP = /[ \t\r\n]*/
|
7
|
-
INSIDE_REGEXP = /#{INSIDE_SPACE_REGEXP}([+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:(?<=[0-9])[eE][+-]?[0-9]+)?)/
|
8
|
-
|
7
|
+
INSIDE_REGEXP = /#{INSIDE_SPACE_REGEXP}(?>([+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:(?<=[0-9])[eE][+-]?[0-9]+)?))/
|
8
|
+
FLAG_REGEXP = /#{INSIDE_SPACE_REGEXP}([01])/
|
9
9
|
COMMAND_REGEXP = /^#{OUTSIDE_SPACE_REGEXP}([A-Za-z])((?:#{INSIDE_REGEXP})*)#{OUTSIDE_SPACE_REGEXP}/
|
10
10
|
|
11
|
+
A_PARAMETERS_REGEXP = /^#{INSIDE_REGEXP}#{INSIDE_REGEXP}#{INSIDE_REGEXP}#{FLAG_REGEXP}#{FLAG_REGEXP}#{INSIDE_REGEXP}#{INSIDE_REGEXP}/
|
12
|
+
ONE_PARAMETER_REGEXP = /^#{INSIDE_REGEXP}/
|
13
|
+
TWO_PARAMETER_REGEXP = /^#{INSIDE_REGEXP}#{INSIDE_REGEXP}/
|
14
|
+
FOUR_PARAMETER_REGEXP = /^#{INSIDE_REGEXP}#{INSIDE_REGEXP}#{INSIDE_REGEXP}#{INSIDE_REGEXP}/
|
15
|
+
SIX_PARAMETER_REGEXP = /^#{INSIDE_REGEXP}#{INSIDE_REGEXP}#{INSIDE_REGEXP}#{INSIDE_REGEXP}#{INSIDE_REGEXP}#{INSIDE_REGEXP}/
|
16
|
+
|
17
|
+
COMMAND_MATCH_MAP = {
|
18
|
+
'A' => A_PARAMETERS_REGEXP,
|
19
|
+
'C' => SIX_PARAMETER_REGEXP,
|
20
|
+
'H' => ONE_PARAMETER_REGEXP,
|
21
|
+
'L' => TWO_PARAMETER_REGEXP,
|
22
|
+
'M' => TWO_PARAMETER_REGEXP,
|
23
|
+
'Q' => FOUR_PARAMETER_REGEXP,
|
24
|
+
'S' => FOUR_PARAMETER_REGEXP,
|
25
|
+
'T' => TWO_PARAMETER_REGEXP,
|
26
|
+
'V' => ONE_PARAMETER_REGEXP,
|
27
|
+
'Z' => //,
|
28
|
+
}
|
29
|
+
|
30
|
+
PARAMETERLESS_COMMANDS = COMMAND_MATCH_MAP.select { |_, v| v == // }.map(&:first)
|
31
|
+
|
11
32
|
FLOAT_ERROR_DELTA = 1e-10
|
12
33
|
|
13
34
|
attr_reader :commands
|
@@ -23,14 +44,13 @@ class Prawn::SVG::Elements::Path < Prawn::SVG::Elements::Base
|
|
23
44
|
matched_commands = match_all(data, COMMAND_REGEXP)
|
24
45
|
raise SkipElementError, "Invalid/unsupported syntax for SVG path data" if matched_commands.nil?
|
25
46
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
end
|
47
|
+
matched_commands.each do |(command, parameters)|
|
48
|
+
regexp = COMMAND_MATCH_MAP[command.upcase] or break
|
49
|
+
matched_values = match_all(parameters, regexp) or break
|
50
|
+
values = matched_values.map { |value| value.map(&:to_f) }
|
51
|
+
break if values.empty? && !PARAMETERLESS_COMMANDS.include?(command.upcase)
|
52
|
+
|
53
|
+
parse_path_command(command, values)
|
34
54
|
end
|
35
55
|
end
|
36
56
|
|
@@ -49,8 +69,7 @@ class Prawn::SVG::Elements::Path < Prawn::SVG::Elements::Base
|
|
49
69
|
|
50
70
|
case upcase_command
|
51
71
|
when 'M' # moveto
|
52
|
-
x = values.shift
|
53
|
-
y = values.shift or throw :invalid_command
|
72
|
+
x, y = values.shift
|
54
73
|
|
55
74
|
if relative && @last_point
|
56
75
|
x += @last_point.first
|
@@ -69,8 +88,7 @@ class Prawn::SVG::Elements::Path < Prawn::SVG::Elements::Base
|
|
69
88
|
|
70
89
|
when 'L' # lineto
|
71
90
|
while values.any?
|
72
|
-
x = values.shift
|
73
|
-
y = values.shift or throw :invalid_command
|
91
|
+
x, y = values.shift
|
74
92
|
if relative && @last_point
|
75
93
|
x += @last_point.first
|
76
94
|
y += @last_point.last
|
@@ -81,22 +99,21 @@ class Prawn::SVG::Elements::Path < Prawn::SVG::Elements::Base
|
|
81
99
|
|
82
100
|
when 'H' # horizontal lineto
|
83
101
|
while values.any?
|
84
|
-
x = values.shift
|
102
|
+
x = values.shift.first
|
85
103
|
x += @last_point.first if relative && @last_point
|
86
104
|
push_command Prawn::SVG::Pathable::Line.new([x, @last_point.last])
|
87
105
|
end
|
88
106
|
|
89
107
|
when 'V' # vertical lineto
|
90
108
|
while values.any?
|
91
|
-
y = values.shift
|
109
|
+
y = values.shift.first
|
92
110
|
y += @last_point.last if relative && @last_point
|
93
111
|
push_command Prawn::SVG::Pathable::Line.new([@last_point.first, y])
|
94
112
|
end
|
95
113
|
|
96
114
|
when 'C' # curveto
|
97
115
|
while values.any?
|
98
|
-
x1, y1, x2, y2, x, y = values.shift
|
99
|
-
throw :invalid_command unless y
|
116
|
+
x1, y1, x2, y2, x, y = values.shift
|
100
117
|
|
101
118
|
if relative && @last_point
|
102
119
|
x += @last_point.first
|
@@ -113,8 +130,7 @@ class Prawn::SVG::Elements::Path < Prawn::SVG::Elements::Base
|
|
113
130
|
|
114
131
|
when 'S' # shorthand/smooth curveto
|
115
132
|
while values.any?
|
116
|
-
x2, y2, x, y = values.shift
|
117
|
-
throw :invalid_command unless y
|
133
|
+
x2, y2, x, y = values.shift
|
118
134
|
|
119
135
|
if relative && @last_point
|
120
136
|
x += @last_point.first
|
@@ -137,13 +153,11 @@ class Prawn::SVG::Elements::Path < Prawn::SVG::Elements::Base
|
|
137
153
|
when 'Q', 'T' # quadratic curveto
|
138
154
|
while values.any?
|
139
155
|
if shorthand = upcase_command == 'T'
|
140
|
-
x, y = values.shift
|
156
|
+
x, y = values.shift
|
141
157
|
else
|
142
|
-
x1, y1, x, y = values.shift
|
158
|
+
x1, y1, x, y = values.shift
|
143
159
|
end
|
144
160
|
|
145
|
-
throw :invalid_command unless y
|
146
|
-
|
147
161
|
if relative && @last_point
|
148
162
|
x += @last_point.first
|
149
163
|
x1 += @last_point.first if x1
|
@@ -175,8 +189,7 @@ class Prawn::SVG::Elements::Path < Prawn::SVG::Elements::Base
|
|
175
189
|
return unless @last_point
|
176
190
|
|
177
191
|
while values.any?
|
178
|
-
rx, ry, phi, fa, fs, x2, y2 = values.shift
|
179
|
-
throw :invalid_command unless y2
|
192
|
+
rx, ry, phi, fa, fs, x2, y2 = values.shift
|
180
193
|
|
181
194
|
x1, y1 = @last_point
|
182
195
|
|
@@ -277,9 +290,8 @@ class Prawn::SVG::Elements::Path < Prawn::SVG::Elements::Base
|
|
277
290
|
def match_all(string, regexp) # regexp must start with ^
|
278
291
|
result = []
|
279
292
|
while string != ""
|
280
|
-
matches = string.match(regexp)
|
281
|
-
result << matches
|
282
|
-
return if matches.nil?
|
293
|
+
matches = string.match(regexp) or return
|
294
|
+
result << matches.captures
|
283
295
|
string = matches.post_match
|
284
296
|
end
|
285
297
|
result
|
@@ -8,7 +8,10 @@ class Prawn::SVG::Elements::Root < Prawn::SVG::Elements::Base
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def apply
|
11
|
-
|
11
|
+
if [nil, 'inherit', 'none', 'currentColor'].include?(properties.fill)
|
12
|
+
add_call 'fill_color', '000000'
|
13
|
+
end
|
14
|
+
|
12
15
|
add_call 'transformation_matrix', @document.sizing.x_scale, 0, 0, @document.sizing.y_scale, 0, 0
|
13
16
|
add_call 'transformation_matrix', 1, 0, 0, 1, -@document.sizing.x_offset, @document.sizing.y_offset
|
14
17
|
end
|
@@ -46,9 +46,11 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
|
|
46
46
|
opts = {
|
47
47
|
size: computed_properties.numerical_font_size,
|
48
48
|
style: font && font.subfamily,
|
49
|
-
text_anchor: computed_properties.text_anchor
|
49
|
+
text_anchor: computed_properties.text_anchor,
|
50
50
|
}
|
51
51
|
|
52
|
+
opts[:decoration] = computed_properties.text_decoration unless computed_properties.text_decoration == 'none'
|
53
|
+
|
52
54
|
if state.text.parent
|
53
55
|
add_call_and_enter 'character_spacing', state.text.spacing unless state.text.spacing == state.text.parent.spacing
|
54
56
|
add_call_and_enter 'text_rendering_mode', state.text.mode unless state.text.mode == state.text.parent.mode
|
@@ -183,7 +185,7 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
|
|
183
185
|
end
|
184
186
|
|
185
187
|
def find_referenced_element
|
186
|
-
href =
|
188
|
+
href = href_attribute
|
187
189
|
|
188
190
|
if href && href[0..0] == '#'
|
189
191
|
element = document.elements_by_id[href[1..-1]]
|
@@ -1,19 +1,35 @@
|
|
1
1
|
class Prawn::SVG::Elements::Use < Prawn::SVG::Elements::Base
|
2
|
-
attr_reader :
|
2
|
+
attr_reader :referenced_element_class
|
3
|
+
attr_reader :referenced_element_source
|
3
4
|
|
4
5
|
def parse
|
5
|
-
|
6
|
-
|
7
|
-
|
6
|
+
href = href_attribute
|
7
|
+
if href.nil?
|
8
|
+
raise SkipElementError, "use tag must have an href or xlink:href"
|
9
|
+
end
|
8
10
|
|
9
11
|
if href[0..0] != '#'
|
10
12
|
raise SkipElementError, "use tag has an href that is not a reference to an id; this is not supported"
|
11
13
|
end
|
12
14
|
|
13
15
|
id = href[1..-1]
|
14
|
-
|
16
|
+
referenced_element = @document.elements_by_id[id]
|
17
|
+
|
18
|
+
if referenced_element
|
19
|
+
@referenced_element_class = referenced_element.class
|
20
|
+
@referenced_element_source = referenced_element.source
|
21
|
+
else
|
22
|
+
# Perhaps the element is defined further down in the document. This is not recommended but still valid SVG,
|
23
|
+
# so we'll support it with an exception case that's not particularly performant.
|
24
|
+
raw_element = REXML::XPath.match(@document.root, %(//*[@id="#{id.gsub('"', '\"')}"])).first
|
25
|
+
|
26
|
+
if raw_element
|
27
|
+
@referenced_element_class = Prawn::SVG::Elements::TAG_CLASS_MAPPING[raw_element.name.to_sym]
|
28
|
+
@referenced_element_source = raw_element
|
29
|
+
end
|
30
|
+
end
|
15
31
|
|
16
|
-
if
|
32
|
+
if referenced_element_class.nil?
|
17
33
|
raise SkipElementError, "no tag with ID '#{id}' was found, referenced by use tag"
|
18
34
|
end
|
19
35
|
|
@@ -36,7 +52,7 @@ class Prawn::SVG::Elements::Use < Prawn::SVG::Elements::Base
|
|
36
52
|
def process_child_elements
|
37
53
|
add_call "save"
|
38
54
|
|
39
|
-
child =
|
55
|
+
child = referenced_element_class.new(document, referenced_element_source, calls, state.dup)
|
40
56
|
child.process
|
41
57
|
|
42
58
|
add_call "restore"
|
data/lib/prawn/svg/interface.rb
CHANGED
@@ -119,7 +119,7 @@ module Prawn
|
|
119
119
|
|
120
120
|
if skip
|
121
121
|
# the call has been overridden
|
122
|
-
elsif children.empty?
|
122
|
+
elsif children.empty? && call != 'transparent' # some prawn calls complain if they aren't supplied a block
|
123
123
|
prawn.send(call, *arguments)
|
124
124
|
else
|
125
125
|
prawn.send(call, *arguments, &proc_creator(prawn, children))
|
@@ -172,6 +172,15 @@ module Prawn
|
|
172
172
|
@cursor = [at[0] + width, at[1]]
|
173
173
|
end
|
174
174
|
|
175
|
+
decoration = options.delete(:decoration)
|
176
|
+
if decoration == 'underline'
|
177
|
+
prawn.save_graphics_state do
|
178
|
+
prawn.line_width 1
|
179
|
+
prawn.line [at[0], at[1] - 1.25], [at[0] + width, at[1] - 1.25]
|
180
|
+
prawn.stroke
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
175
184
|
when 'transformation_matrix'
|
176
185
|
left = prawn.bounds.absolute_left
|
177
186
|
top = prawn.bounds.absolute_top
|
data/lib/prawn/svg/properties.rb
CHANGED
@@ -37,6 +37,7 @@ class Prawn::SVG::Properties
|
|
37
37
|
"stroke-opacity" => Config.new("1", true),
|
38
38
|
"stroke-width" => Config.new("1", true),
|
39
39
|
"text-anchor" => Config.new("start", true, %w(inherit start middle end), true),
|
40
|
+
'text-decoration' => Config.new('none', true, %w(inherit none underline), true),
|
40
41
|
}.freeze
|
41
42
|
|
42
43
|
PROPERTIES.each do |name, value|
|
data/lib/prawn/svg/version.rb
CHANGED
data/prawn-svg.gemspec
CHANGED
@@ -22,5 +22,5 @@ Gem::Specification.new do |gem|
|
|
22
22
|
gem.add_runtime_dependency "prawn", ">= 0.11.1", "< 3"
|
23
23
|
gem.add_runtime_dependency "css_parser", "~> 1.6"
|
24
24
|
gem.add_development_dependency "rspec", "~> 3.0"
|
25
|
-
gem.add_development_dependency "rake", "~>
|
25
|
+
gem.add_development_dependency "rake", "~> 13.0"
|
26
26
|
end
|
@@ -9,7 +9,7 @@ describe Prawn::SVG::Elements::Gradient do
|
|
9
9
|
element.process
|
10
10
|
end
|
11
11
|
|
12
|
-
describe "object bounding box" do
|
12
|
+
describe "object bounding box with linear gradient" do
|
13
13
|
let(:svg) do
|
14
14
|
<<-SVG
|
15
15
|
<linearGradient id="flag" x1="0" x2="0.2" y1="0" y2="1">
|
@@ -40,7 +40,35 @@ describe Prawn::SVG::Elements::Gradient do
|
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
43
|
-
describe "
|
43
|
+
describe "object bounding box with radial gradient" do
|
44
|
+
let(:svg) do
|
45
|
+
<<-SVG
|
46
|
+
<radialGradient id="flag" cx="0" cy="0.2" fx="0.5" r="0.8">
|
47
|
+
<stop offset="25%" stop-color="red"/>
|
48
|
+
<stop offset="50%" stop-color="white"/>
|
49
|
+
<stop offset="75%" stop-color="blue"/>
|
50
|
+
</radialGradient>
|
51
|
+
SVG
|
52
|
+
end
|
53
|
+
|
54
|
+
it "is stored in the document gradients table" do
|
55
|
+
expect(document.gradients["flag"]).to eq element
|
56
|
+
end
|
57
|
+
|
58
|
+
it "returns correct gradient arguments for an element" do
|
59
|
+
arguments = element.gradient_arguments(double(bounding_box: [100, 100, 200, 0]))
|
60
|
+
expect(arguments).to eq(
|
61
|
+
from: [150, 80],
|
62
|
+
to: [100, 80],
|
63
|
+
r1: 0,
|
64
|
+
r2: Math.sqrt((0.8 * 100) ** 2 + (0.8 * 100) ** 2),
|
65
|
+
stops: [[0, "ff0000"], [0.25, "ff0000"], [0.5, "ffffff"], [0.75, "0000ff"], [1, "0000ff"]],
|
66
|
+
apply_transformations: true,
|
67
|
+
)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "user space on use with linear gradient" do
|
44
72
|
let(:svg) do
|
45
73
|
<<-SVG
|
46
74
|
<linearGradient id="flag" gradientUnits="userSpaceOnUse" x1="100" y1="500" x2="200" y2="600">
|
@@ -61,6 +89,29 @@ describe Prawn::SVG::Elements::Gradient do
|
|
61
89
|
end
|
62
90
|
end
|
63
91
|
|
92
|
+
describe "user space on use with radial gradient" do
|
93
|
+
let(:svg) do
|
94
|
+
<<-SVG
|
95
|
+
<radialGradient id="flag" gradientUnits="userSpaceOnUse" fx="100" fy="500" cx="200" cy="600" r="150">
|
96
|
+
<stop offset="0" stop-color="red"/>
|
97
|
+
<stop offset="1" stop-color="blue"/>
|
98
|
+
</radialGradient>
|
99
|
+
SVG
|
100
|
+
end
|
101
|
+
|
102
|
+
it "returns correct gradient arguments for an element" do
|
103
|
+
arguments = element.gradient_arguments(double)
|
104
|
+
expect(arguments).to eq(
|
105
|
+
from: [100.0, 100.0],
|
106
|
+
to: [200.0, 0.0],
|
107
|
+
r1: 0,
|
108
|
+
r2: 150,
|
109
|
+
stops: [[0, "ff0000"], [1, "0000ff"]],
|
110
|
+
apply_transformations: true,
|
111
|
+
)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
64
115
|
context "when gradientTransform is specified" do
|
65
116
|
let(:svg) do
|
66
117
|
<<-SVG
|
@@ -11,22 +11,22 @@ describe Prawn::SVG::Elements::Path do
|
|
11
11
|
|
12
12
|
describe "command parsing" do
|
13
13
|
context "with a valid path" do
|
14
|
-
let(:d) { "
|
14
|
+
let(:d) { "m12.34 -56.78 1 2M4 5 12-34 -.5.7+3 2.3e3 4e4 4e+4 L31,-2e-5L 6,7 Z ZZa50 50 0 100 100" }
|
15
15
|
|
16
16
|
it "correctly parses" do
|
17
17
|
calls = []
|
18
|
-
path.
|
18
|
+
allow(path).to receive(:parse_path_command) {|*args| calls << args}
|
19
19
|
path.parse
|
20
20
|
|
21
|
-
calls.
|
22
|
-
["
|
23
|
-
["
|
24
|
-
["
|
25
|
-
["
|
26
|
-
["
|
27
|
-
["
|
28
|
-
["
|
29
|
-
["
|
21
|
+
expect(calls).to eq [
|
22
|
+
["m", [[12.34, -56.78], [1, 2]]],
|
23
|
+
["M", [[4, 5], [12, -34], [-0.5, 0.7], [3, 2.3e3], [4e4, 4e4]]],
|
24
|
+
["L", [[31, -2e-5]]],
|
25
|
+
["L", [[6, 7]]],
|
26
|
+
["Z", []],
|
27
|
+
["Z", []],
|
28
|
+
["Z", []],
|
29
|
+
["a", [[50, 50, 0, 1, 0, 0, 100]]],
|
30
30
|
]
|
31
31
|
end
|
32
32
|
end
|
@@ -36,12 +36,12 @@ describe Prawn::SVG::Elements::Path do
|
|
36
36
|
|
37
37
|
it "treats subsequent points to m/M command as relative/absolute depending on command" do
|
38
38
|
[
|
39
|
-
["M", [1,2,3,4]],
|
40
|
-
["L", [3,4]],
|
41
|
-
["m", [5,6,7,8]],
|
42
|
-
["l", [7,8]]
|
39
|
+
["M", [[1,2],[3,4]]],
|
40
|
+
["L", [[3,4]]],
|
41
|
+
["m", [[5,6],[7,8]]],
|
42
|
+
["l", [[7,8]]]
|
43
43
|
].each do |args|
|
44
|
-
path.
|
44
|
+
expect(path).to receive(:parse_path_command).with(*args).and_call_original
|
45
45
|
end
|
46
46
|
|
47
47
|
path.parse
|
@@ -52,7 +52,7 @@ describe Prawn::SVG::Elements::Path do
|
|
52
52
|
let(:d) { "" }
|
53
53
|
|
54
54
|
it "correctly parses" do
|
55
|
-
path.
|
55
|
+
expect(path).not_to receive(:run_path_command)
|
56
56
|
path.parse
|
57
57
|
end
|
58
58
|
end
|
@@ -292,5 +292,17 @@ describe Prawn::SVG::Elements::Path do
|
|
292
292
|
]
|
293
293
|
end
|
294
294
|
end
|
295
|
+
|
296
|
+
context "with highly-compressed flags" do
|
297
|
+
let(:d) { "M100,100a50 50 0 100 100" }
|
298
|
+
|
299
|
+
it "correctly parses them" do
|
300
|
+
expect(subject).to eq [
|
301
|
+
Prawn::SVG::Elements::Path::Move.new([100.0, 100.0]),
|
302
|
+
Prawn::SVG::Elements::Path::Curve.new([50.0, 150.0], [72.57081148225681, 100.0], [50.0, 122.57081148225681]),
|
303
|
+
Prawn::SVG::Elements::Path::Curve.new([99.99999999999999, 200.0], [50.0, 177.42918851774317], [72.5708114822568, 200.0])
|
304
|
+
]
|
305
|
+
end
|
306
|
+
end
|
295
307
|
end
|
296
308
|
end
|
@@ -88,6 +88,21 @@ Even more
|
|
88
88
|
end
|
89
89
|
end
|
90
90
|
|
91
|
+
describe "underline" do
|
92
|
+
let(:svg) { '<text text-decoration="underline">underlined</text>' }
|
93
|
+
|
94
|
+
it "marks the element to be underlined" do
|
95
|
+
element.process
|
96
|
+
|
97
|
+
expect(element.base_calls).to eq [
|
98
|
+
["text_group", [], [
|
99
|
+
["font", ["Helvetica", {:style=>:normal}], []],
|
100
|
+
["draw_text", ["underlined", default_style.merge(decoration: 'underline')], []]
|
101
|
+
]]
|
102
|
+
]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
91
106
|
describe "fill/stroke modes" do
|
92
107
|
context "with a stroke and no fill" do
|
93
108
|
let(:svg) { '<text stroke="red" fill="none">stroked</text>' }
|
@@ -0,0 +1,26 @@
|
|
1
|
+
<?xml version="1.0" standalone="no"?>
|
2
|
+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
3
|
+
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
4
|
+
<svg width="8cm" height="4cm" viewBox="0 0 800 400" version="1.1"
|
5
|
+
xmlns="http://www.w3.org/2000/svg">
|
6
|
+
<desc>Example radgrad01 - fill a rectangle by referencing a
|
7
|
+
radial gradient paint server</desc>
|
8
|
+
<g>
|
9
|
+
<defs>
|
10
|
+
<radialGradient id="MyGradient"
|
11
|
+
cx="0.5" cy="0.5" r="0.2" fx="0.5" fy="0.5">
|
12
|
+
<stop offset="0%" stop-color="red" />
|
13
|
+
<stop offset="50%" stop-color="blue" />
|
14
|
+
<stop offset="100%" stop-color="red" />
|
15
|
+
</radialGradient>
|
16
|
+
</defs>
|
17
|
+
|
18
|
+
<!-- Outline the drawing area in blue -->
|
19
|
+
<rect fill="none" stroke="blue"
|
20
|
+
x="1" y="1" width="798" height="398"/>
|
21
|
+
|
22
|
+
<!-- The rectangle is filled using a radial gradient paint server -->
|
23
|
+
<rect fill="url(#MyGradient)" stroke="black" stroke-width="5"
|
24
|
+
x="100" y="100" width="600" height="200"/>
|
25
|
+
</g>
|
26
|
+
</svg>
|
@@ -0,0 +1,26 @@
|
|
1
|
+
<?xml version="1.0" standalone="no"?>
|
2
|
+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
3
|
+
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
4
|
+
<svg width="8cm" height="4cm" viewBox="0 0 800 400" version="1.1"
|
5
|
+
xmlns="http://www.w3.org/2000/svg">
|
6
|
+
<desc>Example radgrad01 - fill a rectangle by referencing a
|
7
|
+
radial gradient paint server</desc>
|
8
|
+
<g>
|
9
|
+
<defs>
|
10
|
+
<radialGradient id="MyGradient" gradientUnits="userSpaceOnUse"
|
11
|
+
cx="400" cy="200" r="50%" fx="200" fy="200">
|
12
|
+
<stop offset="0%" stop-color="red" />
|
13
|
+
<stop offset="50%" stop-color="blue" />
|
14
|
+
<stop offset="100%" stop-color="red" />
|
15
|
+
</radialGradient>
|
16
|
+
</defs>
|
17
|
+
|
18
|
+
<!-- Outline the drawing area in blue -->
|
19
|
+
<rect fill="none" stroke="blue"
|
20
|
+
x="1" y="1" width="798" height="398"/>
|
21
|
+
|
22
|
+
<!-- The rectangle is filled using a radial gradient paint server -->
|
23
|
+
<rect fill="url(#MyGradient)" stroke="black" stroke-width="5"
|
24
|
+
x="100" y="100" width="600" height="200"/>
|
25
|
+
</g>
|
26
|
+
</svg>
|
@@ -0,0 +1,5 @@
|
|
1
|
+
<?xml version="1.0" standalone="no"?>
|
2
|
+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
3
|
+
<svg width="12cm" height="5.25cm" viewBox="0 0 1200 400" xmlns="http://www.w3.org/2000/svg" version="1.1" fill="red">
|
4
|
+
<text style="font-size: 40pt;">This is red text.</text>
|
5
|
+
</svg>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<?xml version="1.0" standalone="no"?>
|
2
|
+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
3
|
+
<svg width="12cm" height="4cm" viewBox="0 0 1200 400" xmlns="http://www.w3.org/2000/svg" xmlns:xlink='http://www.w3.org/1999/xlink' version="1.1">
|
4
|
+
<use xlink:href="#two-rects"/>
|
5
|
+
<use xlink:href="#two-rects" x="90" y="90"/>
|
6
|
+
<use xlink:href="#two-rects" x="180" y="180"/>
|
7
|
+
|
8
|
+
<use xlink:href="#circle" x="0" y="0" style="opacity: 0.5;" transform="scale(1.3)"/>
|
9
|
+
<use xlink:href="#circle" x="0" y="0" style="opacity: 0.2;" transform="scale(1.7)"/>
|
10
|
+
|
11
|
+
<circle id="circle" cx="600" cy="200" r="100" fill="red" stroke="blue" stroke-width="10" />
|
12
|
+
|
13
|
+
<symbol id="two-rects">
|
14
|
+
<rect x="1" y="1" width="1198" height="398" fill="none" stroke="blue" stroke-width="2"/>
|
15
|
+
<rect x="400" y="100" width="400" height="200" fill="yellow" stroke="navy" stroke-width="10" />
|
16
|
+
</symbol>
|
17
|
+
</svg>
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: prawn-svg
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.31.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Roger Nesbitt
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-10-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: prawn
|
@@ -64,14 +64,14 @@ dependencies:
|
|
64
64
|
requirements:
|
65
65
|
- - "~>"
|
66
66
|
- !ruby/object:Gem::Version
|
67
|
-
version: '
|
67
|
+
version: '13.0'
|
68
68
|
type: :development
|
69
69
|
prerelease: false
|
70
70
|
version_requirements: !ruby/object:Gem::Requirement
|
71
71
|
requirements:
|
72
72
|
- - "~>"
|
73
73
|
- !ruby/object:Gem::Version
|
74
|
-
version: '
|
74
|
+
version: '13.0'
|
75
75
|
description: This gem allows you to render SVG directly into a PDF using the 'prawn'
|
76
76
|
gem. Since PDF is vector-based, you'll get nice scaled graphics if you use SVG
|
77
77
|
instead of an image.
|
@@ -213,6 +213,8 @@ files:
|
|
213
213
|
- spec/sample_svg/polyline01.svg
|
214
214
|
- spec/sample_svg/preserve-space.svg
|
215
215
|
- spec/sample_svg/quad01.svg
|
216
|
+
- spec/sample_svg/radgrad01-bounding.svg
|
217
|
+
- spec/sample_svg/radgrad01.svg
|
216
218
|
- spec/sample_svg/rect01.svg
|
217
219
|
- spec/sample_svg/rect02.svg
|
218
220
|
- spec/sample_svg/rotate_scale.svg
|
@@ -221,6 +223,8 @@ files:
|
|
221
223
|
- spec/sample_svg/subfamilies.svg
|
222
224
|
- spec/sample_svg/subviewports.svg
|
223
225
|
- spec/sample_svg/subviewports2.svg
|
226
|
+
- spec/sample_svg/svg_fill.svg
|
227
|
+
- spec/sample_svg/text-decoration.svg
|
224
228
|
- spec/sample_svg/text_entities.svg
|
225
229
|
- spec/sample_svg/text_stroke.svg
|
226
230
|
- spec/sample_svg/transform.svg
|
@@ -234,6 +238,7 @@ files:
|
|
234
238
|
- spec/sample_svg/tspan05.svg
|
235
239
|
- spec/sample_svg/tspan91.svg
|
236
240
|
- spec/sample_svg/use.svg
|
241
|
+
- spec/sample_svg/use_disordered.svg
|
237
242
|
- spec/sample_svg/viewbox.svg
|
238
243
|
- spec/sample_svg/viewport.svg
|
239
244
|
- spec/sample_svg/warning-radioactive.svg
|
@@ -243,7 +248,7 @@ homepage: http://github.com/mogest/prawn-svg
|
|
243
248
|
licenses:
|
244
249
|
- MIT
|
245
250
|
metadata: {}
|
246
|
-
post_install_message:
|
251
|
+
post_install_message:
|
247
252
|
rdoc_options: []
|
248
253
|
require_paths:
|
249
254
|
- lib
|
@@ -258,8 +263,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
258
263
|
- !ruby/object:Gem::Version
|
259
264
|
version: '0'
|
260
265
|
requirements: []
|
261
|
-
rubygems_version: 3.0.
|
262
|
-
signing_key:
|
266
|
+
rubygems_version: 3.0.3
|
267
|
+
signing_key:
|
263
268
|
specification_version: 4
|
264
269
|
summary: SVG renderer for Prawn PDF library
|
265
270
|
test_files:
|
@@ -335,6 +340,8 @@ test_files:
|
|
335
340
|
- spec/sample_svg/polyline01.svg
|
336
341
|
- spec/sample_svg/preserve-space.svg
|
337
342
|
- spec/sample_svg/quad01.svg
|
343
|
+
- spec/sample_svg/radgrad01-bounding.svg
|
344
|
+
- spec/sample_svg/radgrad01.svg
|
338
345
|
- spec/sample_svg/rect01.svg
|
339
346
|
- spec/sample_svg/rect02.svg
|
340
347
|
- spec/sample_svg/rotate_scale.svg
|
@@ -343,6 +350,8 @@ test_files:
|
|
343
350
|
- spec/sample_svg/subfamilies.svg
|
344
351
|
- spec/sample_svg/subviewports.svg
|
345
352
|
- spec/sample_svg/subviewports2.svg
|
353
|
+
- spec/sample_svg/svg_fill.svg
|
354
|
+
- spec/sample_svg/text-decoration.svg
|
346
355
|
- spec/sample_svg/text_entities.svg
|
347
356
|
- spec/sample_svg/text_stroke.svg
|
348
357
|
- spec/sample_svg/transform.svg
|
@@ -356,6 +365,7 @@ test_files:
|
|
356
365
|
- spec/sample_svg/tspan05.svg
|
357
366
|
- spec/sample_svg/tspan91.svg
|
358
367
|
- spec/sample_svg/use.svg
|
368
|
+
- spec/sample_svg/use_disordered.svg
|
359
369
|
- spec/sample_svg/viewbox.svg
|
360
370
|
- spec/sample_svg/viewport.svg
|
361
371
|
- spec/sample_svg/warning-radioactive.svg
|