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
@@ -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
@@ -7,7 +7,7 @@ class Prawn::SVG::Elements::Text < Prawn::SVG::Elements::DepthFirstBase
7
7
  # of the element, delegating it to our root text component.
8
8
 
9
9
  def parse_step
10
- state.text = Prawn::SVG::Elements::TextComponent::PositionsList.new([], [], [], [], [], nil)
10
+ state.text = Prawn::SVG::Elements::TextComponent::TextState.new
11
11
 
12
12
  @text_root = Prawn::SVG::Elements::TextComponent.new(document, source, nil, state.dup)
13
13
  @text_root.parse_step
@@ -2,14 +2,22 @@ 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
- PositionsList = Struct.new(:x, :y, :dx, :dy, :rotation, :parent)
5
+ TextState = Struct.new(:parent, :x, :y, :dx, :dy, :rotation, :spacing, :mode, :text_length, :length_adjust)
6
6
 
7
7
  def parse
8
- state.text.x = attributes['x'].split(COMMA_WSP_REGEXP).collect {|n| x(n)} if attributes['x']
9
- state.text.y = attributes['y'].split(COMMA_WSP_REGEXP).collect {|n| y(n)} if attributes['y']
10
- state.text.dx = attributes['dx'].split(COMMA_WSP_REGEXP).collect {|n| x_pixels(n)} if attributes['dx']
11
- state.text.dy = attributes['dy'].split(COMMA_WSP_REGEXP).collect {|n| y_pixels(n)} if attributes['dy']
12
- state.text.rotation = attributes['rotate'].split(COMMA_WSP_REGEXP).collect(&:to_f) if attributes['rotate']
8
+ if state.inside_clip_path
9
+ raise SkipElementError, "<text> elements are not supported in clip paths"
10
+ end
11
+
12
+ state.text.x = (attributes['x'] || "").split(COMMA_WSP_REGEXP).collect { |n| x(n) }
13
+ state.text.y = (attributes['y'] || "").split(COMMA_WSP_REGEXP).collect { |n| y(n) }
14
+ state.text.dx = (attributes['dx'] || "").split(COMMA_WSP_REGEXP).collect { |n| x_pixels(n) }
15
+ state.text.dy = (attributes['dy'] || "").split(COMMA_WSP_REGEXP).collect { |n| y_pixels(n) }
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']
19
+ state.text.spacing = calculate_character_spacing
20
+ state.text.mode = calculate_text_rendering_mode
13
21
 
14
22
  @commands = []
15
23
 
@@ -38,13 +46,18 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
38
46
  opts = {
39
47
  size: computed_properties.numerical_font_size,
40
48
  style: font && font.subfamily,
41
- text_anchor: computed_properties.text_anchor
49
+ text_anchor: computed_properties.text_anchor,
42
50
  }
43
51
 
44
- spacing = computed_properties.letter_spacing
45
- spacing = spacing == 'normal' ? 0 : pixels(spacing)
52
+ opts[:decoration] = computed_properties.text_decoration unless computed_properties.text_decoration == 'none'
46
53
 
47
- add_call_and_enter 'character_spacing', spacing
54
+ if state.text.parent
55
+ add_call_and_enter 'character_spacing', state.text.spacing unless state.text.spacing == state.text.parent.spacing
56
+ add_call_and_enter 'text_rendering_mode', state.text.mode unless state.text.mode == state.text.parent.mode
57
+ else
58
+ add_call_and_enter 'character_spacing', state.text.spacing unless state.text.spacing == 0
59
+ add_call_and_enter 'text_rendering_mode', state.text.mode unless state.text.mode == :fill
60
+ end
48
61
 
49
62
  @commands.each do |command|
50
63
  case command
@@ -59,8 +72,8 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
59
72
  end
60
73
  end
61
74
 
62
- # It's possible there was no text to render. In that case, add a 'noop' so
63
- # character_spacing doesn't blow up when it finds it doesn't have a block to execute.
75
+ # It's possible there was no text to render. In that case, add a 'noop' so character_spacing/text_rendering_mode
76
+ # don't blow up when they find they don't have a block to execute.
64
77
  add_call 'noop' if calls.empty?
65
78
  end
66
79
 
@@ -81,7 +94,7 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
81
94
 
82
95
  def append_child(child)
83
96
  new_state = state.dup
84
- new_state.text = PositionsList.new([], [], [], [], [], state.text)
97
+ new_state.text = TextState.new(state.text)
85
98
 
86
99
  element = self.class.new(document, child, calls, new_state)
87
100
  @commands << element
@@ -124,6 +137,14 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
124
137
  opts.delete(:rotate)
125
138
  end
126
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
+
127
148
  if remaining
128
149
  add_call 'draw_text', text[0..0], opts.dup
129
150
  text = text[1..-1]
@@ -164,7 +185,7 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
164
185
  end
165
186
 
166
187
  def find_referenced_element
