hexapdf 0.26.2 → 0.28.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +115 -1
  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/019-acro_form.rb +14 -3
  8. data/examples/020-column_box.rb +3 -3
  9. data/examples/023-images.rb +30 -0
  10. data/lib/hexapdf/cli/info.rb +5 -1
  11. data/lib/hexapdf/cli/inspect.rb +2 -2
  12. data/lib/hexapdf/cli/split.rb +8 -8
  13. data/lib/hexapdf/cli/watermark.rb +2 -2
  14. data/lib/hexapdf/configuration.rb +3 -2
  15. data/lib/hexapdf/content/canvas.rb +8 -3
  16. data/lib/hexapdf/dictionary.rb +4 -17
  17. data/lib/hexapdf/document/destinations.rb +42 -5
  18. data/lib/hexapdf/document/signatures.rb +265 -48
  19. data/lib/hexapdf/document.rb +6 -10
  20. data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
  21. data/lib/hexapdf/importer.rb +35 -27
  22. data/lib/hexapdf/layout/list_box.rb +1 -5
  23. data/lib/hexapdf/object.rb +5 -0
  24. data/lib/hexapdf/parser.rb +14 -0
  25. data/lib/hexapdf/revision.rb +15 -12
  26. data/lib/hexapdf/revisions.rb +7 -1
  27. data/lib/hexapdf/tokenizer.rb +15 -9
  28. data/lib/hexapdf/type/acro_form/appearance_generator.rb +174 -128
  29. data/lib/hexapdf/type/acro_form/button_field.rb +5 -3
  30. data/lib/hexapdf/type/acro_form/choice_field.rb +2 -0
  31. data/lib/hexapdf/type/acro_form/field.rb +11 -5
  32. data/lib/hexapdf/type/acro_form/form.rb +61 -8
  33. data/lib/hexapdf/type/acro_form/signature_field.rb +2 -0
  34. data/lib/hexapdf/type/acro_form/text_field.rb +12 -2
  35. data/lib/hexapdf/type/annotations/widget.rb +3 -0
  36. data/lib/hexapdf/type/catalog.rb +1 -1
  37. data/lib/hexapdf/type/font_true_type.rb +14 -0
  38. data/lib/hexapdf/type/object_stream.rb +2 -2
  39. data/lib/hexapdf/type/outline.rb +19 -1
  40. data/lib/hexapdf/type/outline_item.rb +72 -14
  41. data/lib/hexapdf/type/page.rb +95 -64
  42. data/lib/hexapdf/type/resources.rb +13 -17
  43. data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +16 -2
  44. data/lib/hexapdf/type/signature.rb +10 -0
  45. data/lib/hexapdf/version.rb +1 -1
  46. data/lib/hexapdf/writer.rb +5 -3
  47. data/test/hexapdf/content/test_canvas.rb +5 -0
  48. data/test/hexapdf/document/test_destinations.rb +41 -0
  49. data/test/hexapdf/document/test_pages.rb +2 -2
  50. data/test/hexapdf/document/test_signatures.rb +139 -19
  51. data/test/hexapdf/encryption/test_aes.rb +1 -1
  52. data/test/hexapdf/filter/test_predictor.rb +0 -1
  53. data/test/hexapdf/layout/test_box.rb +2 -1
  54. data/test/hexapdf/layout/test_column_box.rb +1 -1
  55. data/test/hexapdf/layout/test_list_box.rb +1 -1
  56. data/test/hexapdf/test_document.rb +2 -8
  57. data/test/hexapdf/test_importer.rb +27 -6
  58. data/test/hexapdf/test_parser.rb +19 -2
  59. data/test/hexapdf/test_revision.rb +15 -14
  60. data/test/hexapdf/test_revisions.rb +63 -12
  61. data/test/hexapdf/test_stream.rb +1 -1
  62. data/test/hexapdf/test_tokenizer.rb +10 -1
  63. data/test/hexapdf/test_writer.rb +11 -3
  64. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +135 -56
  65. data/test/hexapdf/type/acro_form/test_button_field.rb +6 -1
  66. data/test/hexapdf/type/acro_form/test_choice_field.rb +4 -0
  67. data/test/hexapdf/type/acro_form/test_field.rb +4 -4
  68. data/test/hexapdf/type/acro_form/test_form.rb +65 -0
  69. data/test/hexapdf/type/acro_form/test_signature_field.rb +4 -0
  70. data/test/hexapdf/type/acro_form/test_text_field.rb +13 -0
  71. data/test/hexapdf/type/signature/common.rb +54 -0
  72. data/test/hexapdf/type/signature/test_adbe_pkcs7_detached.rb +21 -0
  73. data/test/hexapdf/type/test_catalog.rb +5 -2
  74. data/test/hexapdf/type/test_font_true_type.rb +20 -0
  75. data/test/hexapdf/type/test_object_stream.rb +2 -1
  76. data/test/hexapdf/type/test_outline.rb +4 -1
  77. data/test/hexapdf/type/test_outline_item.rb +62 -1
  78. data/test/hexapdf/type/test_page.rb +103 -45
  79. data/test/hexapdf/type/test_page_tree_node.rb +4 -2
  80. data/test/hexapdf/type/test_resources.rb +0 -5
  81. data/test/hexapdf/type/test_signature.rb +8 -0
  82. data/test/test_helper.rb +1 -1
  83. metadata +61 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5f7ff4db8b6417cf6f0101a2e72e2481571a40c9542713be82753376d58c047c
