prawn-svg 0.29.1 → 0.32.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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +18 -0
  3. data/LICENSE +1 -1
  4. data/README.md +10 -9
  5. data/lib/prawn/svg/attributes/opacity.rb +4 -4
  6. data/lib/prawn/svg/attributes/transform.rb +2 -44
  7. data/lib/prawn/svg/elements/base.rb +11 -5
  8. data/lib/prawn/svg/elements/call_duplicator.rb +1 -1
  9. data/lib/prawn/svg/elements/gradient.rb +83 -25
  10. data/lib/prawn/svg/elements/image.rb +2 -2
  11. data/lib/prawn/svg/elements/path.rb +42 -29
  12. data/lib/prawn/svg/elements/root.rb +4 -1
  13. data/lib/prawn/svg/elements/text_component.rb +21 -5
  14. data/lib/prawn/svg/elements/use.rb +23 -7
  15. data/lib/prawn/svg/elements.rb +2 -0
  16. data/lib/prawn/svg/extensions/additional_gradient_transforms.rb +23 -0
  17. data/lib/prawn/svg/interface.rb +38 -8
  18. data/lib/prawn/svg/loaders/data.rb +1 -1
  19. data/lib/prawn/svg/loaders/file.rb +3 -1
  20. data/lib/prawn/svg/properties.rb +1 -0
  21. data/lib/prawn/svg/transform_parser.rb +72 -0
  22. data/lib/prawn/svg/version.rb +1 -1
  23. data/lib/prawn-svg.rb +4 -0
  24. data/prawn-svg.gemspec +3 -2
  25. data/spec/integration_spec.rb +24 -24
  26. data/spec/prawn/svg/attributes/opacity_spec.rb +85 -0
  27. data/spec/prawn/svg/attributes/transform_spec.rb +30 -35
  28. data/spec/prawn/svg/css/stylesheets_spec.rb +1 -19
  29. data/spec/prawn/svg/elements/base_spec.rb +16 -16
  30. data/spec/prawn/svg/elements/gradient_spec.rb +79 -4
  31. data/spec/prawn/svg/elements/line_spec.rb +12 -12
  32. data/spec/prawn/svg/elements/marker_spec.rb +27 -27
  33. data/spec/prawn/svg/elements/path_spec.rb +29 -17
  34. data/spec/prawn/svg/elements/polygon_spec.rb +9 -9
  35. data/spec/prawn/svg/elements/polyline_spec.rb +7 -7
  36. data/spec/prawn/svg/elements/text_spec.rb +65 -50
  37. data/spec/prawn/svg/loaders/data_spec.rb +8 -0
  38. data/spec/prawn/svg/pathable_spec.rb +4 -4
  39. data/spec/prawn/svg/transform_parser_spec.rb +94 -0
  40. data/spec/sample_svg/double_opacity.svg +6 -0
  41. data/spec/sample_svg/gradient_transform.svg +19 -0
  42. data/spec/sample_svg/links.svg +18 -0
  43. data/spec/sample_svg/radgrad01-bounding.svg +26 -0
  44. data/spec/sample_svg/radgrad01.svg +26 -0
  45. data/spec/sample_svg/svg_fill.svg +5 -0
  46. data/spec/sample_svg/text-decoration.svg +4 -0
  47. data/spec/sample_svg/transform.svg +20 -0
  48. data/spec/sample_svg/use_disordered.svg +17 -0
  49. data/spec/spec_helper.rb +2 -2
  50. metadata +48 -10
  51. data/.travis.yml +0 -8
@@ -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"
@@ -13,6 +13,7 @@ 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
+ a: Prawn::SVG::Elements::Container,
16
17
  clipPath: Prawn::SVG::Elements::ClipPath,
17
18
  switch: Prawn::SVG::Elements::Container,
18
19
  svg: Prawn::SVG::Elements::Viewport,
@@ -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,
@@ -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)
@@ -37,6 +37,7 @@ class Prawn::SVG::Properties
37
37
  "stroke-opacity" => Config.new("1", true),
38
38
  "stroke-width" => Config.new("1", true),
39
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),
40
41
  }.freeze
41
42
 
42
43
  PROPERTIES.each do |name, value|
@@ -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.29.1'
3
+ VERSION = '0.32.0'
4
4
  end
