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 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
  - - ">="