4
- data.tar.gz: 0b62a42fe5b91bdd8f11c4c31105e560cd4cf2f27259a86b9fd07db2f2280d6e
3
+ metadata.gz: 874e09b094ea4e793d1d123cfbaded6d1cc5ba93af3b57587e9faf402786a30f
4
+ data.tar.gz: c1eed6a778936cd360b4f1878a18abc3e5727c1f36b5eab4838a9a72817dff7b
5
5
  SHA512:
6
- metadata.gz: 0ef60dd2da6c1c233768e564c5afb9330fb07065a33f293a2fbca6c71b8948d2ab754b2c9383ed4a84400e944e98f6c5a3638bf0c970c2b819eb53a0e024a2ca
7
- data.tar.gz: 9c0190529f0d25d9ec655bef27d25054b9bfb5dc2ec980742807e73e0eb953550033f5362249cad1ec83ff8e78a1f1ad91785614cecdbaed113ad6365a77ade5
6
+ metadata.gz: b66a7587a239acbeb9ebbb20f851b2fa7738c5a4c2f8a95ae7ae3d7419b84ae37b5d1fb48a6b7023bff0df3e1728c395b5351c9c42c05707fabd5a1722e2b88a
7
+ data.tar.gz: 62ac7d070bb8ae3426685af497a047096dd32dc16a102baa961512652222b388198561776fc1227be69bdd0713183d91fa25e411262eec34a29227aae9723d5c
data/CHANGELOG.md CHANGED
@@ -1,3 +1,117 @@
1
+ ## 0.28.0 - 2022-12-30
2
+
3
+ ### Added
4
+
5
+ * [HexaPDF::Type::AcroForm::AppearanceGenerator#create_push_button_appearances]
6
+ to allow customizing the behaviour
7
+ * [HexaPDF::Parser#linearized?] for determining whether a document is linearized
8
+ * Information on linearization to `hexapdf info` output
9
+ * Support for `AFNumber_Format` Javascript method to the form field appearance
10
+ generator
11
+ * Support for using fully embedded, simple TrueType fonts for drawing operations
12
+
13
+ ### Changed
14
+
15
+ * **Breaking change**: `HexaPDF::Revision#reset_objects` has been removed
16
+ * **Breaking change**: Method signature of [HexaPDF::Importer::for] has been
17
+ changed
18
+ * **Breaking change**: [HexaPDF::Type::AcroForm::Field#each_widget] now has the
19
+ default value of the argument `direct_only` set to `true` instead of `false`
20
+ * [HexaPDF::Revision#each_modified_object] to allow deleting the modified
21
+ objects from the active objects' container
22
+ * [HexaPDF::Revision#each_modified_object] to allow ignoring added object and
23
+ cross-reference stream objects
24
+ * [HexaPDF::Revisions::from_io] to merge the two revisions of a linearized PDF
25
+ * [HexaPDF::Importer] and [HexaPDF::Document#import] to make working with them
26
+ easier by allowing the import of arbitrary objects
27
+ * `HexaPDF::Type::AcroForm::Form#perform_validation` to combine fields with the
28
+ same name
29
+
30
+ ### Fixed
31
+
32
+ * [HexaPDF::Type::AcroForm::AppearanceGenerator#create_check_box_appearances] to
33
+ correctly handle a field value of `nil`
34
+ * Return value of `#type` method for all AcroForm field classes
35
+ * [HexaPDF::Type::Page#flatten_annotations] to work correctly in case no
36
+ annotations are on the page
37
+ * [HexaPDF::Type::AcroForm::ButtonField#create_appearances] to avoid creating
38
+ appearances in case of as-yet unresolved references to existing appearances
39
+ * [HexaPDF::Type::AcroForm::TextField#create_appearances] to avoid creating
40
+ appearances in case of pre-existing ones
41
+ * `HexaPDF::Tokenizer#parse_number` to treat invalid indirect object references
42
+ with an object number of 0 as null values
43
+ * [HexaPDF::Type::AcroForm::AppearanceGenerator] to handle empty appearance
44
+ characteristics dictionary marker style strings
45
+ * Writing of encrypted files containing two or more revisions
46
+ * Generation of object streams to never allow storing the catalog object to
47
+ avoid problems with certain viewers
48
+ * `HexaPDF::Type::Outline#perform_validation` to not show validation error when
49
+ `/Count` is zero
50
+ * Writing of documents with two or more revisions in non-incremental mode when
51
+ `optimize: true` is used and the original document used cross-reference tables
52
+ * [HexaPDF::Type::AcroForm::AppearanceGenerator] to take a widget's rotation
53
+ value into account
54
+ * [HexaPDF::Type::Page#flatten_annotations] to correctly flatten all
55
+ annotations, including ones with custom rotations
56
+ * [HexaPDF::Type::Page#rotate] to also rotate annotations
57
+
58
+
59
+ ## 0.27.0 - 2022-11-18
60
+
61
+ ### Added
62
+
63
+ * Support for timestamp signatures through the
64
+ [HexaPDF::Document::Signatures::TimestampHandler]
65
+ * [HexaPDF::Document::Destinations#resolve] for resolving destination values
66
+ * [HexaPDF::Document::Destinations::Destination#value] to return the destination
67
+ array
68
+ * Support for verifying document timestamp signatures
69
+ * [HexaPDF::Document::Signatures::DefaultHandler#signature_size] to support
70
+ setting custom signature sizes
71
+ * [HexaPDF::Document::Signatures::DefaultHandler#external_signing] to support
72
+ signing via custom mechanisms
73
+ * [HexaPDF::Document::Signatures::embed_signature] to enable asynchronous
74
+ external signing
75
+
76
+ ### Changed
77
+
78
+ * **Breaking change**: The crop box is now used instead of the media box in most
79
+ cases to be in line with the specification
80
+ * [HexaPDF::Document::Signatures::DefaultHandler] to allow setting the used
81
+ signature method
82
+ * **Breaking change**: [HexaPDF::Document::Signatures::DefaultHandler#sign]
83
+ needs to accept the IO object and the byte range instead of just the data
84
+ * **Breaking change**: Enhanced support for outline items with new methods
85
+ `#level` and `#destination_page` as well as changes to `#add` and `#each_item`
86
+ * **Breaking change**: Removed `#filter_name` and `#sub_filter_name` from
87
+ [HexaPDF::Document::Signatures::DefaultHandler]
88
+ * `HexaPDF::Type::Resources#perform_validation` to not add a default procedure
89
+ set since this feature is deprecated
90
+
91
+ ### Fixed
92
+
93
+ * [HexaPDF::Document::Destinations::Destination::new] to also accept a hash
94
+ * [HexaPDF::Type::Catalog] auto-conversion of /Outlines to correct class
95
+ * [HexaPDF::Type::AcroForm::Form#flatten] to return the unflattened form fields
96
+ instead of the widgets
97
+ * [HexaPDF::Writer#write_incremental] to set the /Version in the catalog
98
+ dictionary when necessary
99
+ * [HexaPDF::Importer#import] to always return an imported object with the same
100
+ class as the argument
101
+ * [HexaPDF::Type::OutlineItem] to always be an indirect object
102
+ * `HexaPDF::Tokenizer#parse_number` to handle references correctly in all cases
103
+ * [HexaPDF::Type::Page#rotate] to correctly flatten all page boxes
104
+ * [HexaPDF::Document::Signatures#add] to raise an error if the reserved space
105
+ for the signature is not enough
106
+ * `HexaPDF::Type::AcroForm::Form#perform_validation` to fix broken /Parent
107
+ entries and to remove invalid objects from the field hierarchy
108
+ * `HexaPDF::Type::OutlineItem#perform_validation` bug where a missing /Count key
109
+ was deemed invalid
110
+ * [HexaPDF::Revisions::from_io] to use the correct /Prev offset when revisions
111
+ have been merged
112
+ * Handling of indirect objects with invalid values for more situations
113
+
114
+
1
115
  ## 0.26.2 - 2022-10-22
2
116
 
3
117
  ### Added
@@ -180,7 +294,7 @@
180
294
  ### Added
181
295
 
182
296
  - [HexaPDF::Composer#create_stamp] for creating a form Xobject
183
- - [HexaPDF::Revision#reset_objects] for deleting all live loaded and added
297
+ - `HexaPDF::Revision#reset_objects` for deleting all live loaded and added
184
298
  objects
185
299
  - Support for removing or flattening annotations to the `hexapdf modify` command
186
300
  - Option to CLI command `hexapdf form` to allow generation of a template file
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'),
@@ -6,6 +6,9 @@
6
6
  # This example show-cases how to create the various form field types and their
7
7
  # possible standard appearances.
8
8
  #
9
+ # Note the 'number format' text field which uses a JavaScript function for
10
+ # formatting a number.
11
+ #
9
12
  # Usage:
10
13
  # : `ruby acro_form.rb`
11
14
  #
@@ -42,13 +45,21 @@ rb = form.create_radio_button("Radio")
42
45
  end
43
46
  rb.field_value = :button0
44
47
 
45
- canvas.text("Text fields", at: [50, 450])
48
+ canvas.text("Text fields", at: [50, 480])
46
49
 
47
- canvas.text("Single line", at: [70, 420])
50
+ canvas.text("Single line", at: [70, 450])
48
51
  tx = form.create_text_field("Single Line", font_size: 16)
49
- widget = tx.create_widget(page, Rect: [200, 415, 500, 435])
52
+ widget = tx.create_widget(page, Rect: [200, 445, 500, 465])
50
53
  tx.field_value = "A sample test string!"
51
54
 
55
+ canvas.text("Number format", at: [70, 420])
56
+ tx = form.create_text_field("Number format", font_size: 16)
57
+ widget = tx.create_widget(page, Rect: [200, 415, 500, 435])
58
+ widget[:AA] = {
59
+ F: {S: :JavaScript, JS: 'AFNumber_Format(2, 2, 0, 0, "EUR ", true);'},
60
+ }
61
+ tx.field_value = "123456,789"
62
+
52
63
  canvas.text("Multiline", at: [70, 390])
53
64
  tx = form.create_multiline_text_field("Multiline", font_size: 0, align: :right)
54
65
  widget = tx.create_widget(page, Rect: [200, 325, 500, 405])
@@ -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
@@ -0,0 +1,30 @@
1
+ # # Images
2
+ #
3
+ # This example shows how to embed images into a PDF document, directly on a
4
+ # page's canvas and through the high-level [HexaPDF::Composer].
5
+ #
6
+ # Usage:
7
+ # : `ruby images.rb`
8
+ #
9
+
10
+ require 'hexapdf'
11
+
12
+ file = File.join(__dir__, 'machupicchu.jpg')
13
+
14
+ doc = HexaPDF::Document.new
15
+ # Image only added to PDF once though used multiple times
16
+ canvas = doc.pages.add.canvas
17
+ canvas.image(file, at: [100, 500]) # auto-size based on image size
18
+ canvas.image(file, at: [100, 300], width: 100) # height based on w/h ratio
19
+ canvas.image(file, at: [300, 300], height: 100) # width based on w/h ratio
20
+ canvas.image(file, at: [100, 100], width: 300, height: 100)
21
+
22
+ HexaPDF::Composer.create('images.pdf') do |composer|
23
+ composer.image(file) # fill current rectangular region
24
+ composer.image(file, width: 100) # height based on w/h ratio
25
+ composer.image(file, height: 100) # width based on w/h ratio
26
+ composer.image(file, width: 300, height: 100)
27
+
28
+ # Add the page created above as second page
29
+ composer.document.pages << composer.document.import(doc.pages[0])
30
+ end
@@ -131,6 +131,10 @@ module HexaPDF
131
131
  output_line("Encrypted", "yes (no or wrong password given)")
132
132
  end
133
133
 
134
+ if doc.revisions.parser.linearized?
135
+ output_line("Linearized", "yes")
136
+ end
137
+
134
138
  signatures = doc.signatures.to_a
135
139
  unless signatures.empty?
136
140
  nr_sigs = signatures.count
@@ -186,7 +190,7 @@ module HexaPDF
186
190
  end
187
191
 
188
192
  def output_line(header, text) #:nodoc:
189
- puts(("#{header}:").ljust(COLUMN_WIDTH) << text.to_s)
193
+ puts("#{header}:".ljust(COLUMN_WIDTH) << text.to_s)
190
194
  end
191
195
 
192
196
  end
@@ -335,9 +335,9 @@ module HexaPDF
335
335
  # - The signature dictionary if this revision was signed
336
336
  # - The byte offset from the start of the file to the end of the revision
337
337
  def revision_information
338
- signatures = @doc.signatures.map do |sig|
338
+ signatures = @doc.signatures.to_h do |sig|
339
339
  [@doc.revisions.find {|rev| rev.object(sig) == sig }, sig]
340
- end.to_h
340
+ end
341
341
  io = @doc.revisions.parser.io
342
342
 
343
343
  startxrefs = @doc.revisions.map {|rev| rev.trailer[:Prev] }
@@ -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
- 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 }
134
+ paper_size = HexaPDF::Type::Page::PAPER_SIZE.find do |_name, paper_box|
135
+ paper_box.each_with_index.all? {|entry, index| (entry - paper_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)
@@ -422,8 +422,7 @@ module HexaPDF
422
422
  'encryption.filter_map' => {
423
423
  Standard: 'HexaPDF::Encryption::StandardSecurityHandler',
424
424
  },
425
- 'encryption.sub_filter_map' => {
426
- },
425
+ 'encryption.sub_filter_map' => {},
427
426
  'filter.map' => {
428
427
  ASCIIHexDecode: 'HexaPDF::Filter::ASCIIHexDecode',
429
428
  AHx: 'HexaPDF::Filter::ASCIIHexDecode',
@@ -488,11 +487,13 @@ module HexaPDF
488
487
  },
489
488
  'signature.signing_handler' => {
490
489
  default: 'HexaPDF::Document::Signatures::DefaultHandler',
490
+ timestamp: 'HexaPDF::Document::Signatures::TimestampHandler',
491
491
  },
492
492
  'signature.sub_filter_map' => {
493
493
  'adbe.x509.rsa_sha1': 'HexaPDF::Type::Signature::AdbeX509RsaSha1',
494
494
  'adbe.pkcs7.detached': 'HexaPDF::Type::Signature::AdbePkcs7Detached',
495
495
  'ETSI.CAdES.detached': 'HexaPDF::Type::Signature::AdbePkcs7Detached',
496
+ 'ETSI.RFC3161': 'HexaPDF::Type::Signature::AdbePkcs7Detached',
496
497
  },
497
498
  'task.map' => {
498
499
  optimize: 'HexaPDF::Task::Optimize',
@@ -1626,17 +1626,22 @@ module HexaPDF
1626
1626
  end
1627
1627
  return obj if obj.width == 0 || obj.height == 0
1628
1628
 
1629
+ left, bottom = *at
1629
1630
  width, height = calculate_dimensions(obj.width, obj.height,
1630
1631
  rwidth: width, rheight: height)
1631
1632
  if obj[:Subtype] != :Image
1632
1633
  width /= obj.box.width.to_f
1633
1634
  height /= obj.box.height.to_f
1634
- at[0] -= obj.box.left
1635
- at[1] -= obj.box.bottom
1635
+ left -= obj.box.left
1636
+ bottom -= obj.box.bottom
1636
1637
  end
1637
1638
 
1638
- transform(width, 0, 0, height, at[0], at[1]) do
1639
+ if left == 0 && bottom == 0 && width == 1 && height == 1
1639
1640
  invoke1(:Do, resources.add_xobject(obj))
1641
+ else
1642
+ transform(width, 0, 0, height, left, bottom) do
1643
+ invoke1(:Do, resources.add_xobject(obj))
1644
+ end
1640
1645
  end
1641
1646
 
1642
1647
  obj
@@ -108,11 +108,7 @@ module HexaPDF
108
108
  # The ancestor classes are also searched for such a field entry if none is found for the
109
109
  # current class.
110
110
  def self.field(name)
111
- if defined?(@fields) && @fields.key?(name)
112
- @fields[name]
113
- elsif superclass.respond_to?(:field)
114
- superclass.field(name)
115
- end
111
+ @fields&.[](name) || superclass.field(name)
116
112
  end
117
113
 
118
114
  # :call-seq:
@@ -259,22 +255,13 @@ module HexaPDF
259
255
  end
260
256
  end
261
257
 
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
258
  # Performs validation tasks based on the currently set keys and defined fields.
271
259
  def perform_validation(&block)
272
260
  super
273
- each_set_key_or_required_field do |name, field|
274
- obj = key?(name) ? self[name] : nil
261
+ self.class.each_field do |name, field|
262
+ next unless field.required? || value.key?(name)
275
263
 
276
- # The checks below need a valid field definition
277
- next if field.nil?
264
+ obj = key?(name) ? self[name] : nil
278
265
 
279
266
  # Check that required fields are set
280
267
  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
  #