hexapdf 0.26.2 → 0.27.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +56 -0
  3. data/README.md +1 -1
  4. data/examples/013-text_layouter_shapes.rb +8 -8
  5. data/examples/016-frame_automatic_box_placement.rb +3 -3
  6. data/examples/017-frame_text_flow.rb +3 -3
  7. data/examples/020-column_box.rb +3 -3
  8. data/lib/hexapdf/cli/split.rb +7 -7
  9. data/lib/hexapdf/cli/watermark.rb +2 -2
  10. data/lib/hexapdf/configuration.rb +2 -0
  11. data/lib/hexapdf/dictionary.rb +3 -12
  12. data/lib/hexapdf/document/destinations.rb +42 -5
  13. data/lib/hexapdf/document/signatures.rb +265 -48
  14. data/lib/hexapdf/importer.rb +3 -0
  15. data/lib/hexapdf/parser.rb +1 -0
  16. data/lib/hexapdf/revisions.rb +3 -1
  17. data/lib/hexapdf/tokenizer.rb +2 -2
  18. data/lib/hexapdf/type/acro_form/form.rb +28 -1
  19. data/lib/hexapdf/type/catalog.rb +1 -1
  20. data/lib/hexapdf/type/outline.rb +18 -0
  21. data/lib/hexapdf/type/outline_item.rb +72 -14
  22. data/lib/hexapdf/type/page.rb +56 -35
  23. data/lib/hexapdf/type/resources.rb +13 -17
  24. data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +16 -2
  25. data/lib/hexapdf/type/signature.rb +10 -0
  26. data/lib/hexapdf/version.rb +1 -1
  27. data/lib/hexapdf/writer.rb +3 -0
  28. data/test/hexapdf/document/test_destinations.rb +41 -0
  29. data/test/hexapdf/document/test_signatures.rb +139 -19
  30. data/test/hexapdf/test_importer.rb +14 -0
  31. data/test/hexapdf/test_parser.rb +2 -2
  32. data/test/hexapdf/test_revisions.rb +20 -12
  33. data/test/hexapdf/test_tokenizer.rb +11 -1
  34. data/test/hexapdf/test_writer.rb +11 -3
  35. data/test/hexapdf/type/acro_form/test_form.rb +47 -0
  36. data/test/hexapdf/type/signature/common.rb +52 -0
  37. data/test/hexapdf/type/signature/test_adbe_pkcs7_detached.rb +21 -0
  38. data/test/hexapdf/type/test_catalog.rb +5 -2
  39. data/test/hexapdf/type/test_outline.rb +1 -1
  40. data/test/hexapdf/type/test_outline_item.rb +62 -1
  41. data/test/hexapdf/type/test_page.rb +41 -20
  42. data/test/hexapdf/type/test_resources.rb +0 -5
  43. data/test/hexapdf/type/test_signature.rb +8 -0
  44. data/test/test_helper.rb +1 -1
  45. metadata +17 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5f7ff4db8b6417cf6f0101a2e72e2481571a40c9542713be82753376d58c047c
4
- data.tar.gz: 0b62a42fe5b91bdd8f11c4c31105e560cd4cf2f27259a86b9fd07db2f2280d6e
3
+ metadata.gz: 57b852a0648f47b5e3443e9b8aa4480a7a8cd187085c0067baf86af14cee7d9a
4
+ data.tar.gz: 6e28f748f1d8e089b6585748e2f56c3adf79669cf9b00d0d9fe8d83a74e97063
5
5
  SHA512:
6
- metadata.gz: 0ef60dd2da6c1c233768e564c5afb9330fb07065a33f293a2fbca6c71b8948d2ab754b2c9383ed4a84400e944e98f6c5a3638bf0c970c2b819eb53a0e024a2ca
7
- data.tar.gz: 9c0190529f0d25d9ec655bef27d25054b9bfb5dc2ec980742807e73e0eb953550033f5362249cad1ec83ff8e78a1f1ad91785614cecdbaed113ad6365a77ade5
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.5 or
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(:media).width / 2.0 - radius, circle_top)
77
- canvas.circle(page.box(:media).width / 2.0, circle_top - radius, radius).stroke
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(:media).width - radius, circle_top)
83
- canvas.circle(page.box(:media).width, circle_top - radius, radius).stroke
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(:media).width / 2.0 - diamond_width
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(:media).width
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(:media).width / 2.0
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(:media).width / 2.0
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
- media_box = page.box(:media)
23
+ page_box = page.box
24
24
  canvas = page.canvas
25
25
 
26
- frame = Frame.new(media_box.left + 20, media_box.bottom + 20,
27
- media_box.width - 40, media_box.height - 40)
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
- media_box = page.box(:media)
24
- frame = Frame.new(media_box.left + 20, media_box.bottom + 20,
25
- media_box.width - 40, media_box.height - 40)
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'),
@@ -15,9 +15,9 @@ require 'hexapdf'
15
15
 
16
16
  doc = HexaPDF::Document.new
17
17
  page = doc.pages.add
18
- media_box = page.box(:media)
19
- frame = HexaPDF::Layout::Frame.new(media_box.left + 20, media_box.bottom + 20,
20
- media_box.width - 40, media_box.height - 40)
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
@@ -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(:media).value)]
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 media box. If this is not possible, 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(media_box)
130
+ def page_size_name(box)
131
131
  @page_name_cache ||= {}
132
- return @page_name_cache[media_box] if @page_name_cache.key?(media_box)
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 - media_box[index]).abs < 5 }
135
+ box.each_with_index.all? {|entry, index| (entry - box[index]).abs < 5 }
136
136
  end
137
137
 
138
- @page_name_cache[media_box] =
139
- paper_size ? paper_size[0] : sprintf("%.0fx%.0f", *media_box.values_at(2, 3))
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(:media).width.to_f
99
- ph = page.box(:media).height.to_f
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',
@@ -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
- each_set_key_or_required_field do |name, field|
274
- obj = key?(name) ? self[name] : nil
265
+ self.class.each_field do |name, field|
266
+ next unless field.required? || value.key?(name)
275
267
 
276
- # The checks below need a valid field definition
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
- # :nodoc:
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
- # :nodoc:
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 = (destination.kind_of?(HexaPDF::Dictionary) ? destination[:D] : 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
  #