pdf 0.1.0 → 0.1.1
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/.claude/execution/00-overview.md +121 -0
- data/.claude/execution/01-core.md +324 -0
- data/.claude/execution/02-renderer.md +237 -0
- data/.claude/execution/03-components.md +551 -0
- data/.claude/execution/04-builders.md +322 -0
- data/.claude/execution/05-view-layout.md +362 -0
- data/.claude/execution/06-evaluators.md +494 -0
- data/.claude/execution/07-entry-extensibility.md +435 -0
- data/.claude/execution/08-integration-tests.md +978 -0
- data/Rakefile +7 -3
- data/examples/01_basic_invoice.rb +139 -0
- data/examples/02_report_with_layout.rb +266 -0
- data/examples/03_inherited_views.rb +318 -0
- data/examples/04_conditional_content.rb +421 -0
- data/examples/05_custom_components.rb +442 -0
- data/examples/README.md +123 -0
- data/lib/pdf/blueprint.rb +50 -0
- data/lib/pdf/builders/content_builder.rb +96 -0
- data/lib/pdf/builders/footer_builder.rb +24 -0
- data/lib/pdf/builders/header_builder.rb +31 -0
- data/lib/pdf/component.rb +43 -0
- data/lib/pdf/components/alert.rb +42 -0
- data/lib/pdf/components/context.rb +16 -0
- data/lib/pdf/components/date.rb +28 -0
- data/lib/pdf/components/heading.rb +12 -0
- data/lib/pdf/components/hr.rb +12 -0
- data/lib/pdf/components/logo.rb +15 -0
- data/lib/pdf/components/paragraph.rb +12 -0
- data/lib/pdf/components/qr_code.rb +38 -0
- data/lib/pdf/components/spacer.rb +11 -0
- data/lib/pdf/components/span.rb +12 -0
- data/lib/pdf/components/subtitle.rb +12 -0
- data/lib/pdf/components/table.rb +48 -0
- data/lib/pdf/components/title.rb +12 -0
- data/lib/pdf/content_evaluator.rb +218 -0
- data/lib/pdf/dynamic_components.rb +17 -0
- data/lib/pdf/footer_evaluator.rb +66 -0
- data/lib/pdf/header_evaluator.rb +56 -0
- data/lib/pdf/layout.rb +61 -0
- data/lib/pdf/renderer.rb +153 -0
- data/lib/pdf/resolver.rb +36 -0
- data/lib/pdf/version.rb +1 -1
- data/lib/pdf/view.rb +113 -0
- data/lib/pdf.rb +74 -1
- metadata +127 -2
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
# Phase 3: Built-in Components
|
|
2
|
+
|
|
3
|
+
**Principles:**
|
|
4
|
+
- NEVER swallow errors - all failures bubble up to client
|
|
5
|
+
- Each component is single-responsibility
|
|
6
|
+
- Components only render, no business logic
|
|
7
|
+
|
|
8
|
+
## Files
|
|
9
|
+
- `lib/pdf/components/title.rb`
|
|
10
|
+
- `lib/pdf/components/subtitle.rb`
|
|
11
|
+
- `lib/pdf/components/heading.rb`
|
|
12
|
+
- `lib/pdf/components/paragraph.rb`
|
|
13
|
+
- `lib/pdf/components/span.rb`
|
|
14
|
+
- `lib/pdf/components/date.rb`
|
|
15
|
+
- `lib/pdf/components/hr.rb`
|
|
16
|
+
- `lib/pdf/components/spacer.rb`
|
|
17
|
+
- `lib/pdf/components/alert.rb`
|
|
18
|
+
- `lib/pdf/components/table.rb`
|
|
19
|
+
- `lib/pdf/components/logo.rb`
|
|
20
|
+
- `lib/pdf/components/qr_code.rb`
|
|
21
|
+
- `lib/pdf/components/context.rb`
|
|
22
|
+
|
|
23
|
+
Note: PageNumber is handled in FooterEvaluator, not as a component.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 3.1 Title
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
# lib/pdf/components/title.rb
|
|
31
|
+
# frozen_string_literal: true
|
|
32
|
+
|
|
33
|
+
module Pdf
|
|
34
|
+
module Components
|
|
35
|
+
class Title < Component
|
|
36
|
+
def render(content, size: 20, **options)
|
|
37
|
+
text content, size: size, style: :bold, **options
|
|
38
|
+
move_down 10
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## 3.2 Subtitle
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
# lib/pdf/components/subtitle.rb
|
|
51
|
+
# frozen_string_literal: true
|
|
52
|
+
|
|
53
|
+
module Pdf
|
|
54
|
+
module Components
|
|
55
|
+
class Subtitle < Component
|
|
56
|
+
def render(content, size: 16, **options)
|
|
57
|
+
text content, size: size, **options
|
|
58
|
+
move_down 5
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 3.3 Heading
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
# lib/pdf/components/heading.rb
|
|
71
|
+
# frozen_string_literal: true
|
|
72
|
+
|
|
73
|
+
module Pdf
|
|
74
|
+
module Components
|
|
75
|
+
class Heading < Component
|
|
76
|
+
def render(content, size: 14, **options)
|
|
77
|
+
text content, size: size, style: :bold, **options
|
|
78
|
+
move_down 8
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## 3.4 Paragraph
|
|
88
|
+
|
|
89
|
+
```ruby
|
|
90
|
+
# lib/pdf/components/paragraph.rb
|
|
91
|
+
# frozen_string_literal: true
|
|
92
|
+
|
|
93
|
+
module Pdf
|
|
94
|
+
module Components
|
|
95
|
+
class Paragraph < Component
|
|
96
|
+
def render(content, size: 10, leading: 3, **options)
|
|
97
|
+
text content, size: size, leading: leading, **options
|
|
98
|
+
move_down 10
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## 3.5 Span
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
# lib/pdf/components/span.rb
|
|
111
|
+
# frozen_string_literal: true
|
|
112
|
+
|
|
113
|
+
module Pdf
|
|
114
|
+
module Components
|
|
115
|
+
class Span < Component
|
|
116
|
+
def render(content, size: 10, **options)
|
|
117
|
+
text content, size: size, **options
|
|
118
|
+
move_down 3
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## 3.6 Date
|
|
128
|
+
|
|
129
|
+
Real Date component for formatted date rendering.
|
|
130
|
+
|
|
131
|
+
```ruby
|
|
132
|
+
# lib/pdf/components/date.rb
|
|
133
|
+
# frozen_string_literal: true
|
|
134
|
+
|
|
135
|
+
module Pdf
|
|
136
|
+
module Components
|
|
137
|
+
class Date < Component
|
|
138
|
+
DEFAULT_FORMAT = "%B %d, %Y"
|
|
139
|
+
|
|
140
|
+
def render(value, format: DEFAULT_FORMAT, size: 10, align: :right, **options)
|
|
141
|
+
formatted = format_date(value, format)
|
|
142
|
+
text formatted, size: size, align: align, **options
|
|
143
|
+
move_down 5
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
private
|
|
147
|
+
|
|
148
|
+
def format_date(value, format)
|
|
149
|
+
case value
|
|
150
|
+
when ::Date, ::Time, ::DateTime
|
|
151
|
+
value.strftime(format)
|
|
152
|
+
when String
|
|
153
|
+
value
|
|
154
|
+
else
|
|
155
|
+
value.to_s
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## 3.7 Hr
|
|
166
|
+
|
|
167
|
+
```ruby
|
|
168
|
+
# lib/pdf/components/hr.rb
|
|
169
|
+
# frozen_string_literal: true
|
|
170
|
+
|
|
171
|
+
module Pdf
|
|
172
|
+
module Components
|
|
173
|
+
class Hr < Component
|
|
174
|
+
def render(**_options)
|
|
175
|
+
stroke_horizontal_rule
|
|
176
|
+
move_down 10
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## 3.8 Spacer
|
|
186
|
+
|
|
187
|
+
Explicit vertical space component.
|
|
188
|
+
|
|
189
|
+
```ruby
|
|
190
|
+
# lib/pdf/components/spacer.rb
|
|
191
|
+
# frozen_string_literal: true
|
|
192
|
+
|
|
193
|
+
module Pdf
|
|
194
|
+
module Components
|
|
195
|
+
class Spacer < Component
|
|
196
|
+
def render(amount: 20, **_options)
|
|
197
|
+
move_down amount
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## 3.9 Alert
|
|
207
|
+
|
|
208
|
+
```ruby
|
|
209
|
+
# lib/pdf/components/alert.rb
|
|
210
|
+
# frozen_string_literal: true
|
|
211
|
+
|
|
212
|
+
module Pdf
|
|
213
|
+
module Components
|
|
214
|
+
class Alert < Component
|
|
215
|
+
COLORS = {
|
|
216
|
+
"green" => { bg: "C8E6C9", text: "2E7D32", border: "4CAF50" },
|
|
217
|
+
"orange" => { bg: "FFE0B2", text: "E65100", border: "FF9800" },
|
|
218
|
+
"red" => { bg: "FFCDD2", text: "C62828", border: "F44336" },
|
|
219
|
+
"blue" => { bg: "E1F5FE", text: "0277BD", border: "2196F3" }
|
|
220
|
+
}.freeze
|
|
221
|
+
|
|
222
|
+
def render(title:, description: nil, color: "blue", **_options)
|
|
223
|
+
scheme = COLORS[color.to_s] || COLORS["blue"]
|
|
224
|
+
|
|
225
|
+
title_h = 20
|
|
226
|
+
desc_h = description ? (@pdf.height_of(description.to_s, width: bounds.width - 20, size: 10) + 10) : 0
|
|
227
|
+
total_h = title_h + desc_h + 6
|
|
228
|
+
|
|
229
|
+
bounding_box([bounds.left, cursor], width: bounds.width, height: total_h) do
|
|
230
|
+
@pdf.stroke_color scheme[:border]
|
|
231
|
+
@pdf.line_width 0.5
|
|
232
|
+
@pdf.fill_color scheme[:bg]
|
|
233
|
+
@pdf.fill_and_stroke_rounded_rectangle [bounds.left, bounds.top], bounds.width, bounds.height, 5
|
|
234
|
+
|
|
235
|
+
@pdf.fill_color scheme[:text]
|
|
236
|
+
|
|
237
|
+
y = bounds.top - 10
|
|
238
|
+
@pdf.text_box title.to_s, at: [10, y], width: bounds.width - 20, size: 11, style: :bold
|
|
239
|
+
|
|
240
|
+
if description
|
|
241
|
+
y -= 18
|
|
242
|
+
@pdf.text_box description.to_s, at: [10, y], width: bounds.width - 20, size: 10
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
@pdf.fill_color "000000"
|
|
247
|
+
move_down 15
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## 3.10 Table
|
|
257
|
+
|
|
258
|
+
```ruby
|
|
259
|
+
# lib/pdf/components/table.rb
|
|
260
|
+
# frozen_string_literal: true
|
|
261
|
+
|
|
262
|
+
module Pdf
|
|
263
|
+
module Components
|
|
264
|
+
class Table < Component
|
|
265
|
+
def render(headers:, rows:, widths: nil, **_options)
|
|
266
|
+
data = [headers] + rows
|
|
267
|
+
|
|
268
|
+
opts = {
|
|
269
|
+
header: true,
|
|
270
|
+
cell_style: { borders: [:top, :bottom, :left, :right], padding: [4, 6], size: 8 }
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
opts[:column_widths] = calculate_widths(widths) if widths
|
|
274
|
+
opts[:width] = bounds.width unless widths
|
|
275
|
+
|
|
276
|
+
@pdf.table(data, **opts) do
|
|
277
|
+
row(0).background_color = "333333"
|
|
278
|
+
row(0).text_color = "FFFFFF"
|
|
279
|
+
row(0).font_style = :bold
|
|
280
|
+
|
|
281
|
+
(1..row_length - 1).each do |i|
|
|
282
|
+
row(i).background_color = i.odd? ? "F5F5F5" : "FFFFFF"
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
move_down 15
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
private
|
|
290
|
+
|
|
291
|
+
def calculate_widths(widths)
|
|
292
|
+
total = bounds.width
|
|
293
|
+
widths.map do |w|
|
|
294
|
+
case w
|
|
295
|
+
when String then w.end_with?("%") ? (w.to_f / 100 * total).round : w.to_f
|
|
296
|
+
when Numeric then w.to_f
|
|
297
|
+
else raise ArgumentError, "Invalid width: #{w}"
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## 3.11 Logo
|
|
309
|
+
|
|
310
|
+
No silent failures - raises if file not found.
|
|
311
|
+
|
|
312
|
+
```ruby
|
|
313
|
+
# lib/pdf/components/logo.rb
|
|
314
|
+
# frozen_string_literal: true
|
|
315
|
+
|
|
316
|
+
module Pdf
|
|
317
|
+
module Components
|
|
318
|
+
class Logo < Component
|
|
319
|
+
def render(path, width: 120, height: nil, **_options)
|
|
320
|
+
raise Pdf::FileNotFoundError, "Logo not found: #{path}" unless File.exist?(path)
|
|
321
|
+
|
|
322
|
+
opts = { width: width }
|
|
323
|
+
opts[:height] = height if height
|
|
324
|
+
@pdf.image path, **opts
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## 3.12 QrCode
|
|
334
|
+
|
|
335
|
+
Generates QR code from data using rqrcode gem. No pre-generated image needed.
|
|
336
|
+
|
|
337
|
+
```ruby
|
|
338
|
+
# lib/pdf/components/qr_code.rb
|
|
339
|
+
# frozen_string_literal: true
|
|
340
|
+
|
|
341
|
+
require "rqrcode"
|
|
342
|
+
|
|
343
|
+
module Pdf
|
|
344
|
+
module Components
|
|
345
|
+
class QrCode < Component
|
|
346
|
+
def render(data:, size: 80, position: :right, **_options)
|
|
347
|
+
png_data = generate_qr_png(data, size)
|
|
348
|
+
|
|
349
|
+
if position == :right
|
|
350
|
+
@pdf.float do
|
|
351
|
+
bounding_box([bounds.width - size, cursor], width: size) do
|
|
352
|
+
@pdf.image StringIO.new(png_data), width: size, height: size
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
else
|
|
356
|
+
@pdf.image StringIO.new(png_data), width: size, height: size
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
private
|
|
361
|
+
|
|
362
|
+
def generate_qr_png(data, size)
|
|
363
|
+
qrcode = RQRCode::QRCode.new(data.to_s)
|
|
364
|
+
png = qrcode.as_png(
|
|
365
|
+
bit_depth: 1,
|
|
366
|
+
border_modules: 2,
|
|
367
|
+
color_mode: ChunkyPNG::COLOR_GRAYSCALE,
|
|
368
|
+
color: "black",
|
|
369
|
+
fill: "white",
|
|
370
|
+
size: size * 4 # Higher resolution for quality
|
|
371
|
+
)
|
|
372
|
+
png.to_s
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
|
|
381
|
+
## 3.13 Context
|
|
382
|
+
|
|
383
|
+
```ruby
|
|
384
|
+
# lib/pdf/components/context.rb
|
|
385
|
+
# frozen_string_literal: true
|
|
386
|
+
|
|
387
|
+
module Pdf
|
|
388
|
+
module Components
|
|
389
|
+
class Context < Component
|
|
390
|
+
def render(lines, label: "CONTEXT", **_options)
|
|
391
|
+
raise ArgumentError, "Context lines cannot be nil" if lines.nil?
|
|
392
|
+
return if lines.empty?
|
|
393
|
+
|
|
394
|
+
text label, size: 12, style: :bold
|
|
395
|
+
lines.each { |line| text line.to_s, size: 10 }
|
|
396
|
+
move_down 20
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
end
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
---
|
|
404
|
+
|
|
405
|
+
## Tests (examples)
|
|
406
|
+
|
|
407
|
+
```ruby
|
|
408
|
+
# test/pdf/components/title_test.rb
|
|
409
|
+
# frozen_string_literal: true
|
|
410
|
+
|
|
411
|
+
require "test_helper"
|
|
412
|
+
|
|
413
|
+
class TitleComponentTest < Minitest::Test
|
|
414
|
+
def setup
|
|
415
|
+
@renderer = Pdf::Renderer.new.setup
|
|
416
|
+
@component = Pdf::Components::Title.new(@renderer.pdf)
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
def test_renders_title_text
|
|
420
|
+
@component.render("Hello")
|
|
421
|
+
# Just verify it doesn't raise - actual rendering tested via integration
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
# test/pdf/components/alert_test.rb
|
|
426
|
+
# frozen_string_literal: true
|
|
427
|
+
|
|
428
|
+
require "test_helper"
|
|
429
|
+
|
|
430
|
+
class AlertComponentTest < Minitest::Test
|
|
431
|
+
def setup
|
|
432
|
+
@renderer = Pdf::Renderer.new.setup
|
|
433
|
+
@component = Pdf::Components::Alert.new(@renderer.pdf)
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
def test_renders_without_error
|
|
437
|
+
@component.render(title: "Test", color: "green")
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
def test_defaults_to_blue
|
|
441
|
+
@component.render(title: "Test")
|
|
442
|
+
end
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
# test/pdf/components/table_test.rb
|
|
446
|
+
# frozen_string_literal: true
|
|
447
|
+
|
|
448
|
+
require "test_helper"
|
|
449
|
+
|
|
450
|
+
class TableComponentTest < Minitest::Test
|
|
451
|
+
def setup
|
|
452
|
+
@renderer = Pdf::Renderer.new.setup
|
|
453
|
+
@component = Pdf::Components::Table.new(@renderer.pdf)
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
def test_renders_table
|
|
457
|
+
@component.render(headers: ["A", "B"], rows: [["1", "2"]])
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
def test_calculates_percentage_widths
|
|
461
|
+
@component.render(headers: ["A", "B"], rows: [["1", "2"]], widths: ["50%", "50%"])
|
|
462
|
+
end
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
# test/pdf/components/date_test.rb
|
|
466
|
+
# frozen_string_literal: true
|
|
467
|
+
|
|
468
|
+
require "test_helper"
|
|
469
|
+
|
|
470
|
+
class DateComponentTest < Minitest::Test
|
|
471
|
+
def setup
|
|
472
|
+
@renderer = Pdf::Renderer.new.setup
|
|
473
|
+
@component = Pdf::Components::Date.new(@renderer.pdf)
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
def test_formats_date_objects
|
|
477
|
+
date = ::Date.new(2024, 12, 25)
|
|
478
|
+
@component.render(date)
|
|
479
|
+
# Actual format verified in integration tests
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
def test_accepts_custom_format
|
|
483
|
+
date = ::Date.new(2024, 12, 25)
|
|
484
|
+
@component.render(date, format: "%d/%m/%Y")
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
def test_passes_through_strings
|
|
488
|
+
@component.render("Custom date")
|
|
489
|
+
end
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
# test/pdf/components/logo_test.rb
|
|
493
|
+
# frozen_string_literal: true
|
|
494
|
+
|
|
495
|
+
require "test_helper"
|
|
496
|
+
|
|
497
|
+
class LogoComponentTest < Minitest::Test
|
|
498
|
+
def setup
|
|
499
|
+
@renderer = Pdf::Renderer.new.setup
|
|
500
|
+
@component = Pdf::Components::Logo.new(@renderer.pdf)
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
def test_raises_file_not_found_error
|
|
504
|
+
error = assert_raises(Pdf::FileNotFoundError) do
|
|
505
|
+
@component.render("/nonexistent/logo.png")
|
|
506
|
+
end
|
|
507
|
+
assert_match(/Logo not found/, error.message)
|
|
508
|
+
end
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
# test/pdf/components/qr_code_test.rb
|
|
512
|
+
# frozen_string_literal: true
|
|
513
|
+
|
|
514
|
+
require "test_helper"
|
|
515
|
+
|
|
516
|
+
class QrCodeComponentTest < Minitest::Test
|
|
517
|
+
def setup
|
|
518
|
+
@renderer = Pdf::Renderer.new.setup
|
|
519
|
+
@component = Pdf::Components::QrCode.new(@renderer.pdf)
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
def test_generates_qr_code_from_data
|
|
523
|
+
@component.render(data: "https://example.com")
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
def test_accepts_size_option
|
|
527
|
+
@component.render(data: "test", size: 100)
|
|
528
|
+
end
|
|
529
|
+
end
|
|
530
|
+
|
|
531
|
+
# test/pdf/components/spacer_test.rb
|
|
532
|
+
# frozen_string_literal: true
|
|
533
|
+
|
|
534
|
+
require "test_helper"
|
|
535
|
+
|
|
536
|
+
class SpacerComponentTest < Minitest::Test
|
|
537
|
+
def setup
|
|
538
|
+
@renderer = Pdf::Renderer.new.setup
|
|
539
|
+
@component = Pdf::Components::Spacer.new(@renderer.pdf)
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
def test_moves_down_by_default_amount
|
|
543
|
+
@component.render
|
|
544
|
+
# Default is 20, but we just verify no error
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
def test_accepts_custom_amount
|
|
548
|
+
@component.render(amount: 50)
|
|
549
|
+
end
|
|
550
|
+
end
|
|
551
|
+
```
|