5
5
  end
data/lib/prawn-svg.rb CHANGED
@@ -10,6 +10,7 @@ require 'prawn/svg/calculators/arc_to_bezier_curve'
10
10
  require 'prawn/svg/calculators/aspect_ratio'
11
11
  require 'prawn/svg/calculators/document_sizing'
12
12
  require 'prawn/svg/calculators/pixels'
13
+ require 'prawn/svg/transform_parser'
13
14
  require 'prawn/svg/url_loader'
14
15
  require 'prawn/svg/loaders/data'
15
16
  require 'prawn/svg/loaders/file'
@@ -29,6 +30,9 @@ require 'prawn/svg/font'
29
30
  require 'prawn/svg/document'
30
31
  require 'prawn/svg/state'
31
32
 
33
+ require 'prawn/svg/extensions/additional_gradient_transforms'
34
+ Prawn::Document.prepend Prawn::SVG::Extensions::AdditionalGradientTransforms
35
+
32
36
  module Prawn
33
37
  Svg = SVG # backwards compatibility
34
38
  end
data/prawn-svg.gemspec CHANGED
@@ -17,10 +17,11 @@ Gem::Specification.new do |gem|
17
17
  gem.name = "prawn-svg"
18
18
  gem.require_paths = ["lib"]
19
19
 
20
- gem.required_ruby_version = '>= 2.1.0'
20
+ gem.required_ruby_version = '>= 2.3.0'
21
21
 
22
22
  gem.add_runtime_dependency "prawn", ">= 0.11.1", "< 3"
23
23
  gem.add_runtime_dependency "css_parser", "~> 1.6"
24
+ gem.add_runtime_dependency "rexml", "~> 3.2"
24
25
  gem.add_development_dependency "rspec", "~> 3.0"
25
- gem.add_development_dependency "rake", "~> 10.1"
26
+ gem.add_development_dependency "rake", "~> 13.0"
26
27
  end
@@ -28,34 +28,34 @@ describe "Integration test" do
28
28
  element.process
29
29
 
