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.
@@ -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 '')
@@ -10,6 +10,10 @@ describe Prawn::SVG::Attributes::Transform do
10
10
  @warnings = []
11
11
  @attributes = {}
12
12
  end
13
+
14
+ def transformable?
15
+ true
16
+ end
13
17
  end
14
18
 
15
19
  let(:element) { TransformTestElement.new }
@@ -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, text_anchor: 'start', at: [:relative, :relative], offset: [0, 0] }
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
- element.process
93
+ setup_basic_mocks
94
+ process_and_render
22
95
 
23
- expect(flatten_calls(element.calls)).to include ['draw_text', ['some text'],
24
- { size: 16, style: :normal, text_anchor: 'start', at: [:relative, :relative], offset: [0, 0] }]
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
- element.process
105
+ setup_basic_mocks
106
+ process_and_render
33
107
 
34
- expect(flatten_calls(element.calls)).to include ['draw_text', ['some text'],
35
- { size: 16, style: :normal, text_anchor: 'start', at: [:relative, :relative], offset: [0, 0] }]
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
- element.process
62
- calls = element.calls.flatten
63
- expect(calls).to include 'Some text here '
64
- expect(calls).to include 'More text'
65
- expect(calls).to include 'Even more'
66
- expect(calls).to include ' leading goodness '
67
- expect(calls).to include 'ok'
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
- expect(element.calls.flatten).to include(size: 12.0, style: :normal, text_anchor: 'middle',
78
- at: [50.0, 586.0], offset: [0, 0])
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
- element.process
166
+ setup_basic_mocks
167
+ process_and_render
87
168
 
88
- expect(element.base_calls).to eq [
89
- ['text_group', [], {}, [
90
- ['font', ['Helvetica'], { style: :normal }, []],
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
- element.process
104
-
105
- expect(element.base_calls).to eq [
106
- ['text_group', [], {}, [
107
- ['font', ['Helvetica'], { style: :normal }, []],
108
- ['draw_text', ['underlined'], default_style.merge(decoration: 'underline'), []]
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
- expect(element.base_calls).to eq [
122
- ['text_group', [], {}, [
123
- ['stroke_color', ['ff0000'], {}, []],
124
- ['font', ['Helvetica'], { style: :normal }, []],
125
- ['text_rendering_mode', [:stroke], {}, [
126
- ['draw_text', ['stroked'], default_style, []]
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
- expect(element.base_calls).to eq [
142
- ['text_group', [], {}, [
143
- ['stroke_color', ['ff0000'], {}, []],
144
- ['font', ['Helvetica'], { style: :normal }, []],
145
- ['text_rendering_mode', [:stroke], {}, [
146
- ['draw_text', ['stroked '], default_style, []],
147
- ['save', [], {}, []],
148
- ['fill_color', ['000000'], {}, []],
149
- ['font', ['Helvetica'], { style: :normal }, []],
150
- ['text_rendering_mode', [:fill_stroke], {}, [
151
- ['draw_text', ['both'], default_style, []]
152
- ]],
153
- ['restore', [], {}, []],
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
- element.process
173
- expect(flatten_calls(element.base_calls)).to include ['font', ['Courier'], { style: :normal }]
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
- element.process
182
- expect(flatten_calls(element.base_calls)).to include ['font', ['Times-Roman'], { style: :normal }]
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
- element.process
190
- expect(element.base_calls.flatten).to_not include 'font'
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
- expect(flatten_calls(element.base_calls)[9..11]).to eq [
204
- ['fill_color', ['ff0000'], {}],
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
- element.process
292
+ setup_basic_mocks
293
+ process_and_render
217
294
 
218
- expect(flatten_calls(element.base_calls)).to eq [
219
- ['text_group', [], {}],
220
- ['font', ['Helvetica'], { style: :normal }],
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 calculates the positions of the text' do
235
- element.process
304
+ it 'correctly processes rotated text' do
305
+ setup_basic_mocks
306
+ process_and_render
236
307
 
237
- expect(flatten_calls(element.base_calls)).to eq [
238
- ['text_group', [], {}],
239
- ['font', ['Helvetica'], { style: :normal }],
240
- ['draw_text', ['H'],
241
- { size: 16, style: :normal, text_anchor: 'start', at: [:relative, :relative], offset: [0, 0], rotate: -10.0 }],
242
- ['draw_text', ['i'],
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
- expect(flatten_calls(element.calls)).to eq [
270
- ['text_group', [], {}],
271
- ['font', ['Helvetica'], { style: :normal }],
272
- ['draw_text',
273
- ['Hi '],
274
- { size: 16,
275
- style: :normal,
276
- text_anchor: 'start',
277
- at: [:relative, :relative],
278
- offset: [0, 0] }],
279
- ['draw_text',
280
- ['there'],
281
- { size: 16,
282
- style: :normal,
283
- text_anchor: 'start',
284
- at: [:relative, :relative],
285
- offset: [0, 0] }]
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.should == 'Courier'
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.should
14
- font_registry.load('blah, serif').name.should
15
- font_registry.load('blah, serif , test').name.should == 'Times-Roman'
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.should
21
- font_registry.load('VERDANA, sans-serif').name.should
22
- font_registry.load('something, "Times New Roman", serif').name.should
23
- font_registry.load('something, Times New Roman, serif').name.should == 'Times New Roman'
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').should be_nil
31
- font_registry.load('').should be_nil
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