hexapdf 0.46.0 → 1.0.0
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 +83 -16
- data/lib/hexapdf/composer.rb +7 -0
- data/lib/hexapdf/configuration.rb +13 -0
- data/lib/hexapdf/content/parser.rb +3 -1
- data/lib/hexapdf/digital_signature/cms_handler.rb +13 -0
- data/lib/hexapdf/digital_signature/signature.rb +1 -1
- data/lib/hexapdf/digital_signature/signing/default_handler.rb +1 -0
- data/lib/hexapdf/document.rb +14 -3
- data/lib/hexapdf/encryption/standard_security_handler.rb +32 -26
- data/lib/hexapdf/font/cmap/writer.rb +58 -4
- data/lib/hexapdf/font/cmap.rb +7 -0
- data/lib/hexapdf/font/true_type_wrapper.rb +41 -16
- data/lib/hexapdf/importer.rb +1 -1
- data/lib/hexapdf/layout/table_box.rb +57 -10
- data/lib/hexapdf/layout/text_fragment.rb +2 -1
- data/lib/hexapdf/object.rb +1 -1
- data/lib/hexapdf/parser.rb +1 -1
- data/lib/hexapdf/reference.rb +1 -1
- data/lib/hexapdf/task/merge_acro_form.rb +164 -0
- data/lib/hexapdf/task/optimize.rb +4 -4
- data/lib/hexapdf/task.rb +1 -0
- data/lib/hexapdf/tokenizer.rb +2 -0
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +8 -4
- data/lib/hexapdf/type/acro_form/form.rb +14 -24
- data/lib/hexapdf/type/acro_form/signature_field.rb +18 -7
- data/lib/hexapdf/type/acro_form/variable_text_field.rb +12 -4
- data/lib/hexapdf/type/actions/go_to.rb +1 -0
- data/lib/hexapdf/type/actions/go_to_r.rb +1 -0
- data/lib/hexapdf/type/actions/launch.rb +5 -1
- data/lib/hexapdf/type/annotation.rb +6 -1
- data/lib/hexapdf/type/annotations/markup_annotation.rb +14 -1
- data/lib/hexapdf/type/annotations/widget.rb +4 -2
- data/lib/hexapdf/type/catalog.rb +3 -0
- data/lib/hexapdf/type/cid_font.rb +4 -1
- data/lib/hexapdf/type/file_specification.rb +17 -14
- data/lib/hexapdf/type/font_descriptor.rb +4 -3
- data/lib/hexapdf/type/font_simple.rb +3 -1
- data/lib/hexapdf/type/font_true_type.rb +2 -0
- data/lib/hexapdf/type/font_type0.rb +1 -1
- data/lib/hexapdf/type/font_type1.rb +7 -0
- data/lib/hexapdf/type/font_type3.rb +0 -1
- data/lib/hexapdf/type/form.rb +5 -2
- data/lib/hexapdf/type/graphics_state_parameter.rb +7 -4
- data/lib/hexapdf/type/image.rb +8 -4
- data/lib/hexapdf/type/info.rb +2 -2
- data/lib/hexapdf/type/mark_information.rb +2 -2
- data/lib/hexapdf/type/optional_content_configuration.rb +1 -1
- data/lib/hexapdf/type/optional_content_membership.rb +1 -1
- data/lib/hexapdf/type/page.rb +5 -3
- data/lib/hexapdf/type/resources.rb +6 -6
- data/lib/hexapdf/type/viewer_preferences.rb +4 -3
- data/lib/hexapdf/version.rb +1 -1
- data/lib/hexapdf/writer.rb +1 -0
- data/test/data/standard-security-handler/bothpwd-aes-256bit-V5-R5.pdf +43 -0
- data/test/data/standard-security-handler/nopwd-aes-256bit-V5-R5.pdf +44 -0
- data/test/data/standard-security-handler/ownerpwd-aes-256bit-V5-R5.pdf +43 -0
- data/test/data/standard-security-handler/userpwd-aes-256bit-V5-R5.pdf +0 -0
- data/test/hexapdf/common_tokenizer_tests.rb +5 -0
- data/test/hexapdf/digital_signature/signing/test_default_handler.rb +6 -0
- data/test/hexapdf/digital_signature/test_cms_handler.rb +12 -7
- data/test/hexapdf/digital_signature/test_signature.rb +7 -0
- data/test/hexapdf/digital_signature/test_signatures.rb +12 -7
- data/test/hexapdf/encryption/test_standard_security_handler.rb +5 -2
- data/test/hexapdf/font/cmap/test_writer.rb +73 -16
- data/test/hexapdf/font/test_true_type_wrapper.rb +17 -3
- data/test/hexapdf/layout/test_list_box.rb +7 -7
- data/test/hexapdf/layout/test_table_box.rb +52 -0
- data/test/hexapdf/layout/test_text_fragment.rb +3 -3
- data/test/hexapdf/layout/test_text_layouter.rb +4 -2
- data/test/hexapdf/task/test_merge_acro_form.rb +104 -0
- data/test/hexapdf/task/test_optimize.rb +2 -0
- data/test/hexapdf/test_composer.rb +8 -0
- data/test/hexapdf/test_document.rb +12 -3
- data/test/hexapdf/test_importer.rb +7 -0
- data/test/hexapdf/test_parser.rb +7 -0
- data/test/hexapdf/test_writer.rb +19 -5
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +40 -23
- data/test/hexapdf/type/acro_form/test_form.rb +7 -8
- data/test/hexapdf/type/acro_form/test_signature_field.rb +3 -1
- data/test/hexapdf/type/acro_form/test_variable_text_field.rb +14 -1
- data/test/hexapdf/type/actions/test_launch.rb +6 -2
- data/test/hexapdf/type/annotations/test_widget.rb +4 -0
- data/test/hexapdf/type/test_font_type1.rb +5 -0
- data/test/hexapdf/type/test_form.rb +1 -1
- data/test/hexapdf/type/test_page.rb +7 -1
- metadata +8 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2889ba1d03e2c351efd694b1583063023fff97c0da636ff5103f88538255735c
|
|
4
|
+
data.tar.gz: 6fb4727db05900e8fccba2ad4e093d1092e17305e5b5616ded97a76cf835673c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 00be8ed2c306a88e5bfc0eada97a7e6bf802ec269e832bb21b3521d4077b18ecad11946ddc6f8a6d575820e66339059e59ba2c4cdd2b74d6c7d6defd0f2f5256
|
|
7
|
+
data.tar.gz: 94c6a8178ead2a986921b72b07ef5dc388a5fa6a67945573eec921db30c9940d241d3f47591c8fbbf9bdcf313df0dda536f0fc78e0e946e27dfa3bc13dad9a28
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,70 @@
|
|
|
1
|
+
## 1.0.0 - 2024-10-26
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
|
|
5
|
+
* [HexaPDF::Task::MergeAcroForm] for merging AcroForm information for imported
|
|
6
|
+
pages
|
|
7
|
+
* [HexaPDF::Document#write_to_string] and [HexaPDF::Composer#write_to_string]
|
|
8
|
+
for easily writing a document to a String
|
|
9
|
+
* [HexaPDF::Font::CMap::Writer#create_cid_cmap] for creating a character code to
|
|
10
|
+
CID CMap file
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
* [HexaPDF::Type::AcroForm::Form] text-like field creation methods to always set
|
|
15
|
+
a default appearance string and the quadding
|
|
16
|
+
* Convenience methods for accessing resources to not add the deprecated /ProcSet
|
|
17
|
+
entry by default
|
|
18
|
+
* [HexaPDF::DigitalSignature::CMSHandler] to add informational output regarding
|
|
19
|
+
the certificate chain on verification
|
|
20
|
+
* Validation of [HexaPDF::Type::FontType1] to ensure correct /Encoding value
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
* [HexaPDF::DigitalSignature::Signature#signed_data] to work for invalid offsets
|
|
25
|
+
* [HexaPDF::DigitalSignature::Signing::DefaultHandler] to update the document's
|
|
26
|
+
version to 2.0 when using PAdES
|
|
27
|
+
* Parsing of invalid `)` character in PDF objects and content streams
|
|
28
|
+
* Handling of files that contain stream length values that are indirect objects
|
|
29
|
+
that do not exist
|
|
30
|
+
* [HexaPDF::Font::TrueTypeWrapper] to correctly handle the situation when
|
|
31
|
+
multiple codepoints refer to the same glyph ID
|
|
32
|
+
* [HexaPDF::Type::Page#contents] to handle null values in /Contents array
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
## 0.47.0 - 2024-09-07
|
|
36
|
+
|
|
37
|
+
### Added
|
|
38
|
+
|
|
39
|
+
* Configuration option 'acro_form.fallback_default_appearance' to allow setting
|
|
40
|
+
a standard default appearance string for a variable text field if none is
|
|
41
|
+
found
|
|
42
|
+
* Support for decrypting files with the proprietary algorithm /R 5
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
* [HexaPDF::Task::Optimize] to not remove optional /Type entries containing
|
|
47
|
+
default values
|
|
48
|
+
* Validation of [HexaPDF::Type::AcroForm::Form] to not add a /DA entry
|
|
49
|
+
|
|
50
|
+
### Fixed
|
|
51
|
+
|
|
52
|
+
* [HexaPDF::Layout::TableBox] to correctly calculcate and distribute row
|
|
53
|
+
heights when row spans are involved
|
|
54
|
+
* [HexaPDF::Type::AcroForm::AppearanceGenerator] to work for files where check
|
|
55
|
+
boxes don't define the name of the on state
|
|
56
|
+
* [HexaPDF::Importer#import] to handle null values in all cases
|
|
57
|
+
* [HexaPDF::Type::AcroForm::VariableTextField] to handle parsing of invalid PDFs
|
|
58
|
+
with symbolic appearance strings
|
|
59
|
+
* [HexaPDF::Type::Annotations::Widget#marker_style] to handle invalid /DA values
|
|
60
|
+
with missing font size or color information
|
|
61
|
+
* [HexaPDF::Type::AcroForm::SignatureField#field_value] to always return a
|
|
62
|
+
correctly wrapped object
|
|
63
|
+
* [HexaPDF::Writer] to remove /Type entry from trailer
|
|
64
|
+
* [HexaPDF::Type::AcroForm::AppearanceGenerator#create_text_appearances] to
|
|
65
|
+
handle invalid appearance streams that are not correct Form XObjects
|
|
66
|
+
|
|
67
|
+
|
|
1
68
|
## 0.46.0 - 2024-08-11
|
|
2
69
|
|
|
3
70
|
### Added
|
|
@@ -20,7 +87,7 @@
|
|
|
20
87
|
signatures
|
|
21
88
|
* [HexaPDF::DigitalSignature::CMSHandler#signing_time] to use time from an
|
|
22
89
|
embedded timestamp authority signature if possible
|
|
23
|
-
*
|
|
90
|
+
* HexaPDF::Layout::Box#fit to return success for boxes with content
|
|
24
91
|
width/height of zero
|
|
25
92
|
* [HexaPDF::Importer::copy] to optionally allow copying the catalog and page
|
|
26
93
|
tree nodes
|
|
@@ -28,7 +95,7 @@
|
|
|
28
95
|
### Fixed
|
|
29
96
|
|
|
30
97
|
* Setting of correct x-position in fit result for boxes with flow positioning
|
|
31
|
-
*
|
|
98
|
+
* HexaPDF::Layout::ListBox#fit to respect the set height
|
|
32
99
|
* CLI command `hexapdf inspect` to work in case of missing Unicde mappings
|
|
33
100
|
* [HexaPDF::Type::AcroForm::Form#delete_field] to correctly work for fields with
|
|
34
101
|
an embedded widget
|
|
@@ -47,7 +114,7 @@
|
|
|
47
114
|
|
|
48
115
|
### Changed
|
|
49
116
|
|
|
50
|
-
*
|
|
117
|
+
* HexaPDF::Layout::Box#fit to set width/height correctly for boxes with
|
|
51
118
|
position `:flow`
|
|
52
119
|
|
|
53
120
|
### Fixed
|
|
@@ -93,9 +160,9 @@
|
|
|
93
160
|
|
|
94
161
|
### Fixed
|
|
95
162
|
|
|
96
|
-
*
|
|
163
|
+
* HexaPDF::Layout::TextBox#fit to correctly calculate width in case of flowing
|
|
97
164
|
text around other boxes
|
|
98
|
-
*
|
|
165
|
+
* HexaPDF::Layout::TextBox#draw to correctly draw border, background... on
|
|
99
166
|
boxes using position 'flow'
|
|
100
167
|
* Comparison of Hash with [HexaPDF::Dictionary] objects by implementing
|
|
101
168
|
`#to_hash`
|
|
@@ -149,7 +216,7 @@
|
|
|
149
216
|
JavaScript action that formats the field's value
|
|
150
217
|
* [HexaPDF::Type::AcroForm::TextField#set_calculate_action] for setting a
|
|
151
218
|
JavaScript action that calculates the field's value
|
|
152
|
-
* [HexaPDF::Type::AcroForm#recalculate_fields] for recalculating fields
|
|
219
|
+
* [HexaPDF::Type::AcroForm::Form#recalculate_fields] for recalculating fields
|
|
153
220
|
|
|
154
221
|
### Changed
|
|
155
222
|
|
|
@@ -206,7 +273,7 @@
|
|
|
206
273
|
|
|
207
274
|
### Changed
|
|
208
275
|
|
|
209
|
-
*
|
|
276
|
+
* HexaPDF::Layout::Frame::FitResult#draw to provide better optional content
|
|
210
277
|
group names
|
|
211
278
|
|
|
212
279
|
### Fixed
|
|
@@ -285,8 +352,8 @@
|
|
|
285
352
|
|
|
286
353
|
### Changed
|
|
287
354
|
|
|
288
|
-
*
|
|
289
|
-
*
|
|
355
|
+
* HexaPDF::Layout::Frame::FitResult#draw to allow drawing at an offset
|
|
356
|
+
* HexaPDF::Layout::Box#fit to delegate the actual content fitting to the
|
|
290
357
|
`#fit_content` method
|
|
291
358
|
* [HexaPDF::Document::Layout#box] to allow using the block as drawing block for
|
|
292
359
|
the base box class
|
|
@@ -363,8 +430,8 @@
|
|
|
363
430
|
|
|
364
431
|
### Fixed
|
|
365
432
|
|
|
366
|
-
*
|
|
367
|
-
*
|
|
433
|
+
* HexaPDF::Layout::ColumnBox#fit to correctly take initial height into account
|
|
434
|
+
* HexaPDF::Layout::ColumnBox#fit to ensure correct results in case the
|
|
368
435
|
requested dimensions are larger than the current region
|
|
369
436
|
* [HexaPDF::Document::Layout#formatted_text_box] to correctly handle properties
|
|
370
437
|
* [HexaPDF::Layout::Frame#fit] to raise an error if an invalid value for the
|
|
@@ -410,7 +477,7 @@
|
|
|
410
477
|
context argument (a page or Form XObject instance)
|
|
411
478
|
* [HexaPDF::Layout::ListBox] to use its 'fill_color' style property for the item
|
|
412
479
|
marker color
|
|
413
|
-
*
|
|
480
|
+
* HexaPDF::Layout::Frame::FitResult#draw to use optional content groups for
|
|
414
481
|
debug output
|
|
415
482
|
|
|
416
483
|
### Fixed
|
|
@@ -419,7 +486,7 @@
|
|
|
419
486
|
default range starting at page 1
|
|
420
487
|
* [HexaPDF::Type::Page#flatten_annotations] to correctly handle scaled
|
|
421
488
|
appearances
|
|
422
|
-
* Using an unknown style name in [HexaPDF
|
|
489
|
+
* Using an unknown style name in [HexaPDF::Document::Layout] method by providing
|
|
423
490
|
a useful error message
|
|
424
491
|
* [HexaPDF::Layout::Box::new] to ensure that the properties attribute is always
|
|
425
492
|
a hash
|
|
@@ -480,7 +547,7 @@
|
|
|
480
547
|
final box positions into account
|
|
481
548
|
* [HexaPDF::Content::Canvas#text] to set the leading only when multiple lines
|
|
482
549
|
are drawn
|
|
483
|
-
*
|
|
550
|
+
* HexaPDF::Layout::TextBox#split to use float comparison
|
|
484
551
|
* Validation of standard encryption dictionary to auto-correct invalid /U and /O
|
|
485
552
|
fields in case they are padded with zeros
|
|
486
553
|
* [HexaPDF::Document#wrap] handling of sub-type mapping in case of missing type
|
|
@@ -897,7 +964,7 @@
|
|
|
897
964
|
* [HexaPDF::Layout::WidthFromPolygon] to work correctly in case of very small
|
|
898
965
|
floating point errors
|
|
899
966
|
* HexaPDF::Layout::TextFragment#inspect to work in case of interspersed numbers
|
|
900
|
-
*
|
|
967
|
+
* HexaPDF::Layout::TextBox#split to work for position :flow when box is wider
|
|
901
968
|
than the initial available width
|
|
902
969
|
* [HexaPDF::Layout::Frame#fit] to create minimally sized mask rectangles
|
|
903
970
|
* [HexaPDF::Content::GraphicObject::Geom2D] to close the path when drawing
|
|
@@ -1833,7 +1900,7 @@
|
|
|
1833
1900
|
objects
|
|
1834
1901
|
* [HexaPDF::Revision#each_modified_object] for iterating over all modified
|
|
1835
1902
|
objects of a revision
|
|
1836
|
-
* [HexaPDF::Layout::Box#split] and
|
|
1903
|
+
* [HexaPDF::Layout::Box#split] and HexaPDF::Layout::TextBox#split for
|
|
1837
1904
|
splitting a box into two parts
|
|
1838
1905
|
* [HexaPDF::Layout::Frame#full?] for testing whether the frame has any space
|
|
1839
1906
|
left
|
data/lib/hexapdf/composer.rb
CHANGED
|
@@ -231,6 +231,13 @@ module HexaPDF
|
|
|
231
231
|
@document.write(output, optimize: optimize, **options)
|
|
232
232
|
end
|
|
233
233
|
|
|
234
|
+
# Writes the created PDF document to a string and returns that string.
|
|
235
|
+
#
|
|
236
|
+
# See HexaPDF::Document#write for details.
|
|
237
|
+
def write_to_string(optimize: true, **options)
|
|
238
|
+
@document.write_to_string(optimize: optimize, **options)
|
|
239
|
+
end
|
|
240
|
+
|
|
234
241
|
# :call-seq:
|
|
235
242
|
# composer.style(name) -> style
|
|
236
243
|
# composer.style(name, base: :base, **properties) -> style
|
|
@@ -182,6 +182,16 @@ module HexaPDF
|
|
|
182
182
|
# acro_form.default_font_size::
|
|
183
183
|
# A number specifying the default font size of AcroForm text fields which should be auto-sized.
|
|
184
184
|
#
|
|
185
|
+
# acro_form.fallback_default_appearance::
|
|
186
|
+
# A hash containging arguments for
|
|
187
|
+
# HexaPDF::Type::AcroForm::VariableTextField#set_defaut_appearance_string which is used as
|
|
188
|
+
# fallback for fields without a default appearance.
|
|
189
|
+
#
|
|
190
|
+
# If this value is set to +nil+, an error is raised in case a variable text field cannot
|
|
191
|
+
# resolve a default appearance string.
|
|
192
|
+
#
|
|
193
|
+
# The default is the empty hash meaning the defaults from the method are used.
|
|
194
|
+
#
|
|
185
195
|
# acro_form.fallback_font::
|
|
186
196
|
# The font that should be used when a variable text field references a font that cannot be used.
|
|
187
197
|
#
|
|
@@ -485,6 +495,7 @@ module HexaPDF
|
|
|
485
495
|
Configuration.new('acro_form.appearance_generator' => 'HexaPDF::Type::AcroForm::AppearanceGenerator',
|
|
486
496
|
'acro_form.create_appearances' => true,
|
|
487
497
|
'acro_form.default_font_size' => 10,
|
|
498
|
+
'acro_form.fallback_default_appearance' => {},
|
|
488
499
|
'acro_form.fallback_font' => 'Helvetica',
|
|
489
500
|
'acro_form.on_invalid_value' => proc do |field, value|
|
|
490
501
|
raise HexaPDF::Error, "Invalid value #{value.inspect} for " \
|
|
@@ -587,6 +598,7 @@ module HexaPDF
|
|
|
587
598
|
optimize: 'HexaPDF::Task::Optimize',
|
|
588
599
|
dereference: 'HexaPDF::Task::Dereference',
|
|
589
600
|
pdfa: 'HexaPDF::Task::PDFA',
|
|
601
|
+
merge_acro_form: 'HexaPDF::Task::MergeAcroForm',
|
|
590
602
|
})
|
|
591
603
|
|
|
592
604
|
# The global configuration object, providing the following options:
|
|
@@ -709,6 +721,7 @@ module HexaPDF
|
|
|
709
721
|
Metadata: 'HexaPDF::Type::Metadata',
|
|
710
722
|
OutputIntent: 'HexaPDF::Type::OutputIntent',
|
|
711
723
|
XXDestOutputProfileRef: 'HexaPDF::Type::OutputIntent::DestOutputProfileRef',
|
|
724
|
+
ExData: 'HexaPDF::Type::Annotations::MarkupAnnotation::ExData',
|
|
712
725
|
},
|
|
713
726
|
'object.subtype_map' => {
|
|
714
727
|
nil => {
|
|
@@ -112,7 +112,9 @@ module HexaPDF
|
|
|
112
112
|
elsif byte == 93 # ]
|
|
113
113
|
@ss.pos += 1
|
|
114
114
|
TOKEN_ARRAY_END
|
|
115
|
-
elsif byte ==
|
|
115
|
+
elsif byte == 41 # )
|
|
116
|
+
raise HexaPDF::MalformedPDFError.new("Delimiter ')' found at invalid position", pos: pos)
|
|
117
|
+
elsif byte == 123 || byte == 125 # { } )
|
|
116
118
|
Token.new(@ss.get_byte)
|
|
117
119
|
elsif byte == 37 # %
|
|
118
120
|
unless @ss.skip_until(/(?=[\r\n])/)
|
|
@@ -155,6 +155,19 @@ module HexaPDF
|
|
|
155
155
|
result.log(:error, "Signature verification failed")
|
|
156
156
|
end
|
|
157
157
|
|
|
158
|
+
certs = [signer_certificate]
|
|
159
|
+
cur_cert = certs.first
|
|
160
|
+
while true
|
|
161
|
+
cur_cert = certificate_chain.find {|cert| cert.subject == cur_cert.issuer }
|
|
162
|
+
if cur_cert && !certs.include?(cur_cert)
|
|
163
|
+
certs << cur_cert
|
|
164
|
+
else
|
|
165
|
+
break
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
cert_subjects = certs.map {|cert| cert.subject.to_a.assoc("CN")&.[](1) }
|
|
169
|
+
result.log(:info, "Certificate chain: #{cert_subjects.join(" -> ")}")
|
|
170
|
+
|
|
158
171
|
result
|
|
159
172
|
end
|
|
160
173
|
|
|
@@ -289,6 +289,7 @@ module HexaPDF
|
|
|
289
289
|
signature[:Location] = location if location
|
|
290
290
|
signature[:ContactInfo] = contact_info if contact_info
|
|
291
291
|
signature[:Prop_Build] = {App: {Name: :HexaPDF, REx: HexaPDF::VERSION}}
|
|
292
|
+
signature.document.version = '2.0' if signature_type == :pades
|
|
292
293
|
|
|
293
294
|
if doc_mdp_permissions
|
|
294
295
|
doc = signature.document
|
data/lib/hexapdf/document.rb
CHANGED
|
@@ -724,10 +724,12 @@ module HexaPDF
|
|
|
724
724
|
end
|
|
725
725
|
|
|
726
726
|
# :call-seq:
|
|
727
|
-
# doc.write(filename, incremental: false, validate: true, update_fields: true, optimize: false)
|
|
728
|
-
# doc.write(io, incremental: false, validate: true, update_fields: true, optimize: false)
|
|
727
|
+
# doc.write(filename, incremental: false, validate: true, update_fields: true, optimize: false) -> [start_xref, section]
|
|
728
|
+
# doc.write(io, incremental: false, validate: true, update_fields: true, optimize: false) -> [start_xref, section]
|
|
729
729
|
#
|
|
730
|
-
# Writes the document to the given file (in case +io+ is a String) or IO stream.
|
|
730
|
+
# Writes the document to the given file (in case +io+ is a String) or IO stream. Returns the
|
|
731
|
+
# file position of the start of the last cross-reference section and the last XRefSection object
|
|
732
|
+
# written.
|
|
731
733
|
#
|
|
732
734
|
# Before the document is written, it is validated using #validate and an error is raised if the
|
|
733
735
|
# document is not valid. However, this step can be skipped if needed.
|
|
@@ -784,6 +786,15 @@ module HexaPDF
|
|
|
784
786
|
end
|
|
785
787
|
end
|
|
786
788
|
|
|
789
|
+
# Writes the document to a string and returns the string.
|
|
790
|
+
#
|
|
791
|
+
# See #write for further information and details on the available arguments.
|
|
792
|
+
def write_to_string(**args)
|
|
793
|
+
io = StringIO.new(''.b)
|
|
794
|
+
write(io)
|
|
795
|
+
io.string
|
|
796
|
+
end
|
|
797
|
+
|
|
787
798
|
def inspect #:nodoc:
|
|
788
799
|
"<#{self.class.name}:#{object_id}>"
|
|
789
800
|
end
|
|
@@ -106,6 +106,10 @@ module HexaPDF
|
|
|
106
106
|
# password is supplied. To open such an encrypted PDF file, the +decryption_opts+ provided to
|
|
107
107
|
# HexaPDF::Document.new needs to contain a :password key with the password.
|
|
108
108
|
#
|
|
109
|
+
# **Note**: While HexaPDF supports reading files encrypted with revision 5, it doesn't support
|
|
110
|
+
# writing such files. This is no problem in practice since revision 5 was an inofficial Adobe
|
|
111
|
+
# extension to PDF 1.7 and revision 6 specified in PDF 2.0 is practically the same.
|
|
112
|
+
#
|
|
109
113
|
# See: PDF2.0 s7.6.4
|
|
110
114
|
class StandardSecurityHandler < SecurityHandler
|
|
111
115
|
|
|
@@ -340,13 +344,13 @@ module HexaPDF
|
|
|
340
344
|
# Uses the given password (or the default password if none given) to retrieve the encryption
|
|
341
345
|
# key.
|
|
342
346
|
#
|
|
343
|
-
# If the optional +check_permissions+ argument is +true+, the permissions for files
|
|
344
|
-
#
|
|
347
|
+
# If the optional +check_permissions+ argument is +true+, the permissions for files encrypted
|
|
348
|
+
# with revision 5 or 6 are checked. Otherwise, permission changes are ignored.
|
|
345
349
|
def prepare_decryption(password: '', check_permissions: true)
|
|
346
350
|
if dict[:Filter] != :Standard
|
|
347
351
|
raise(HexaPDF::UnsupportedEncryptionError,
|
|
348
352
|
"Invalid /Filter value #{dict[:Filter]} for standard security handler")
|
|
349
|
-
elsif ![2, 3, 4, 6].include?(dict[:R])
|
|
353
|
+
elsif ![2, 3, 4, 5, 6].include?(dict[:R])
|
|
350
354
|
raise(HexaPDF::UnsupportedEncryptionError,
|
|
351
355
|
"Invalid /R value #{dict[:R]} for standard security handler")
|
|
352
356
|
elsif dict[:R] <= 4 && !document.trailer[:ID].kind_of?(PDFArray)
|
|
@@ -369,7 +373,7 @@ module HexaPDF
|
|
|
369
373
|
raise HexaPDF::EncryptionError, "Invalid password specified"
|
|
370
374
|
end
|
|
371
375
|
|
|
372
|
-
check_perms_field(encryption_key) if check_permissions && dict[:R]
|
|
376
|
+
check_perms_field(encryption_key) if check_permissions && dict[:R] >= 5
|
|
373
377
|
|
|
374
378
|
encryption_key
|
|
375
379
|
end
|
|
@@ -396,8 +400,8 @@ module HexaPDF
|
|
|
396
400
|
# For revisions <= 4 this is the *only* way for generating the encryption key needed to
|
|
397
401
|
# encrypt or decrypt a file.
|
|
398
402
|
#
|
|
399
|
-
# For revision 6 the file encryption key is a string of random bytes that has been
|
|
400
|
-
# with the user password. If the password is the owner password,
|
|
403
|
+
# For revision 5 and 6 the file encryption key is a string of random bytes that has been
|
|
404
|
+
# encrypted with the user password. If the password is the owner password,
|
|
401
405
|
# #compute_owner_encryption_key has to be used instead.
|
|
402
406
|
#
|
|
403
407
|
# See: PDF2.0 s7.6.4.3.2 (algorithm 2), PDF2.0 s7.6.4.3.3 (algorithm 2.A (a)-(b),(e))
|
|
@@ -416,7 +420,7 @@ module HexaPDF
|
|
|
416
420
|
end
|
|
417
421
|
|
|
418
422
|
data[0, n]
|
|
419
|
-
elsif dict[:R]
|
|
423
|
+
elsif dict[:R] <= 6
|
|
420
424
|
key = compute_hash(password, dict[:U][40, 8])
|
|
421
425
|
aes_algorithm.new(key, "\0" * 16, :decrypt).process(dict[:UE])
|
|
422
426
|
end
|
|
@@ -427,15 +431,15 @@ module HexaPDF
|
|
|
427
431
|
# For revisions <= 4 this is done by first retrieving the user password through the use of
|
|
428
432
|
# the owner password and then using the #compute_user_encryption_key method.
|
|
429
433
|
#
|
|
430
|
-
# For
|
|
431
|
-
# with the owner password. If the password is the user password,
|
|
432
|
-
# has to be used.
|
|
434
|
+
# For revisions 5 and 6 the file encryption key is a string of random bytes that has been
|
|
435
|
+
# encrypted with the owner password. If the password is the user password,
|
|
436
|
+
# #compute_user_encryption_key has to be used.
|
|
433
437
|
#
|
|
434
438
|
# See: PDF2.0 s7.6.4.3.2 (algorithm 2.A (a)-(d))
|
|
435
439
|
def compute_owner_encryption_key(password)
|
|
436
440
|
if dict[:R] <= 4
|
|
437
441
|
compute_user_encryption_key(user_password_from_owner_password(password))
|
|
438
|
-
elsif dict[:R]
|
|
442
|
+
elsif dict[:R] <= 6
|
|
439
443
|
key = compute_hash(password, dict[:O][40, 8], dict[:U])
|
|
440
444
|
aes_algorithm.new(key, "\0" * 16, :decrypt).process(dict[:OE])
|
|
441
445
|
end
|
|
@@ -447,7 +451,7 @@ module HexaPDF
|
|
|
447
451
|
# the owner password. For revision 6 the /O value is a hash computed from the password and
|
|
448
452
|
# the /U value with added validation and key salts.
|
|
449
453
|
#
|
|
450
|
-
# *Attention*: If revision 6 is used, the /U value has to be computed and set before this
|
|
454
|
+
# *Attention*: If revision 5 or 6 is used, the /U value has to be computed and set before this
|
|
451
455
|
# method is used, otherwise the return value is incorrect!
|
|
452
456
|
#
|
|
453
457
|
# See: PDF2.0 s7.6.4.4.2 (algorithm 3), PDF2.0 s7.6.4.4.8 (algorithm 9 (a))
|
|
@@ -465,14 +469,14 @@ module HexaPDF
|
|
|
465
469
|
end
|
|
466
470
|
|
|
467
471
|
data
|
|
468
|
-
elsif dict[:R]
|
|
472
|
+
elsif dict[:R] <= 6
|
|
469
473
|
validation_salt = random_bytes(8)
|
|
470
474
|
key_salt = random_bytes(8)
|
|
471
475
|
compute_hash(owner_password, validation_salt, dict[:U]) << validation_salt << key_salt
|
|
472
476
|
end
|
|
473
477
|
end
|
|
474
478
|
|
|
475
|
-
# Computes the encryption dictionary's /OE (owner encryption key) value (for
|
|
479
|
+
# Computes the encryption dictionary's /OE (owner encryption key) value (for revisions 5 and 6
|
|
476
480
|
# only).
|
|
477
481
|
#
|
|
478
482
|
# Short explanation: Encrypts the file encryption key with a key based on the password and
|
|
@@ -487,7 +491,7 @@ module HexaPDF
|
|
|
487
491
|
# Computes the encryption dictionary's /U (user password) value.
|
|
488
492
|
#
|
|
489
493
|
# Short explanation: For revisions <= 4, the password padding string is encrypted with a key
|
|
490
|
-
# based on the user password. For
|
|
494
|
+
# based on the user password. For revisions 5 and 6 the /U value is a hash computed from the
|
|
491
495
|
# password with added validation and key salts.
|
|
492
496
|
#
|
|
493
497
|
# See: PDF2.0 s7.6.4.4.3 (algorithm 4 for R=2), PDF s7.6.4.4.4 (algorithm 5 for R=3 and R=4)
|
|
@@ -502,14 +506,14 @@ module HexaPDF
|
|
|
502
506
|
data = arc4_algorithm.encrypt(key, data)
|
|
503
507
|
19.times {|i| data = arc4_algorithm.encrypt(xor_key(key, i + 1), data) }
|
|
504
508
|
data << "hexapdfhexapdfhe"
|
|
505
|
-
elsif dict[:R]
|
|
509
|
+
elsif dict[:R] <= 6
|
|
506
510
|
validation_salt = random_bytes(8)
|
|
507
511
|
key_salt = random_bytes(8)
|
|
508
512
|
compute_hash(password, validation_salt) << validation_salt << key_salt
|
|
509
513
|
end
|
|
510
514
|
end
|
|
511
515
|
|
|
512
|
-
# Computes the encryption dictionary's /UE (user encryption key) value (for revision 6
|
|
516
|
+
# Computes the encryption dictionary's /UE (user encryption key) value (for revision 5 and 6
|
|
513
517
|
# only).
|
|
514
518
|
#
|
|
515
519
|
# Short explanation: Encrypts the file encryption key with a key based on the password and
|
|
@@ -521,7 +525,8 @@ module HexaPDF
|
|
|
521
525
|
aes_algorithm.new(key, "\0" * 16, :encrypt).process(file_encryption_key)
|
|
522
526
|
end
|
|
523
527
|
|
|
524
|
-
# Computes the encryption dictionary's /Perms (permissions) value (for
|
|
528
|
+
# Computes the encryption dictionary's /Perms (permissions) value (for revisions 5 and 6
|
|
529
|
+
# only).
|
|
525
530
|
#
|
|
526
531
|
# Uses /P and /EncryptMetadata values, so these have to be set beforehand.
|
|
527
532
|
#
|
|
@@ -543,7 +548,7 @@ module HexaPDF
|
|
|
543
548
|
compute_u_field(password) == dict[:U]
|
|
544
549
|
elsif dict[:R] <= 4
|
|
545
550
|
compute_u_field(password)[0, 16] == dict[:U][0, 16]
|
|
546
|
-
elsif dict[:R]
|
|
551
|
+
elsif dict[:R] <= 6
|
|
547
552
|
compute_hash(password, dict[:U][32, 8]) == dict[:U][0, 32]
|
|
548
553
|
end
|
|
549
554
|
end
|
|
@@ -554,14 +559,14 @@ module HexaPDF
|
|
|
554
559
|
def owner_password_valid?(password)
|
|
555
560
|
if dict[:R] <= 4
|
|
556
561
|
user_password_valid?(user_password_from_owner_password(password))
|
|
557
|
-
elsif dict[:R]
|
|
562
|
+
elsif dict[:R] <= 6
|
|
558
563
|
compute_hash(password, dict[:O][32, 8], dict[:U]) == dict[:O][0, 32]
|
|
559
564
|
end
|
|
560
565
|
end
|
|
561
566
|
|
|
562
567
|
# Checks if the decrypted /Perms entry matches the /P and /EncryptMetadata entries.
|
|
563
568
|
#
|
|
564
|
-
# This method can only be used for
|
|
569
|
+
# This method can only be used for revisions 5 and 6.
|
|
565
570
|
#
|
|
566
571
|
# See: PDF2.0 s7.6.4.4.12 (algorithm 13)
|
|
567
572
|
def check_perms_field(encryption_key)
|
|
@@ -596,17 +601,18 @@ module HexaPDF
|
|
|
596
601
|
end
|
|
597
602
|
|
|
598
603
|
# Computes a hash that is used extensively for all operations in security handlers of
|
|
599
|
-
# revision 6.
|
|
604
|
+
# revision 5 and 6.
|
|
600
605
|
#
|
|
601
606
|
# Note: The original input (as defined by the spec) is calculated as
|
|
602
607
|
# "#{password}#{salt}#{user_key}" where +user_key+ has to be empty when doing operations
|
|
603
608
|
# with the user password.
|
|
604
609
|
#
|
|
605
|
-
# See: PDF2.0 s7.6.4.3.4 (algorithm 2.B)
|
|
610
|
+
# See: PDF2.0 s7.6.4.3.4 (algorithm 2.B) and ADB Extension Level 3 s3.5.2
|
|
606
611
|
def compute_hash(password, salt, user_key = '')
|
|
607
612
|
k = Digest::SHA256.digest("#{password}#{salt}#{user_key}")
|
|
608
|
-
|
|
613
|
+
return k if dict[:R] == 5
|
|
609
614
|
|
|
615
|
+
e = ''
|
|
610
616
|
i = 0
|
|
611
617
|
while i < 64 || e.getbyte(-1) > i - 32
|
|
612
618
|
k1 = "#{password}#{k}#{user_key}" * 64
|
|
@@ -627,7 +633,7 @@ module HexaPDF
|
|
|
627
633
|
# * For revisions <= 4, the password is converted into ISO-8859-1 encoding, padded with
|
|
628
634
|
# PASSWORD_PADDING and truncated to a maximum of 32 bytes.
|
|
629
635
|
#
|
|
630
|
-
# * For revision 6 the password is converted into UTF-8 encoding that is normalized
|
|
636
|
+
# * For revision 5 and 6 the password is converted into UTF-8 encoding that is normalized
|
|
631
637
|
# according to the PDF2.0 specification.
|
|
632
638
|
#
|
|
633
639
|
# See: PDF2.0 s7.6.4.3.2 (algorithm 2 step a)),
|
|
@@ -636,7 +642,7 @@ module HexaPDF
|
|
|
636
642
|
if dict[:R] <= 4
|
|
637
643
|
password.to_s[0, 32].encode(Encoding::ISO_8859_1).force_encoding(Encoding::BINARY).
|
|
638
644
|
ljust(32, PASSWORD_PADDING)
|
|
639
|
-
elsif dict[:R]
|
|
645
|
+
elsif dict[:R] <= 6
|
|
640
646
|
password.to_s.encode(Encoding::UTF_8).force_encoding(Encoding::BINARY)[0, 127]
|
|
641
647
|
end
|
|
642
648
|
rescue Encoding::UndefinedConversionError => e
|
|
@@ -40,9 +40,7 @@ module HexaPDF
|
|
|
40
40
|
module Font
|
|
41
41
|
class CMap
|
|
42
42
|
|
|
43
|
-
# Creates a CMap file.
|
|
44
|
-
#
|
|
45
|
-
# Currently only ToUnicode CMaps are supported.
|
|
43
|
+
# Creates a CMap file, either a ToUnicode CMap or a CID CMap.
|
|
46
44
|
class Writer
|
|
47
45
|
|
|
48
46
|
# Maximum number of entries in one section.
|
|
@@ -74,6 +72,28 @@ module HexaPDF
|
|
|
74
72
|
to_unicode_template % result.chop!
|
|
75
73
|
end
|
|
76
74
|
|
|
75
|
+
# Returns a CID CMap for the given input code to CID mapping which needs to be sorted by
|
|
76
|
+
# input codes.
|
|
77
|
+
#
|
|
78
|
+
# Note that the returned CMap always uses a 16-bit input code space!
|
|
79
|
+
def create_cid_cmap(mapping)
|
|
80
|
+
return cid_template % '' if mapping.empty?
|
|
81
|
+
|
|
82
|
+
chars, ranges = compute_section_entries(mapping)
|
|
83
|
+
|
|
84
|
+
result = create_sections("cidchar", chars.size / 2) do |index|
|
|
85
|
+
index *= 2
|
|
86
|
+
sprintf("<%04X>", chars[index]) << " #{chars[index + 1]}\n"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
result << create_sections("cidrange", ranges.size / 3) do |index|
|
|
90
|
+
index *= 3
|
|
91
|
+
sprintf("<%04X><%04X>", ranges[index], ranges[index + 1]) << " #{ranges[index + 2]}\n"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
cid_template % result.chop!
|
|
95
|
+
end
|
|
96
|
+
|
|
77
97
|
private
|
|
78
98
|
|
|
79
99
|
# Computes the entries for the "char" and "range" sections based on the given mapping.
|
|
@@ -146,7 +166,7 @@ module HexaPDF
|
|
|
146
166
|
result
|
|
147
167
|
end
|
|
148
168
|
|
|
149
|
-
# Returns the
|
|
169
|
+
# Returns the template for a ToUnicode CMap.
|
|
150
170
|
def to_unicode_template
|
|
151
171
|
<<~TEMPLATE
|
|
152
172
|
/CIDInit /ProcSet findresource begin
|
|
@@ -170,6 +190,40 @@ module HexaPDF
|
|
|
170
190
|
TEMPLATE
|
|
171
191
|
end
|
|
172
192
|
|
|
193
|
+
# Returns the template for a CID CMap.
|
|
194
|
+
def cid_template
|
|
195
|
+
<<~TEMPLATE
|
|
196
|
+
%%!PS-Adobe-3.0 Resource-CMap
|
|
197
|
+
%%%%DocumentNeededResources: ProcSet (CIDInit)
|
|
198
|
+
%%%%IncludeResource: ProcSet (CIDInit)
|
|
199
|
+
%%%%BeginResource: CMap (Custom)
|
|
200
|
+
%%%%Title: (Custom Adobe Identity 0)
|
|
201
|
+
%%%%Version: 1
|
|
202
|
+
/CIDInit /ProcSet findresource begin
|
|
203
|
+
12 dict begin
|
|
204
|
+
begincmap
|
|
205
|
+
/CIDSystemInfo 3 dict dup begin
|
|
206
|
+
/Registry (Adobe) def
|
|
207
|
+
/Ordering (Identity) def
|
|
208
|
+
/Supplement 0 def
|
|
209
|
+
end def
|
|
210
|
+
/CMapName /Custom def
|
|
211
|
+
/CMapType 1 def
|
|
212
|
+
/CMapVersion 1 def
|
|
213
|
+
/WMode 0 def
|
|
214
|
+
1 begincodespacerange
|
|
215
|
+
<0000> <FFFF>
|
|
216
|
+
endcodespacerange
|
|
217
|
+
%s
|
|
218
|
+
endcmap
|
|
219
|
+
CMapName currentdict /CMap defineresource pop
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
%%%%EndResource
|
|
223
|
+
%%%%EOF
|
|
224
|
+
TEMPLATE
|
|
225
|
+
end
|
|
226
|
+
|
|
173
227
|
end
|
|
174
228
|
|
|
175
229
|
end
|
data/lib/hexapdf/font/cmap.rb
CHANGED
|
@@ -85,6 +85,13 @@ module HexaPDF
|
|
|
85
85
|
Writer.new.create_to_unicode_cmap(mapping)
|
|
86
86
|
end
|
|
87
87
|
|
|
88
|
+
# Returns a string containing a CID CMap that represents the given code to CID mapping.
|
|
89
|
+
#
|
|
90
|
+
# See: Writer#create_cid_cmap
|
|
91
|
+
def self.create_cid_cmap(mapping)
|
|
92
|
+
Writer.new.create_cid_cmap(mapping)
|
|
93
|
+
end
|
|
94
|
+
|
|
88
95
|
# The registry part of the CMap version.
|
|
89
96
|
attr_accessor :registry
|
|
90
97
|
|