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 +4 -4
- data/CHANGELOG.md +13 -0
- data/examples/027-composer_optional_content.rb +11 -15
- data/lib/hexapdf/image_loader/png.rb +31 -15
- data/lib/hexapdf/type/acro_form/form.rb +2 -1
- data/lib/hexapdf/type/optional_content_group.rb +20 -4
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/type/acro_form/test_form.rb +1 -0
- data/test/hexapdf/type/test_optional_content_group.rb +8 -4
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8cf7d72b14ba19f840f63adbcfa3eb5b1fd7b876731c52c615788f395d23d10d
|
|
4
|
+
data.tar.gz: 48d506505dcc50d5cc5c596f7e11b8d0961dbea82cea7223c1ae8d705bb9b57e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
a2 =
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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' =>
|
|
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' =>
|
|
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' =>
|
|
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
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
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
|
-
|
|
375
|
-
|
|
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) ||
|
|
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
|
-
#
|
|
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:
|
data/lib/hexapdf/version.rb
CHANGED
|
@@ -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
|