prawn-svg 0.28.0 → 0.32.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/test.yml +18 -0
- data/LICENSE +1 -1
- data/README.md +14 -9
- data/lib/prawn-svg.rb +4 -0
- data/lib/prawn/svg/attributes/opacity.rb +4 -4
- data/lib/prawn/svg/attributes/transform.rb +2 -44
- data/lib/prawn/svg/calculators/document_sizing.rb +2 -2
- data/lib/prawn/svg/css/stylesheets.rb +40 -19
- data/lib/prawn/svg/elements.rb +2 -0
- data/lib/prawn/svg/elements/base.rb +11 -5
- data/lib/prawn/svg/elements/call_duplicator.rb +1 -1
- data/lib/prawn/svg/elements/gradient.rb +83 -25
- data/lib/prawn/svg/elements/image.rb +2 -2
- data/lib/prawn/svg/elements/path.rb +42 -29
- data/lib/prawn/svg/elements/root.rb +4 -1
- data/lib/prawn/svg/elements/text_component.rb +21 -5
- data/lib/prawn/svg/elements/use.rb +23 -7
- data/lib/prawn/svg/extensions/additional_gradient_transforms.rb +23 -0
- data/lib/prawn/svg/interface.rb +38 -8
- data/lib/prawn/svg/loaders/data.rb +1 -1
- data/lib/prawn/svg/loaders/file.rb +4 -2
- data/lib/prawn/svg/properties.rb +1 -0
- data/lib/prawn/svg/transform_parser.rb +72 -0
- data/lib/prawn/svg/version.rb +1 -1
- data/prawn-svg.gemspec +3 -3
- data/spec/integration_spec.rb +24 -24
- data/spec/prawn/svg/attributes/opacity_spec.rb +85 -0
- data/spec/prawn/svg/attributes/transform_spec.rb +30 -35
- data/spec/prawn/svg/calculators/document_sizing_spec.rb +4 -4
- data/spec/prawn/svg/css/stylesheets_spec.rb +17 -6
- data/spec/prawn/svg/elements/base_spec.rb +16 -16
- data/spec/prawn/svg/elements/gradient_spec.rb +79 -4
- data/spec/prawn/svg/elements/line_spec.rb +12 -12
- data/spec/prawn/svg/elements/marker_spec.rb +27 -27
- data/spec/prawn/svg/elements/path_spec.rb +29 -17
- data/spec/prawn/svg/elements/polygon_spec.rb +9 -9
- data/spec/prawn/svg/elements/polyline_spec.rb +7 -7
- data/spec/prawn/svg/elements/text_spec.rb +65 -50
- data/spec/prawn/svg/loaders/data_spec.rb +8 -0
- data/spec/prawn/svg/pathable_spec.rb +4 -4
- data/spec/prawn/svg/transform_parser_spec.rb +94 -0
- data/spec/sample_svg/double_opacity.svg +6 -0
- data/spec/sample_svg/gradient_transform.svg +19 -0
- data/spec/sample_svg/links.svg +18 -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/transform.svg +20 -0
- data/spec/sample_svg/use_disordered.svg +17 -0
- data/spec/spec_helper.rb +2 -2
- metadata +48 -11
- data/.travis.yml +0 -6
@@ -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
|
@@ -16,20 +37,20 @@ class Prawn::SVG::Elements::Path < Prawn::SVG::Elements::Base
|
|
16
37
|
require_attributes 'd'
|
17
38
|
|
18
39
|
@commands = []
|
40
|
+
@last_point = nil
|
19
41
|
|
20
42
|
data = attributes["d"].gsub(/#{OUTSIDE_SPACE_REGEXP}$/, '')
|
21
43
|
|
22
44
|
matched_commands = match_all(data, COMMAND_REGEXP)
|
23
45
|
raise SkipElementError, "Invalid/unsupported syntax for SVG path data" if matched_commands.nil?
|
24
46
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
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)
|
33
54
|
end
|
34
55
|
end
|
35
56
|
|
@@ -48,8 +69,7 @@ class Prawn::SVG::Elements::Path < Prawn::SVG::Elements::Base
|
|
48
69
|
|
49
70
|
case upcase_command
|
50
71
|
when 'M' # moveto
|
51
|
-
x = values.shift
|
52
|
-
y = values.shift or throw :invalid_command
|
72
|
+
x, y = values.shift
|
53
73
|
|
54
74
|
if relative && @last_point
|
55
75
|
x += @last_point.first
|
@@ -68,8 +88,7 @@ class Prawn::SVG::Elements::Path < Prawn::SVG::Elements::Base
|
|
68
88
|
|
69
89
|
when 'L' # lineto
|
70
90
|
while values.any?
|
71
|
-
x = values.shift
|
72
|
-
y = values.shift or throw :invalid_command
|
91
|
+
x, y = values.shift
|
73
92
|
if relative && @last_point
|
74
93
|
x += @last_point.first
|
75
94
|
y += @last_point.last
|
@@ -80,22 +99,21 @@ class Prawn::SVG::Elements::Path < Prawn::SVG::Elements::Base
|
|
80
99
|
|
81
100
|
when 'H' # horizontal lineto
|
82
101
|
while values.any?
|
83
|
-
x = values.shift
|
102
|
+
x = values.shift.first
|
84
103
|
x += @last_point.first if relative && @last_point
|
85
104
|
push_command Prawn::SVG::Pathable::Line.new([x, @last_point.last])
|
86
105
|
end
|
87
106
|
|
88
107
|
when 'V' # vertical lineto
|
89
108
|
while values.any?
|
90
|
-
y = values.shift
|
109
|
+
y = values.shift.first
|
91
110
|
y += @last_point.last if relative && @last_point
|
92
111
|
push_command Prawn::SVG::Pathable::Line.new([@last_point.first, y])
|
93
112
|
end
|
94
113
|
|
95
114
|
when 'C' # curveto
|
96
115
|
while values.any?
|
97
|
-
x1, y1, x2, y2, x, y = values.shift
|
98
|
-
throw :invalid_command unless y
|
116
|
+
x1, y1, x2, y2, x, y = values.shift
|
99
117
|
|
100
118
|
if relative && @last_point
|
101
119
|
x += @last_point.first
|
@@ -112,8 +130,7 @@ class Prawn::SVG::Elements::Path < Prawn::SVG::Elements::Base
|
|
112
130
|
|
113
131
|
when 'S' # shorthand/smooth curveto
|
114
132
|
while values.any?
|
115
|
-
x2, y2, x, y = values.shift
|
116
|
-
throw :invalid_command unless y
|
133
|
+
x2, y2, x, y = values.shift
|
117
134
|
|
118
135
|
if relative && @last_point
|
119
136
|
x += @last_point.first
|
@@ -136,13 +153,11 @@ class Prawn::SVG::Elements::Path < Prawn::SVG::Elements::Base
|
|
136
153
|
when 'Q', 'T' # quadratic curveto
|
137
154
|
while values.any?
|
138
155
|
if shorthand = upcase_command == 'T'
|
139
|
-
x, y = values.shift
|
156
|
+
x, y = values.shift
|
140
157
|
else
|
141
|
-
x1, y1, x, y = values.shift
|
158
|
+
x1, y1, x, y = values.shift
|
142
159
|
end
|
143
160
|
|
144
|
-
throw :invalid_command unless y
|
145
|
-
|
146
161
|
if relative && @last_point
|
147
162
|
x += @last_point.first
|
148
163
|
x1 += @last_point.first if x1
|
@@ -174,8 +189,7 @@ class Prawn::SVG::Elements::Path < Prawn::SVG::Elements::Base
|
|
174
189
|
return unless @last_point
|
175
190
|
|
176
191
|
while values.any?
|
177
|
-
rx, ry, phi, fa, fs, x2, y2 = values.shift
|
178
|
-
throw :invalid_command unless y2
|
192
|
+
rx, ry, phi, fa, fs, x2, y2 = values.shift
|
179
193
|
|
180
194
|
x1, y1 = @last_point
|
181
195
|
|
@@ -276,9 +290,8 @@ class Prawn::SVG::Elements::Path < Prawn::SVG::Elements::Base
|
|
276
290
|
def match_all(string, regexp) # regexp must start with ^
|
277
291
|
result = []
|
278
292
|
while string != ""
|
279
|
-
matches = string.match(regexp)
|
280
|
-
result << matches
|
281
|
-
return if matches.nil?
|
293
|
+
matches = string.match(regexp) or return
|
294
|
+
result << matches.captures
|
282
295
|
string = matches.post_match
|
283
296
|
end
|
284
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
|
@@ -2,7 +2,7 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
|
|
2
2
|
attr_reader :commands
|
3
3
|
|
4
4
|
Printable = Struct.new(:element, :text, :leading_space?, :trailing_space?)
|
5
|
-
TextState = Struct.new(:parent, :x, :y, :dx, :dy, :rotation, :spacing, :mode)
|
5
|
+
TextState = Struct.new(:parent, :x, :y, :dx, :dy, :rotation, :spacing, :mode, :text_length, :length_adjust)
|
6
6
|
|
7
7
|
def parse
|
8
8
|
if state.inside_clip_path
|
@@ -14,6 +14,8 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
|
|
14
14
|
state.text.dx = (attributes['dx'] || "").split(COMMA_WSP_REGEXP).collect { |n| x_pixels(n) }
|
15
15
|
state.text.dy = (attributes['dy'] || "").split(COMMA_WSP_REGEXP).collect { |n| y_pixels(n) }
|
16
16
|
state.text.rotation = (attributes['rotate'] || "").split(COMMA_WSP_REGEXP).collect(&:to_f)
|
17
|
+
state.text.text_length = normalize_length(attributes['textLength'])
|
18
|
+
state.text.length_adjust = attributes['lengthAdjust']
|
17
19
|
state.text.spacing = calculate_character_spacing
|
18
20
|
state.text.mode = calculate_text_rendering_mode
|
19
21
|
|
@@ -44,9 +46,11 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
|
|
44
46
|
opts = {
|
45
47
|
size: computed_properties.numerical_font_size,
|
46
48
|
style: font && font.subfamily,
|
47
|
-
text_anchor: computed_properties.text_anchor
|
49
|
+
text_anchor: computed_properties.text_anchor,
|
48
50
|
}
|
49
51
|
|
52
|
+
opts[:decoration] = computed_properties.text_decoration unless computed_properties.text_decoration == 'none'
|
53
|
+
|
50
54
|
if state.text.parent
|
51
55
|
add_call_and_enter 'character_spacing', state.text.spacing unless state.text.spacing == state.text.parent.spacing
|
52
56
|
add_call_and_enter 'text_rendering_mode', state.text.mode unless state.text.mode == state.text.parent.mode
|
@@ -133,11 +137,19 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
|
|
133
137
|
opts.delete(:rotate)
|
134
138
|
end
|
135
139
|
|
140
|
+
if state.text.text_length
|
141
|
+
if state.text.length_adjust == 'spacingAndGlyphs'
|
142
|
+
opts[:stretch_to_width] = state.text.text_length
|
143
|
+
else
|
144
|
+
opts[:pad_to_width] = state.text.text_length
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
136
148
|
if remaining
|
137
|
-
add_call 'draw_text', text[0..0], opts.dup
|
149
|
+
add_call 'draw_text', text[0..0], **opts.dup
|
138
150
|
text = text[1..-1]
|
139
151
|
else
|
140
|
-
add_call 'draw_text', text, opts.dup
|
152
|
+
add_call 'draw_text', text, **opts.dup
|
141
153
|
|
142
154
|
# we can get to this path with rotations still pending
|
143
155
|
# solve this by shifting them out by the number of
|
@@ -173,7 +185,7 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
|
|
173
185
|
end
|
174
186
|
|
175
187
|
def find_referenced_element
|
176
|
-
href =
|
188
|
+
href = href_attribute
|
177
189
|
|
178
190
|
if href && href[0..0] == '#'
|
179
191
|
element = document.elements_by_id[href[1..-1]]
|
@@ -222,4 +234,8 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
|
|
222
234
|
# overridden, we don't want to call fill/stroke as draw_text does this for us
|
223
235
|
def apply_drawing_call
|
224
236
|
end
|
237
|
+
|
238
|
+
def normalize_length(length)
|
239
|
+
x_pixels(length) if length && length.match(/\d/)
|
240
|
+
end
|
225
241
|
end
|
@@ -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"
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Prawn::SVG::Extensions
|
2
|
+
module AdditionalGradientTransforms
|
3
|
+
def gradient_coordinates(gradient)
|
4
|
+
# As of Prawn 2.2.0, apply_transformations is used as purely a boolean.
|
5
|
+
#
|
6
|
+
# Here we're using it to optionally pass in a 6-tuple transformation matrix that gets applied to the
|
7
|
+
# gradient. This should be added to Prawn properly, and then this monkey patch will not be necessary.
|
8
|
+
|
9
|
+
if gradient.apply_transformations.is_a?(Array)
|
10
|
+
x1, y1, x2, y2, transformation = super
|
11
|
+
a, b, c, d, e, f = transformation
|
12
|
+
na, nb, nc, nd, ne, nf = gradient.apply_transformations
|
13
|
+
|
14
|
+
matrix = Matrix[[a, c, e], [b, d, f], [0, 0, 1]] * Matrix[[na, nc, ne], [nb, nd, nf], [0, 0, 1]]
|
15
|
+
new_transformation = matrix.to_a[0..1].transpose.flatten
|
16
|
+
|
17
|
+
[x1, y1, x2, y2, new_transformation]
|
18
|
+
else
|
19
|
+
super
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/prawn/svg/interface.rb
CHANGED
@@ -109,32 +109,40 @@ module Prawn
|
|
109
109
|
end
|
110
110
|
|
111
111
|
def issue_prawn_command(prawn, calls)
|
112
|
-
calls.each do |call, arguments, children|
|
112
|
+
calls.each do |call, arguments, kwarguments, children|
|
113
113
|
skip = false
|
114
114
|
|
115
|
-
rewrite_call_arguments(prawn, call, arguments) do
|
115
|
+
rewrite_call_arguments(prawn, call, arguments, kwarguments) do
|
116
116
|
issue_prawn_command(prawn, children) if children.any?
|
117
117
|
skip = true
|
118
118
|
end
|
119
119
|
|
120
120
|
if skip
|
121
121
|
# the call has been overridden
|
122
|
-
elsif children.empty?
|
123
|
-
|
122
|
+
elsif children.empty? && call != 'transparent' # some prawn calls complain if they aren't supplied a block
|
123
|
+
if RUBY_VERSION >= '2.7' || !kwarguments.empty?
|
124
|
+
prawn.send(call, *arguments, **kwarguments)
|
125
|
+
else
|
126
|
+
prawn.send(call, *arguments)
|
127
|
+
end
|
124
128
|
else
|
125
|
-
|
129
|
+
if RUBY_VERSION >= '2.7' || !kwarguments.empty?
|
130
|
+
prawn.send(call, *arguments, **kwarguments, &proc_creator(prawn, children))
|
131
|
+
else
|
132
|
+
prawn.send(call, *arguments, &proc_creator(prawn, children))
|
133
|
+
end
|
126
134
|
end
|
127
135
|
end
|
128
136
|
end
|
129
137
|
|
130
|
-
def rewrite_call_arguments(prawn, call, arguments)
|
138
|
+
def rewrite_call_arguments(prawn, call, arguments, kwarguments)
|
131
139
|
case call
|
132
140
|
when 'text_group'
|
133
141
|
@cursor = [0, document.sizing.output_height]
|
134
142
|
yield
|
135
143
|
|
136
144
|
when 'draw_text'
|
137
|
-
text, options = arguments
|
145
|
+
text, options = arguments.first, kwarguments
|
138
146
|
|
139
147
|
at = options.fetch(:at)
|
140
148
|
|
@@ -148,6 +156,19 @@ module Prawn
|
|
148
156
|
|
149
157
|
width = prawn.width_of(text, options.merge(kerning: true))
|
150
158
|
|
159
|
+
if stretch_to_width = options.delete(:stretch_to_width)
|
160
|
+
factor = stretch_to_width.to_f * 100 / width.to_f
|
161
|
+
prawn.add_content "#{factor} Tz"
|
162
|
+
width = stretch_to_width.to_f
|
163
|
+
end
|
164
|
+
|
165
|
+
if pad_to_width = options.delete(:pad_to_width)
|
166
|
+
padding_required = pad_to_width.to_f - width.to_f
|
167
|
+
padding_per_character = padding_required / text.length.to_f
|
168
|
+
prawn.add_content "#{padding_per_character} Tc"
|
169
|
+
width = pad_to_width.to_f
|
170
|
+
end
|
171
|
+
|
151
172
|
case options.delete(:text_anchor)
|
152
173
|
when 'middle'
|
153
174
|
at[0] -= width / 2
|
@@ -159,6 +180,15 @@ module Prawn
|
|
159
180
|
@cursor = [at[0] + width, at[1]]
|
160
181
|
end
|
161
182
|
|
183
|
+
decoration = options.delete(:decoration)
|
184
|
+
if decoration == 'underline'
|
185
|
+
prawn.save_graphics_state do
|
186
|
+
prawn.line_width 1
|
187
|
+
prawn.line [at[0], at[1] - 1.25], [at[0] + width, at[1] - 1.25]
|
188
|
+
prawn.stroke
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
162
192
|
when 'transformation_matrix'
|
163
193
|
left = prawn.bounds.absolute_left
|
164
194
|
top = prawn.bounds.absolute_top
|
@@ -186,7 +216,7 @@ module Prawn
|
|
186
216
|
# prawn (as at 2.0.1 anyway) uses 'b' for its fill_and_stroke. 'b' is 'h' (closepath) + 'B', and we
|
187
217
|
# never want closepath to be automatically run as it stuffs up many drawing operations, such as dashes
|
188
218
|
# and line caps, and makes paths close that we didn't ask to be closed when fill is specified.
|
189
|
-
even_odd =
|
219
|
+
even_odd = kwarguments[:fill_rule] == :even_odd
|
190
220
|
content = even_odd ? 'B*' : 'B'
|
191
221
|
prawn.add_content content
|
192
222
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'addressable/uri'
|
2
|
+
|
1
3
|
#
|
2
4
|
# Load a file from disk.
|
3
5
|
#
|
@@ -56,7 +58,7 @@ module Prawn::SVG::Loaders
|
|
56
58
|
private
|
57
59
|
|
58
60
|
def load_file(path)
|
59
|
-
path = URI.
|
61
|
+
path = Addressable::URI.unencode(path)
|
60
62
|
path = build_absolute_and_expand_path(path)
|
61
63
|
assert_valid_path!(path)
|
62
64
|
assert_file_exists!(path)
|
@@ -85,7 +87,7 @@ module Prawn::SVG::Loaders
|
|
85
87
|
end
|
86
88
|
|
87
89
|
def assert_valid_file_uri!(uri)
|
88
|
-
|
90
|
+
unless uri.host.nil? || uri.host.empty?
|
89
91
|
raise Prawn::SVG::UrlLoader::Error, "prawn-svg does not suport file: URLs with a host. Your URL probably doesn't start with three slashes, and it should."
|
90
92
|
end
|
91
93
|
end
|