hexapdf-extras 1.1.1 → 1.2.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/README.rdoc +57 -0
- data/lib/hexapdf/extras/graphic_object/zint.rb +228 -0
- data/lib/hexapdf/extras/graphic_object.rb +1 -0
- data/lib/hexapdf/extras/layout/swiss_qr_bill.rb +40 -15
- data/lib/hexapdf/extras/layout/zint_box.rb +56 -0
- data/lib/hexapdf/extras/layout.rb +1 -0
- data/lib/hexapdf/extras/version.rb +1 -1
- data/lib/hexapdf/extras.rb +4 -1
- data/test/hexapdf/extras/graphic_object/test_zint.rb +152 -0
- data/test/hexapdf/extras/layout/test_swiss_qr_bill.rb +20 -2
- data/test/hexapdf/extras/layout/test_zint_box.rb +34 -0
- metadata +23 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 33a96708a4a5370678eefced16946538c63b6a078631d0f5c6b431a997874ff1
|
4
|
+
data.tar.gz: 82dadc4117398db36f731c1aeb0fe2a360f299a1d4cc19ae039297ec39129efa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7182df01f81a1712c18e8145ca27f0a2b33fc05b7c1dedc6f725be302029938afb76d35d2d3c08d15145bdf240963b20dcf1e4e641aed450327d704a3f765e45
|
7
|
+
data.tar.gz: 6473c0cbf68559279f377fdfc2862050d7ebb2ab0521d97c8fe1660321a7636a1a9aed992bc669afac25c8d15f5fbaa9b4b7092a4fff9369afde4bf2ab0fe2aa
|
data/README.rdoc
CHANGED
@@ -36,3 +36,60 @@ for details.
|
|
36
36
|
|
37
37
|
Note: There was a {bug in poppler}[https://gitlab.freedesktop.org/poppler/poppler/-/issues/1281]
|
38
38
|
(already fixed) which leads to invalid rendering in Okular (as of 2022-08-06).
|
39
|
+
|
40
|
+
|
41
|
+
=== Barcode Generator
|
42
|
+
|
43
|
+
This extensions provides access to generating nearly any kind of barcode. It taps into the graphic
|
44
|
+
objects and boxes system of HexaPDF to easily allow creating barcodes:
|
45
|
+
|
46
|
+
require 'hexapdf'
|
47
|
+
require 'hexapdf-extras'
|
48
|
+
|
49
|
+
doc = HexaPDF::Document.new
|
50
|
+
canvas = doc.pages.add.canvas
|
51
|
+
canvas.draw(:barcode, at: [100, 100], width: 300, symbology: :code128, value: "Hello HexaPDF!")
|
52
|
+
doc.write('zint.pdf')
|
53
|
+
|
54
|
+
Underneath the +ruby-zint+ library is used which relies on the libzint library via FFI. This means
|
55
|
+
that you need to install the +ruby-zint+ library for this extension to work.
|
56
|
+
|
57
|
+
See
|
58
|
+
{HexaPDF::Extras::GraphicObject::Zint}[https://hexapdf-extras.gettalong.org/api/HexaPDF/Extras/GraphicObject/Zint.html]
|
59
|
+
and
|
60
|
+
{HexaPDF::Extras::Layout::ZintBox}[https://hexapdf-extras.gettalong.org/api/HexaPDF/Extras/Layout/ZintBox.html]
|
61
|
+
for details.
|
62
|
+
|
63
|
+
|
64
|
+
=== Swiss QR-bill generator
|
65
|
+
|
66
|
+
This extension provides a box class for the document layouting facilities of HexaPDF to easily
|
67
|
+
create a {Swiss QR-bill}[https://www.six-group.com/en/products-services/banking-services/payment-standardization/standards/qr-bill.html]:
|
68
|
+
|
69
|
+
HexaPDF::Composer.create("sample-qr-bill.pdf", margin: 0) do |composer|
|
70
|
+
data = {
|
71
|
+
lang: :de,
|
72
|
+
creditor: {
|
73
|
+
iban: "CH44 3199 9123 0008 8901 2",
|
74
|
+
name: "Max Muster & Söhne",
|
75
|
+
address_line1: "Musterstrasse",
|
76
|
+
address_line2: "123",
|
77
|
+
postal_code: "8000",
|
78
|
+
town: "Seldwyla",
|
79
|
+
country: "CH",
|
80
|
+
},
|
81
|
+
debtor: {
|
82
|
+
address_type: :combined,
|
83
|
+
name: "Simon Muster",
|
84
|
+
address_line1: "Musterstrasse 1",
|
85
|
+
address_line2: "8000 Seldwyla",
|
86
|
+
country: "CH"
|
87
|
+
},
|
88
|
+
amount: 2500.25,
|
89
|
+
currency: 'CHF',
|
90
|
+
}
|
91
|
+
composer.swiss_qr_bill(data: data, style: {valign: :bottom})
|
92
|
+
end
|
93
|
+
|
94
|
+
See {HexaPDF::Extras::Layout::SwissQRBill}[https://hexapdf-extras.gettalong.org/api/HexaPDF/Extras/Layout/SwissQRBill.html]
|
95
|
+
for details.
|
@@ -0,0 +1,228 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'zint'
|
4
|
+
|
5
|
+
module HexaPDF
|
6
|
+
module Extras
|
7
|
+
module GraphicObject
|
8
|
+
|
9
|
+
# Generates a barcode using the {ruby-zint}[https://github.com/eliasfroehner/ruby-zint]
|
10
|
+
# library that uses the libzint barcode generation library.
|
11
|
+
#
|
12
|
+
# It implements the {HexaPDF graphic object
|
13
|
+
# interface}[https://hexapdf.gettalong.org/documentation/api/HexaPDF/Content/GraphicObject/index.html]
|
14
|
+
# and can therefore easily be used via the +:barcode+ name:
|
15
|
+
#
|
16
|
+
# canvas.draw(:barcode, width: 50, at: [10, 40], value: 'Hello!', symbology: :code128)
|
17
|
+
#
|
18
|
+
# Except for a few keyword arguments all are passed through to ruby-zint, so everything that
|
19
|
+
# is supported by Zint::Barcode can be used. To make specifying symbologies easier, it is
|
20
|
+
# possible to use symbolic names instead of the constants, see #configure.
|
21
|
+
#
|
22
|
+
# == Examples
|
23
|
+
#
|
24
|
+
# * Linear barcode
|
25
|
+
#
|
26
|
+
# #>pdf-canvas100
|
27
|
+
# canvas.draw(:barcode, width: 60, at: [20, 45], value: '1123456', symbology: :upce)
|
28
|
+
# canvas.draw(:barcode, width: 60, at: [20, 5], value: 'Hello!', symbology: :code128)
|
29
|
+
#
|
30
|
+
# * Stacked barcode
|
31
|
+
#
|
32
|
+
# #>pdf-canvas100
|
33
|
+
# canvas.draw(:barcode, width: 80, at: [10, 40], symbology: :codablockf,
|
34
|
+
# value: 'Hello HexaPDF!', option_1: 3)
|
35
|
+
#
|
36
|
+
# * Composite barcode
|
37
|
+
#
|
38
|
+
# #>pdf-canvas100
|
39
|
+
# canvas.draw(:barcode, width: 80, at: [10, 20], symbology: :gs1_128_cc,
|
40
|
+
# value: '[99]1234-abcd', primary: "[01]03312345678903", option_1: 3)
|
41
|
+
#
|
42
|
+
# * 2D barcode
|
43
|
+
#
|
44
|
+
# #>pdf-canvas100
|
45
|
+
# canvas.draw(:barcode, width: 80, at: [10, 10], symbology: :datamatrix,
|
46
|
+
# value: 'Hello HexaPDF!', option_3: 100, output_options: 0x0100)
|
47
|
+
class Zint
|
48
|
+
|
49
|
+
# Creates and configures a new Zint drawing support object.
|
50
|
+
#
|
51
|
+
# See #configure for the allowed keyword arguments.
|
52
|
+
def self.configure(**kwargs)
|
53
|
+
new.configure(**kwargs)
|
54
|
+
end
|
55
|
+
|
56
|
+
# The position of the bottom-left corner of the barcode.
|
57
|
+
#
|
58
|
+
# Default: [0, 0].
|
59
|
+
#
|
60
|
+
# Examples:
|
61
|
+
#
|
62
|
+
# #>pdf-canvas100
|
63
|
+
# canvas.draw(:barcode, height: 50, value: 'test', symbology: :code128)
|
64
|
+
# canvas.draw(:barcode, height: 50, value: 'test', symbology: :code128, at: [20, 50])
|
65
|
+
attr_accessor :at
|
66
|
+
|
67
|
+
# The width of resulting barcode.
|
68
|
+
#
|
69
|
+
# The resulting size of the barcode depends on whether width and #height are set:
|
70
|
+
#
|
71
|
+
# * If neither width nor height are set, the barcode uses the size returned by ruby-zint.
|
72
|
+
#
|
73
|
+
# * If both are set, the barcode is fit exactly into the given rectangle.
|
74
|
+
#
|
75
|
+
# * If either width or height is set, the other dimension is based on the set dimension so
|
76
|
+
# that the original aspect ratio is maintained.
|
77
|
+
#
|
78
|
+
# Default: nil.
|
79
|
+
#
|
80
|
+
# Examples:
|
81
|
+
#
|
82
|
+
# * No dimension set
|
83
|
+
#
|
84
|
+
# #>pdf-canvas100
|
85
|
+
# canvas.draw(:barcode, value: 'test', symbology: :code128)
|
86
|
+
#
|
87
|
+
# * One dimension set
|
88
|
+
#
|
89
|
+
# #>pdf-canvas100
|
90
|
+
# canvas.draw(:barcode, width: 60, value: 'test', symbology: :code128)
|
91
|
+
# canvas.draw(:barcode, height: 50, value: 'test', symbology: :code128, at: [0, 50])
|
92
|
+
#
|
93
|
+
# * Both dimensions set
|
94
|
+
#
|
95
|
+
# #>pdf-canvas100
|
96
|
+
# canvas.draw(:barcode, width: 60, height: 60, value: 'test', symbology: :code128)
|
97
|
+
attr_accessor :width
|
98
|
+
|
99
|
+
# The height of the barcode.
|
100
|
+
#
|
101
|
+
# For details and examples see #width.
|
102
|
+
#
|
103
|
+
# Default: nil.
|
104
|
+
attr_accessor :height
|
105
|
+
|
106
|
+
# The font used when outputting strings.
|
107
|
+
#
|
108
|
+
# Any font that is supported by the HexaPDF::Document::Layout module is supported.
|
109
|
+
#
|
110
|
+
# Default: 'Helvetica'.
|
111
|
+
#
|
112
|
+
# Examples:
|
113
|
+
#
|
114
|
+
# #>pdf-canvas100
|
115
|
+
# canvas.draw(:barcode, height: 50, font: 'Courier', value: 'test', symbology: :code128)
|
116
|
+
attr_accessor :font
|
117
|
+
|
118
|
+
# The keyword arguments that are passed on to Zint::Barcode.new.
|
119
|
+
#
|
120
|
+
# Default: {}.
|
121
|
+
attr_accessor :zint_kws
|
122
|
+
|
123
|
+
# Creates a Zint graphic object.
|
124
|
+
def initialize
|
125
|
+
@at = [0, 0]
|
126
|
+
@width = nil
|
127
|
+
@height = nil
|
128
|
+
@font = 'Helvetica'
|
129
|
+
@zint_kws = {}
|
130
|
+
end
|
131
|
+
|
132
|
+
# Configures the Zint graphic object and returns self.
|
133
|
+
#
|
134
|
+
# The following arguments are allowed:
|
135
|
+
#
|
136
|
+
# :at::
|
137
|
+
# The position of the bottom-left corner (see #at).
|
138
|
+
#
|
139
|
+
# :width::
|
140
|
+
# The width of the barcode (see #width).
|
141
|
+
#
|
142
|
+
# :height::
|
143
|
+
# The height of the barcode (see #height).
|
144
|
+
#
|
145
|
+
# :font::
|
146
|
+
# The font to use when outputting strings (see #font).
|
147
|
+
#
|
148
|
+
# :symbology::
|
149
|
+
# The type of barcode. Supports using symbols instead of constants, e.g. +:code128+
|
150
|
+
# instead of Zint::BARCODE_CODE128.
|
151
|
+
#
|
152
|
+
# :zint_kws::
|
153
|
+
# Keyword arguments that are passed on to ruby-zint.
|
154
|
+
#
|
155
|
+
# Any arguments not specified are not modified and retain their old value, see the attribute
|
156
|
+
# methods for the inital default values.
|
157
|
+
def configure(at: nil, width: nil, height: nil, font: nil, symbology: nil, **zint_kws)
|
158
|
+
@at = at if at
|
159
|
+
@width = width if width
|
160
|
+
@height = height if height
|
161
|
+
@font = font if font
|
162
|
+
@zint_kws = zint_kws unless zint_kws.empty?
|
163
|
+
@zint_kws[:symbology] = if symbology && symbology.kind_of?(Symbol)
|
164
|
+
::Zint.const_get("BARCODE_#{symbology.upcase}")
|
165
|
+
elsif symbology
|
166
|
+
symbology
|
167
|
+
end
|
168
|
+
self
|
169
|
+
end
|
170
|
+
|
171
|
+
# Maps the Zint color codes to HexaPDF color names.
|
172
|
+
COLOR_CODES = {
|
173
|
+
1 => "cyan",
|
174
|
+
2 => "blue",
|
175
|
+
3 => "magenta",
|
176
|
+
4 => "red",
|
177
|
+
5 => "yellow",
|
178
|
+
6 => "green",
|
179
|
+
7 => "black",
|
180
|
+
8 => "white",
|
181
|
+
}
|
182
|
+
|
183
|
+
# Draws the Zint::Barcode object onto the given canvas, with the bottom-left corner at the
|
184
|
+
# position specified by #at and the size specified by #width and #height.
|
185
|
+
def draw(canvas)
|
186
|
+
form = form_xobject(canvas.context.document)
|
187
|
+
canvas.xobject(form, at: @at, width: @width, height: @height)
|
188
|
+
end
|
189
|
+
|
190
|
+
# Creates a Form XObject for the given HexaPDF::Document that contains the visual
|
191
|
+
# representation of the barcode.
|
192
|
+
def form_xobject(document)
|
193
|
+
barcode = ::Zint::Barcode.new(**@zint_kws)
|
194
|
+
vector = barcode.to_vector
|
195
|
+
|
196
|
+
height = vector.height
|
197
|
+
form = document.add({Type: :XObject, Subtype: :Form, BBox: [0, 0, vector.width, height]})
|
198
|
+
canvas = form.canvas
|
199
|
+
|
200
|
+
canvas.fill_color(barcode.bgcolour).rectangle(0, 0, vector.width, height).fill
|
201
|
+
vector.each_rectangle.group_by {|rect| rect.colour }.each do |color, rects|
|
202
|
+
canvas.fill_color(COLOR_CODES.fetch(color, barcode.fgcolour))
|
203
|
+
rects.each {|rect| canvas.rectangle(rect.x, height - rect.y, rect.width, -rect.height) }
|
204
|
+
canvas.fill
|
205
|
+
end
|
206
|
+
vector.each_circle.group_by {|circle| circle.colour }.each do |color, circles|
|
207
|
+
canvas.fill_color(COLOR_CODES.fetch(color, barcode.fgcolour))
|
208
|
+
circles.each {|circle| canvas.circle(circle.x, height - circle.y, circle.diameter / 2.0) }
|
209
|
+
canvas.fill
|
210
|
+
end
|
211
|
+
layout = canvas.context.document.layout
|
212
|
+
vector.each_string do |string|
|
213
|
+
fragment = layout.text_fragments(string.text, font: @font, font_size: string.fsize)[0]
|
214
|
+
x = string.x + case string.halign
|
215
|
+
when 0 then -fragment.width / 2.0
|
216
|
+
when 1 then 0
|
217
|
+
when 2 then -fragment.width
|
218
|
+
end
|
219
|
+
fragment.draw(canvas, x, height - string.y)
|
220
|
+
end
|
221
|
+
|
222
|
+
form
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
@@ -4,15 +4,7 @@ require 'hexapdf/error'
|
|
4
4
|
require 'hexapdf/layout/box'
|
5
5
|
require 'hexapdf/extras/layout'
|
6
6
|
|
7
|
-
|
8
|
-
refine Numeric do
|
9
|
-
def mm
|
10
|
-
self * 72 / 25.4
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
using HexaPDF::Extras::Layout::NumericMeasurementHelper
|
7
|
+
using HexaPDF::Utils
|
16
8
|
|
17
9
|
module HexaPDF
|
18
10
|
module Extras
|
@@ -66,8 +58,8 @@ module HexaPDF
|
|
66
58
|
# following elements:
|
67
59
|
#
|
68
60
|
# :iban::
|
69
|
-
# (required) The IBAN of the creditor (21 characters,
|
70
|
-
#
|
61
|
+
# (required) The IBAN of the creditor (21 characters, only IBANs for CH or LI). The
|
62
|
+
# IBAN is only validated with respect to its check digits.
|
71
63
|
#
|
72
64
|
# :name::
|
73
65
|
# (required) The name of the creditor (maximum 70 characters).
|
@@ -158,7 +150,7 @@ module HexaPDF
|
|
158
150
|
# amount: 2500.25,
|
159
151
|
# currency: 'CHF',
|
160
152
|
# }
|
161
|
-
# composer.swiss_qr_bill(data: data, valign: :bottom)
|
153
|
+
# composer.swiss_qr_bill(data: data, style: {valign: :bottom})
|
162
154
|
# end
|
163
155
|
#
|
164
156
|
# == References
|
@@ -262,6 +254,25 @@ module HexaPDF
|
|
262
254
|
raise Error, "Data field :amount must be between 0.01 and 999_999_999.99"
|
263
255
|
end
|
264
256
|
|
257
|
+
if !@data[:creditor]
|
258
|
+
raise Error, "Data field :creditor is missing"
|
259
|
+
end
|
260
|
+
|
261
|
+
value = @data[:creditor][:iban]
|
262
|
+
if !value
|
263
|
+
raise Error, "Data field :iban of :creditor is missing"
|
264
|
+
end
|
265
|
+
value.gsub!(/\s+/, '')
|
266
|
+
value.upcase!
|
267
|
+
if value.size != 21
|
268
|
+
raise Error, "Data field :iban of :creditor must contain exactly 21 characters"
|
269
|
+
end
|
270
|
+
# https://en.wikipedia.org/wiki/International_Bank_Account_Number#Validating_the_IBAN
|
271
|
+
result = "#{value[4..-1]}#{value[0, 4]}".gsub(/[A-Z]/) {|c| c.ord - 55 }.to_i % 97
|
272
|
+
unless result == 1
|
273
|
+
raise Error, "Data field :iban of :creditor has invalid check digits"
|
274
|
+
end
|
275
|
+
|
265
276
|
validate_address = lambda do |hash|
|
266
277
|
hash[:address_type] ||= :structured
|
267
278
|
if hash[:address_type] != :structured && hash[:address_type] != :combined
|
@@ -452,7 +463,7 @@ module HexaPDF
|
|
452
463
|
col.text(text('Receipt'), height: 7.mm, style: styles[:section_heading])
|
453
464
|
col.container(height: 56.mm) do |info|
|
454
465
|
info.text(text('Account / Payable to'), style: styles[:receipt_heading])
|
455
|
-
info.text("#{
|
466
|
+
info.text("#{formatted_iban}\n#{address(@data[:creditor])}", style: styles[:receipt_value])
|
456
467
|
|
457
468
|
if @data[:reference_type] != 'NON'
|
458
469
|
info.text(text('Reference'), style: styles[:receipt_heading])
|
@@ -506,11 +517,11 @@ module HexaPDF
|
|
506
517
|
end
|
507
518
|
col.container(height: 85.mm) do |info|
|
508
519
|
info.text(text('Account / Payable to'), style: styles[:payment_heading])
|
509
|
-
info.text("#{
|
520
|
+
info.text("#{formatted_iban}\n#{address(@data[:creditor])}", style: styles[:payment_value])
|
510
521
|
|
511
522
|
if @data[:reference_type] != 'NON'
|
512
523
|
info.text(text('Reference'), style: styles[:payment_heading])
|
513
|
-
info.text(
|
524
|
+
info.text(formatted_reference, style: styles[:payment_value])
|
514
525
|
end
|
515
526
|
|
516
527
|
if @data[:message] || @data[:billing_information]
|
@@ -563,6 +574,11 @@ module HexaPDF
|
|
563
574
|
TEXT_LITERALS.dig(@data[:lang], str) || str
|
564
575
|
end
|
565
576
|
|
577
|
+
# Returns the formatted IBAN.
|
578
|
+
def formatted_iban
|
579
|
+
@data[:creditor][:iban].gsub(/(.{4})/, '\1 ')
|
580
|
+
end
|
581
|
+
|
566
582
|
# Returns a string containing the formatted address for output using the provided data.
|
567
583
|
def address(data)
|
568
584
|
result = +''
|
@@ -584,6 +600,15 @@ module HexaPDF
|
|
584
600
|
a.gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1 ") << '.' << b
|
585
601
|
end
|
586
602
|
|
603
|
+
# Returns the reference field correctly formatted.
|
604
|
+
def formatted_reference
|
605
|
+
if @data[:reference_type] == 'QRR'
|
606
|
+
"#{@data[:reference][0, 2]} #{@data[:reference][2..-1].gsub(/(.{5})/, '\1 ').strip}"
|
607
|
+
else
|
608
|
+
@data[:reference].gsub(/(.{4})/, '\1 ').strip
|
609
|
+
end
|
610
|
+
end
|
611
|
+
|
587
612
|
# Creates the content of the QR code using the information provided in #data.
|
588
613
|
def qr_code_data
|
589
614
|
qr_code_data = []
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'hexapdf/layout/box'
|
4
|
+
require 'hexapdf/extras/graphic_object/zint'
|
5
|
+
|
6
|
+
module HexaPDF
|
7
|
+
module Extras
|
8
|
+
module Layout
|
9
|
+
|
10
|
+
# A ZintBox object is used for displaying a barcode.
|
11
|
+
#
|
12
|
+
# Internally, GraphicObject::Zint is used, so any option except +at+, +width+ and +height+
|
13
|
+
# supported there can be used here.
|
14
|
+
#
|
15
|
+
# The size of the barcode is determined by the width and height of the box. For details on how
|
16
|
+
# this works see GraphicObject::Zint#width.
|
17
|
+
#
|
18
|
+
# Example:
|
19
|
+
#
|
20
|
+
# #>pdf-composer100
|
21
|
+
# composer.box(:barcode, height: 30, style: {position: :float},
|
22
|
+
# data: {value: 'Test', symbology: :qrcode})
|
23
|
+
# composer.box(:barcode, width: 60, style: {position: :float},
|
24
|
+
# data: {value: 'Test', symbology: :code128, bgcolour: 'ff0000',
|
25
|
+
# fgcolour: '00ffff'})
|
26
|
+
# composer.box(:barcode, width: 30, height: 50, style: {position: :float},
|
27
|
+
# data: {value: 'Test', symbology: :code128})
|
28
|
+
# composer.box(:barcode, data: {value: 'Test', symbology: :code128})
|
29
|
+
class ZintBox < HexaPDF::Layout::ImageBox
|
30
|
+
|
31
|
+
# The HexaPDF::Extras::GraphicObject::Zint object that will be drawn.
|
32
|
+
attr_reader :barcode
|
33
|
+
|
34
|
+
# Creates a new ZintBox object with the given arguments.
|
35
|
+
#
|
36
|
+
# The argument +data+ needs to contain a hash with the arguments that are passed on to
|
37
|
+
# GraphicObject::Zint.
|
38
|
+
#
|
39
|
+
# Note: Although this box derives from ImageBox, the #image method will only return the
|
40
|
+
# correct object after #fit was called.
|
41
|
+
def initialize(data:, **kwargs)
|
42
|
+
super(image: nil, **kwargs)
|
43
|
+
@barcode = GraphicObject::Zint.configure(**data)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Fits the barcode into the given area.
|
47
|
+
def fit(available_width, available_height, frame)
|
48
|
+
@image ||= @barcode.form_xobject(frame.document)
|
49
|
+
super
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/hexapdf/extras.rb
CHANGED
@@ -11,10 +11,13 @@ end
|
|
11
11
|
|
12
12
|
HexaPDF::DefaultDocumentConfiguration['graphic_object.map'][:qrcode] =
|
13
13
|
'HexaPDF::Extras::GraphicObject::QRCode'
|
14
|
+
HexaPDF::DefaultDocumentConfiguration['graphic_object.map'][:barcode] =
|
15
|
+
'HexaPDF::Extras::GraphicObject::Zint'
|
14
16
|
|
15
17
|
HexaPDF::DefaultDocumentConfiguration['layout.boxes.map'][:qrcode] =
|
16
18
|
'HexaPDF::Extras::Layout::QRCodeBox'
|
17
|
-
|
19
|
+
HexaPDF::DefaultDocumentConfiguration['layout.boxes.map'][:barcode] =
|
20
|
+
'HexaPDF::Extras::Layout::ZintBox'
|
18
21
|
HexaPDF::DefaultDocumentConfiguration['layout.boxes.map'][:swiss_qr_bill] =
|
19
22
|
'HexaPDF::Extras::Layout::SwissQRBill'
|
20
23
|
|
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'hexapdf'
|
3
|
+
require 'hexapdf/extras/graphic_object/zint'
|
4
|
+
|
5
|
+
describe HexaPDF::Extras::GraphicObject::Zint do
|
6
|
+
before do
|
7
|
+
@obj = HexaPDF::Extras::GraphicObject::Zint.new
|
8
|
+
end
|
9
|
+
|
10
|
+
it "allows creation via the ::configure method" do
|
11
|
+
obj = HexaPDF::Extras::GraphicObject::Zint.configure(value: 'test')
|
12
|
+
assert_equal('test', obj.zint_kws[:value])
|
13
|
+
end
|
14
|
+
|
15
|
+
it "creates a default drawing support object" do
|
16
|
+
assert_equal([0, 0], @obj.at)
|
17
|
+
assert_nil(@obj.width)
|
18
|
+
assert_nil(@obj.height)
|
19
|
+
assert_equal('Helvetica', @obj.font)
|
20
|
+
assert_equal({}, @obj.zint_kws)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "allows configuration of the object" do
|
24
|
+
assert_same(@obj, @obj.configure(value: 'test', width: 30, symbology: :code128))
|
25
|
+
assert_equal('test', @obj.zint_kws[:value])
|
26
|
+
assert_equal(::Zint::BARCODE_CODE128, @obj.zint_kws[:symbology])
|
27
|
+
assert_equal(30, @obj.width)
|
28
|
+
@obj.configure(symbology: ::Zint::BARCODE_DATAMATRIX)
|
29
|
+
assert_equal(::Zint::BARCODE_DATAMATRIX, @obj.zint_kws[:symbology])
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "draw" do
|
33
|
+
before do
|
34
|
+
doc = HexaPDF::Document.new
|
35
|
+
@canvas = doc.pages.add.canvas
|
36
|
+
end
|
37
|
+
|
38
|
+
it "draws a barcode onto the canvas" do
|
39
|
+
@obj.configure(value: 'test', width: 50, symbology: :code128)
|
40
|
+
@obj.draw(@canvas)
|
41
|
+
assert_operators(@canvas.contents,
|
42
|
+
[[:save_graphics_state],
|
43
|
+
[:concatenate_matrix, [0.316456, 0, 0, 0.316456, 0, 0]],
|
44
|
+
[:paint_xobject, [:XO1]],
|
45
|
+
[:restore_graphics_state]])
|
46
|
+
assert_operators(@canvas.context.resources.xobject(:XO1).contents,
|
47
|
+
[[:set_device_rgb_non_stroking_color, [1.0, 1.0, 1.0]],
|
48
|
+
[:append_rectangle, [0, 0, 158.0, 118.900002]],
|
49
|
+
[:fill_path_non_zero],
|
50
|
+
[:set_device_rgb_non_stroking_color, [0.0, 0.0, 0.0]],
|
51
|
+
[:append_rectangle, [0.0, 118.900002, 4.0, -100.0]],
|
52
|
+
[:append_rectangle, [6.0, 118.900002, 2.0, -100.0]],
|
53
|
+
[:append_rectangle, [12.0, 118.900002, 2.0, -100.0]],
|
54
|
+
[:append_rectangle, [22.0, 118.900002, 2.0, -100.0]],
|
55
|
+
[:append_rectangle, [28.0, 118.900002, 8.0, -100.0]],
|
56
|
+
[:append_rectangle, [38.0, 118.900002, 2.0, -100.0]],
|
57
|
+
[:append_rectangle, [44.0, 118.900002, 2.0, -100.0]],
|
58
|
+
[:append_rectangle, [48.0, 118.900002, 4.0, -100.0]],
|
59
|
+
[:append_rectangle, [56.0, 118.900002, 2.0, -100.0]],
|
60
|
+
[:append_rectangle, [66.0, 118.900002, 2.0, -100.0]],
|
61
|
+
[:append_rectangle, [70.0, 118.900002, 8.0, -100.0]],
|
62
|
+
[:append_rectangle, [82.0, 118.900002, 2.0, -100.0]],
|
63
|
+
[:append_rectangle, [88.0, 118.900002, 2.0, -100.0]],
|
64
|
+
[:append_rectangle, [94.0, 118.900002, 8.0, -100.0]],
|
65
|
+
[:append_rectangle, [104.0, 118.900002, 2.0, -100.0]],
|
66
|
+
[:append_rectangle, [110.0, 118.900002, 8.0, -100.0]],
|
67
|
+
[:append_rectangle, [122.0, 118.900002, 2.0, -100.0]],
|
68
|
+
[:append_rectangle, [126.0, 118.900002, 2.0, -100.0]],
|
69
|
+
[:append_rectangle, [132.0, 118.900002, 4.0, -100.0]],
|
70
|
+
[:append_rectangle, [142.0, 118.900002, 6.0, -100.0]],
|
71
|
+
[:append_rectangle, [150.0, 118.900002, 2.0, -100.0]],
|
72
|
+
[:append_rectangle, [154.0, 118.900002, 4.0, -100.0]],
|
73
|
+
[:fill_path_non_zero],
|
74
|
+
[:set_font_and_size, [:F1, 14.0]],
|
75
|
+
[:set_device_gray_non_stroking_color, [0.0]],
|
76
|
+
[:begin_text],
|
77
|
+
[:set_text_matrix, [1, 0, 0, 1, 67.716, 3.5]],
|
78
|
+
[:show_text, ["test"]],
|
79
|
+
[:end_text]])
|
80
|
+
end
|
81
|
+
|
82
|
+
it "supports all string alignments" do
|
83
|
+
@obj.configure(value: '1123456', width: 50, symbology: :upce)
|
84
|
+
@obj.draw(@canvas)
|
85
|
+
assert_operators(@canvas.context.resources.xobject(:XO1).contents,
|
86
|
+
[[:set_device_rgb_non_stroking_color, [1.0, 1.0, 1.0]],
|
87
|
+
[:append_rectangle, [0, 0, 134.0, 116.400002]],
|
88
|
+
[:fill_path_non_zero],
|
89
|
+
[:set_device_rgb_non_stroking_color, [0.0, 0.0, 0.0]],
|
90
|
+
[:append_rectangle, [18.0, 116.400002, 2.0, -110.0]],
|
91
|
+
[:append_rectangle, [22.0, 116.400002, 2.0, -110.0]],
|
92
|
+
[:append_rectangle, [28.0, 116.400002, 4.0, -100.0]],
|
93
|
+
[:append_rectangle, [36.0, 116.400002, 2.0, -100.0]],
|
94
|
+
[:append_rectangle, [42.0, 116.400002, 2.0, -100.0]],
|
95
|
+
[:append_rectangle, [48.0, 116.400002, 4.0, -100.0]],
|
96
|
+
[:append_rectangle, [54.0, 116.400002, 2.0, -100.0]],
|
97
|
+
[:append_rectangle, [64.0, 116.400002, 2.0, -100.0]],
|
98
|
+
[:append_rectangle, [70.0, 116.400002, 6.0, -100.0]],
|
99
|
+
[:append_rectangle, [78.0, 116.400002, 2.0, -100.0]],
|
100
|
+
[:append_rectangle, [82.0, 116.400002, 4.0, -100.0]],
|
101
|
+
[:append_rectangle, [92.0, 116.400002, 2.0, -100.0]],
|
102
|
+
[:append_rectangle, [102.0, 116.400002, 2.0, -100.0]],
|
103
|
+
[:append_rectangle, [106.0, 116.400002, 2.0, -100.0]],
|
104
|
+
[:append_rectangle, [110.0, 116.400002, 2.0, -110.0]],
|
105
|
+
[:append_rectangle, [114.0, 116.400002, 2.0, -110.0]],
|
106
|
+
[:append_rectangle, [118.0, 116.400002, 2.0, -110.0]],
|
107
|
+
[:fill_path_non_zero],
|
108
|
+
[:set_font_and_size, [:F1, 14.0]],
|
109
|
+
[:set_device_gray_non_stroking_color, [0.0]],
|
110
|
+
[:begin_text],
|
111
|
+
[:set_text_matrix, [1, 0, 0, 1, 0.216, 0.400002]],
|
112
|
+
[:show_text, ["1"]],
|
113
|
+
[:set_font_and_size, [:F1, 20.0]],
|
114
|
+
[:move_text, [32.424, 0]],
|
115
|
+
[:show_text, ["123456"]],
|
116
|
+
[:set_font_and_size, [:F1, 14.0]],
|
117
|
+
[:move_text, [93.36, 0]],
|
118
|
+
[:show_text, ["2"]],
|
119
|
+
[:end_text]])
|
120
|
+
end
|
121
|
+
|
122
|
+
it "supports dotty mode" do
|
123
|
+
@obj.configure(value: 't', width: 50, symbology: :datamatrix, output_options: 0x0100)
|
124
|
+
@obj.draw(@canvas)
|
125
|
+
form_contents = @canvas.context.resources.xobject(:XO1).contents
|
126
|
+
assert_operators(form_contents,
|
127
|
+
[[:set_device_rgb_non_stroking_color, [1.0, 1.0, 1.0]],
|
128
|
+
[:append_rectangle, [0, 0, 20, 20]],
|
129
|
+
[:fill_path_non_zero],
|
130
|
+
[:set_device_rgb_non_stroking_color, [0.0, 0.0, 0.0]],
|
131
|
+
[:move_to, [1.8, 19.0]],
|
132
|
+
[:curve_to, [1.8, 19.285458, 1.647214, 19.550091, 1.4, 19.69282]],
|
133
|
+
[:curve_to, [1.152786, 19.835549, 0.847214, 19.835549, 0.6, 19.69282]],
|
134
|
+
[:curve_to, [0.352786, 19.550091, 0.2, 19.285458, 0.2, 19.0]],
|
135
|
+
[:curve_to, [0.2, 18.714542, 0.352786, 18.449909, 0.6, 18.30718]],
|
136
|
+
[:curve_to, [0.847214, 18.164451, 1.152786, 18.164451, 1.4, 18.30718]],
|
137
|
+
[:curve_to, [1.647214, 18.449909, 1.8, 18.714542, 1.8, 19.0]],
|
138
|
+
[:close_subpath],
|
139
|
+
[:move_to, [5.8, 19]]], range: 0..12)
|
140
|
+
assert_operators(form_contents,
|
141
|
+
[[:move_to, [19.8, 1.0]],
|
142
|
+
[:curve_to, [19.8, 1.285458, 19.647214, 1.550091, 19.4, 1.69282]],
|
143
|
+
[:curve_to, [19.152786, 1.835549, 18.847214, 1.835549, 18.6, 1.69282]],
|
144
|
+
[:curve_to, [18.352786, 1.550091, 18.2, 1.285458, 18.2, 1.0]],
|
145
|
+
[:curve_to, [18.2, 0.714542, 18.352786, 0.449909, 18.6, 0.30718]],
|
146
|
+
[:curve_to, [18.847214, 0.164451, 19.152786, 0.164451, 19.4, 0.30718]],
|
147
|
+
[:curve_to, [19.647214, 0.449909, 19.8, 0.714542, 19.8, 1.0]],
|
148
|
+
[:close_subpath],
|
149
|
+
[:fill_path_non_zero]], range: -9..-1)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -3,7 +3,7 @@ require 'hexapdf'
|
|
3
3
|
require 'hexapdf/extras'
|
4
4
|
require 'hexapdf/extras/layout/swiss_qr_bill'
|
5
5
|
|
6
|
-
using HexaPDF::
|
6
|
+
using HexaPDF::Utils
|
7
7
|
|
8
8
|
describe HexaPDF::Extras::Layout::SwissQRBill do
|
9
9
|
def create_box(data, **kwargs)
|
@@ -13,7 +13,7 @@ describe HexaPDF::Extras::Layout::SwissQRBill do
|
|
13
13
|
def data
|
14
14
|
@data ||= {
|
15
15
|
creditor: {
|
16
|
-
iban: "
|
16
|
+
iban: "CH4431999123000889012",
|
17
17
|
name: "Max Muster & Söhne",
|
18
18
|
address_line1: "Musterstrasse",
|
19
19
|
address_line2: "123",
|
@@ -64,6 +64,22 @@ describe HexaPDF::Extras::Layout::SwissQRBill do
|
|
64
64
|
assert_equal('NICHT ZUR ZAHLUNG VERWENDEN', create_box(data).data[:message])
|
65
65
|
end
|
66
66
|
|
67
|
+
it "ensures the creditor value exists" do
|
68
|
+
data.delete(:creditor)
|
69
|
+
assert_invalid_data(/:creditor is missing/)
|
70
|
+
end
|
71
|
+
|
72
|
+
it "ensures a correct iban value in the creditor field" do
|
73
|
+
data[:creditor].delete(:iban)
|
74
|
+
assert_invalid_data(/:iban of :creditor is missing/)
|
75
|
+
data[:creditor][:iban] = 'CH44 319 39912300088901 2'
|
76
|
+
assert_invalid_data(/:iban of :creditor.*21/)
|
77
|
+
data[:creditor][:iban] = 'CH4431999123000889013'
|
78
|
+
assert_invalid_data(/:iban of :creditor.*invalid check digits/)
|
79
|
+
data[:creditor][:iban] = 'CH4431999123000889012'
|
80
|
+
assert(create_box(data))
|
81
|
+
end
|
82
|
+
|
67
83
|
it "sets the address type to structured by default" do
|
68
84
|
assert_equal(:structured, create_box(data, width: 10, height: 15).data[:creditor][:address_type])
|
69
85
|
end
|
@@ -220,6 +236,8 @@ describe HexaPDF::Extras::Layout::SwissQRBill do
|
|
220
236
|
it "works with no amount and no debtor" do
|
221
237
|
data.delete(:debtor)
|
222
238
|
data.delete(:amount)
|
239
|
+
data[:reference_type] = 'SCOR'
|
240
|
+
data[:reference] = 'RF48 5000056789012345'
|
223
241
|
assert(@composer.box(:swiss_qr_bill, data: data))
|
224
242
|
end
|
225
243
|
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'hexapdf'
|
3
|
+
require 'hexapdf/extras/layout/zint_box'
|
4
|
+
|
5
|
+
describe HexaPDF::Extras::Layout::ZintBox do
|
6
|
+
def create_box(**kwargs)
|
7
|
+
HexaPDF::Extras::Layout::ZintBox.new(**kwargs)
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "initialize" do
|
11
|
+
it "takes the common box arguments" do
|
12
|
+
box = create_box(width: 10, height: 15, data: {})
|
13
|
+
assert_equal(10, box.width)
|
14
|
+
assert_equal(15, box.height)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "creates the zint barcode graphic object" do
|
18
|
+
box = create_box(data: {value: 'test', symbology: :code128})
|
19
|
+
assert_equal({value: 'test', symbology: 20}, box.barcode.zint_kws)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "fit" do
|
24
|
+
it "creates the form xobject and uses that as image for its superclass" do
|
25
|
+
doc = HexaPDF::Document.new
|
26
|
+
frame = HexaPDF::Layout::Frame.new(0, 0, 100, 100, context: doc.pages.add)
|
27
|
+
|
28
|
+
box = create_box(data: {value: 'test', symbology: :code128})
|
29
|
+
assert_nil(box.image)
|
30
|
+
box.fit(100, 100, frame)
|
31
|
+
assert_equal(:Form, box.image[:Subtype])
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hexapdf-extras
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Thomas Leitner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-05-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: hexapdf
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '0.
|
19
|
+
version: '0.42'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '0.
|
26
|
+
version: '0.42'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rqrcode_core
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '1.2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: ruby-zint
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.3'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.3'
|
41
55
|
description:
|
42
56
|
email: t_leitner@gmx.at
|
43
57
|
executables: []
|
@@ -51,13 +65,17 @@ files:
|
|
51
65
|
- lib/hexapdf/extras.rb
|
52
66
|
- lib/hexapdf/extras/graphic_object.rb
|
53
67
|
- lib/hexapdf/extras/graphic_object/qr_code.rb
|
68
|
+
- lib/hexapdf/extras/graphic_object/zint.rb
|
54
69
|
- lib/hexapdf/extras/layout.rb
|
55
70
|
- lib/hexapdf/extras/layout/qr_code_box.rb
|
56
71
|
- lib/hexapdf/extras/layout/swiss_qr_bill.rb
|
72
|
+
- lib/hexapdf/extras/layout/zint_box.rb
|
57
73
|
- lib/hexapdf/extras/version.rb
|
58
74
|
- test/hexapdf/extras/graphic_object/test_qr_code.rb
|
75
|
+
- test/hexapdf/extras/graphic_object/test_zint.rb
|
59
76
|
- test/hexapdf/extras/layout/test_qr_code_box.rb
|
60
77
|
- test/hexapdf/extras/layout/test_swiss_qr_bill.rb
|
78
|
+
- test/hexapdf/extras/layout/test_zint_box.rb
|
61
79
|
- test/test_helper.rb
|
62
80
|
homepage: https://hexapdf-extras.gettalong.org
|
63
81
|
licenses:
|
@@ -71,7 +89,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
71
89
|
requirements:
|
72
90
|
- - ">="
|
73
91
|
- !ruby/object:Gem::Version
|
74
|
-
version: '2.
|
92
|
+
version: '2.7'
|
75
93
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
94
|
requirements:
|
77
95
|
- - ">="
|