hexapdf 1.11.0 → 1.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: da17a2a9dd8459e6f4624f59621c84f1b0be73f4d23347bffcdd6c534b58680e
4
- data.tar.gz: ecdcfedc4f89722f799d04fda467acbed24bf256a1fa626623fce1d6399d6d98
3
+ metadata.gz: 8cf7d72b14ba19f840f63adbcfa3eb5b1fd7b876731c52c615788f395d23d10d
4
+ data.tar.gz: 48d506505dcc50d5cc5c596f7e11b8d0961dbea82cea7223c1ae8d705bb9b57e
5
5
  SHA512:
6
- metadata.gz: 5a39a7635005c7ef8be1dc0a1efc21b1aca00fba3bedb80886e5d909d9fc0ebfed0f967fff6f10f96ed6b05a883fee0337fea3802bfcde3a93a644e512d0ee3b
7
- data.tar.gz: 4e9ccdbe854d0e5fb00e7042d61c758da93c5b354982b48a20f4a6cec3a07d209dac5711d1eb9b3b6274d8662acac8851124a838bed5795cc6e40c924f4bba26
6
+ metadata.gz: 47be93a799dd59bcab3fcb92c150a0e1b43b8b115dc4d1b38fc029907222a856dde0e717309c85c312fbd3351f0483ccd50f72194f5fce1352eeb1330d92ef39
7
+ data.tar.gz: 154edc9c8bf7f2e4f3e354c38c5b52a6f1099760ace8d86c4feb38e0092d0dd0ee3630f615196890e98db339204e636b43fdaac9dd2884b21b36780b59da7bde
data/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ ## 1.11.1 - 2026-10-06
2
+
3
+ ### Changed
4
+
5
+ * [HexaPDF::Type::OptionalContentGroup] methods `#apply_intent`, `#on!`,
6
+ `#off!`, `#add_to_ui` to return self
7
+
8
+ ### Fixed
9
+
10
+ * [HexaPDF::Type::AcroForm::Form] validation to also remove objects with an
11
+ invalid /Type field
12
+
13
+
1
14
  ## 1.11.0 - 2026-09-22
2
15
 
3
16
  ### Added
@@ -21,13 +21,14 @@ HexaPDF::Composer.create('composer_optional_content.pdf') do |composer|
21
21
  answer: {font: 'ZapfDingbats', fill_color: "green"},
22
22
  )
23
23
 
24
- all = composer.document.optional_content.ocg('All answers')
25
- a1 = composer.document.optional_content.ocg('Answer 1')
26
- a1m = composer.document.optional_content.create_ocmd([a1, all], policy: :any_on)
27
- a2 = composer.document.optional_content.ocg('Answer 2')
28
- a2m = composer.document.optional_content.create_ocmd([a2, all], policy: :any_on)
29
- a3 = composer.document.optional_content.ocg('Answer 3')
30
- a3m = composer.document.optional_content.create_ocmd([a3, all], policy: :any_on)
24
+ oc = composer.document.optional_content
25
+ all = oc.ocg('All answers').off!.add_to_ui
26
+ a1 = oc.ocg('Answer 1').off!.add_to_ui
27
+ a2 = oc.ocg('Answer 2').off!.add_to_ui
28
+ a3 = oc.ocg('Answer 3').off!.add_to_ui
29
+ a1m = oc.create_ocmd([a1, all], policy: :any_on)
30
+ a2m = oc.create_ocmd([a2, all], policy: :any_on)
31
+ a3m = oc.create_ocmd([a3, all], policy: :any_on)
31
32
 
32
33
  composer.text('The Great Ruby Quiz', text_align: :center, margin: [0, 0, 24],
33
34
  font: 'Helvetica bold', font_size: 24)
@@ -41,7 +42,7 @@ HexaPDF::Composer.create('composer_optional_content.pdf') do |composer|
41
42
  answers.multiple do |answer|
42
43
  answer.text('Yukihiro “Matz” Matsumoto', position: :float)
43
44
  answer.text("\u{a0}\u{a0}✔", style: :answer,
44
- properties: {'optional_content' => a1})
45
+ properties: {'optional_content' => a1m})
45
46
  end
46
47
  answers.text('Rob Pike')
47
48
  end
@@ -57,7 +58,7 @@ HexaPDF::Composer.create('composer_optional_content.pdf') do |composer|
57
58
  answers.multiple do |answer|
58
59
  answer.text('1993', position: :float)
59
60
  answer.text("\u{a0}\u{a0}✔", style: :answer,
60
- properties: {'optional_content' => a2})
61
+ properties: {'optional_content' => a2m})
61
62
  end
62
63
  end
63
64
  end
