hexapdf 0.26.2 → 0.27.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +56 -0
- data/README.md +1 -1
- data/examples/013-text_layouter_shapes.rb +8 -8
- data/examples/016-frame_automatic_box_placement.rb +3 -3
- data/examples/017-frame_text_flow.rb +3 -3
- data/examples/020-column_box.rb +3 -3
- data/lib/hexapdf/cli/split.rb +7 -7
- data/lib/hexapdf/cli/watermark.rb +2 -2
- data/lib/hexapdf/configuration.rb +2 -0
- data/lib/hexapdf/dictionary.rb +3 -12
- data/lib/hexapdf/document/destinations.rb +42 -5
- data/lib/hexapdf/document/signatures.rb +265 -48
- data/lib/hexapdf/importer.rb +3 -0
- data/lib/hexapdf/parser.rb +1 -0
- data/lib/hexapdf/revisions.rb +3 -1
- data/lib/hexapdf/tokenizer.rb +2 -2
- data/lib/hexapdf/type/acro_form/form.rb +28 -1
- data/lib/hexapdf/type/catalog.rb +1 -1
- data/lib/hexapdf/type/outline.rb +18 -0
- data/lib/hexapdf/type/outline_item.rb +72 -14
- data/lib/hexapdf/type/page.rb +56 -35
- data/lib/hexapdf/type/resources.rb +13 -17
- data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +16 -2
- data/lib/hexapdf/type/signature.rb +10 -0
- data/lib/hexapdf/version.rb +1 -1
- data/lib/hexapdf/writer.rb +3 -0
- data/test/hexapdf/document/test_destinations.rb +41 -0
- data/test/hexapdf/document/test_signatures.rb +139 -19
- data/test/hexapdf/test_importer.rb +14 -0
- data/test/hexapdf/test_parser.rb +2 -2
- data/test/hexapdf/test_revisions.rb +20 -12
- data/test/hexapdf/test_tokenizer.rb +11 -1
- data/test/hexapdf/test_writer.rb +11 -3
- data/test/hexapdf/type/acro_form/test_form.rb +47 -0
- data/test/hexapdf/type/signature/common.rb +52 -0
- data/test/hexapdf/type/signature/test_adbe_pkcs7_detached.rb +21 -0
- data/test/hexapdf/type/test_catalog.rb +5 -2
- data/test/hexapdf/type/test_outline.rb +1 -1
- data/test/hexapdf/type/test_outline_item.rb +62 -1
- data/test/hexapdf/type/test_page.rb +41 -20
- data/test/hexapdf/type/test_resources.rb +0 -5
- data/test/hexapdf/type/test_signature.rb +8 -0
- data/test/test_helper.rb +1 -1
- metadata +17 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 57b852a0648f47b5e3443e9b8aa4480a7a8cd187085c0067baf86af14cee7d9a
|
4
|
+
data.tar.gz: 6e28f748f1d8e089b6585748e2f56c3adf79669cf9b00d0d9fe8d83a74e97063
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a1d2cd75344a3fc9cd54f0dafa6a0fd23b9821d67f54581090a6db2820f7e04a0989da01d7b5f9c558e02bb57cf254f4ebaab791b950d4dacc1e02a29c5f3844
|
7
|
+
data.tar.gz: ea758cdeb96d8282e3c6581785b690b8d79e9bee318a7cf80bfb4201b47fc0f1d721a78b5c703b32f20b8671211b604e78f83dec6768187321fef46e5dac3a81
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,59 @@
|
|
1
|
+
## 0.27.0 - 2022-11-18
|
2
|
+
|
3
|
+
### Added
|
4
|
+
|
5
|
+
* Support for timestamp signatures through the
|
6
|
+
[HexaPDF::Document::Signatures::TimestampHandler]
|
7
|
+
* [HexaPDF::Document::Destinations#resolve] for resolving destination values
|
8
|
+
* [HexaPDF::Document::Destinations::Destination#value] to return the destination
|
9
|
+
array
|
10
|
+
* Support for verifying document timestamp signatures
|
11
|
+
* [HexaPDF::Document::Signatures::DefaultHandler#signature_size] to support
|
12
|
+
setting custom signature sizes
|
13
|
+
* [HexaPDF::Document::Signatures::DefaultHandler#external_signing] to support
|
14
|
+
signing via custom mechanisms
|
15
|
+
* [HexaPDF::Document::Signatures::embed_signature] to enable asynchronous
|
16
|
+
external signing
|
17
|
+
|
18
|
+
### Changed
|
19
|
+
|
20
|
+
* **Breaking change**: The crop box is now used instead of the media box in most
|
21
|
+
cases to be in line with the specification
|
22
|
+
* [HexaPDF::Document::Signatures::DefaultHandler] to allow setting the used
|
23
|
+
signature method
|
24
|
+
* **Breaking change**: [HexaPDF::Document::Signatures::DefaultHandler#sign]
|
25
|
+
needs to accept the IO object and the byte range instead of just the data
|
26
|
+
* **Breaking change**: Enhanced support for outline items with new methods
|
27
|
+
`#level` and `#destination_page` as well as changes to `#add` and `#each_item`
|
28
|
+
* **Breaking change**: Removed `#filter_name` and `#sub_filter_name` from
|
29
|
+
[HexaPDF::Document::Signatures::DefaultHandler]
|
30
|
+
* `HexaPDF::Type::Resources#perform_validation` to not add a default procedure
|
31
|
+
set since this feature is deprecated
|
32
|
+
|
33
|
+
### Fixed
|
34
|
+
|
35
|
+
* [HexaPDF::Document::Destinations::Destination::new] to also accept a hash
|
36
|
+
* [HexaPDF::Type::Catalog] auto-conversion of /Outlines to correct class
|
37
|
+
* [HexaPDF::Type::AcroForm::Form#flatten] to return the unflattened form fields
|
38
|
+
instead of the widgets
|
39
|
+
* [HexaPDF::Writer#write_incremental] to set the /Version in the catalog
|
40
|
+
dictionary when necessary
|
41
|
+
* [HexaPDF::Importer#import] to always return an imported object with the same
|
42
|
+
class as the argument
|
43
|
+
* [HexaPDF::Type::OutlineItem] to always be an indirect object
|
44
|
+
* `HexaPDF::Tokenizer#parse_number` to handle references correctly in all cases
|
45
|
+
* [HexaPDF::Type::Page#rotate] to correctly flatten all page boxes
|
46
|
+
* [HexaPDF::Document::Signatures#add] to raise an error if the reserved space
|
47
|
+
for the signature is not enough
|
48
|
+
* `HexaPDF::Type::AcroForm::Form#perform_validation` to fix broken /Parent
|
49
|
+
entries and to remove invalid objects from the field hierarchy
|
50
|
+
* `HexaPDF::Type::OutlineItem#perform_validation` bug where a missing /Count key
|
51
|
+
was deemed invalid
|
52
|
+
* [HexaPDF::Revisions::from_io] to use the correct /Prev offset when revisions
|
53
|
+
have been merged
|
54
|
+
* Handling of indirect objects with invalid values for more situations
|
55
|
+
|
56
|
+
|
1
57
|
## 0.26.2 - 2022-10-22
|
2
58
|
|
3
59
|
### Added
|
data/README.md
CHANGED
@@ -91,7 +91,7 @@ with example graphics and PDF files and tightly integrated into the rest of the
|
|
91
91
|
## Requirements and Installation
|
92
92
|
|
93
93
|
Since HexaPDF is written in Ruby, a working Ruby installation is needed - see the
|
94
|
-
[official installation documentation][rbinstall] for details. Note that you need Ruby version 2.
|
94
|
+
[official installation documentation][rbinstall] for details. Note that you need Ruby version 2.6 or
|
95
95
|
higher as prior versions are not supported!
|
96
96
|
|
97
97
|
HexaPDF works on all Ruby implementations that are CRuby compatible, e.g. TruffleRuby, and on any
|
@@ -73,14 +73,14 @@ canvas.circle(0, circle_top - radius, radius).stroke
|
|
73
73
|
# Center: full circle
|
74
74
|
layouter.style.align = :justify
|
75
75
|
result = layouter.fit(items, circle, radius * 2)
|
76
|
-
result.draw(canvas, page.box
|
77
|
-
canvas.circle(page.box
|
76
|
+
result.draw(canvas, page.box.width / 2.0 - radius, circle_top)
|
77
|
+
canvas.circle(page.box.width / 2.0, circle_top - radius, radius).stroke
|
78
78
|
|
79
79
|
# Right: left half circle
|
80
80
|
layouter.style.align = :right
|
81
81
|
result = layouter.fit(items, left_half_circle, radius * 2)
|
82
|
-
result.draw(canvas, page.box
|
83
|
-
canvas.circle(page.box
|
82
|
+
result.draw(canvas, page.box.width - radius, circle_top)
|
83
|
+
canvas.circle(page.box.width, circle_top - radius, radius).stroke
|
84
84
|
|
85
85
|
|
86
86
|
########################################################################
|
@@ -115,7 +115,7 @@ canvas.polyline(0, diamond_top, diamond_width, diamond_top - diamond_width,
|
|
115
115
|
# Center: full diamond
|
116
116
|
layouter.style.align = :justify
|
117
117
|
result = layouter.fit(items, full_diamond, 2 * diamond_width)
|
118
|
-
left = page.box
|
118
|
+
left = page.box.width / 2.0 - diamond_width
|
119
119
|
result.draw(canvas, left, diamond_top)
|
120
120
|
canvas.polyline(left + diamond_width, diamond_top,
|
121
121
|
left + 2 * diamond_width, diamond_top - diamond_width,
|
@@ -125,7 +125,7 @@ canvas.polyline(left + diamond_width, diamond_top,
|
|
125
125
|
# Right: left half diamond
|
126
126
|
layouter.style.align = :right
|
127
127
|
result = layouter.fit(items, left_half_diamond, 2 * diamond_width)
|
128
|
-
middle = page.box
|
128
|
+
middle = page.box.width
|
129
129
|
result.draw(canvas, middle - diamond_width, diamond_top)
|
130
130
|
canvas.polyline(middle, diamond_top,
|
131
131
|
middle - diamond_width, diamond_top - diamond_width,
|
@@ -144,7 +144,7 @@ sine_wave = lambda do |height, line_height|
|
|
144
144
|
end
|
145
145
|
layouter.style.align = :justify
|
146
146
|
result = layouter.fit(items, sine_wave, sine_wave_height)
|
147
|
-
middle = page.box
|
147
|
+
middle = page.box.width / 2.0
|
148
148
|
result.draw(canvas, middle - (sine_wave_height + 100) / 2, sine_wave_top)
|
149
149
|
|
150
150
|
########################################################################
|
@@ -170,7 +170,7 @@ end
|
|
170
170
|
layouter.style.align = :justify
|
171
171
|
result = layouter.fit(items, house, 200)
|
172
172
|
|
173
|
-
middle = page.box
|
173
|
+
middle = page.box.width / 2.0
|
174
174
|
result.draw(canvas, middle - (outer_width / 2), house_top)
|
175
175
|
|
176
176
|
doc.write("text_layouter_shapes.pdf", optimize: true)
|
@@ -20,11 +20,11 @@ include HexaPDF::Layout
|
|
20
20
|
|
21
21
|
doc = HexaPDF::Document.new
|
22
22
|
page = doc.pages.add
|
23
|
-
|
23
|
+
page_box = page.box
|
24
24
|
canvas = page.canvas
|
25
25
|
|
26
|
-
frame = Frame.new(
|
27
|
-
|
26
|
+
frame = Frame.new(page_box.left + 20, page_box.bottom + 20,
|
27
|
+
page_box.width - 40, page_box.height - 40)
|
28
28
|
|
29
29
|
box_counter = 1
|
30
30
|
draw_box = lambda do |**args|
|
@@ -20,9 +20,9 @@ include HexaPDF::Utils::GraphicsHelpers
|
|
20
20
|
doc = HexaPDF::Document.new
|
21
21
|
|
22
22
|
page = doc.pages.add
|
23
|
-
|
24
|
-
frame = Frame.new(
|
25
|
-
|
23
|
+
page_box = page.box
|
24
|
+
frame = Frame.new(page_box.left + 20, page_box.bottom + 20,
|
25
|
+
page_box.width - 40, page_box.height - 40)
|
26
26
|
|
27
27
|
boxes = []
|
28
28
|
boxes << doc.layout.image_box(File.join(__dir__, 'machupicchu.jpg'),
|
data/examples/020-column_box.rb
CHANGED
@@ -15,9 +15,9 @@ require 'hexapdf'
|
|
15
15
|
|
16
16
|
doc = HexaPDF::Document.new
|
17
17
|
page = doc.pages.add
|
18
|
-
|
19
|
-
frame = HexaPDF::Layout::Frame.new(
|
20
|
-
|
18
|
+
page_box = page.box
|
19
|
+
frame = HexaPDF::Layout::Frame.new(page_box.left + 20, page_box.bottom + 20,
|
20
|
+
page_box.width - 40, page_box.height - 40)
|
21
21
|
|
22
22
|
boxes = []
|
23
23
|
5.times do
|
data/lib/hexapdf/cli/split.rb
CHANGED
@@ -114,7 +114,7 @@ module HexaPDF
|
|
114
114
|
end
|
115
115
|
|
116
116
|
doc.pages.each do |page|
|
117
|
-
out = out_files[page_size_name(page.box
|
117
|
+
out = out_files[page_size_name(page.box.value)]
|
118
118
|
out.pages.add(out.import(page))
|
119
119
|
end
|
120
120
|
|
@@ -125,18 +125,18 @@ module HexaPDF
|
|
125
125
|
end
|
126
126
|
end
|
127
127
|
|
128
|
-
# Tries to retrieve a page size name based on the
|
128
|
+
# Tries to retrieve a page size name based on the given page box. If this is not possible, the
|
129
129
|
# returned page size name consists of width x height.
|
130
|
-
def page_size_name(
|
130
|
+
def page_size_name(box)
|
131
131
|
@page_name_cache ||= {}
|
132
|
-
return @page_name_cache[
|
132
|
+
return @page_name_cache[box] if @page_name_cache.key?(box)
|
133
133
|
|
134
134
|
paper_size = HexaPDF::Type::Page::PAPER_SIZE.find do |_name, box|
|
135
|
-
box.each_with_index.all? {|entry, index| (entry -
|
135
|
+
box.each_with_index.all? {|entry, index| (entry - box[index]).abs < 5 }
|
136
136
|
end
|
137
137
|
|
138
|
-
@page_name_cache[
|
139
|
-
paper_size ? paper_size[0] : sprintf("%.0fx%.0f", *
|
138
|
+
@page_name_cache[box] =
|
139
|
+
paper_size ? paper_size[0] : sprintf("%.0fx%.0f", *box.values_at(2, 3))
|
140
140
|
end
|
141
141
|
|
142
142
|
end
|
@@ -95,8 +95,8 @@ module HexaPDF
|
|
95
95
|
doc.pages.each do |page|
|
96
96
|
index = indices.next
|
97
97
|
xobject = xobject_map[index] ||= doc.import(watermark.pages[index].to_form_xobject)
|
98
|
-
pw = page.box
|
99
|
-
ph = page.box
|
98
|
+
pw = page.box.width.to_f
|
99
|
+
ph = page.box.height.to_f
|
100
100
|
xw = xobject.width.to_f
|
101
101
|
xh = xobject.height.to_f
|
102
102
|
canvas = page.canvas(type: @type)
|
@@ -488,11 +488,13 @@ module HexaPDF
|
|
488
488
|
},
|
489
489
|
'signature.signing_handler' => {
|
490
490
|
default: 'HexaPDF::Document::Signatures::DefaultHandler',
|
491
|
+
timestamp: 'HexaPDF::Document::Signatures::TimestampHandler',
|
491
492
|
},
|
492
493
|
'signature.sub_filter_map' => {
|
493
494
|
'adbe.x509.rsa_sha1': 'HexaPDF::Type::Signature::AdbeX509RsaSha1',
|
494
495
|
'adbe.pkcs7.detached': 'HexaPDF::Type::Signature::AdbePkcs7Detached',
|
495
496
|
'ETSI.CAdES.detached': 'HexaPDF::Type::Signature::AdbePkcs7Detached',
|
497
|
+
'ETSI.RFC3161': 'HexaPDF::Type::Signature::AdbePkcs7Detached',
|
496
498
|
},
|
497
499
|
'task.map' => {
|
498
500
|
optimize: 'HexaPDF::Task::Optimize',
|
data/lib/hexapdf/dictionary.rb
CHANGED
@@ -259,22 +259,13 @@ module HexaPDF
|
|
259
259
|
end
|
260
260
|
end
|
261
261
|
|
262
|
-
# Iterates over all currently set fields and those that are required.
|
263
|
-
def each_set_key_or_required_field #:yields: name, field
|
264
|
-
value.keys.each {|name| yield(name, self.class.field(name)) }
|
265
|
-
self.class.each_field do |name, field|
|
266
|
-
yield(name, field) if field.required? && !value.key?(name)
|
267
|
-
end
|
268
|
-
end
|
269
|
-
|
270
262
|
# Performs validation tasks based on the currently set keys and defined fields.
|
271
263
|
def perform_validation(&block)
|
272
264
|
super
|
273
|
-
|
274
|
-
|
265
|
+
self.class.each_field do |name, field|
|
266
|
+
next unless field.required? || value.key?(name)
|
275
267
|
|
276
|
-
|
277
|
-
next if field.nil?
|
268
|
+
obj = key?(name) ? self[name] : nil
|
278
269
|
|
279
270
|
# Check that required fields are set
|
280
271
|
if field.required? && obj.nil?
|
@@ -107,8 +107,7 @@ module HexaPDF
|
|
107
107
|
# not changing it from the current value.
|
108
108
|
class Destination
|
109
109
|
|
110
|
-
|
111
|
-
TYPE_MAPPING = {
|
110
|
+
TYPE_MAPPING = { #:nodoc:
|
112
111
|
XYZ: :xyz,
|
113
112
|
Fit: :fit_page,
|
114
113
|
FitH: :fit_page_horizontal,
|
@@ -119,8 +118,7 @@ module HexaPDF
|
|
119
118
|
FitBV: :fit_bounding_box_vertical,
|
120
119
|
}
|
121
120
|
|
122
|
-
|
123
|
-
REVERSE_TYPE_MAPPING = Hash[*TYPE_MAPPING.flatten.reverse]
|
121
|
+
REVERSE_TYPE_MAPPING = Hash[*TYPE_MAPPING.flatten.reverse] #:nodoc:
|
124
122
|
|
125
123
|
# Returns +true+ if the destination is valid.
|
126
124
|
def self.valid?(destination)
|
@@ -132,7 +130,11 @@ module HexaPDF
|
|
132
130
|
# Creates a new Destination for the given +destination+ which may be an explicit destination
|
133
131
|
# array or a dictionary with a /D entry (as allowed for a named destination).
|
134
132
|
def initialize(destination)
|
135
|
-
@destination =
|
133
|
+
@destination = if destination.kind_of?(HexaPDF::Dictionary) || destination.kind_of?(Hash)
|
134
|
+
destination[:D]
|
135
|
+
else
|
136
|
+
destination
|
137
|
+
end
|
136
138
|
end
|
137
139
|
|
138
140
|
# Returns +true+ if the destination references a destination in a remote document.
|
@@ -212,6 +214,11 @@ module HexaPDF
|
|
212
214
|
self.class.valid?(@destination)
|
213
215
|
end
|
214
216
|
|
217
|
+
# Returns the wrapped destination array.
|
218
|
+
def value
|
219
|
+
@destination
|
220
|
+
end
|
221
|
+
|
215
222
|
end
|
216
223
|
|
217
224
|
include Enumerable
|
@@ -447,6 +454,36 @@ module HexaPDF
|
|
447
454
|
destinations.delete_entry(name)
|
448
455
|
end
|
449
456
|
|
457
|
+
# :call-seq:
|
458
|
+
# destinations.resolve(string_name) -> destination or nil
|
459
|
+
# destinations.resolve(symbol_name) -> destination or nil
|
460
|
+
# destinations.resolve(dest_array) -> destination or nil
|
461
|
+
#
|
462
|
+
# Resolves the given value to a valid destination object, if possible, or otherwise returns
|
463
|
+
# +nil+.
|
464
|
+
#
|
465
|
+
# * If the given value is a string, it is treated as a destination name and looked up in the
|
466
|
+
# destination name tree.
|
467
|
+
#
|
468
|
+
# * If the given value is a symbol, it is treated as an old-style destination name and looked
|
469
|
+
# up in the destination dictionary.
|
470
|
+
#
|
471
|
+
# * If the given value is an array, it is treated as a destination array itself.
|
472
|
+
def resolve(value)
|
473
|
+
result = case value
|
474
|
+
when String
|
475
|
+
destinations.find_entry(value)
|
476
|
+
when PDFArray
|
477
|
+
value.value
|
478
|
+
when Array
|
479
|
+
value
|
480
|
+
when Symbol
|
481
|
+
@document.catalog[:Dests]&.[](value)
|
482
|
+
end
|
483
|
+
result = Destination.new(result) if result
|
484
|
+
result&.valid? ? result : nil
|
485
|
+
end
|
486
|
+
|
450
487
|
# :call-seq:
|
451
488
|
# destinations[name] -> destination
|
452
489
|
#
|