prawn-svg 0.27.1 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +6 -4
  3. data/LICENSE +1 -1
  4. data/README.md +23 -9
  5. data/lib/prawn-svg.rb +7 -1
  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.rb → css/font_family_parser.rb} +3 -4
  10. data/lib/prawn/svg/css/selector_parser.rb +174 -0
  11. data/lib/prawn/svg/css/stylesheets.rb +146 -0
  12. data/lib/prawn/svg/document.rb +3 -15
  13. data/lib/prawn/svg/elements.rb +4 -2
  14. data/lib/prawn/svg/elements/base.rb +26 -23
  15. data/lib/prawn/svg/elements/clip_path.rb +12 -0
  16. data/lib/prawn/svg/elements/container.rb +1 -3
  17. data/lib/prawn/svg/elements/gradient.rb +83 -25
  18. data/lib/prawn/svg/elements/image.rb +2 -2
  19. data/lib/prawn/svg/elements/path.rb +42 -29
  20. data/lib/prawn/svg/elements/root.rb +4 -1
  21. data/lib/prawn/svg/elements/text.rb +1 -1
  22. data/lib/prawn/svg/elements/text_component.rb +63 -14
  23. data/lib/prawn/svg/elements/use.rb +23 -7
  24. data/lib/prawn/svg/extensions/additional_gradient_transforms.rb +23 -0
  25. data/lib/prawn/svg/font_registry.rb +4 -3
  26. data/lib/prawn/svg/interface.rb +26 -2
  27. data/lib/prawn/svg/loaders/data.rb +1 -1
  28. data/lib/prawn/svg/loaders/file.rb +4 -2
  29. data/lib/prawn/svg/properties.rb +2 -0
  30. data/lib/prawn/svg/state.rb +6 -3
  31. data/lib/prawn/svg/transform_parser.rb +72 -0
  32. data/lib/prawn/svg/version.rb +1 -1
  33. data/prawn-svg.gemspec +3 -4
  34. data/spec/prawn/svg/attributes/opacity_spec.rb +85 -0
  35. data/spec/prawn/svg/attributes/transform_spec.rb +30 -35
  36. data/spec/prawn/svg/calculators/document_sizing_spec.rb +4 -4
  37. data/spec/prawn/svg/{css_spec.rb → css/font_family_parser_spec.rb} +3 -3
  38. data/spec/prawn/svg/css/selector_parser_spec.rb +33 -0
  39. data/spec/prawn/svg/css/stylesheets_spec.rb +142 -0
  40. data/spec/prawn/svg/document_spec.rb +0 -33
  41. data/spec/prawn/svg/elements/base_spec.rb +58 -2
  42. data/spec/prawn/svg/elements/gradient_spec.rb +79 -4
  43. data/spec/prawn/svg/elements/path_spec.rb +29 -17
  44. data/spec/prawn/svg/elements/text_spec.rb +74 -16
  45. data/spec/prawn/svg/font_registry_spec.rb +30 -0
  46. data/spec/prawn/svg/interface_spec.rb +33 -1
  47. data/spec/prawn/svg/loaders/data_spec.rb +8 -0
  48. data/spec/prawn/svg/transform_parser_spec.rb +94 -0
  49. data/spec/sample_output/{directory → .keep} +0 -0
  50. data/spec/sample_svg/double_opacity.svg +6 -0
  51. data/spec/sample_svg/gradient_transform.svg +19 -0
  52. data/spec/sample_svg/links.svg +18 -0
  53. data/spec/sample_svg/radgrad01-bounding.svg +26 -0
  54. data/spec/sample_svg/radgrad01.svg +26 -0
  55. data/spec/sample_svg/svg_fill.svg +5 -0
  56. data/spec/sample_svg/text-decoration.svg +4 -0
  57. data/spec/sample_svg/text_stroke.svg +41 -0
  58. data/spec/sample_svg/transform.svg +20 -0
  59. data/spec/sample_svg/use_disordered.svg +17 -0
  60. data/spec/sample_svg/warning-radioactive.svg +98 -0
  61. data/spec/spec_helper.rb +2 -2
  62. metadata +137 -15
