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
@@ -9,7 +9,7 @@ describe Prawn::SVG::Elements::Gradient do
9
9
  element.process
10
10
  end
11
11
 
12
- describe "object bounding box" do
12
+ describe "object bounding box with linear gradient" do
13
13
  let(:svg) do
14
14
  <<-SVG
15
15
  <linearGradient id="flag" x1="0" x2="0.2" y1="0" y2="1">
@@ -29,7 +29,8 @@ describe Prawn::SVG::Elements::Gradient do
29
29
  expect(arguments).to eq(
30
30
  from: [100.0, 100.0],
31
31
  to: [120.0, 0.0],
32
- stops: [[0, "ff0000"], [0.25, "ff0000"], [0.5, "ffffff"], [0.75, "0000ff"], [1, "0000ff"]]
32
+ stops: [[0, "ff0000"], [0.25, "ff0000"], [0.5, "ffffff"], [0.75, "0000ff"], [1, "0000ff"]],
33
+ apply_transformations: true,
33
34
  )
34
35
  end
35
36
 
@@ -39,7 +40,35 @@ describe Prawn::SVG::Elements::Gradient do
39
40
  end
40
41
  end
41
42
 
42
- describe "user space on use" do
43
+ describe "object bounding box with radial gradient" do
44
+ let(:svg) do
45
+ <<-SVG
46
+ <radialGradient id="flag" cx="0" cy="0.2" fx="0.5" r="0.8">
47
+ <stop offset="25%" stop-color="red"/>
48
+ <stop offset="50%" stop-color="white"/>
49
+ <stop offset="75%" stop-color="blue"/>
50
+ </radialGradient>
51
+ SVG
52
+ end
53
+
54
+ it "is stored in the document gradients table" do
55
+ expect(document.gradients["flag"]).to eq element
56
+ end
57
+
58
+ it "returns correct gradient arguments for an element" do
59
+ arguments = element.gradient_arguments(double(bounding_box: [100, 100, 200, 0]))
60
+ expect(arguments).to eq(
61
+ from: [150, 80],
62
+ to: [100, 80],
63
+ r1: 0,
64
+ r2: Math.sqrt((0.8 * 100) ** 2 + (0.8 * 100) ** 2),
65
+ stops: [[0, "ff0000"], [0.25, "ff0000"], [0.5, "ffffff"], [0.75, "0000ff"], [1, "0000ff"]],
66
+ apply_transformations: true,
67
+ )
68
+ end
69
+ end
70
+
71
+ describe "user space on use with linear gradient" do
43
72
  let(:svg) do
44
73
  <<-SVG
45
74
  <linearGradient id="flag" gradientUnits="userSpaceOnUse" x1="100" y1="500" x2="200" y2="600">
@@ -54,7 +83,53 @@ describe Prawn::SVG::Elements::Gradient do
54
83
  expect(arguments).to eq(
55
84
  from: [100.0, 100.0],
56
85
  to: [200.0, 0.0],
57
- stops: [[0, "ff0000"], [1, "0000ff"]]
86
+ stops: [[0, "ff0000"], [1, "0000ff"]],
87
+ apply_transformations: true,
88
+ )
89
+ end
90
+ end
91
+
92
+ describe "user space on use with radial gradient" do
93
+ let(:svg) do
94
+ <<-SVG
95
+ <radialGradient id="flag" gradientUnits="userSpaceOnUse" fx="100" fy="500" cx="200" cy="600" r="150">
96
+ <stop offset="0" stop-color="red"/>
97
+ <stop offset="1" stop-color="blue"/>
98
+ </radialGradient>
99
+ SVG
100
+ end
101
+
102
+ it "returns correct gradient arguments for an element" do
103
+ arguments = element.gradient_arguments(double)
104
+ expect(arguments).to eq(
105
+ from: [100.0, 100.0],
106
+ to: [200.0, 0.0],
107
+ r1: 0,
108
+ r2: 150,
109
+ stops: [[0, "ff0000"], [1, "0000ff"]],
110
+ apply_transformations: true,
111
+ )
112
+ end
113
+ end
114
+
115
+ context "when gradientTransform is specified" do
116
+ let(:svg) do
117
+ <<-SVG
118
+ <linearGradient id="flag" gradientTransform="translateX(10) scale(2)" x1="0" y1="0" x2="10" y2="10">
119
+ <stop offset="0" stop-color="red"/>
120
+ <stop offset="1" stop-color="blue"/>
121
+ </linearGradient>
122
+ SVG
123
+ end
124
+
125
+ it "passes in the transform via the apply_transformations option" do
126
+ arguments = element.gradient_arguments(double(bounding_box: [0, 0, 10, 10]))
127
+
128
+ expect(arguments).to eq(
129
+ from: [0, 0],
130
+ to: [10, 10],
131
+ stops: [[0, "ff0000"], [1, "0000ff"]],
132
+ apply_transformations: [2, 0, 0, 2, 10, 0],
58
133
  )