30
30
  expect(element.calls).to eq [
31
- ["fill_color", ["000000"], []],
32
- ["transformation_matrix", [1, 0, 0, 1, 0, 0], []],
33
- ["transformation_matrix", [1, 0, 0, 1, 0, 0], []],
34
- ["save", [], []], ["restore", [], []],
35
- ["save", [], []],
36
- ["fill_color", ["0000ff"], []],
37
- ["fill", [], [
38
- ["rectangle", [[0.0, 200.0], 10.0, 10.0], []]
31
+ ["fill_color", ["000000"], {}, []],
32
+ ["transformation_matrix", [1, 0, 0, 1, 0, 0], {}, []],
33
+ ["transformation_matrix", [1, 0, 0, 1, 0, 0], {}, []],
34
+ ["save", [], {}, []], ["restore", [], {}, []],
35
+ ["save", [], {}, []],
36
+ ["fill_color", ["0000ff"], {}, []],
37
+ ["fill", [], {}, [
38
+ ["rectangle", [[0.0, 200.0], 10.0, 10.0], {}, []]
39
39
  ]],
40
- ["restore", [], []],
41
- ["save", [], []],
42
- ["fill_color", ["008000"], []],
43
- ["fill", [], [
44
- ["rectangle", [[10.0, 200.0], 10.0, 10.0], []]
40
+ ["restore", [], {}, []],
41
+ ["save", [], {}, []],
42
+ ["fill_color", ["008000"], {}, []],
43
+ ["fill", [], {}, [
44
+ ["rectangle", [[10.0, 200.0], 10.0, 10.0], {}, []]
45
45
  ]],
46
- ["restore", [], []],
47
- ["save", [], []],
48
- ["fill_color", ["ff0000"], []],
49
- ["fill", [], [
50
- ["rectangle", [[20.0, 200.0], 10.0, 10.0], []]
46
+ ["restore", [], {}, []],
47
+ ["save", [], {}, []],
48
+ ["fill_color", ["ff0000"], {}, []],
49
+ ["fill", [], {}, [
50
+ ["rectangle", [[20.0, 200.0], 10.0, 10.0], {}, []]
51
51
  ]],
52
- ["restore", [], []],
53
- ["save", [], []],
54
- ["fill_color", ["ffff00"], []],
55
- ["fill", [], [
56
- ["rectangle", [[30.0, 200.0], 10.0, 10.0], []]
52
+ ["restore", [], {}, []],
53
+ ["save", [], {}, []],
54
+ ["fill_color", ["ffff00"], {}, []],
55
+ ["fill", [], {}, [
56
+ ["rectangle", [[30.0, 200.0], 10.0, 10.0], {}, []]
57
57
  ]],
58
- ["restore", [], []]
58
+ ["restore", [], {}, []]
59
59
  ]
60
60
  end
61
61
  end
@@ -0,0 +1,85 @@
1
+ require 'spec_helper'
2
+
3
+ describe Prawn::SVG::Attributes::Opacity do
4
+ class OpacityTestElement
5
+ include Prawn::SVG::Attributes::Opacity
6
+
7
+ attr_accessor :properties, :state
8
+
9
+ def initialize
10
+ @properties = ::Prawn::SVG::Properties.new
11
+ @state = ::Prawn::SVG::State.new
12
+ end
13
+
14
+ def clamp(value, min_value, max_value)
15
+ [[value, min_value].max, max_value].min
16
+ end
17
+ end
18
+
19
+ let(:element) { OpacityTestElement.new }
20
+
21
+ describe "#parse_opacity_attributes_and_call" do
22
+ subject { element.parse_opacity_attributes_and_call }
23
+
24
+ context "with no opacity specified" do
25
+ it "does nothing" do
26
+ expect(element).not_to receive(:add_call_and_enter)
27
+ subject
28
+ end
29
+ end
30
+
31
+ context "with opacity" do
32
+ it "sets fill and stroke opacity" do
33
+ element.properties.opacity = '0.4'
34
+
35
+ expect(element).to receive(:add_call_and_enter).with('transparent', 0.4, 0.4)
36
+ subject
37
+
38
+ expect(element.state.fill_opacity).to eq 0.4
39
+ expect(element.state.stroke_opacity).to eq 0.4
40
+ end
41
+ end
42
+
43
+ context "with just fill opacity" do
44
+ it "sets fill opacity and sets stroke opacity to 1" do
45
+ element.properties.fill_opacity = '0.4'
46
+
47
+ expect(element).to receive(:add_call_and_enter).with('transparent', 0.4, 1)
48
+ subject
49
+
50
+ expect(element.state.fill_opacity).to eq 0.4
51
+ expect(element.state.stroke_opacity).to eq 1
52
+ end
53
+ end
54
+
55
+ context "with an existing fill/stroke opacity" do
56
+ it "multiplies the new opacity by the old" do
57
+ element.state.fill_opacity = 0.5
58
+ element.state.stroke_opacity = 0.8
59
+
60
+ element.properties.fill_opacity = '0.4'
61
+ element.properties.stroke_opacity = '0.5'
62
+
63
+ expect(element).to receive(:add_call_and_enter).with('transparent', 0.2, 0.4)
64
+ subject
65
+
66
+ expect(element.state.fill_opacity).to eq 0.2
67
+ expect(element.state.stroke_opacity).to eq 0.4
68
+ end
69
+ end
70
+
71
+ context "with stroke, fill, and opacity all specified" do
72
+ it "choses the lower of them" do
73
+ element.properties.fill_opacity = '0.4'
74
+ element.properties.stroke_opacity = '0.6'
75
+ element.properties.opacity = '0.5'
76
+
77
+ expect(element).to receive(:add_call_and_enter).with('transparent', 0.4, 0.5)
78
+ subject
79
+
80
+ expect(element.state.fill_opacity).to eq 0.4
81
+ expect(element.state.stroke_opacity).to eq 0.5
82
+ end
83
+ end
84
+ end
85
+ end
@@ -14,43 +14,38 @@ describe Prawn::SVG::Attributes::Transform do
14
14
 
15
15
  let(:element) { TransformTestElement.new }
16
16
 
17
- describe "#parse_transform_attribute_and_call" do
18
- subject { element.send :parse_transform_attribute_and_call }
19
-
20
- describe "translate" do
21
- it "handles a missing y argument" do
22
- expect(element).to receive(:add_call_and_enter).with('translate', -5.5, 0)
23
- expect(element).to receive(:x_pixels).with(-5.5).and_return(-5.5)
24
- expect(element).to receive(:y_pixels).with(0.0).and_return(0.0)
25
-
26
- element.attributes['transform'] = 'translate(-5.5)'
27
- subject
28
- end
17
+ subject { element.send :parse_transform_attribute_and_call }
18
+
19
+ context "when a non-identity matrix is requested" do
20
+ let(:transform) { 'translate(-5.5)' }
21
+
22
+ it "passes the transform and executes the returned matrix" do
23
+ expect(element).to receive(:parse_transform_attribute).with(transform).and_return([1, 2, 3, 4, 5, 6])
24
+ expect(element).to receive(:add_call_and_enter).with('transformation_matrix', 1, 2, 3, 4, 5, 6)
25
+
26
+ element.attributes['transform'] = transform
27
+ subject
28
+ end
29
+ end
30
+
31
+ context "when an identity matrix is requested" do
32
+ let(:transform) { 'translate(0)' }
33
+
34
+ it "does not execute any commands" do
35
+ expect(element).to receive(:parse_transform_attribute).with(transform).and_return([1, 0, 0, 1, 0, 0])
36
+ expect(element).not_to receive(:add_call_and_enter)
37
+
38
+ element.attributes['transform'] = transform
39
+ subject
29
40
  end
41
+ end
42
+
43
+ context "when transform is blank" do
44
+ it "does nothing" do
45
+ expect(element).not_to receive(:parse_transform_attribute)
46
+ expect(element).not_to receive(:add_call_and_enter)
30
47
 
31
- describe "rotate" do
32
- it "handles a single angle argument" do
33
- expect(element).to receive(:add_call_and_enter).with('rotate', -5.5, :origin => [0, 0])
34
- expect(element).to receive(:y).with('0').and_return(0)
35
-
36
- element.attributes['transform'] = 'rotate(5.5)'
37
- subject
38
- end
39
-
40
- it "handles three arguments" do
41
- expect(element).to receive(:add_call_and_enter).with('rotate', -5.5, :origin => [1.0, 2.0])
42
- expect(element).to receive(:x).with(1.0).and_return(1.0)
43
- expect(element).to receive(:y).with(2.0).and_return(2.0)
44
-
45
- element.attributes['transform'] = 'rotate(5.5 1 2)'
46
- subject
47
- end
48
-
49
- it "does nothing and warns if two arguments" do
50
- expect(element).to receive(:warnings).and_return([])
51
- element.attributes['transform'] = 'rotate(5.5 1)'
52
- subject
53
- end
48
+ subject
54
49
  end
55
50
  end
56
51
  end
@@ -68,25 +68,7 @@ RSpec.describe Prawn::SVG::CSS::Stylesheets do
68
68
  [4, [["fill", "#ff0000", false], ["fill", "#330000", false], ["fill", "#440000", false], ["fill", "#00ff00", false]]],
69
69
  ]
70
70
 
71
- #
72
- # Under ruby < 2.6, a bug in REXML causes the /following-sibling selector to
73
- # only pick the first matching sibling. This means the + CSS combinator behaves
74
- # incorrectly in the following example:
75
- #
76
- # <a>
77
- # <b a="1" />
78
- # <b a="2" />
79
- # <b a="3" />
80
- # </a>
81
- #
82
- # The css selector `a b + b` will only pick the second <b>, whereas it should
83
- # pick both the second and third <b> elements.
84
- #
85
- if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.6.0')
86
- expected << [5, [["fill", "#ff0000", false], ["fill", "#330000", false], ["fill", "#330000", false], ["fill", "#00ff00", false]]]
87
- else
88
- expected << [5, [["fill", "#ff0000", false], ["fill", "#330000", false], ["fill", "#330000", false], ["fill", "#440000", false], ["fill", "#00ff00", false]]]
89
- end
71
+ expected << [5, [["fill", "#ff0000", false], ["fill", "#330000", false], ["fill", "#330000", false], ["fill", "#440000", false], ["fill", "#00ff00", false]]]
90
72
 
91
73
  expected.concat [
92
74
  [6, [["fill", "#ff0000", false], ["fill", "#441234", false], ["fill", "#0000ff", false]]],
@@ -25,19 +25,19 @@ describe Prawn::SVG::Elements::Base do
25
25
  describe "applying calls from the standard attributes" do
26
26
  let(:svg) do
27
27
  <<-SVG
28
- <something transform="rotate(90)" fill-opacity="0.5" fill="red" stroke="blue" stroke-width="5"/>
28
+ <something transform="scale(2)" fill-opacity="0.5" fill="red" stroke="blue" stroke-width="5"/>
29
29
  SVG
30
30
  end
31
31
 
32
32
  it "appends the relevant calls" do
33
33
  element.process
34
34
  expect(element.base_calls).to eq [
35
- ["rotate", [-90.0, {origin: [0, 600.0]}], [
36
- ["transparent", [0.5, 1], [
37
- ["fill_color", ["ff0000"], []],
38
- ["stroke_color", ["0000ff"], []],
39
- ["line_width", [5.0], []],
40
- ["fill_and_stroke", [], []]
35
+ ["transformation_matrix", [2, 0, 0, 2, 0, 0], {}, [
36
+ ["transparent", [0.5, 1], {}, [
37
+ ["fill_color", ["ff0000"], {}, []],
38
+ ["stroke_color", ["0000ff"], {}, []],
39
+ ["line_width", [5.0], {}, []],
40
+ ["fill_and_stroke", [], {}, []]
41
41
  ]]
42
42
  ]]
43
43
  ]
@@ -50,37 +50,37 @@ describe Prawn::SVG::Elements::Base do
50
50
 
51
51
  context "with neither fill nor stroke" do
52
52
  let(:svg) { '<rect style="fill: none;"></rect>' }
53
- it { is_expected.to eq ['end_path', [], []] }
53
+ it { is_expected.to eq ['end_path', [], {}, []] }
54
54
  end
55
55
 
56
56
  context "with a fill only" do
57
57
  let(:svg) { '<rect style="fill: black;"></rect>' }
58
- it { is_expected.to eq ['fill', [], []] }
58
+ it { is_expected.to eq ['fill', [], {}, []] }
59
59
  end
60
60
 
61
61
  context "with a stroke only" do
62
62
  let(:svg) { '<rect style="fill: none; stroke: black;"></rect>' }
63
- it { is_expected.to eq ['stroke', [], []] }
63
+ it { is_expected.to eq ['stroke', [], {}, []] }
64
64
  end
65
65
 
66
66
  context "with fill and stroke" do
67
67
  let(:svg) { '<rect style="fill: black; stroke: black;"></rect>' }
68
- it { is_expected.to eq ['fill_and_stroke', [], []] }
68
+ it { is_expected.to eq ['fill_and_stroke', [], {}, []] }
69
69
  end
70
70
 
71
71
  context "with fill with evenodd fill rule" do
72
72
  let(:svg) { '<rect style="fill: black; fill-rule: evenodd;"></rect>' }
73
- it { is_expected.to eq ['fill', [{fill_rule: :even_odd}], []] }
73
+ it { is_expected.to eq ['fill', [], {fill_rule: :even_odd}, []] }
74
74
  end
75
75
  end
76
76
 
77
77
  it "appends calls to the parent element" do
78
78
  expect(element).to receive(:apply) do
79
- element.send :add_call, "test", "argument"
79
+ element.send :add_call, "test", "argument", kw: 'argument'
80
80
  end
81
81
 
82
82
  element.process
83
- expect(element.parent_calls).to eq [["fill", [], [["test", ["argument"], []]]]]
83
+ expect(element.parent_calls).to eq [["fill", [], {}, [["test", ["argument"], {kw: 'argument'}, []]]]]
84
84
  end
85
85
 
86
86
  it "quietly absorbs a SkipElementQuietly exception" do
@@ -149,8 +149,8 @@ describe Prawn::SVG::Elements::Base do
149
149
  it "uses the parent's color element if 'currentColor' fill attribute provided" do
150
150
  element.process
151
151
 
152
- expect(flattened_calls).to include ['fill_color', ['ff0000']]
153
- expect(flattened_calls).not_to include ['fill_color', ['00ff00']]
152
+ expect(flattened_calls).to include ['fill_color', ['ff0000'], {}]
153
+ expect(flattened_calls).not_to include ['fill_color', ['00ff00'], {}]
154
154
  end
155
155
  end
156
156