@@ -12,7 +12,8 @@ class Prawn::SVG::Document
12
12
  :fallback_font_name,
13
13
  :font_registry,
14
14
  :url_loader,
15
- :css_parser, :elements_by_id, :gradients
15
+ :elements_by_id, :gradients,
16
+ :element_styles
16
17
 
17
18
  def initialize(data, bounds, options, font_registry: nil, css_parser: CssParser::Parser.new)
18
19
  @root = REXML::Document.new(data).root
@@ -31,7 +32,6 @@ class Prawn::SVG::Document
31
32
  @gradients = {}
32
33
  @fallback_font_name = options.fetch(:fallback_font_name, DEFAULT_FALLBACK_FONT_NAME)
33
34
  @font_registry = font_registry
34
- @css_parser = css_parser
35
35
 
36
36
  @url_loader = Prawn::SVG::UrlLoader.new(
37
37
  enable_cache: options[:cache_images],
@@ -42,7 +42,7 @@ class Prawn::SVG::Document
42
42
  @sizing = Prawn::SVG::Calculators::DocumentSizing.new(bounds, @root.attributes)
43
43
  calculate_sizing(requested_width: options[:width], requested_height: options[:height])
44
44
 
45
- parse_style_elements
45
+ @element_styles = Prawn::SVG::CSS::Stylesheets.new(css_parser, root).load
46
46
 
47
47
  yield self if block_given?
48
48
  end
@@ -52,16 +52,4 @@ class Prawn::SVG::Document
52
52
  sizing.requested_height = requested_height
53
53
  sizing.calculate
54
54
  end
55
-
56
- private
57
-
58
- # <style> elements specified anywhere in the document apply to the entire
59
- # document. Because of this, we load all <style> elements before parsing
60
- # the rest of the document.
61
- def parse_style_elements
62
- REXML::XPath.match(root, '//style').each do |source|
63
- data = source.texts.map(&:value).join
64
- css_parser.add_block!(data)
65
- end
66
- end
67
55
  end
@@ -4,7 +4,7 @@ end
4
4
 
5
5
  require 'prawn/svg/elements/call_duplicator'
6
6
 
7
- %w(base depth_first_base root container viewport text text_component line polyline polygon circle ellipse rect path use image gradient marker ignored).each do |filename|
7
+ %w(base depth_first_base root container clip_path viewport text text_component line polyline polygon circle ellipse rect path use image gradient marker ignored).each do |filename|
8
8
  require "prawn/svg/elements/#{filename}"
9
9
  end
10
10
 
@@ -13,7 +13,8 @@ module Prawn::SVG::Elements
13
13
  g: Prawn::SVG::Elements::Container,
14
14
  symbol: Prawn::SVG::Elements::Container,
15
15
  defs: Prawn::SVG::Elements::Container,
16
- clipPath: Prawn::SVG::Elements::Container,
16
+ a: Prawn::SVG::Elements::Container,
17
+ clipPath: Prawn::SVG::Elements::ClipPath,
17
18
  switch: Prawn::SVG::Elements::Container,
18
19
  svg: Prawn::SVG::Elements::Viewport,
19
20
  text: Prawn::SVG::Elements::Text,
@@ -27,6 +28,7 @@ module Prawn::SVG::Elements
27
28
  use: Prawn::SVG::Elements::Use,
28
29
  image: Prawn::SVG::Elements::Image,
29
30
  linearGradient: Prawn::SVG::Elements::Gradient,
31
+ radialGradient: Prawn::SVG::Elements::Gradient,
30
32
  marker: Prawn::SVG::Elements::Marker,
31
33
  style: Prawn::SVG::Elements::Ignored, # because it is pre-parsed by Document
32
34
  title: Prawn::SVG::Elements::Ignored,
@@ -11,6 +11,8 @@ class Prawn::SVG::Elements::Base
11
11
  include Prawn::SVG::Attributes::Stroke
12
12
  include Prawn::SVG::Attributes::Space
13
13
 
14
+ include Prawn::SVG::TransformParser
15
+
14
16
  PAINT_TYPES = %w(fill stroke)
15
17
  COMMA_WSP_REGEXP = Prawn::SVG::Elements::COMMA_WSP_REGEXP
16
18
  SVG_NAMESPACE = "http://www.w3.org/2000/svg"
@@ -157,14 +159,23 @@ class Prawn::SVG::Elements::Base
157
159
  end
158
160
 
159
161
  def apply_drawing_call
160
- if !state.disable_drawing && drawable?
161
- draw_types = PAINT_TYPES.select { |property| computed_properties.send(property) != 'none' }
162
+ return if state.disable_drawing || !drawable?
163
+
164
+ fill = computed_properties.fill != 'none'
165
+ stroke = computed_properties.stroke != 'none'
162
166
 
163
- if draw_types.empty?
164
- add_call_and_enter("end_path")
167
+ if fill
168
+ command = stroke ? 'fill_and_stroke' : 'fill'
169
+
170
+ if computed_properties.fill_rule == 'evenodd'
171
+ add_call_and_enter(command, {fill_rule: :even_odd})
165
172
  else
166
- add_call_and_enter(draw_types.join("_and_"))
173
+ add_call_and_enter(command)
167
174
  end
175
+ elsif stroke
176
+ add_call_and_enter('stroke')
177
+ else
178
+ add_call_and_enter('end_path')
168
179
  end
169
180
  end
170
181
 
@@ -207,32 +218,20 @@ class Prawn::SVG::Elements::Base
207
218
  end
208
219
 
209
220
  def extract_attributes_and_properties
210
- if @document && @document.css_parser
211
- tag_style = @document.css_parser.find_by_selector(source.name)
212
- id_style = @document.css_parser.find_by_selector("##{source.attributes["id"]}") if source.attributes["id"]
213
-
214
- if classes = source.attributes["class"]
215
- class_styles = classes.split(' ').collect do |class_name|
216
- @document.css_parser.find_by_selector(".#{class_name}")
217
- end
221
+ if styles = document.element_styles[source]
222
+ # TODO : implement !important, at the moment it's just ignored
223
+ styles.each do |name, value, _important|
224
+ @properties.set(name, value)
218
225
  end
219
-
220
- element_style = source.attributes['style']
221
-
222
- style = [tag_style, class_styles, id_style, element_style].flatten.collect do |s|
223
- s.nil? || s.strip == "" ? "" : "#{s}#{";" unless s.match(/;\s*\z/)}"
224
- end.join
225
- else
226
- style = source.attributes['style'] || ""
227
226
  end
228
227
 
228
+ @properties.load_hash(parse_css_declarations(source.attributes['style'] || ''))
229
+
229
230
  source.attributes.each do |name, value|
230
231
  # Properties#set returns nil if it's not a recognised property name
231
232
  @properties.set(name, value) or @attributes[name] = value
232
233
  end
233
234
 
234
- @properties.load_hash(parse_css_declarations(style))
235
-
236
235
  state.computed_properties.compute_properties(@properties)
237
236
  end
238
237
 
@@ -268,4 +267,8 @@ class Prawn::SVG::Elements::Base
268
267
  element = document.elements_by_id[matches[1]] if matches
269
268
  element if element && (expected_type.nil? || element.name == expected_type)
270
269
  end
270
+
271
+ def href_attribute
272
+ attributes['xlink:href'] || attributes['href']
273
+ end
271
274
  end
@@ -0,0 +1,12 @@
1
+ class Prawn::SVG::Elements::ClipPath < Prawn::SVG::Elements::Base
2
+ def parse
3
+ state.inside_clip_path = true
4
+ properties.display = 'none'
5
+ computed_properties.display = 'none'
6
+ end
7
+
8
+ def container?
9
+ true
10
+ end
11
+ end
12
+
@@ -1,9 +1,7 @@
1
1
  class Prawn::SVG::Elements::Container < Prawn::SVG::Elements::Base
2
2
  def parse
3
- state.disable_drawing = true if name == 'clipPath'
4
-
5
3
  set_display_none if name == 'symbol' && !state.inside_use
6
- set_display_none if %w(defs clipPath).include?(name)
4
+ set_display_none if name == 'defs'
7
5
  end
8
6
 
9
7
  def container?
@@ -1,10 +1,18 @@
1
1
  class Prawn::SVG::Elements::Gradient < Prawn::SVG::Elements::Base
2
- TAG_NAME_TO_TYPE = {"linearGradient" => :linear}
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,26 +24,56 @@ 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?
27
+ # Passing in a transformation matrix to the apply_transformations option is supported
28
+ # by a monkey patch installed by prawn-svg. Prawn only sees this as a truthy variable.
29
+ #
30
+ # See Prawn::SVG::Extensions::AdditionalGradientTransforms for details.
31
+ base_arguments = {stops: stops, apply_transformations: transform_matrix || true}
32
+
33
+ arguments = specific_gradient_arguments(element)
34
+ arguments.merge(base_arguments) if arguments
35
+ end
23
36
 
24
- width = x2 - x1
25
- height = y1 - y2
37
+ private
26
38
 
27
- from = [x1 + width * @x1, y1 - height * @y1]
28
- to = [x1 + width * @x2, y1 - height * @y2]
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?
29
43
 
30
- when :user_space
31
- from = [@x1, @y1]
32
- to = [@x2, @y2]
44
+ width = bounding_x2 - bounding_x1
45
+ height = bounding_y1 - bounding_y2
33
46
  end
34
47
 
35
- {from: from, to: to, stops: @stops}
36
- end
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]
37
52
 