59
134
  end
60
135
  end
@@ -11,22 +11,22 @@ describe Prawn::SVG::Elements::Path do
11
11
 
12
12
  describe "command parsing" do
13
13
  context "with a valid path" do
14
- let(:d) { "A12.34 -56.78 89B4 5 12-34 -.5.7+3 2.3e3 4e4 4e+4 c31,-2e-5C 6,7 T QX 0 Z" }
14
+ let(:d) { "m12.34 -56.78 1 2M4 5 12-34 -.5.7+3 2.3e3 4e4 4e+4 L31,-2e-5L 6,7 Z ZZa50 50 0 100 100" }
15
15
 
16
16
  it "correctly parses" do
17
17
  calls = []
18
- path.stub(:parse_path_command) {|*args| calls << args}
18
+ allow(path).to receive(:parse_path_command) {|*args| calls << args}
19
19
  path.parse
20
20
 
21
- calls.should == [
22
- ["A", [12.34, -56.78, 89]],
23
- ["B", [4, 5, 12, -34, -0.5, 0.7, 3, 2.3e3, 4e4, 4e4]],
24
- ["c", [31, -2e-5]],
25
- ["C", [6, 7]],
26
- ["T", []],
27
- ["Q", []],
28
- ["X", [0]],
29
- ["Z", []]
21
+ expect(calls).to eq [
22
+ ["m", [[12.34, -56.78], [1, 2]]],
23
+ ["M", [[4, 5], [12, -34], [-0.5, 0.7], [3, 2.3e3], [4e4, 4e4]]],
24
+ ["L", [[31, -2e-5]]],
25
+ ["L", [[6, 7]]],
26
+ ["Z", []],
27
+ ["Z", []],
28
+ ["Z", []],
29
+ ["a", [[50, 50, 0, 1, 0, 0, 100]]],
30
30
  ]
31
31
  end
32
32
  end
@@ -36,12 +36,12 @@ describe Prawn::SVG::Elements::Path do
36
36
 
37
37
  it "treats subsequent points to m/M command as relative/absolute depending on command" do
38
38
  [
39
- ["M", [1,2,3,4]],
40
- ["L", [3,4]],
41
- ["m", [5,6,7,8]],
42
- ["l", [7,8]]
39
+ ["M", [[1,2],[3,4]]],
40
+ ["L", [[3,4]]],
41
+ ["m", [[5,6],[7,8]]],
42
+ ["l", [[7,8]]]
43
43
  ].each do |args|
44
- path.should_receive(:parse_path_command).with(*args).and_call_original
44
+ expect(path).to receive(:parse_path_command).with(*args).and_call_original
45
45
  end
46
46
 
47
47
  path.parse
@@ -52,7 +52,7 @@ describe Prawn::SVG::Elements::Path do
52
52
  let(:d) { "" }
53
53
 
54
54
  it "correctly parses" do
55
- path.should_not_receive(:run_path_command)
55
+ expect(path).not_to receive(:run_path_command)
56
56
  path.parse
57
57
  end
58
58
  end
@@ -292,5 +292,17 @@ describe Prawn::SVG::Elements::Path do
292
292
  ]
293
293
  end
294
294
  end
295
+
296
+ context "with highly-compressed flags" do
297
+ let(:d) { "M100,100a50 50 0 100 100" }
298
+
299
+ it "correctly parses them" do
300
+ expect(subject).to eq [
301
+ Prawn::SVG::Elements::Path::Move.new([100.0, 100.0]),
302
+ Prawn::SVG::Elements::Path::Curve.new([50.0, 150.0], [72.57081148225681, 100.0], [50.0, 122.57081148225681]),
303
+ Prawn::SVG::Elements::Path::Curve.new([99.99999999999999, 200.0], [50.0, 177.42918851774317], [72.5708114822568, 200.0])
304
+ ]
305
+ end
306
+ end
295
307
  end