@@ -68,7 +69,7 @@ HexaPDF::Composer.create('composer_optional_content.pdf') do |composer|
68
69
  answer = composer.document.layout.text('There are several PDF libraries for ' \
69
70
  'Ruby but the best is HexaPDF! :)',
70
71
  width: 400,
71
- properties: {'optional_content' => a3})
72
+ properties: {'optional_content' => a3m})
72
73
  item.formatted_text([{box: answer}], border: {width: [0, 0, 1]})
73
74
  end
74
75
  end
@@ -77,9 +78,4 @@ HexaPDF::Composer.create('composer_optional_content.pdf') do |composer|
77
78
  action.add_state_change(:toggle, [a1, a2, a3])
78
79
  composer.text("Click to toggle answers", border: {width: 1, color: "red"},
79
80
  align: :right, padding: 2, overlays: [[:link, action: action]])
80
-
81
- composer.document.optional_content.default_configuration(
82
- BaseState: :OFF,
83
- Order: [all, a1, a2, a3],
84
- )
85
81
  end
@@ -346,33 +346,49 @@ module HexaPDF
346
346
  def separate_alpha_channel(offset, decode_parms)
347
347
  bytes_per_colors = (decode_parms[:BitsPerComponent] * decode_parms[:Colors] + 7) / 8
348
348
  bytes_per_alpha = (decode_parms[:BitsPerComponent] + 7) / 8
349
+ bytes_per_pixel = bytes_per_colors + bytes_per_alpha
349
350
  bytes_per_row = (decode_parms[:Columns] * decode_parms[:BitsPerComponent] *
350
351
  (decode_parms[:Colors] + 1) + 7) / 8 + 1
351
- image_data = ''.b
352
- mask_data = ''.b
353
352
 
354
353
  flate_decode = @document.config.constantize('filter.map', :FlateDecode)
355
354
  source = flate_decode.decoder(Fiber.new(&image_data_proc(offset)))
356
355
 
356
+ image_deflate = Zlib::Deflate.new(HexaPDF::GlobalConfiguration['filter.flate_compression'],
357
+ Zlib::MAX_WBITS,
358
+ HexaPDF::GlobalConfiguration['filter.flate_memory'])
359
+ mask_deflate = Zlib::Deflate.new(HexaPDF::GlobalConfiguration['filter.flate_compression'],
360
+ Zlib::MAX_WBITS,
361
+ HexaPDF::GlobalConfiguration['filter.flate_memory'])
362
+
363
+ image_data = ''.b
364
+ mask_data = ''.b
365
+ image_temp = ''.b
366
+ mask_temp = ''.b
357
367
  data = ''.b
358
368
  while source.alive? && (new_data = source.resume)
359
369
  data << new_data
360
- while data.length >= bytes_per_row
361
- i = 1
362
- image_data << data.getbyte(0)
363
- mask_data << data.getbyte(0)
364
- while i < bytes_per_row
365
- bytes_per_colors.times {|j| image_data << data.getbyte(i + j) }
366
- i += bytes_per_colors
367
- bytes_per_alpha.times {|j| mask_data << data.getbyte(i + j) }
368
- i += bytes_per_alpha
370
+ i = 0
371
+ image_temp.clear
372
+ mask_temp.clear
373
+ while i + bytes_per_row <= data.length
374
+ image_temp << data.getbyte(i)
375
+ mask_temp << data.getbyte(i)
376
+ i += 1
377
+ row_loop_end_index = i + bytes_per_row - 1
378
+ while i < row_loop_end_index
379
+ image_temp << data.byteslice(i, bytes_per_colors)
380
+ mask_temp << data.byteslice(i + bytes_per_colors, bytes_per_alpha)
381
+ i += bytes_per_pixel
369
382
  end
370
- data = data[bytes_per_row..-1]
371
383
  end
384
+ image_data << image_deflate.deflate(image_temp)
385
+ mask_data << mask_deflate.deflate(mask_temp)
386
+ data = data[i..-1]
372
387
  end
373
-
374
- image_data = Filter.string_from_source(flate_decode.encoder(Fiber.new { image_data }))
375
- mask_data = Filter.string_from_source(flate_decode.encoder(Fiber.new { mask_data }))
388
+ image_data << image_deflate.finish
389
+ mask_data << mask_deflate.finish
390
+ image_deflate.close
391
+ mask_deflate.close
376
392
 
377
393
  [image_data, mask_data]
378
394
  end
@@ -573,7 +573,8 @@ module HexaPDF
573
573
 
574
574
  validate_array = lambda do |parent, container|
575
575
  container.map! do |field|
576
- if !field.kind_of?(HexaPDF::Object) || !field.kind_of?(HexaPDF::Dictionary) || field.null?
576
+ if !field.kind_of?(HexaPDF::Object) || !field.kind_of?(HexaPDF::Dictionary) ||
577
+ field.null? || (field.key?(:Type) && field[:Type] != :Annot)
577
578
  yield("Invalid object in AcroForm field hierarchy", true)
