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.
Files changed (54) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/test.yml +18 -0
  3. data/LICENSE +1 -1
  4. data/README.md +14 -9
  5. data/lib/prawn-svg.rb +4 -0
  6. data/lib/prawn/svg/attributes/opacity.rb +4 -4
  7. data/lib/prawn/svg/attributes/transform.rb +2 -44
  8. data/lib/prawn/svg/calculators/document_sizing.rb +2 -2
  9. data/lib/prawn/svg/css/stylesheets.rb +40 -19
  10. data/lib/prawn/svg/elements.rb +2 -0
  11. data/lib/prawn/svg/elements/base.rb +11 -5
  12. data/lib/prawn/svg/elements/call_duplicator.rb +1 -1
  13. data/lib/prawn/svg/elements/gradient.rb +83 -25
  14. data/lib/prawn/svg/elements/image.rb +2 -2
  15. data/lib/prawn/svg/elements/path.rb +42 -29
  16. data/lib/prawn/svg/elements/root.rb +4 -1
  17. data/lib/prawn/svg/elements/text_component.rb +21 -5
  18. data/lib/prawn/svg/elements/use.rb +23 -7
  19. data/lib/prawn/svg/extensions/additional_gradient_transforms.rb +23 -0
  20. data/lib/prawn/svg/interface.rb +38 -8
  21. data/lib/prawn/svg/loaders/data.rb +1 -1
  22. data/lib/prawn/svg/loaders/file.rb +4 -2
  23. data/lib/prawn/svg/properties.rb +1 -0
  24. data/lib/prawn/svg/transform_parser.rb +72 -0
  25. data/lib/prawn/svg/version.rb +1 -1
  26. data/prawn-svg.gemspec +3 -3
  27. data/spec/integration_spec.rb +24 -24
  28. data/spec/prawn/svg/attributes/opacity_spec.rb +85 -0
  29. data/spec/prawn/svg/attributes/transform_spec.rb +30 -35
  30. data/spec/prawn/svg/calculators/document_sizing_spec.rb +4 -4
  31. data/spec/prawn/svg/css/stylesheets_spec.rb +17 -6
  32. data/spec/prawn/svg/elements/base_spec.rb +16 -16
  33. data/spec/prawn/svg/elements/gradient_spec.rb +79 -4
  34. data/spec/prawn/svg/elements/line_spec.rb +12 -12
  35. data/spec/prawn/svg/elements/marker_spec.rb +27 -27
  36. data/spec/prawn/svg/elements/path_spec.rb +29 -17
  37. data/spec/prawn/svg/elements/polygon_spec.rb +9 -9
  38. data/spec/prawn/svg/elements/polyline_spec.rb +7 -7
  39. data/spec/prawn/svg/elements/text_spec.rb +65 -50
  40. data/spec/prawn/svg/loaders/data_spec.rb +8 -0
  41. data/spec/prawn/svg/pathable_spec.rb +4 -4
  42. data/spec/prawn/svg/transform_parser_spec.rb +94 -0
  43. data/spec/sample_svg/double_opacity.svg +6 -0
  44. data/spec/sample_svg/gradient_transform.svg +19 -0
  45. data/spec/sample_svg/links.svg +18 -0
  46. data/spec/sample_svg/radgrad01-bounding.svg +26 -0
  47. data/spec/sample_svg/radgrad01.svg +26 -0
  48. data/spec/sample_svg/svg_fill.svg +5 -0
  49. data/spec/sample_svg/text-decoration.svg +4 -0
  50. data/spec/sample_svg/transform.svg +20 -0
  51. data/spec/sample_svg/use_disordered.svg +17 -0
  52. data/spec/spec_helper.rb +2 -2
  53. metadata +48 -11
  54. 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 = attributes['xlink:href'] || attributes['href']
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
- VALUES_REGEXP = /^#{INSIDE_REGEXP}/
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
- catch :invalid_command do
26
- matched_commands.each do |matched_command|
27
- command = matched_command[1]
28
- matched_values = match_all(matched_command[2], VALUES_REGEXP)
29
- raise "should be impossible to have invalid inside data, but we ended up here" if matched_values.nil?
30
- values = matched_values.collect {|value| value[1].to_f}
31
- parse_path_command(command, values)
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 or throw :invalid_command
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(6)
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(4)
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(2)
156
+ x, y = values.shift
140
157
  else
141
- x1, y1, x, y = values.shift(4)
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(7)
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
- add_call 'fill_color', '000000'
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 = attributes['xlink: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 :referenced_element
2
+ attr_reader :referenced_element_class
3
+ attr_reader :referenced_element_source
3
4
 
4
5
  def parse
5
- require_attributes 'xlink:href'
6
-
7
- href = attributes['xlink:href']
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
- @referenced_element = @document.elements_by_id[id]
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 referenced_element.nil?
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 = referenced_element.class.new(referenced_element.document, referenced_element.source, calls, state.dup)
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
@@ -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
- prawn.send(call, *arguments)
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
- prawn.send(call, *arguments, &proc_creator(prawn, children))
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 = arguments[0].is_a?(Hash) && arguments[0][:fill_rule] == :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
 
@@ -2,7 +2,7 @@ require 'base64'
2
2
 
3
3
  module Prawn::SVG::Loaders
4
4
  class Data
5
- REGEXP = %r[\A(?i:data):image/(png|jpeg);base64(;[a-z0-9]+)*,]
5
+ REGEXP = %r[\Adata:image/(png|jpeg);base64(;[a-z0-9]+)*,]i
6
6
 
7
7
  def from_url(url)
8
8
  return if url[0..4].downcase != "data:"
@@ -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.decode(path)
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
- if uri.host
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