hexapdf-extras 1.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8e7fe626cf7fd851cd203b739c69bdd193b5debffcd129c656d59b51c2d326ad
4
- data.tar.gz: 588a1a744477c437035ca66fd187f4c3563b135946efb8a21d5f73e77f15d238
3
+ metadata.gz: 33a96708a4a5370678eefced16946538c63b6a078631d0f5c6b431a997874ff1
4
+ data.tar.gz: 82dadc4117398db36f731c1aeb0fe2a360f299a1d4cc19ae039297ec39129efa
5
5
  SHA512:
6
- metadata.gz: fb195eef642905d2797b96c545f63eaee7edff117982e16e8c3965d05c9abdf537331d35cfa09df6d9aebac3d03e4a28dbb921714bc3a4968984789f2f4548ea
7
- data.tar.gz: 8682207937162bef5c86ca5f0f061dbb25469b989e65fa51dc4fbe7f3bc75739cc382911efee4c954e30a14650322bd870aa308e56436a1e18a665e0f4174a9d
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,6 +4,7 @@ module HexaPDF
4
4
  module Extras
5
5
  module GraphicObject
6
6
  autoload(:QRCode, 'hexapdf/extras/graphic_object/qr_code')
7
+ autoload(:Zint, 'hexapdf/extras/graphic_object/zint')
7
8
  end
8
9
  end
9
10
  end
@@ -4,15 +4,7 @@ require 'hexapdf/error'
4
4
  require 'hexapdf/layout/box'
5
5
  require 'hexapdf/extras/layout'
6
6
 
7
- module HexaPDF::Extras::Layout::NumericMeasurementHelper #:nodoc:
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, no spaces, only IBANs for CH or
70
- # LI). Note that the IBAN is not checked for validity.
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("#{@data[:creditor][:iban]}\n#{address(@data[:creditor])}", style: styles[:receipt_value])
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("#{@data[:creditor][:iban]}\n#{address(@data[:creditor])}", style: styles[:payment_value])
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(@data[:reference], style: styles[:payment_value])
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
@@ -5,6 +5,7 @@ module HexaPDF
5
5
  module Layout
6
6
  autoload(:QRCodeBox, 'hexapdf/extras/layout/qr_code_box')
7
7
  autoload(:SwissQRBill, 'hexapdf/extras/layout/swiss_qr_bill')
8
+ autoload(:ZintBox, 'hexapdf/extras/layout/zint_box')
8
9
  end
9
10
  end
10
11
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module HexaPDF
4
4
  module Extras
5
- VERSION = '1.1.1'
5
+ VERSION = '1.2.0'
6
6
  end
7
7
  end
@@ -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::Extras::Layout::NumericMeasurementHelper
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: "CH44 3199 9123 0008 8901 2",
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.1.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-01-20 00:00:00.000000000 Z
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.36'
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.36'
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.5'
92
+ version: '2.7'
75
93
  required_rubygems_version: !ruby/object:Gem::Requirement
76
94
  requirements:
77
95
  - - ">="