578
579
  next nil
579
580
  end
@@ -187,10 +187,14 @@ module HexaPDF
187
187
  end
188
188
  end
189
189
 
190
- # Applies the given intent (:View, :Design or a custom intent) to the OCG.
190
+ # :call-seq:
191
+ # ocg.apply_intent(intent) -> ocg
192
+ #
193
+ # Applies the given intent (:View, :Design or a custom intent) to the OCG and returns +self+.
191
194
  def apply_intent(intent)
192
195
  self[:Intent] = key?(:Intent) ? Array(self[:Intent]) : []
193
196
  self[:Intent] << intent
197
+ self
194
198
  end
195
199
 
196
200
  # Returns +true+ if this OCG has an intent of :View.
@@ -209,23 +213,35 @@ module HexaPDF
209
213
  document.optional_content.default_configuration.ocg_on?(self)
210
214
  end
211
215
 
216
+ # :call-seq:
217
+ # ocg.on! -> ocg
218
+ #
212
219
  # Sets the state of the OCG to on in the default configuration (see
213
- # OptionalContentProperties#default_configuration).
220
+ # OptionalContentProperties#default_configuration) and returns +self+.
214
221
  def on!
215
222
  document.optional_content.default_configuration.ocg_state(self, :on)
223
+ self
216
224
  end
217
225
 
226
+ # :call-seq:
227
+ # ocg.off! -> ocg
228
+ #
218
229
  # Sets the state of the OCG to off in the default configuration (see
219
- # OptionalContentProperties#default_configuration).
230
+ # OptionalContentProperties#default_configuration) and returns +self+.
220
231
  def off!
221
232
  document.optional_content.default_configuration.ocg_state(self, :off)
233
+ self
222
234
  end
223
235
 
236
+ # :call-seq:
237
+ # ocg.add_to_ui(path: nil) -> ocg
238
+ #
224
239
  # Adds the OCG to the PDF processor's user interface in the default configuration (see
225
240
  # OptionalContentProperties#default_configuration), either at the top-level or under the given
226
- # hierarchical +path+ but always as the last item.
241
+ # hierarchical +path+ but always as the last item, and returns +self+.
227
242
  def add_to_ui(path: nil)
228
243
  document.optional_content.default_configuration.add_ocg_to_ui(self, path: path)
244
+ self
229
245
  end
230
246
 
231
247
  # :call-seq:
@@ -37,6 +37,6 @@
37
37
  module HexaPDF
38
38
 
39
39
  # The version of HexaPDF.
40
- VERSION = '1.11.0'
40
+ VERSION = '1.11.1'
41
41
 
42
42
  end
@@ -537,6 +537,7 @@ describe HexaPDF::Type::AcroForm::Form do
537
537
  @doc.add({T: :Tx3, FT: :Tx, Kids: [@doc.add({T: :Tx4}),
538
538
  [:nothing],
539
539
  @doc.add({T: :Tx5, Kids: [@doc.add({T: :Tx6})]})]}),
540
+ @doc.wrap({Type: :Page}),
540
541
  ]
541
542
  @acro_form[:Fields][6][:Kids][0][:Parent] = @acro_form[:Fields][6]
542
543
  @acro_form[:Fields][6][:Kids][2][:Parent] = @acro_form[:Fields][6]
@@ -69,7 +69,8 @@ describe HexaPDF::Type::OptionalContentGroup do
69
69
  end
70
70
 
71
71
  it "can apply one or more intents" do
72
- @ocg.apply_intent(:View)
72
+ ocg = @ocg.apply_intent(:View)
73
+ assert_same(@ocg, ocg)
73
74
  @ocg.apply_intent(:Design)
74
75
  assert(@ocg.intent_view?)
75
76
  assert(@ocg.intent_design?)
@@ -85,17 +86,20 @@ describe HexaPDF::Type::OptionalContentGroup do
85
86
 
86
87
  it "can set its default state to on" do
87
88
  @doc.optional_content.default_configuration[:OFF] = [@ocg]
88
- @ocg.on!
89
+ ocg = @ocg.on!
90
+ assert_same(@ocg, ocg)
89
91
  assert(@ocg.on?)
90
92
  end
91
93
 
92
94
  it "can set its default state to off" do
93
- @ocg.off!
95
+ ocg = @ocg.off!
96
+ assert_same(@ocg, ocg)
94
97
  refute(@ocg.on?)
95
98
  end
96
99
 
97
100
  it "can add itself to the UI" do
98
- @ocg.add_to_ui(path: 'Test')
101
+ ocg = @ocg.add_to_ui(path: 'Test')
102
+ assert_same(@ocg, ocg)
99
103
  assert_equal([['Test', @ocg]], @doc.optional_content.default_configuration[:Order].value)
100
104
  end
101
105
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hexapdf
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.11.0
4
+ version: 1.11.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas Leitner