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