296
308
  end
@@ -4,6 +4,8 @@ describe Prawn::SVG::Elements::Text do
4
4
  let(:document) { Prawn::SVG::Document.new(svg, [800, 600], {}, font_registry: Prawn::SVG::FontRegistry.new("Helvetica" => {:normal => nil}, "Courier" => {normal: nil}, 'Times-Roman' => {normal: nil})) }
5
5
  let(:element) { Prawn::SVG::Elements::Text.new(document, document.root, [], fake_state) }
6
6
 
7
+ let(:default_style) { {:size=>16, :style=>:normal, :text_anchor=>'start', :at=>[:relative, :relative], :offset=>[0,0]} }
8
+
7
9
  describe "xml:space preserve" do
8
10
  let(:svg) { %(<text#{attributes}>some\n\t text</text>) }
9
11
 
@@ -77,17 +79,81 @@ Even more
77
79
 
78
80
  expect(element.base_calls).to eq [
79
81
  ["text_group", [], [
80
- ["fill", [], [
81
- ["font", ["Helvetica", {style: :normal}], []],
82
- ["character_spacing", [5.0], [
83
- ["draw_text", ["spaced", {:size=>16, :style=>:normal, :text_anchor=>'start', :at=>[:relative, :relative], :offset=>[0,0]}], []]
84
- ]]
82
+ ["font", ["Helvetica", {style: :normal}], []],
83
+ ["character_spacing", [5.0], [
84
+ ["draw_text", ["spaced", default_style], []]
85
85
  ]]
86
86
  ]]
87
87
  ]
88
88
  end
89
89
  end
90
90
 
91
+ describe "underline" do
92
+ let(:svg) { '<text text-decoration="underline">underlined</text>' }
93
+
94
+ it "marks the element to be underlined" do
95
+ element.process
96
+
97
+ expect(element.base_calls).to eq [
98
+ ["text_group", [], [
99
+ ["font", ["Helvetica", {:style=>:normal}], []],
100
+ ["draw_text", ["underlined", default_style.merge(decoration: 'underline')], []]
101
+ ]]
102
+ ]
103
+ end
104
+ end
105
+
106
+ describe "fill/stroke modes" do
107
+ context "with a stroke and no fill" do
108
+ let(:svg) { '<text stroke="red" fill="none">stroked</text>' }
109
+
110
+ it "calls text_rendering_mode with the requested options" do
111
+ element.process
112
+
113
+ expect(element.base_calls).to eq [
114
+ ["text_group", [], [
115
+ ["stroke_color", ["ff0000"], []],
116
+ ["font", ["Helvetica", {style: :normal}], []],
117
+ ["text_rendering_mode", [:stroke], [
118
+ ["draw_text", ["stroked", default_style], []]
119
+ ]]
120
+ ]]
121
+ ]
122
+ end
123
+ end
124
+
125
+ context "with a mixture of everything" do
126
+ let(:svg) { '<text stroke="red" fill="none">stroked <tspan fill="black">both</tspan><tspan stroke="none">neither</tspan></text>' }
127
+
128
+ it "calls text_rendering_mode with the requested options" do
129
+ element.process
130
+
131
+ expect(element.base_calls).to eq [
132
+ ["text_group", [], [
133
+ ["stroke_color", ["ff0000"], []],
134
+ ["font", ["Helvetica", {style: :normal}], []],
135
+ ["text_rendering_mode", [:stroke], [
136
+ ["draw_text", ["stroked ", default_style], []],
137
+ ["save", [], []],
138
+ ["fill_color", ["000000"], []],
139
+ ["font", ["Helvetica", {style: :normal}], []],
140
+ ["text_rendering_mode", [:fill_stroke], [
141
+ ["draw_text", ["both", default_style], []]
142
+ ]],
143
+ ["restore", [], []],
144
+ ["save", [], []],
145
+ ["font", ["Helvetica", {style: :normal}], []],
146
+ ["text_rendering_mode", [:invisible], [
147
+ ["draw_text", ["neither", default_style], []]
148
+ ]],
149
+ ["restore", [], []],
150
+ ]]
151
+ ]]
152
+ ]
153
+ end
154
+ end
155
+ end
156
+
91
157
  describe "font finding" do
92
158
  context "with a font that exists" do
93
159
  let(:svg) { '<text font-family="monospace">hello</text>' }
@@ -124,11 +190,9 @@ Even more
124
190
 
125
191
  it "references the text" do
126
192
  element.process
127
- expect(flatten_calls(element.base_calls)[11..15]).to eq [
193
+ expect(flatten_calls(element.base_calls)[9..11]).to eq [
128
194
  ["fill_color", ["ff0000"]],
129
- ["fill", []],
130
195
  ["font", ["Helvetica", {:style=>:normal}]],
131
- ["character_spacing", [0]],
132
196
  ["draw_text", ["my reference text", {:size=>16, :style=>:normal, :text_anchor=>"start", :at=>[10.0, :relative], :offset=>[0,0]}]],
133
197
  ]
134
198
  end
@@ -137,14 +201,12 @@ Even more
137
201
  describe "dx and dy attributes" do
138
202
  let(:svg) { '<text x="10 20" dx="30 50 80" dy="2">Hi there, this is a good test</text>' }
139
203
 
140
- it "correctly calculates the positinos of the text" do
204
+ it "correctly calculates the positions of the text" do
141
205
  element.process
142
206
 
143
207
  expect(flatten_calls(element.base_calls)).to eq [
144
208
  ["text_group", []],
145
- ["fill", []],
146
209
  ["font", ["Helvetica", {:style=>:normal}]],
147
- ["character_spacing", [0]],
148
210
  ["draw_text", ["H", {:size=>16, :style=>:normal, :text_anchor=>"start", :at=>[10.0, :relative], :offset=>[30.0, 2.0]}]],
149
211
  ["draw_text", ["i", {:size=>16, :style=>:normal, :text_anchor=>"start", :at=>[20.0, :relative], :offset=>[50.0, 0]}]],
150
212
  ["draw_text", [" there, this is a good test", {:size=>16, :style=>:normal, :text_anchor=>"start", :at=>[:relative, :relative], :offset=>[80.0, 0]}]]
@@ -155,21 +217,17 @@ Even more
155
217
  describe "rotate attribute" do
156
218
  let(:svg) { '<text rotate="10 20 30 40 50 60 70 80 90 100">Hi <tspan rotate="0">this</tspan> ok!</text>' }
157
219
 
158
- it "correctly calculates the positinos of the text" do
220
+ it "correctly calculates the positions of the text" do
159
221
  element.process
160
222
 
161
223
  expect(flatten_calls(element.base_calls)).to eq [
162
224
  ["text_group", []],
163
- ["fill", []],
164
225
  ["font", ["Helvetica", {:style=>:normal}]],
165
- ["character_spacing", [0]],
166
226
  ["draw_text", ["H", {:size=>16, :style=>:normal, :text_anchor=>"start", :at=>[:relative, :relative], :offset=>[0, 0], :rotate=>-10.0}]],
167
227
  ["draw_text", ["i", {:size=>16, :style=>:normal, :text_anchor=>"start", :at=>[:relative, :relative], :offset=>[0, 0], :rotate=>-20.0}]],
168
228
  ["draw_text", [" ", {:size=>16, :style=>:normal, :text_anchor=>"start", :at=>[:relative, :relative], :offset=>[0, 0], :rotate=>-30.0}]],
169
229
  ["save", []],
170
- ["fill", []],
171
230
  ["font", ["Helvetica", {:style=>:normal}]],
172
- ["character_spacing", [0]],
173
231
  ["draw_text", ["this", {:size=>16, :style=>:normal, :text_anchor=>"start", :at=>[:relative, :relative], :offset=>[0, 0]}]],
174
232
  ["restore", []],
175
233
  ["draw_text", [" ", {:size=>16, :style=>:normal, :text_anchor=>"start", :at=>[:relative, :relative], :offset=>[0, 0], :rotate=>-80.0}]],
@@ -32,6 +32,36 @@ RSpec.describe Prawn::SVG::FontRegistry do
32
32
  end
33
33
  end
34
34
 
35
+ describe "#installed_fonts" do
36
+ let(:ttf) { instance_double(Prawn::SVG::TTF, family: "Awesome Font", subfamily: "Italic") }
37
+ let(:ttf2) { instance_double(Prawn::SVG::TTF, family: "Awesome Font", subfamily: "Regular") }
38
+ before { Prawn::SVG::FontRegistry.external_font_families.clear }
39
+
40
+ let(:pdf) do
41
+ doc = Prawn::Document.new
42
+ doc.font_families.update({
43
+ "Awesome Font" => {:italic => "second.ttf", :normal => "file.ttf"}
44
+ })
45
+ doc
46
+ end
47
+
48
+ let(:font_registry) { Prawn::SVG::FontRegistry.new(pdf.font_families) }
49
+
50
+ it "does not override existing entries in pdf when loading external fonts" do
51
+ expect(Prawn::SVG::FontRegistry).to receive(:font_path).and_return(["x"])
52
+ expect(Dir).to receive(:[]).with("x/**/*").and_return(["file.ttf", "second.ttf"])
53
+ expect(Prawn::SVG::TTF).to receive(:new).with("file.ttf").and_return(ttf)
54
+ expect(Prawn::SVG::TTF).to receive(:new).with("second.ttf").and_return(ttf2)
55
+ expect(File).to receive(:file?).at_least(:once).and_return(true)
56
+
57
+ Prawn::SVG::FontRegistry.load_external_fonts
58
+ font_registry.installed_fonts
59
+
60
+ existing_font = font_registry.installed_fonts["Awesome Font"]
61
+ expect(existing_font).to eq(:italic => "second.ttf",:normal => "file.ttf")
62
+ end
63
+ end
64
+
35
65
  describe "::load_external_fonts" do
36
66
  let(:ttf) { instance_double(Prawn::SVG::TTF, family: "Awesome Font", subfamily: "Italic") }
37
67
  let(:ttf2) { instance_double(Prawn::SVG::TTF, family: "Awesome Font", subfamily: "Regular") }
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Prawn::SVG::Interface do
4
- let(:bounds) { double(width: 800, height: 600) }
4
+ let(:bounds) { double(width: 800, height: 600, absolute_left: 0, absolute_top: 0) }
5
5
  let(:prawn) { instance_double(Prawn::Document, font_families: {}, bounds: bounds, cursor: 600) }
6
6
  let(:svg) { '<svg width="250" height="100"></svg>' }
7
7
 
@@ -30,6 +30,38 @@ describe Prawn::SVG::Interface do
30
30
  expect(interface.document.warnings).to eq ["Zero or negative sizing data means this SVG cannot be rendered"]
31
31
  end
32
32
  end
33
+
34
+ describe "rewrites" do
35
+ before do
36
+ [:save_font, :bounding_box].each { |message| allow(prawn).to receive(message).and_yield }
37
+ allow(prawn).to receive_messages([:move_to, :line_to, :close_path, :fill_color, :stroke_color, :transformation_matrix, :restore_graphics_state])
38
+ allow(prawn).to receive(:save_graphics_state) { |&block| block.call if block }
39
+ end
40
+
41
+ context "when fill_and_stroke is issued" do
42
+ context "and fill rule is not set" do
43
+ let(:interface) { Prawn::SVG::Interface.new('<svg width="250" height="100"><rect width="10" height="10" stroke="red"></rect></svg>', prawn, {}) }
44
+
45
+ it "adds content 'B'" do
46
+ expect(prawn).to receive(:rectangle).with([0, 100], 10, 10)
47
+ expect(prawn).to receive(:add_content).with("W n")
48
+ expect(prawn).to receive(:add_content).with("B")
49
+ interface.draw
50
+ end
51
+ end
52
+
53
+ context "and fill rule is evenodd" do
54
+ let(:interface) { Prawn::SVG::Interface.new('<svg width="250" height="100"><rect width="10" height="10" stroke="red" fill-rule="evenodd"></rect></svg>', prawn, {}) }
55
+
56
+ it "adds content 'B*'" do
57
+ expect(prawn).to receive(:rectangle).with([0, 100], 10, 10)
58
+ expect(prawn).to receive(:add_content).with("W n")
59
+ expect(prawn).to receive(:add_content).with("B*")
60
+ interface.draw
61
+ end
62
+ end
63
+ end
64
+ end
33
65
  end
34
66
 
35
67
  describe "#position" do
@@ -29,6 +29,14 @@ RSpec.describe Prawn::SVG::Loaders::Data do
29
29
  end
30
30
  end
31
31
 
32
+ context "with a data URL that's uppercase" do
33
+ let(:url) { "DATA:IMAGE/PNG;BASE64;METADATA;HERE,aGVsbG8=" }
34
+
35
+ it "loads the data" do
36
+ expect(subject).to eq "hello"
37
+ end
38
+ end
39
+
32
40
  context "with a URL that's not a data scheme" do
33
41
  let(:url) { "http://some.host" }
34
42
 
@@ -0,0 +1,94 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Prawn::SVG::TransformParser do
4
+ class Test
5
+ include Prawn::SVG::Calculators::Pixels
6
+ include Prawn::SVG::TransformParser
7
+
8
+ State = Struct.new(:viewport_sizing)
9
+ Properties = Struct.new(:numerical_font_size)
10
+ Document = Struct.new(:sizing)
11
+
12
+ def document
13
+ Document.new(_sizing)
14
+ end
15
+
16
+ def state
17
+ State.new(_sizing)
18
+ end
19
+
20
+ def computed_properties
21
+ Properties.new(14)
22
+ end
23
+
24
+ def _sizing
25
+ Prawn::SVG::Calculators::DocumentSizing.new([1000, 800])
26
+ end
27
+ end
28
+
29
+ subject { Test.new.parse_transform_attribute(transform) }
30
+
31
+ context "with no transform" do
32
+ let(:transform) { '' }
33
+ it { is_expected.to eq [1, 0, 0, 1, 0, 0] }
34
+ end
35
+
36
+ context "with translate" do
37
+ let(:transform) { 'translate(10 20)' }
38
+ it { is_expected.to eq [1, 0, 0, 1, 10, -20] }
39
+ end
40
+
41
+ context "with single argument translate" do
42
+ let(:transform) { 'translate(10)' }
43
+ it { is_expected.to eq [1, 0, 0, 1, 10, 0] }
44
+ end
45
+
46
+ context "with translateX" do
47
+ let(:transform) { 'translateX(10)' }
48
+ it { is_expected.to eq [1, 0, 0, 1, 10, 0] }
49
+ end
50
+
51
+ context "with translateY" do
52
+ let(:transform) { 'translateY(10)' }
53
+ it { is_expected.to eq [1, 0, 0, 1, 0, -10] }
54
+ end
55
+
56
+ let(:sin30) { Math.sin(30 * Math::PI / 180.0) }
57
+ let(:cos30) { Math.cos(30 * Math::PI / 180.0) }
58
+ let(:tan30) { Math.tan(30 * Math::PI / 180.0) }
59
+
60
+ context "with single argument rotate" do
61
+ let(:transform) { 'rotate(30)' }
62
+ it { is_expected.to eq [cos30, -sin30, sin30, cos30, 0, 0] }
63
+ end
64
+
65
+ context "with triple argument rotate" do
66
+ let(:transform) { 'rotate(30 100 200)' }
67
+ it { is_expected.to eq [cos30, -sin30, sin30, cos30, 113.39745962155611, 23.205080756887753] }
68
+ end
69
+
70
+ context "with scale" do
71
+ let(:transform) { 'scale(1.5)' }
72
+ it { is_expected.to eq [1.5, 0, 0, 1.5, 0, 0] }
73
+ end
74
+
75
+ context "with skewX" do
76
+ let(:transform) { 'skewX(30)' }
77
+ it { is_expected.to eq [1, 0, -tan30, 1, 0, 0] }
78
+ end
79
+
80
+ context "with skewY" do
81
+ let(:transform) { 'skewY(30)' }
82
+ it { is_expected.to eq [1, -tan30, 0, 1, 0, 0] }
83
+ end
84
+
85
+ context "with matrix" do
86
+ let(:transform) { 'matrix(1 2 3 4 5 6)' }
87
+ it { is_expected.to eq [1, -2, -3, 4, 5, -6] }
88
+ end
89
+
90
+ context "with multiple" do
91
+ let(:transform) { 'scale(2) translate(7) scale(3)' }
92
+ it { is_expected.to eq [6, 0, 0, 6, 14, 0] }
93
+ end
94
+ end