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.
- checksums.yaml +5 -5
- data/.travis.yml +6 -4
- data/LICENSE +1 -1
- data/README.md +23 -9
- data/lib/prawn-svg.rb +7 -1
- data/lib/prawn/svg/attributes/opacity.rb +4 -4
- data/lib/prawn/svg/attributes/transform.rb +2 -44
- data/lib/prawn/svg/calculators/document_sizing.rb +2 -2
- data/lib/prawn/svg/{css.rb → css/font_family_parser.rb} +3 -4
- data/lib/prawn/svg/css/selector_parser.rb +174 -0
- data/lib/prawn/svg/css/stylesheets.rb +146 -0
- data/lib/prawn/svg/document.rb +3 -15
- data/lib/prawn/svg/elements.rb +4 -2
- data/lib/prawn/svg/elements/base.rb +26 -23
- data/lib/prawn/svg/elements/clip_path.rb +12 -0
- data/lib/prawn/svg/elements/container.rb +1 -3
- data/lib/prawn/svg/elements/gradient.rb +83 -25
- data/lib/prawn/svg/elements/image.rb +2 -2
- data/lib/prawn/svg/elements/path.rb +42 -29
- data/lib/prawn/svg/elements/root.rb +4 -1
- data/lib/prawn/svg/elements/text.rb +1 -1
- data/lib/prawn/svg/elements/text_component.rb +63 -14
- data/lib/prawn/svg/elements/use.rb +23 -7
- data/lib/prawn/svg/extensions/additional_gradient_transforms.rb +23 -0
- data/lib/prawn/svg/font_registry.rb +4 -3
- data/lib/prawn/svg/interface.rb +26 -2
- data/lib/prawn/svg/loaders/data.rb +1 -1
- data/lib/prawn/svg/loaders/file.rb +4 -2
- data/lib/prawn/svg/properties.rb +2 -0
- data/lib/prawn/svg/state.rb +6 -3
- data/lib/prawn/svg/transform_parser.rb +72 -0
- data/lib/prawn/svg/version.rb +1 -1
- data/prawn-svg.gemspec +3 -4
- data/spec/prawn/svg/attributes/opacity_spec.rb +85 -0
- data/spec/prawn/svg/attributes/transform_spec.rb +30 -35
- data/spec/prawn/svg/calculators/document_sizing_spec.rb +4 -4
- data/spec/prawn/svg/{css_spec.rb → css/font_family_parser_spec.rb} +3 -3
- data/spec/prawn/svg/css/selector_parser_spec.rb +33 -0
- data/spec/prawn/svg/css/stylesheets_spec.rb +142 -0
- data/spec/prawn/svg/document_spec.rb +0 -33
- data/spec/prawn/svg/elements/base_spec.rb +58 -2
- data/spec/prawn/svg/elements/gradient_spec.rb +79 -4
- data/spec/prawn/svg/elements/path_spec.rb +29 -17
- data/spec/prawn/svg/elements/text_spec.rb +74 -16
- data/spec/prawn/svg/font_registry_spec.rb +30 -0
- data/spec/prawn/svg/interface_spec.rb +33 -1
- data/spec/prawn/svg/loaders/data_spec.rb +8 -0
- data/spec/prawn/svg/transform_parser_spec.rb +94 -0
- data/spec/sample_output/{directory → .keep} +0 -0
- data/spec/sample_svg/double_opacity.svg +6 -0
- data/spec/sample_svg/gradient_transform.svg +19 -0
- data/spec/sample_svg/links.svg +18 -0
- data/spec/sample_svg/radgrad01-bounding.svg +26 -0
- data/spec/sample_svg/radgrad01.svg +26 -0
- data/spec/sample_svg/svg_fill.svg +5 -0
- data/spec/sample_svg/text-decoration.svg +4 -0
- data/spec/sample_svg/text_stroke.svg +41 -0
- data/spec/sample_svg/transform.svg +20 -0
- data/spec/sample_svg/use_disordered.svg +17 -0
- data/spec/sample_svg/warning-radioactive.svg +98 -0
- data/spec/spec_helper.rb +2 -2
- 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 "
|
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) { "
|
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.
|
18
|
+
allow(path).to receive(:parse_path_command) {|*args| calls << args}
|
19
19
|
path.parse
|
20
20
|
|
21
|
-
calls.
|
22
|
-
["
|
23
|
-
["
|
24
|
-
["
|
25
|
-
["
|
26
|
-
["
|
27
|
-
["
|
28
|
-
["
|
29
|
-
["
|
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.
|
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.
|
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
|
-
["
|
81
|
-
|
82
|
-
["
|
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
|
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
|
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
|
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
|