167
- href = attributes['xlink:href']
188
+ href = href_attribute
168
189
 
169
190
  if href && href[0..0] == '#'
170
191
  element = document.elements_by_id[href[1..-1]]
@@ -189,4 +210,32 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
189
210
  def apply_font(font)
190
211
  add_call 'font', font.name, style: font.subfamily
191
212
  end
213
+
214
+ def calculate_text_rendering_mode
215
+ fill = computed_properties.fill != 'none'
216
+ stroke = computed_properties.stroke != 'none'
217
+
218
+ if fill && stroke
219
+ :fill_stroke
220
+ elsif fill
221
+ :fill
222
+ elsif stroke
223
+ :stroke
224
+ else
225
+ :invisible
226
+ end
227
+ end
228
+
229
+ def calculate_character_spacing
230
+ spacing = computed_properties.letter_spacing
231
+ spacing == 'normal' ? 0 : pixels(spacing)
232
+ end
233
+
234
+ # overridden, we don't want to call fill/stroke as draw_text does this for us
235
+ def apply_drawing_call
236
+ end
237
+
238
+ def normalize_length(length)
239
+ x_pixels(length) if length && length.match(/\d/)
240
+ end
192
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
@@ -24,7 +24,7 @@ class Prawn::SVG::FontRegistry
24
24
  end
25
25
 
26
26
  def load(family, weight = nil, style = nil)
27
- Prawn::SVG::CSS.parse_font_family_string(family).detect do |name|
27
+ Prawn::SVG::CSS::FontFamilyParser.parse(family).detect do |name|
28
28
  name = name.gsub(/\s{2,}/, ' ').downcase
29
29
 
30
30
  font = Prawn::SVG::Font.new(name, weight, style, font_registry: self)
@@ -37,8 +37,9 @@ class Prawn::SVG::FontRegistry
37
37
  def merge_external_fonts
38
38
  if @font_case_mapping.nil?
39
39
  self.class.load_external_fonts unless self.class.external_font_families
40
- @font_families.merge!(self.class.external_font_families)
41
-
40
+ @font_families.merge!(self.class.external_font_families) do |key, v1, v2|
41
+ v1
42
+ end
42
43
  @font_case_mapping = @font_families.keys.each.with_object({}) do |key, result|
43
44
  result[key.downcase] = key
44
45
  end
@@ -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))
@@ -148,6 +148,19 @@ module Prawn
148
148
 
149
149
  width = prawn.width_of(text, options.merge(kerning: true))
150
150
 
151
+ if stretch_to_width = options.delete(:stretch_to_width)
152
+ factor = stretch_to_width.to_f * 100 / width.to_f
153
+ prawn.add_content "#{factor} Tz"
154
+ width = stretch_to_width.to_f
155
+ end
156
+
157
+ if pad_to_width = options.delete(:pad_to_width)
158
+ padding_required = pad_to_width.to_f - width.to_f
159
+ padding_per_character = padding_required / text.length.to_f
160
+ prawn.add_content "#{padding_per_character} Tc"
161
+ width = pad_to_width.to_f
162
+ end
163
+
151
164
  case options.delete(:text_anchor)
152
165
  when 'middle'
153
166
  at[0] -= width / 2
@@ -159,6 +172,15 @@ module Prawn
159
172
  @cursor = [at[0] + width, at[1]]
160
173
  end
161
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
+
162
184
  when 'transformation_matrix'
163
185
  left = prawn.bounds.absolute_left
164
186
  top = prawn.bounds.absolute_top
@@ -186,7 +208,9 @@ module Prawn
186
208
  # prawn (as at 2.0.1 anyway) uses 'b' for its fill_and_stroke. 'b' is 'h' (closepath) + 'B', and we
187
209
  # never want closepath to be automatically run as it stuffs up many drawing operations, such as dashes
188
210
  # and line caps, and makes paths close that we didn't ask to be closed when fill is specified.
189
- prawn.add_content 'B'
211
+ even_odd = arguments[0].is_a?(Hash) && arguments[0][:fill_rule] == :even_odd
212
+ content = even_odd ? 'B*' : 'B'
213
+ prawn.add_content content
190
214
 
191
215
  when 'noop'
192
216
  yield
@@ -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
@@ -18,6 +18,7 @@ class Prawn::SVG::Properties
18
18
  "display" => Config.new("inline", false, %w(inherit inline none), true),
19
19
  "fill" => Config.new("black", true, %w(inherit none currentColor)),
20
20
  "fill-opacity" => Config.new("1", true),
21
+ "fill-rule" => Config.new("nonzero", true, %w(inherit nonzero evenodd)),
21
22
  "font-family" => Config.new("sans-serif", true),
22
23
  "font-size" => Config.new("medium", true, %w(inherit xx-small x-small small medium large x-large xx-large larger smaller)),
23
24
  "font-style" => Config.new("normal", true, %w(inherit normal italic oblique), true),
