prawn-svg 0.27.1 → 0.31.0

Sign up to get free protection for your applications and to get access to all the features.
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