hexapdf 0.46.0 → 1.0.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 +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
|
|