prawn-svg 0.37.0 → 0.38.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 +4 -4
- data/Gemfile.lock +1 -1
- data/lib/prawn/svg/attributes/transform.rb +2 -0
- data/lib/prawn/svg/elements/base.rb +13 -1
- data/lib/prawn/svg/elements/direct_render_base.rb +27 -0
- data/lib/prawn/svg/elements/polygon.rb +2 -2
- data/lib/prawn/svg/elements/text.rb +54 -52
- data/lib/prawn/svg/elements/text_component.rb +176 -198
- data/lib/prawn/svg/elements/text_node.rb +174 -0
- data/lib/prawn/svg/elements.rb +1 -1
- data/lib/prawn/svg/font.rb +13 -57
- data/lib/prawn/svg/font_metrics.rb +97 -0
- data/lib/prawn/svg/font_registry.rb +119 -55
- data/lib/prawn/svg/renderer.rb +20 -64
- data/lib/prawn/svg/version.rb +1 -1
- data/lib/prawn-svg.rb +1 -0
- data/scripts/compare_samples +25 -0
- data/spec/prawn/svg/attributes/transform_spec.rb +4 -0
- data/spec/prawn/svg/elements/text_spec.rb +210 -130
- data/spec/prawn/svg/font_registry_spec.rb +121 -10
- data/spec/prawn/svg/font_spec.rb +30 -8
- data/spec/sample_svg/bytes.svg +121 -0
- data/spec/sample_svg/positioning.svg +26 -0
- data/spec/sample_svg/subfamilies.svg +5 -1
- data/spec/sample_svg/text_use.svg +37 -0
- metadata +9 -3
- data/lib/prawn/svg/elements/depth_first_base.rb +0 -52
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/env fish
|
2
|
+
|
3
|
+
if test (count $argv) -ne 2
|
4
|
+
echo "Usage: compare_samples [file containing sample output files] [new branch name]"
|
5
|
+
echo
|
6
|
+
echo A handy script that generates a sample SVG on both the main branch and the specified branch, and displays
|
7
|
+
echo them alongside the original SVG as rendered by Arc. Used to see whether there have been regressions or
|
8
|
+
echo improvements in rendering code changes.
|
9
|
+
exit 1
|
10
|
+
end
|
11
|
+
|
12
|
+
set files $argv[1]
|
13
|
+
set branch $argv[2]
|
14
|
+
|
15
|
+
set i (cat $files | string trim | fzf)
|
16
|
+
and echo $i
|
17
|
+
and git switch main
|
18
|
+
and bundle exec rspec -e (echo $i | string replace 'spec/sample_output/' '' | string replace '.pdf' '')
|
19
|
+
and cp $i original.pdf
|
20
|
+
and git switch $branch
|
21
|
+
and bundle exec rspec -e (echo $i | string replace 'spec/sample_output/' '' | string replace '.pdf' '')
|
22
|
+
and cp $i new.pdf
|
23
|
+
and open -n original.pdf
|
24
|
+
and open -n new.pdf
|
25
|
+
and open -a Arc (echo $i | string replace sample_output sample_svg | string replace .pdf '')
|
@@ -8,7 +8,79 @@ describe Prawn::SVG::Elements::Text do
|
|
8
8
|
let(:element) { Prawn::SVG::Elements::Text.new(document, document.root, [], fake_state) }
|
9
9
|
|
10
10
|
let(:default_style) do
|
11
|
-
{ size: 16, style: :normal,
|
11
|
+
{ size: 16, style: :normal, at: [:relative, :relative], offset: [0, 0] }
|
12
|
+
end
|
13
|
+
|
14
|
+
let(:prawn) { instance_double('Prawn::Document') }
|
15
|
+
let(:renderer) { instance_double('Prawn::SVG::Renderer') }
|
16
|
+
|
17
|
+
def setup_basic_mocks
|
18
|
+
allow(prawn).to receive(:save_font).and_yield
|
19
|
+
allow(prawn).to receive(:font)
|
20
|
+
allow(prawn).to receive(:width_of).and_return(50.0)
|
21
|
+
allow(prawn).to receive(:draw_text)
|
22
|
+
allow(prawn).to receive(:horizontal_text_scaling).and_yield
|
23
|
+
allow(prawn).to receive(:character_spacing) do |spacing = nil, &block|
|
24
|
+
if spacing.nil?
|
25
|
+
0 # return current character spacing when called without args
|
26
|
+
elsif block
|
27
|
+
block.call # yield when called with spacing
|
28
|
+
end
|
29
|
+
end
|
30
|
+
allow(prawn).to receive(:text_rendering_mode).and_yield
|
31
|
+
allow(prawn).to receive(:translate) do |*_args, &block|
|
32
|
+
block&.call
|
33
|
+
end
|
34
|
+
allow(prawn).to receive(:save_graphics_state)
|
35
|
+
allow(prawn).to receive(:restore_graphics_state)
|
36
|
+
allow(prawn).to receive(:fill_rectangle)
|
37
|
+
allow(prawn).to receive(:fill_color)
|
38
|
+
allow(prawn).to receive(:stroke_color)
|
39
|
+
|
40
|
+
# Mock render_calls to actually execute the procs so we can test the real calls
|
41
|
+
allow(renderer).to receive(:render_calls) do |prawn_doc, calls|
|
42
|
+
calls.each do |call, arguments, kwarguments, children|
|
43
|
+
case call
|
44
|
+
when 'svg:yield'
|
45
|
+
proc = arguments.first
|
46
|
+
proc&.call
|
47
|
+
when 'svg:render'
|
48
|
+
element = arguments.first
|
49
|
+
element.render(prawn_doc, renderer) if element.respond_to?(:render)
|
50
|
+
else
|
51
|
+
if prawn_doc.respond_to?(call) && children.empty?
|
52
|
+
prawn_doc.send(call, *arguments, **kwarguments)
|
53
|
+
elsif prawn_doc.respond_to?(call) && children.any?
|
54
|
+
prawn_doc.send(call, *arguments, **kwarguments) do
|
55
|
+
renderer.render_calls(prawn_doc, children)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def process_and_render
|
64
|
+
element.process
|
65
|
+
element.render(prawn, renderer)
|
66
|
+
end
|
67
|
+
|
68
|
+
describe 'basic text rendering' do
|
69
|
+
let(:svg) { '<text>Hello World</text>' }
|
70
|
+
|
71
|
+
it 'renders simple text' do
|
72
|
+
setup_basic_mocks
|
73
|
+
process_and_render
|
74
|
+
|
75
|
+
expect(prawn).to have_received(:draw_text).with('Hello World', hash_including(:size, :at))
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'lays out the text during rendering' do
|
79
|
+
setup_basic_mocks
|
80
|
+
process_and_render
|
81
|
+
expect(prawn).to have_received(:save_font).at_least(:once)
|
82
|
+
expect(prawn).to have_received(:width_of).with('Hello World', hash_including(:kerning, :size))
|
83
|
+
end
|
12
84
|
end
|
13
85
|
|
14
86
|
describe 'xml:space preserve' do
|
@@ -18,10 +90,11 @@ describe Prawn::SVG::Elements::Text do
|
|
18
90
|
let(:attributes) { ' xml:space="preserve"' }
|
19
91
|
|
20
92
|
it 'converts newlines and tabs to spaces, and preserves spaces' do
|
21
|
-
|
93
|
+
setup_basic_mocks
|
94
|
+
process_and_render
|
22
95
|
|
23
|
-
expect(
|
24
|
-
|
96
|
+
expect(prawn).to have_received(:draw_text).with('some text', hash_including(:size, :at))
|
97
|
+
expect(prawn).to have_received(:width_of).with('some text', hash_including(:kerning, :size))
|
25
98
|
end
|
26
99
|
end
|
27
100
|
|
@@ -29,10 +102,11 @@ describe Prawn::SVG::Elements::Text do
|
|
29
102
|
let(:attributes) { '' }
|
30
103
|
|
31
104
|
it 'strips space' do
|
32
|
-
|
105
|
+
setup_basic_mocks
|
106
|
+
process_and_render
|
33
107
|
|
34
|
-
expect(
|
35
|
-
|
108
|
+
expect(prawn).to have_received(:draw_text).with('some text', hash_including(:size, :at))
|
109
|
+
expect(prawn).to have_received(:width_of).with('some text', hash_including(:kerning, :size))
|
36
110
|
end
|
37
111
|
end
|
38
112
|
end
|
@@ -58,13 +132,14 @@ describe Prawn::SVG::Elements::Text do
|
|
58
132
|
end
|
59
133
|
|
60
134
|
it 'correctly apportions white space between the tags' do
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
expect(
|
65
|
-
expect(
|
66
|
-
expect(
|
67
|
-
expect(
|
135
|
+
setup_basic_mocks
|
136
|
+
process_and_render
|
137
|
+
|
138
|
+
expect(prawn).to have_received(:draw_text).with('Some text here ', anything)
|
139
|
+
expect(prawn).to have_received(:draw_text).with('More text', anything)
|
140
|
+
expect(prawn).to have_received(:draw_text).with('Even more', anything)
|
141
|
+
expect(prawn).to have_received(:draw_text).with(' leading goodness ', anything)
|
142
|
+
expect(prawn).to have_received(:draw_text).with('ok', anything)
|
68
143
|
end
|
69
144
|
end
|
70
145
|
|
@@ -73,9 +148,14 @@ describe Prawn::SVG::Elements::Text do
|
|
73
148
|
let(:element) { Prawn::SVG::Elements::Container.new(document, document.root, [], fake_state) }
|
74
149
|
|
75
150
|
it 'should inherit text-anchor from parent element' do
|
151
|
+
setup_basic_mocks
|
152
|
+
allow(prawn).to receive(:width_of).and_return(40.0)
|
153
|
+
|
76
154
|
element.process
|
77
|
-
|
78
|
-
|
155
|
+
renderer.render_calls(prawn, element.calls)
|
156
|
+
|
157
|
+
expect(prawn).to have_received(:translate).with(-20.0, 0)
|
158
|
+
expect(prawn).to have_received(:draw_text).with('Text', anything)
|
79
159
|
end
|
80
160
|
end
|
81
161
|
|
@@ -83,16 +163,12 @@ describe Prawn::SVG::Elements::Text do
|
|
83
163
|
let(:svg) { '<text letter-spacing="5">spaced</text>' }
|
84
164
|
|
85
165
|
it 'calls character_spacing with the requested size' do
|
86
|
-
|
166
|
+
setup_basic_mocks
|
167
|
+
process_and_render
|
87
168
|
|
88
|
-
expect(
|
89
|
-
|
90
|
-
|
91
|
-
['character_spacing', [5.0], {}, [
|
92
|
-
['draw_text', ['spaced'], default_style, []]
|
93
|
-
]]
|
94
|
-
]]
|
95
|
-
]
|
169
|
+
expect(prawn).to have_received(:font).with('Helvetica', style: :normal).at_least(:once)
|
170
|
+
expect(prawn).to have_received(:character_spacing).with(5.0)
|
171
|
+
expect(prawn).to have_received(:draw_text).with('spaced', hash_including(size: 16, at: anything))
|
96
172
|
end
|
97
173
|
end
|
98
174
|
|
@@ -100,14 +176,14 @@ describe Prawn::SVG::Elements::Text do
|
|
100
176
|
let(:svg) { '<text text-decoration="underline">underlined</text>' }
|
101
177
|
|
102
178
|
it 'marks the element to be underlined' do
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
179
|
+
setup_basic_mocks
|
180
|
+
process_and_render
|
181
|
+
|
182
|
+
expect(prawn).to have_received(:draw_text).with('underlined', hash_including(:size, :at))
|
183
|
+
expect(prawn).to have_received(:fill_rectangle).with(
|
184
|
+
[0, be_within(1).of(598.56)], 50.0, be_within(0.5).of(0.96)
|
185
|
+
)
|
186
|
+
expect(prawn).to have_received(:width_of).with('underlined', hash_including(:kerning, :size))
|
111
187
|
end
|
112
188
|
end
|
113
189
|
|
@@ -116,17 +192,18 @@ describe Prawn::SVG::Elements::Text do
|
|
116
192
|
let(:svg) { '<text stroke="red" fill="none">stroked</text>' }
|
117
193
|
|
118
194
|
it 'calls text_rendering_mode with the requested options' do
|
195
|
+
setup_basic_mocks
|
196
|
+
|
119
197
|
element.process
|
120
198
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
]
|
199
|
+
calls_flat = flatten_calls(element.calls)
|
200
|
+
expect(calls_flat).to include(['stroke_color', ['ff0000'], {}])
|
201
|
+
|
202
|
+
element.render(prawn, renderer)
|
203
|
+
|
204
|
+
expect(prawn).to have_received(:font).with('Helvetica', style: :normal).at_least(:once)
|
205
|
+
expect(prawn).to have_received(:text_rendering_mode).with(:stroke)
|
206
|
+
expect(prawn).to have_received(:draw_text).with('stroked', hash_including(size: 16, at: anything))
|
130
207
|
end
|
131
208
|
end
|
132
209
|
|
@@ -136,30 +213,23 @@ describe Prawn::SVG::Elements::Text do
|
|
136
213
|
end
|
137
214
|
|
138
215
|
it 'calls text_rendering_mode with the requested options' do
|
216
|
+
setup_basic_mocks
|
217
|
+
|
139
218
|
element.process
|
140
219
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
['save', [], {}, []],
|
155
|
-
['font', ['Helvetica'], { style: :normal }, []],
|
156
|
-
['text_rendering_mode', [:invisible], {}, [
|
157
|
-
['draw_text', ['neither'], default_style, []]
|
158
|
-
]],
|
159
|
-
['restore', [], {}, []]
|
160
|
-
]]
|
161
|
-
]]
|
162
|
-
]
|
220
|
+
calls_flat = flatten_calls(element.calls)
|
221
|
+
expect(calls_flat).to include(['stroke_color', ['ff0000'], {}])
|
222
|
+
|
223
|
+
element.render(prawn, renderer)
|
224
|
+
|
225
|
+
expect(prawn).to have_received(:text_rendering_mode).with(:stroke)
|
226
|
+
expect(prawn).to have_received(:text_rendering_mode).with(:fill_stroke)
|
227
|
+
expect(prawn).to have_received(:text_rendering_mode).with(:invisible)
|
228
|
+
expect(prawn).to have_received(:draw_text).with('stroked ', anything)
|
229
|
+
expect(prawn).to have_received(:draw_text).with('both', anything)
|
230
|
+
expect(prawn).to have_received(:draw_text).with('neither', anything)
|
231
|
+
expect(prawn).to have_received(:save_graphics_state).at_least(:once)
|
232
|
+
expect(prawn).to have_received(:restore_graphics_state).at_least(:once)
|
163
233
|
end
|
164
234
|
end
|
165
235
|
end
|
@@ -169,8 +239,11 @@ describe Prawn::SVG::Elements::Text do
|
|
169
239
|
let(:svg) { '<text font-family="monospace">hello</text>' }
|
170
240
|
|
171
241
|
it 'finds the font and uses it' do
|
172
|
-
|
173
|
-
|
242
|
+
setup_basic_mocks
|
243
|
+
process_and_render
|
244
|
+
|
245
|
+
expect(prawn).to have_received(:font).with('Courier', style: :normal).at_least(:once)
|
246
|
+
expect(prawn).to have_received(:draw_text).with('hello', hash_including(size: 16, at: anything))
|
174
247
|
end
|
175
248
|
end
|
176
249
|
|
@@ -178,16 +251,21 @@ describe Prawn::SVG::Elements::Text do
|
|
178
251
|
let(:svg) { '<text font-family="does not exist">hello</text>' }
|
179
252
|
|
180
253
|
it 'uses the fallback font' do
|
181
|
-
|
182
|
-
|
254
|
+
setup_basic_mocks
|
255
|
+
process_and_render
|
256
|
+
|
257
|
+
expect(prawn).to have_received(:font).with('Times-Roman', style: :normal).at_least(:once)
|
258
|
+
expect(prawn).to have_received(:draw_text).with('hello', hash_including(size: 16, at: anything))
|
183
259
|
end
|
184
260
|
|
185
261
|
context 'when there is no fallback font' do
|
186
262
|
before { document.font_registry.installed_fonts.delete('Times-Roman') }
|
187
263
|
|
188
264
|
it "doesn't call the font method and logs a warning" do
|
189
|
-
|
190
|
-
|
265
|
+
setup_basic_mocks
|
266
|
+
process_and_render
|
267
|
+
|
268
|
+
expect(prawn).not_to have_received(:font)
|
191
269
|
expect(document.warnings.first).to include 'is not a known font'
|
192
270
|
end
|
193
271
|
end
|
@@ -199,13 +277,11 @@ describe Prawn::SVG::Elements::Text do
|
|
199
277
|
let(:element) { Prawn::SVG::Elements::Root.new(document, document.root, [], fake_state) }
|
200
278
|
|
201
279
|
it 'references the text' do
|
280
|
+
setup_basic_mocks
|
281
|
+
|
202
282
|
element.process
|
203
|
-
|
204
|
-
|
205
|
-
['font', ['Helvetica'], { style: :normal }],
|
206
|
-
['draw_text', ['my reference text'],
|
207
|
-
{ size: 16, style: :normal, text_anchor: 'start', at: [10.0, :relative], offset: [0, 0] }]
|
208
|
-
]
|
283
|
+
|
284
|
+
expect(element.calls.any? { |call| call[0] == 'svg:render' }).to be true
|
209
285
|
end
|
210
286
|
end
|
211
287
|
|
@@ -213,50 +289,28 @@ describe Prawn::SVG::Elements::Text do
|
|
213
289
|
let(:svg) { '<text x="10 20" dx="30 50 80" dy="2">Hi there, this is a good test</text>' }
|
214
290
|
|
215
291
|
it 'correctly calculates the positions of the text' do
|
216
|
-
|
292
|
+
setup_basic_mocks
|
293
|
+
process_and_render
|
217
294
|
|
218
|
-
expect(
|
219
|
-
|
220
|
-
|
221
|
-
['draw_text', ['H'],
|
222
|
-
{ size: 16, style: :normal, text_anchor: 'start', at: [10.0, :relative], offset: [30.0, 2.0] }],
|
223
|
-
['draw_text', ['i'],
|
224
|
-
{ size: 16, style: :normal, text_anchor: 'start', at: [20.0, :relative], offset: [50.0, 0] }],
|
225
|
-
['draw_text', [' there, this is a good test'],
|
226
|
-
{ size: 16, style: :normal, text_anchor: 'start', at: [:relative, :relative], offset: [80.0, 0] }]
|
227
|
-
]
|
295
|
+
expect(prawn).to have_received(:draw_text).with('H', hash_including(at: [40.0, anything])) # 10 + 30
|
296
|
+
expect(prawn).to have_received(:draw_text).with('i', hash_including(at: [70.0, anything])) # 20 + 50
|
297
|
+
expect(prawn).to have_received(:draw_text).with(' there, this is a good test', anything)
|
228
298
|
end
|
229
299
|
end
|
230
300
|
|
231
301
|
describe 'rotate attribute' do
|
232
302
|
let(:svg) { '<text rotate="10 20 30 40 50 60 70 80 90 100">Hi <tspan rotate="0">this</tspan> ok!</text>' }
|
233
303
|
|
234
|
-
it 'correctly
|
235
|
-
|
304
|
+
it 'correctly processes rotated text' do
|
305
|
+
setup_basic_mocks
|
306
|
+
process_and_render
|
236
307
|
|
237
|
-
expect(
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
{ size: 16, style: :normal, text_anchor: 'start', at: [:relative, :relative], offset: [0, 0], rotate: -20.0 }],
|
244
|
-
['draw_text', [' '],
|
245
|
-
{ size: 16, style: :normal, text_anchor: 'start', at: [:relative, :relative], offset: [0, 0], rotate: -30.0 }],
|
246
|
-
['save', [], {}],
|
247
|
-
['font', ['Helvetica'], { style: :normal }],
|
248
|
-
['draw_text', ['this'],
|
249
|
-
{ size: 16, style: :normal, text_anchor: 'start', at: [:relative, :relative], offset: [0, 0] }],
|
250
|
-
['restore', [], {}],
|
251
|
-
['draw_text', [' '],
|
252
|
-
{ size: 16, style: :normal, text_anchor: 'start', at: [:relative, :relative], offset: [0, 0], rotate: -80.0 }],
|
253
|
-
['draw_text', ['o'],
|
254
|
-
{ size: 16, style: :normal, text_anchor: 'start', at: [:relative, :relative], offset: [0, 0], rotate: -90.0 }],
|
255
|
-
['draw_text', ['k'],
|
256
|
-
{ size: 16, style: :normal, text_anchor: 'start', at: [:relative, :relative], offset: [0, 0], rotate: -100.0 }],
|
257
|
-
['draw_text', ['!'],
|
258
|
-
{ size: 16, style: :normal, text_anchor: 'start', at: [:relative, :relative], offset: [0, 0], rotate: -100.0 }]
|
259
|
-
]
|
308
|
+
expect(prawn).to have_received(:draw_text).with('H', hash_including(rotate: -10.0))
|
309
|
+
expect(prawn).to have_received(:draw_text).with('i', hash_including(rotate: -20.0))
|
310
|
+
expect(prawn).to have_received(:draw_text).with(' ', hash_including(rotate: -30.0))
|
311
|
+
expect(prawn).to have_received(:draw_text).with('this', hash_excluding(:rotate))
|
312
|
+
expect(prawn).to have_received(:draw_text).with('o', hash_including(rotate: -90.0))
|
313
|
+
expect(prawn).to have_received(:draw_text).with('k', hash_including(rotate: -100.0))
|
260
314
|
end
|
261
315
|
end
|
262
316
|
|
@@ -264,26 +318,52 @@ describe Prawn::SVG::Elements::Text do
|
|
264
318
|
let(:svg) { '<text>Hi <!-- comment --> there</text>' }
|
265
319
|
|
266
320
|
it 'ignores the comment' do
|
321
|
+
setup_basic_mocks
|
322
|
+
process_and_render
|
323
|
+
|
324
|
+
expect(renderer).to have_received(:render_calls).with(prawn, anything)
|
325
|
+
expect(prawn).to have_received(:width_of).at_least(:once)
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
describe 'when a use element references a tspan element' do
|
330
|
+
let(:svg) do
|
331
|
+
<<~SVG
|
332
|
+
<svg>
|
333
|
+
<defs>
|
334
|
+
<text>
|
335
|
+
<tspan id="tspan-element">Referenced text</tspan>
|
336
|
+
</text>
|
337
|
+
</defs>
|
338
|
+
<use href="#tspan-element"/>
|
339
|
+
</svg>
|
340
|
+
SVG
|
341
|
+
end
|
342
|
+
let(:element) { Prawn::SVG::Elements::Root.new(document, document.root, [], fake_state) }
|
343
|
+
|
344
|
+
it 'emits a warning that tspan cannot be used' do
|
267
345
|
element.process
|
346
|
+
expect(document.warnings).to include('attempted to <use> a component inside a text element, this is not supported')
|
347
|
+
end
|
348
|
+
end
|
268
349
|
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
]
|
350
|
+
describe 'when a use element references a text element' do
|
351
|
+
let(:svg) do
|
352
|
+
<<~SVG
|
353
|
+
<svg>
|
354
|
+
<defs>
|
355
|
+
<text id="text-element"><tspan>Referenced text</tspan></text>
|
356
|
+
</defs>
|
357
|
+
<use href="#text-element"/>
|
358
|
+
</svg>
|
359
|
+
SVG
|
360
|
+
end
|
361
|
+
let(:element) { Prawn::SVG::Elements::Root.new(document, document.root, [], fake_state) }
|
362
|
+
|
363
|
+
it 'processes the referenced text element' do
|
364
|
+
element.process
|
365
|
+
expect(document.warnings).to eq []
|
366
|
+
expect(flatten_calls(element.base_calls).map(&:first)).to include 'svg:render'
|
287
367
|
end
|
288
368
|
end
|
289
369
|
end
|
@@ -6,29 +6,140 @@ RSpec.describe Prawn::SVG::FontRegistry do
|
|
6
6
|
let(:font_registry) { Prawn::SVG::FontRegistry.new(pdf.font_families) }
|
7
7
|
|
8
8
|
it 'matches a built in font' do
|
9
|
-
font_registry.load("blah, 'courier', nothing").name.
|
9
|
+
expect(font_registry.load("blah, 'courier', nothing").name).to eq('Courier')
|
10
10
|
end
|
11
11
|
|
12
12
|
it 'matches a default font' do
|
13
|
-
font_registry.load('serif').name.
|
14
|
-
font_registry.load('blah, serif').name.
|
15
|
-
font_registry.load('blah, serif , test').name.
|
13
|
+
expect(font_registry.load('serif').name).to be_truthy
|
14
|
+
expect(font_registry.load('blah, serif').name).to be_truthy
|
15
|
+
expect(font_registry.load('blah, serif , test').name).to eq('Times-Roman')
|
16
16
|
end
|
17
17
|
|
18
18
|
if Prawn::SVG::FontRegistry.new({}).installed_fonts['Verdana']
|
19
19
|
it 'matches a font installed on the system' do
|
20
|
-
font_registry.load('verdana, sans-serif').name.
|
21
|
-
font_registry.load('VERDANA, sans-serif').name.
|
22
|
-
font_registry.load('something, "Times New Roman", serif').name.
|
23
|
-
font_registry.load('something, Times New Roman, serif').name.
|
20
|
+
expect(font_registry.load('verdana, sans-serif').name).to eq('Verdana')
|
21
|
+
expect(font_registry.load('VERDANA, sans-serif').name).to eq('Verdana')
|
22
|
+
expect(font_registry.load('something, "Times New Roman", serif').name).to be_truthy
|
23
|
+
expect(font_registry.load('something, Times New Roman, serif').name).to eq('Times New Roman')
|
24
24
|
end
|
25
25
|
else
|
26
26
|
it "not running font test because we couldn't find Verdana installed on the system"
|
27
27
|
end
|
28
28
|
|
29
29
|
it "returns nil if it can't find any such font" do
|
30
|
-
font_registry.load('blah, thing').
|
31
|
-
font_registry.load('').
|
30
|
+
expect(font_registry.load('blah, thing')).to be_nil
|
31
|
+
expect(font_registry.load('')).to be_nil
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'handles CSS font weights' do
|
35
|
+
font = font_registry.load('courier', '700')
|
36
|
+
expect(font.weight).to eq(:bold)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'normalizes multiple spaces in font names' do
|
40
|
+
font = font_registry.load('courier , times')
|
41
|
+
expect(font.name).to eq('Courier')
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'falls back when requested weight is unavailable' do
|
45
|
+
font = font_registry.load('courier', :black)
|
46
|
+
expect(font.name).to eq('Courier')
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'handles font weight and style parameters' do
|
50
|
+
font = font_registry.load('courier', :bold, :italic)
|
51
|
+
expect(font.weight).to eq(:bold)
|
52
|
+
expect(font.style).to eq(:italic)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'converts CSS numeric weights to symbols' do
|
56
|
+
expect(font_registry.load('courier', '400').weight).to eq(:normal)
|
57
|
+
expect(font_registry.load('courier', '700').weight).to eq(:bold)
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'falls back from style when unavailable' do
|
61
|
+
font = font_registry.load('courier', :normal, :italic)
|
62
|
+
expect(font.name).to eq('Courier')
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'processes comma-separated font families' do
|
66
|
+
font = font_registry.load('nonexistent, courier, times')
|
67
|
+
expect(font.name).to eq('Courier')
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'handles quoted font names with commas' do
|
71
|
+
font = font_registry.load('"font, with comma", courier')
|
72
|
+
expect(font.name).to eq('Courier')
|
73
|
+
end
|
74
|
+
|
75
|
+
describe 'weight fallbacks' do
|
76
|
+
before do
|
77
|
+
# Mock a font family with only normal and bold available
|
78
|
+
allow(font_registry).to receive(:installed_fonts).and_return({
|
79
|
+
'TestFont' => { normal: 'test.ttf', bold: 'test-bold.ttf' }
|
80
|
+
})
|
81
|
+
allow(font_registry).to receive(:correctly_cased_font_name).and_return('TestFont')
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'falls back from light to normal' do
|
85
|
+
font = font_registry.load('TestFont', :light)
|
86
|
+
expect(font.weight).to eq(:normal)
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'falls back from semibold to bold to normal' do
|
90
|
+
font = font_registry.load('TestFont', :semibold)
|
91
|
+
expect(font.weight).to eq(:bold)
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'falls back from extrabold to bold' do
|
95
|
+
font = font_registry.load('TestFont', :extrabold)
|
96
|
+
expect(font.weight).to eq(:bold)
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'falls back from black to bold' do
|
100
|
+
font = font_registry.load('TestFont', :black)
|
101
|
+
expect(font.weight).to eq(:bold)
|
102
|
+
end
|
103
|
+
|
104
|
+
context 'when only normal weight is available' do
|
105
|
+
before do
|
106
|
+
allow(font_registry).to receive(:installed_fonts).and_return({
|
107
|
+
'TestFont' => { normal: 'test.ttf' }
|
108
|
+
})
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'falls back from bold to normal' do
|
112
|
+
font = font_registry.load('TestFont', :bold)
|
113
|
+
expect(font.weight).to eq(:normal)
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'falls back through the entire chain to normal' do
|
117
|
+
font = font_registry.load('TestFont', :black)
|
118
|
+
expect(font.weight).to eq(:normal)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
context 'when all weights are available' do
|
123
|
+
before do
|
124
|
+
allow(font_registry).to receive(:installed_fonts).and_return({
|
125
|
+
'TestFont' => {
|
126
|
+
light: 'test-light.ttf',
|
127
|
+
normal: 'test.ttf',
|
128
|
+
semibold: 'test-semibold.ttf',
|
129
|
+
bold: 'test-bold.ttf',
|
130
|
+
extrabold: 'test-extrabold.ttf',
|
131
|
+
black: 'test-black.ttf'
|
132
|
+
}
|
133
|
+
})
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'returns exact weight matches without fallback' do
|
137
|
+
expect(font_registry.load('TestFont', :light).weight).to eq(:light)
|
138
|
+
expect(font_registry.load('TestFont', :semibold).weight).to eq(:semibold)
|
139
|
+
expect(font_registry.load('TestFont', :extrabold).weight).to eq(:extrabold)
|
140
|
+
expect(font_registry.load('TestFont', :black).weight).to eq(:black)
|
141
|
+
end
|
142
|
+
end
|
32
143
|
end
|
33
144
|
end
|
34
145
|
|