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
@@ -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