38
- private
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
39
77
 
40
78
  def type
41
79
  TAG_NAME_TO_TYPE.fetch(name)
@@ -51,10 +89,7 @@ class Prawn::SVG::Elements::Gradient < Prawn::SVG::Elements::Base
51
89
  @units = attributes["gradientUnits"] == 'userSpaceOnUse' ? :user_space : :bounding_box
52
90
 
53
91
  if transform = attributes["gradientTransform"]
54
- matrix = transform.split(COMMA_WSP_REGEXP).map(&:to_f)
55
- if matrix != [1, 0, 0, 1, 0, 0]
56
- raise SkipElementError, "prawn-svg does not yet support gradients with a non-identity gradientTransform attribute"
57
- end
92
+ @transform_matrix = parse_transform_attribute(transform)
58
93
  end
59
94
 
60
95
  if (spread_method = attributes['spreadMethod']) && spread_method != "pad"
@@ -63,18 +98,35 @@ class Prawn::SVG::Elements::Gradient < Prawn::SVG::Elements::Base
63
98
  end
64
99
 
65
100
  def load_coordinates
66
- case @units
67
- when :bounding_box
101
+ case [type, units]
102
+ when [:linear, :bounding_box]
68
103
  @x1 = parse_zero_to_one(attributes["x1"], 0)
69
104
  @y1 = parse_zero_to_one(attributes["y1"], 0)
70
105
  @x2 = parse_zero_to_one(attributes["x2"], 1)
71
106
  @y2 = parse_zero_to_one(attributes["y2"], 0)
72
107
 
73
- when :user_space
108
+ when [:linear, :user_space]
74
109
  @x1 = x(attributes["x1"])
75
110
  @y1 = y(attributes["y1"])
76
111
  @x2 = x(attributes["x2"])
77
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"
78
130
  end
79
131
  end
80
132
 
@@ -100,10 +152,16 @@ class Prawn::SVG::Elements::Gradient < Prawn::SVG::Elements::Base
100
152
  end
101
153
  end
102
154
 
103
- raise SkipElementError, "gradient does not have any valid stops" if @stops.empty?
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
104
159
 
105
- @stops.unshift([0, @stops.first.last]) if @stops.first.first > 0
106
- @stops.push([1, @stops.last.last]) if @stops.last.first < 1
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
107
165
  end
108
166
 
109
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 = 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