@@ -36,6 +37,7 @@ class Prawn::SVG::Properties
36
37
  "stroke-opacity" => Config.new("1", true),
37
38
  "stroke-width" => Config.new("1", true),
38
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),
39
41
  }.freeze
40
42
 
41
43
  PROPERTIES.each do |name, value|
@@ -1,10 +1,9 @@
1
1
  class Prawn::SVG::State
2
- attr_accessor :disable_drawing,
3
- :text, :preserve_space,
2
+ attr_accessor :text, :preserve_space,
4
3
  :fill_opacity, :stroke_opacity, :stroke_width,
5
4
  :computed_properties,
6
5
  :viewport_sizing,
7
- :inside_use
6
+ :inside_use, :inside_clip_path
8
7
 
9
8
  def initialize
10
9
  @stroke_width = 1
@@ -16,4 +15,8 @@ class Prawn::SVG::State
16
15
  def initialize_dup(other)
17
16
  @computed_properties = @computed_properties.dup
18
17
  end
18
+
19
+ def disable_drawing
20
+ inside_clip_path
21
+ end
19
22
  end
@@ -0,0 +1,72 @@
1
+ module Prawn::SVG::TransformParser
2
+ def parse_transform_attribute(transform)
3
+ matrix = Matrix.identity(3)
4
+
5
+ parse_css_method_calls(transform).each do |name, arguments|
6
+ case name
7
+ when 'translate'
8
+ x, y = arguments
9
+ matrix *= Matrix[[1, 0, x_pixels(x.to_f)], [0, 1, -y_pixels(y.to_f)], [0, 0, 1]]
10
+
11
+ when 'translateX'
12
+ x = arguments.first
13
+ matrix *= Matrix[[1, 0, x_pixels(x.to_f)], [0, 1, 0], [0, 0, 1]]
14
+
15
+ when 'translateY'
16
+ y = arguments.first
17
+ matrix *= Matrix[[1, 0, 0], [0, 1, -y_pixels(y.to_f)], [0, 0, 1]]
18
+
19
+ when 'rotate'
20
+ angle, x, y = arguments.collect { |a| a.to_f }
21
+ angle = angle * Math::PI / 180.0
22
+
23
+ case arguments.length
24
+ when 1
25
+ matrix *= Matrix[[Math.cos(angle), Math.sin(angle), 0], [-Math.sin(angle), Math.cos(angle), 0], [0, 0, 1]]
26
+ when 3
27
+ matrix *= Matrix[[1, 0, x_pixels(x.to_f)], [0, 1, -y_pixels(y.to_f)], [0, 0, 1]]
28
+ matrix *= Matrix[[Math.cos(angle), Math.sin(angle), 0], [-Math.sin(angle), Math.cos(angle), 0], [0, 0, 1]]
29
+ matrix *= Matrix[[1, 0, -x_pixels(x.to_f)], [0, 1, y_pixels(y.to_f)], [0, 0, 1]]
30
+ else
31
+ warnings << "transform 'rotate' must have either one or three arguments"
32
+ end
33
+
34
+ when 'scale'
35
+ x_scale = arguments[0].to_f
36
+ y_scale = (arguments[1] || x_scale).to_f
37
+ matrix *= Matrix[[x_scale, 0, 0], [0, y_scale, 0], [0, 0, 1]]
38
+
39
+ when 'skewX'
40
+ angle = arguments[0].to_f * Math::PI / 180.0
41
+ matrix *= Matrix[[1, -Math.tan(angle), 0], [0, 1, 0], [0, 0, 1]]
42
+
43
+ when 'skewY'
44
+ angle = arguments[0].to_f * Math::PI / 180.0
45
+ matrix *= Matrix[[1, 0, 0], [-Math.tan(angle), 1, 0], [0, 0, 1]]
46
+
47
+ when 'matrix'
48
+ if arguments.length != 6
49
+ warnings << "transform 'matrix' must have six arguments"
50
+ else
51
+ a, b, c, d, e, f = arguments.collect { |argument| argument.to_f }
52
+ matrix *= Matrix[[a, -c, e], [-b, d, -f], [0, 0, 1]]
53
+ end
54
+
55
+ else
56
+ warnings << "Unknown/unsupported transformation '#{name}'; ignoring"
57
+ end
58
+ end
59
+
60
+ matrix.to_a[0..1].transpose.flatten
61
+ end
62
+
63
+ private
64
+
65
+ def parse_css_method_calls(string)
66
+ string.scan(/\s*(\w+)\(([^)]+)\)\s*/).collect do |call|
67
+ name, argument_string = call
68
+ arguments = argument_string.strip.split(/\s*[,\s]\s*/)
69
+ [name, arguments]
70
+ end
71
+ end
72
+ end
@@ -1,5 +1,5 @@
1
1
  module Prawn
2
2
  module SVG
3
- VERSION = '0.27.1'
3
+ VERSION = '0.31.0'
4
4
  end
5
5
  end