hexapdf 0.32.1 → 0.33.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 +76 -1
- data/README.md +9 -0
- data/examples/002-graphics.rb +15 -17
- data/examples/003-arcs.rb +9 -9
- data/examples/009-text_layouter_alignment.rb +1 -1
- data/examples/010-text_layouter_inline_boxes.rb +2 -2
- data/examples/011-text_layouter_line_wrapping.rb +1 -1
- data/examples/012-text_layouter_styling.rb +7 -7
- data/examples/013-text_layouter_shapes.rb +1 -1
- data/examples/014-text_in_polygon.rb +1 -1
- data/examples/015-boxes.rb +8 -7
- data/examples/016-frame_automatic_box_placement.rb +2 -2
- data/examples/017-frame_text_flow.rb +2 -1
- data/examples/018-composer.rb +1 -1
- data/examples/020-column_box.rb +2 -1
- data/examples/025-table_box.rb +46 -0
- data/lib/hexapdf/cli/command.rb +5 -2
- data/lib/hexapdf/cli/form.rb +5 -5
- data/lib/hexapdf/cli/inspect.rb +3 -3
- data/lib/hexapdf/cli.rb +4 -0
- data/lib/hexapdf/composer.rb +104 -52
- data/lib/hexapdf/configuration.rb +44 -39
- data/lib/hexapdf/content/canvas.rb +393 -267
- data/lib/hexapdf/content/color_space.rb +72 -25
- data/lib/hexapdf/content/graphic_object/arc.rb +57 -24
- data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +66 -23
- data/lib/hexapdf/content/graphic_object/geom2d.rb +47 -6
- data/lib/hexapdf/content/graphic_object/solid_arc.rb +58 -36
- data/lib/hexapdf/content/graphic_object.rb +6 -7
- data/lib/hexapdf/content/graphics_state.rb +54 -45
- data/lib/hexapdf/content/operator.rb +52 -54
- data/lib/hexapdf/content/parser.rb +2 -2
- data/lib/hexapdf/content/processor.rb +15 -15
- data/lib/hexapdf/content/transformation_matrix.rb +1 -1
- data/lib/hexapdf/content.rb +5 -0
- data/lib/hexapdf/dictionary.rb +6 -5
- data/lib/hexapdf/dictionary_fields.rb +42 -14
- data/lib/hexapdf/digital_signature/cms_handler.rb +2 -2
- data/lib/hexapdf/digital_signature/handler.rb +1 -1
- data/lib/hexapdf/digital_signature/pkcs1_handler.rb +2 -3
- data/lib/hexapdf/digital_signature/signature.rb +6 -6
- data/lib/hexapdf/digital_signature/signatures.rb +13 -12
- data/lib/hexapdf/digital_signature/signing/default_handler.rb +14 -5
- data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +2 -4
- data/lib/hexapdf/digital_signature/signing/timestamp_handler.rb +4 -4
- data/lib/hexapdf/digital_signature/signing.rb +4 -0
- data/lib/hexapdf/digital_signature/verification_result.rb +2 -2
- data/lib/hexapdf/digital_signature.rb +7 -2
- data/lib/hexapdf/document/destinations.rb +12 -11
- data/lib/hexapdf/document/files.rb +1 -1
- data/lib/hexapdf/document/fonts.rb +1 -1
- data/lib/hexapdf/document/layout.rb +167 -39
- data/lib/hexapdf/document/pages.rb +3 -2
- data/lib/hexapdf/document.rb +89 -55
- data/lib/hexapdf/encryption/aes.rb +5 -5
- data/lib/hexapdf/encryption/arc4.rb +1 -1
- data/lib/hexapdf/encryption/fast_aes.rb +2 -2
- data/lib/hexapdf/encryption/fast_arc4.rb +1 -1
- data/lib/hexapdf/encryption/identity.rb +1 -1
- data/lib/hexapdf/encryption/ruby_aes.rb +1 -1
- data/lib/hexapdf/encryption/ruby_arc4.rb +1 -1
- data/lib/hexapdf/encryption/security_handler.rb +31 -24
- data/lib/hexapdf/encryption/standard_security_handler.rb +45 -36
- data/lib/hexapdf/encryption.rb +7 -2
- data/lib/hexapdf/error.rb +18 -0
- data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
- data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
- data/lib/hexapdf/filter/flate_decode.rb +1 -1
- data/lib/hexapdf/filter/lzw_decode.rb +1 -1
- data/lib/hexapdf/filter/pass_through.rb +1 -1
- data/lib/hexapdf/filter/predictor.rb +1 -1
- data/lib/hexapdf/filter/run_length_decode.rb +1 -1
- data/lib/hexapdf/filter.rb +55 -6
- data/lib/hexapdf/font/cmap/parser.rb +2 -2
- data/lib/hexapdf/font/cmap.rb +1 -1
- data/lib/hexapdf/font/encoding/difference_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/mac_expert_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/mac_roman_encoding.rb +2 -2
- data/lib/hexapdf/font/encoding/standard_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/symbol_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/win_ansi_encoding.rb +3 -3
- data/lib/hexapdf/font/encoding/zapf_dingbats_encoding.rb +1 -1
- data/lib/hexapdf/font/invalid_glyph.rb +3 -0
- data/lib/hexapdf/font/true_type_wrapper.rb +17 -4
- data/lib/hexapdf/font/type1_wrapper.rb +19 -4
- data/lib/hexapdf/font_loader/from_configuration.rb +5 -2
- data/lib/hexapdf/font_loader/from_file.rb +5 -5
- data/lib/hexapdf/font_loader/standard14.rb +3 -3
- data/lib/hexapdf/font_loader.rb +3 -0
- data/lib/hexapdf/image_loader/jpeg.rb +2 -2
- data/lib/hexapdf/image_loader/pdf.rb +1 -1
- data/lib/hexapdf/image_loader/png.rb +2 -2
- data/lib/hexapdf/image_loader.rb +1 -1
- data/lib/hexapdf/importer.rb +13 -0
- data/lib/hexapdf/layout/box.rb +9 -2
- data/lib/hexapdf/layout/box_fitter.rb +2 -2
- data/lib/hexapdf/layout/column_box.rb +18 -4
- data/lib/hexapdf/layout/frame.rb +30 -12
- data/lib/hexapdf/layout/image_box.rb +5 -0
- data/lib/hexapdf/layout/inline_box.rb +1 -0
- data/lib/hexapdf/layout/list_box.rb +17 -1
- data/lib/hexapdf/layout/page_style.rb +4 -4
- data/lib/hexapdf/layout/style.rb +18 -3
- data/lib/hexapdf/layout/table_box.rb +682 -0
- data/lib/hexapdf/layout/text_box.rb +5 -3
- data/lib/hexapdf/layout/text_fragment.rb +1 -1
- data/lib/hexapdf/layout/text_layouter.rb +12 -4
- data/lib/hexapdf/layout.rb +1 -0
- data/lib/hexapdf/name_tree_node.rb +1 -1
- data/lib/hexapdf/number_tree_node.rb +1 -1
- data/lib/hexapdf/object.rb +18 -7
- data/lib/hexapdf/parser.rb +8 -8
- data/lib/hexapdf/pdf_array.rb +1 -1
- data/lib/hexapdf/rectangle.rb +1 -1
- data/lib/hexapdf/reference.rb +1 -1
- data/lib/hexapdf/revision.rb +1 -1
- data/lib/hexapdf/revisions.rb +3 -3
- data/lib/hexapdf/serializer.rb +15 -15
- data/lib/hexapdf/stream.rb +4 -2
- data/lib/hexapdf/tokenizer.rb +14 -14
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +22 -22
- data/lib/hexapdf/type/acro_form/button_field.rb +1 -1
- data/lib/hexapdf/type/acro_form/choice_field.rb +1 -1
- data/lib/hexapdf/type/acro_form/field.rb +2 -2
- data/lib/hexapdf/type/acro_form/form.rb +1 -1
- data/lib/hexapdf/type/acro_form/signature_field.rb +4 -4
- data/lib/hexapdf/type/acro_form/text_field.rb +1 -1
- data/lib/hexapdf/type/acro_form/variable_text_field.rb +1 -1
- data/lib/hexapdf/type/acro_form.rb +1 -1
- data/lib/hexapdf/type/action.rb +1 -1
- data/lib/hexapdf/type/actions/go_to.rb +1 -1
- data/lib/hexapdf/type/actions/go_to_r.rb +1 -1
- data/lib/hexapdf/type/actions/launch.rb +1 -1
- data/lib/hexapdf/type/actions/uri.rb +1 -1
- data/lib/hexapdf/type/actions.rb +1 -1
- data/lib/hexapdf/type/annotation.rb +3 -3
- data/lib/hexapdf/type/annotations/link.rb +1 -1
- data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
- data/lib/hexapdf/type/annotations/text.rb +1 -1
- data/lib/hexapdf/type/annotations/widget.rb +2 -2
- data/lib/hexapdf/type/annotations.rb +1 -1
- data/lib/hexapdf/type/catalog.rb +1 -1
- data/lib/hexapdf/type/cid_font.rb +3 -3
- data/lib/hexapdf/type/embedded_file.rb +1 -1
- data/lib/hexapdf/type/file_specification.rb +2 -2
- data/lib/hexapdf/type/font_descriptor.rb +1 -1
- data/lib/hexapdf/type/font_simple.rb +2 -2
- data/lib/hexapdf/type/font_type0.rb +3 -3
- data/lib/hexapdf/type/font_type3.rb +1 -1
- data/lib/hexapdf/type/form.rb +1 -1
- data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
- data/lib/hexapdf/type/icon_fit.rb +1 -1
- data/lib/hexapdf/type/image.rb +1 -1
- data/lib/hexapdf/type/info.rb +1 -1
- data/lib/hexapdf/type/mark_information.rb +1 -1
- data/lib/hexapdf/type/names.rb +2 -2
- data/lib/hexapdf/type/object_stream.rb +7 -3
- data/lib/hexapdf/type/outline.rb +1 -1
- data/lib/hexapdf/type/outline_item.rb +1 -1
- data/lib/hexapdf/type/page.rb +19 -10
- data/lib/hexapdf/type/page_label.rb +1 -1
- data/lib/hexapdf/type/page_tree_node.rb +1 -1
- data/lib/hexapdf/type/resources.rb +1 -1
- data/lib/hexapdf/type/trailer.rb +2 -2
- data/lib/hexapdf/type/viewer_preferences.rb +1 -1
- data/lib/hexapdf/type/xref_stream.rb +2 -2
- data/lib/hexapdf/utils/pdf_doc_encoding.rb +1 -1
- data/lib/hexapdf/version.rb +1 -1
- data/lib/hexapdf/writer.rb +4 -4
- data/lib/hexapdf/xref_section.rb +2 -2
- data/test/hexapdf/content/graphic_object/test_endpoint_arc.rb +11 -1
- data/test/hexapdf/content/graphic_object/test_geom2d.rb +7 -0
- data/test/hexapdf/content/test_canvas.rb +0 -1
- data/test/hexapdf/digital_signature/test_signatures.rb +22 -0
- data/test/hexapdf/document/test_files.rb +2 -2
- data/test/hexapdf/document/test_layout.rb +98 -0
- data/test/hexapdf/encryption/test_security_handler.rb +12 -11
- data/test/hexapdf/encryption/test_standard_security_handler.rb +35 -23
- data/test/hexapdf/font/test_true_type_wrapper.rb +18 -1
- data/test/hexapdf/font/test_type1_wrapper.rb +15 -1
- data/test/hexapdf/layout/test_box.rb +1 -1
- data/test/hexapdf/layout/test_column_box.rb +65 -21
- data/test/hexapdf/layout/test_frame.rb +14 -14
- data/test/hexapdf/layout/test_image_box.rb +4 -0
- data/test/hexapdf/layout/test_inline_box.rb +5 -0
- data/test/hexapdf/layout/test_list_box.rb +40 -6
- data/test/hexapdf/layout/test_page_style.rb +3 -2
- data/test/hexapdf/layout/test_style.rb +50 -0
- data/test/hexapdf/layout/test_table_box.rb +722 -0
- data/test/hexapdf/layout/test_text_box.rb +18 -0
- data/test/hexapdf/layout/test_text_layouter.rb +4 -0
- data/test/hexapdf/test_dictionary_fields.rb +4 -1
- data/test/hexapdf/test_document.rb +1 -0
- data/test/hexapdf/test_filter.rb +8 -0
- data/test/hexapdf/test_importer.rb +9 -0
- data/test/hexapdf/test_object.rb +16 -5
- data/test/hexapdf/test_parser.rb +1 -1
- data/test/hexapdf/test_stream.rb +7 -0
- data/test/hexapdf/test_writer.rb +3 -3
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +13 -5
- data/test/hexapdf/type/acro_form/test_form.rb +4 -3
- data/test/hexapdf/type/test_object_stream.rb +9 -3
- data/test/hexapdf/type/test_page.rb +18 -4
- metadata +17 -8
@@ -47,7 +47,7 @@ module HexaPDF
|
|
47
47
|
# the encryption key and a set of permissions.
|
48
48
|
class StandardEncryptionDictionary < EncryptionDictionary
|
49
49
|
|
50
|
-
define_field :R, type: Integer, required: true
|
50
|
+
define_field :R, type: Integer, required: true, allowed_values: [2, 3, 4, 5, 6]
|
51
51
|
define_field :O, type: PDFByteString, required: true
|
52
52
|
define_field :OE, type: PDFByteString, version: '2.0'
|
53
53
|
define_field :U, type: PDFByteString, required: true
|
@@ -71,12 +71,16 @@ module HexaPDF
|
|
71
71
|
yield("Value of /OE, /UE or /Perms is missing for dictionary revision 6", false)
|
72
72
|
return
|
73
73
|
end
|
74
|
-
|
75
|
-
|
76
|
-
|
74
|
+
[:U, :O].each do |f|
|
75
|
+
if value[f].length != 48
|
76
|
+
yield("Invalid size (#{value[f].length} instead of 48) for /#{f} for revisions 6",
|
77
|
+
value[f].length > 48 && value[f][48..-1].squeeze("\x00").length == 1)
|
78
|
+
value[f].slice!(48..-1)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
if value[:UE].length != 32 || value[:OE].length != 32 || value[:Perms].length != 16
|
82
|
+
yield("Invalid size for /UE, /OE or /Perms values for revisions 6", false)
|
77
83
|
end
|
78
|
-
else
|
79
|
-
yield("Value of /R is not one of 2, 3, 4 or 6", false)
|
80
84
|
end
|
81
85
|
end
|
82
86
|
|
@@ -87,11 +91,13 @@ module HexaPDF
|
|
87
91
|
#
|
88
92
|
# == Overview
|
89
93
|
#
|
90
|
-
# The PDF specification defines one security handler that should be implemented by all
|
91
|
-
#
|
92
|
-
# and a user password as well as an owner password to be set.
|
93
|
-
#
|
94
|
-
#
|
94
|
+
# The PDF specification defines one security handler that should be implemented by all
|
95
|
+
# conforming PDF libraries and applications. This standard security handler allows access
|
96
|
+
# permissions and a user password as well as an owner password to be set.
|
97
|
+
#
|
98
|
+
# See StandardSecurityHandler::EncryptionOptions for all valid options that can be used with
|
99
|
+
# this security handler when encrypting a document. And see #prepare_decryption for all allowed
|
100
|
+
# options when decrypting a document.
|
95
101
|
#
|
96
102
|
# The access permissions (see StandardSecurityHandler::Permissions) can be used to restrict what
|
97
103
|
# a user is allowed to do with a PDF file.
|
@@ -100,7 +106,7 @@ module HexaPDF
|
|
100
106
|
# password is supplied. To open such an encrypted PDF file, the +decryption_opts+ provided to
|
101
107
|
# HexaPDF::Document.new needs to contain a :password key with the password.
|
102
108
|
#
|
103
|
-
# See:
|
109
|
+
# See: PDF2.0 s7.6.4
|
104
110
|
class StandardSecurityHandler < SecurityHandler
|
105
111
|
|
106
112
|
# Defines all available permissions.
|
@@ -109,13 +115,13 @@ module HexaPDF
|
|
109
115
|
# permission set. The used symbols are the lower case versions of the constants, i.e. the
|
110
116
|
# symbol for MODIFY_CONSTANT would be :modify_constant.
|
111
117
|
#
|
112
|
-
# See:
|
118
|
+
# See: PDF2.0 s7.6.4.2
|
113
119
|
module Permissions
|
114
120
|
|
115
121
|
# Printing (if HIGH_QUALITY_PRINT is also set, then high quality printing is allowed)
|
116
122
|
PRINT = 1 << 2
|
117
123
|
|
118
|
-
# Modification of the content by operations that are different from those
|
124
|
+
# Modification of the content by operations that are different from those controlled by
|
119
125
|
# MODIFY_ANNOTATION, FILL_IN_FORMS and ASSEMBLE_DOCUMENT
|
120
126
|
MODIFY_CONTENT = 1 << 3
|
121
127
|
|
@@ -129,6 +135,9 @@ module HexaPDF
|
|
129
135
|
FILL_IN_FORMS = 1 << 8
|
130
136
|
|
131
137
|
# Extracting content
|
138
|
+
#
|
139
|
+
# PDF 2.0 specifies that this bit should always be set by writers and should be ignored by
|
140
|
+
# readers. Therefore this is part of the RESERVED constant.
|
132
141
|
EXTRACT_CONTENT = 1 << 9
|
133
142
|
|
134
143
|
# Assembling of the document (inserting, rotating or deleting of pages and creation of
|
@@ -142,8 +151,8 @@ module HexaPDF
|
|
142
151
|
ALL = PRINT | MODIFY_CONTENT | COPY_CONTENT | MODIFY_ANNOTATION | FILL_IN_FORMS |
|
143
152
|
EXTRACT_CONTENT | ASSEMBLE_DOCUMENT | HIGH_QUALITY_PRINT
|
144
153
|
|
145
|
-
# Reserved permission bits
|
146
|
-
RESERVED = 0xFFFFF000 | 0b11000000
|
154
|
+
# Reserved permission bits that should always be set
|
155
|
+
RESERVED = 0xFFFFF000 | 0b11000000 | EXTRACT_CONTENT
|
147
156
|
|
148
157
|
# Maps permission symbols to their respective value
|
149
158
|
SYMBOL_TO_PERMISSION = {
|
@@ -213,7 +222,7 @@ module HexaPDF
|
|
213
222
|
|
214
223
|
# Maps the permissions to an integer for use by the standard security handler.
|
215
224
|
#
|
216
|
-
# See:
|
225
|
+
# See: PDF2.0 s7.6.4.2, ADB1.7 3.5.2 (table 3.20 and the paragraphs before)
|
217
226
|
def process_permissions(perms)
|
218
227
|
if perms.kind_of?(Array)
|
219
228
|
perms = perms.inject(0) do |result, perm|
|
@@ -363,7 +372,7 @@ module HexaPDF
|
|
363
372
|
|
364
373
|
# The padding used for passwords with fewer than 32 bytes. Only used for revisions <= 4.
|
365
374
|
#
|
366
|
-
# See:
|
375
|
+
# See: PDF2.0 s7.6.4.3
|
367
376
|
PASSWORD_PADDING = "\x28\xBF\x4E\x5E\x4E\x75\x8A\x41\x64\x00\x4E\x56\xFF\xFA\x01\x08" \
|
368
377
|
"\x2E\x2E\x00\xB6\xD0\x68\x3E\x80\x2F\x0C\xA9\xFE\x64\x53\x69\x7A".b
|
369
378
|
|
@@ -376,7 +385,7 @@ module HexaPDF
|
|
376
385
|
# with the user password. If the password is the owner password,
|
377
386
|
# #compute_owner_encryption_key has to be used instead.
|
378
387
|
#
|
379
|
-
# See:
|
388
|
+
# See: PDF2.0 s7.6.4.3.2 (algorithm 2), PDF2.0 s7.6.4.3.3 (algorithm 2.A (a)-(b),(e))
|
380
389
|
def compute_user_encryption_key(password)
|
381
390
|
if dict[:R] <= 4
|
382
391
|
data = password
|
@@ -403,11 +412,11 @@ module HexaPDF
|
|
403
412
|
# For revisions <= 4 this is done by first retrieving the user password through the use of
|
404
413
|
# the owner password and then using the #compute_user_encryption_key method.
|
405
414
|
#
|
406
|
-
# For revision 6 file encryption key is a string of random bytes that has been encrypted
|
407
|
-
# with the owner password. If the password is the user password,
|
408
|
-
#
|
415
|
+
# For revision 6 the file encryption key is a string of random bytes that has been encrypted
|
416
|
+
# with the owner password. If the password is the user password, #compute_user_encryption_key
|
417
|
+
# has to be used.
|
409
418
|
#
|
410
|
-
# See: PDF2.0 s7.6.
|
419
|
+
# See: PDF2.0 s7.6.4.3.2 (algorithm 2.A (a)-(d))
|
411
420
|
def compute_owner_encryption_key(password)
|
412
421
|
if dict[:R] <= 4
|
413
422
|
compute_user_encryption_key(user_password_from_owner_password(password))
|
@@ -426,7 +435,7 @@ module HexaPDF
|
|
426
435
|
# *Attention*: If revision 6 is used, the /U value has to be computed and set before this
|
427
436
|
# method is used, otherwise the return value is incorrect!
|
428
437
|
#
|
429
|
-
# See:
|
438
|
+
# See: PDF2.0 s7.6.4.4.2 (algorithm 3), PDF2.0 s7.6.4.4.8 (algorithm 9 (a))
|
430
439
|
def compute_o_field(owner_password, user_password)
|
431
440
|
if dict[:R] <= 4
|
432
441
|
data = Digest::MD5.digest(owner_password)
|
@@ -454,7 +463,7 @@ module HexaPDF
|
|
454
463
|
# Short explanation: Encrypts the file encryption key with a key based on the password and
|
455
464
|
# the /O and /U values.
|
456
465
|
#
|
457
|
-
# See: PDF2.0 s7.6.
|
466
|
+
# See: PDF2.0 s7.6.4.4.8 (algorithm 9 (b))
|
458
467
|
def compute_oe_field(password, file_encryption_key)
|
459
468
|
key = compute_hash(password, dict[:O][40, 8], dict[:U])
|
460
469
|
aes_algorithm.new(key, "\0" * 16, :encrypt).process(file_encryption_key)
|
@@ -466,8 +475,8 @@ module HexaPDF
|
|
466
475
|
# based on the user password. For revision 6 the /U value is a hash computed from the
|
467
476
|
# password with added validation and key salts.
|
468
477
|
#
|
469
|
-
# See:
|
470
|
-
# PDF2.0 s7.6.
|
478
|
+
# 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)
|
479
|
+
# PDF2.0 s7.6.4.4.7 (algorithm 8 (a) for R=6)
|
471
480
|
def compute_u_field(password)
|
472
481
|
if dict[:R] == 2
|
473
482
|
key = compute_user_encryption_key(password)
|
@@ -491,7 +500,7 @@ module HexaPDF
|
|
491
500
|
# Short explanation: Encrypts the file encryption key with a key based on the password and
|
492
501
|
# the /U value.
|
493
502
|
#
|
494
|
-
# See: PDF2.0 s7.6.
|
503
|
+
# See: PDF2.0 s7.6.4.4.7 (algorithm 8 (b))
|
495
504
|
def compute_ue_field(password, file_encryption_key)
|
496
505
|
key = compute_hash(password, dict[:U][40, 8])
|
497
506
|
aes_algorithm.new(key, "\0" * 16, :encrypt).process(file_encryption_key)
|
@@ -501,7 +510,7 @@ module HexaPDF
|
|
501
510
|
#
|
502
511
|
# Uses /P and /EncryptMetadata values, so these have to be set beforehand.
|
503
512
|
#
|
504
|
-
# See: PDF2.0 s7.6.
|
513
|
+
# See: PDF2.0 s7.6.4.4.9 (algorithm 10)
|
505
514
|
def compute_perms_field(file_encryption_key)
|
506
515
|
data = [dict[:P]].pack('V')
|
507
516
|
data << [0xFFFFFFFF].pack('V')
|
@@ -513,7 +522,7 @@ module HexaPDF
|
|
513
522
|
|
514
523
|
# Authenticates the user password, i.e. decides whether the given user password is valid.
|
515
524
|
#
|
516
|
-
# See:
|
525
|
+
# See: PDF2.0 s7.6.4.4.5 (algorithm 6), PDF2.0 s7.6.4.4.10 (algorithm 11)
|
517
526
|
def user_password_valid?(password)
|
518
527
|
if dict[:R] == 2
|
519
528
|
compute_u_field(password) == dict[:U]
|
@@ -526,7 +535,7 @@ module HexaPDF
|
|
526
535
|
|
527
536
|
# Authenticates the owner password, i.e. decides whether the given owner password is valid.
|
528
537
|
#
|
529
|
-
# See:
|
538
|
+
# See: PDF2.0 s7.6.4.4.6 (algorithm 7), PDF2.0 s7.6.4.4.11 (algorithm 12)
|
530
539
|
def owner_password_valid?(password)
|
531
540
|
if dict[:R] <= 4
|
532
541
|
user_password_valid?(user_password_from_owner_password(password))
|
@@ -539,7 +548,7 @@ module HexaPDF
|
|
539
548
|
#
|
540
549
|
# This method can only be used for revision 6.
|
541
550
|
#
|
542
|
-
# See: PDF2.0 s7.6.
|
551
|
+
# See: PDF2.0 s7.6.4.4.12 (algorithm 13)
|
543
552
|
def check_perms_field(encryption_key)
|
544
553
|
decrypted = aes_algorithm.new(encryption_key, "\0" * 16, :decrypt).process(dict[:Perms])
|
545
554
|
if decrypted[9, 3] != "adb"
|
@@ -553,7 +562,7 @@ module HexaPDF
|
|
553
562
|
|
554
563
|
# Returns the user password when given the owner password for revisions <= 4.
|
555
564
|
#
|
556
|
-
# See:
|
565
|
+
# See: PDF2.0 s7.6.4.4.6 (algorithm 7 (a) and (b))
|
557
566
|
def user_password_from_owner_password(owner_password)
|
558
567
|
data = Digest::MD5.digest(owner_password)
|
559
568
|
if dict[:R] >= 3
|
@@ -578,7 +587,7 @@ module HexaPDF
|
|
578
587
|
# "#{password}#{salt}#{user_key}" where +user_key+ has to be empty when doing operations
|
579
588
|
# with the user password.
|
580
589
|
#
|
581
|
-
# See: PDF2.0 s7.6.
|
590
|
+
# See: PDF2.0 s7.6.4.3.4 (algorithm 2.B)
|
582
591
|
def compute_hash(password, salt, user_key = '')
|
583
592
|
k = Digest::SHA256.digest("#{password}#{salt}#{user_key}")
|
584
593
|
e = ''
|
@@ -606,8 +615,8 @@ module HexaPDF
|
|
606
615
|
# * For revision 6 the password is converted into UTF-8 encoding that is normalized
|
607
616
|
# according to the PDF2.0 specification.
|
608
617
|
#
|
609
|
-
# See:
|
610
|
-
# PDF2.0 s7.6.3.3
|
618
|
+
# See: PDF2.0 s7.6.4.3.2 (algorithm 2 step a)),
|
619
|
+
# PDF2.0 s7.6.4.3.3 (algorithm 2.A steps a) and b))
|
611
620
|
def prepare_password(password)
|
612
621
|
if dict[:R] <= 4
|
613
622
|
password.to_s[0, 32].encode(Encoding::ISO_8859_1).force_encoding(Encoding::BINARY).
|
data/lib/hexapdf/encryption.rb
CHANGED
@@ -41,7 +41,7 @@ module HexaPDF
|
|
41
41
|
# A PDF document may be encrypted so that
|
42
42
|
#
|
43
43
|
# * certain permissions are respected when the document is opened,
|
44
|
-
# * a password must be specified so that a document can be openend or so that
|
44
|
+
# * a password must be specified so that a document can be openend, or so that
|
45
45
|
# * a password must be specified to remove the restrictions and allow full access.
|
46
46
|
#
|
47
47
|
# This module contains all encryption and security related code to facilitate PDF encryption.
|
@@ -61,6 +61,9 @@ module HexaPDF
|
|
61
61
|
# additionally allows setting permission information. This security handler is implemented by
|
62
62
|
# the Encryption::StandardSecurityHandler class.
|
63
63
|
#
|
64
|
+
# There is also a certificate-based security handler defined by the PDF specification. However,
|
65
|
+
# that handler is not implemented.
|
66
|
+
#
|
64
67
|
#
|
65
68
|
# === Encryption Algorithms
|
66
69
|
#
|
@@ -78,8 +81,10 @@ module HexaPDF
|
|
78
81
|
# Pure Ruby implementations of the algorithms which are naturally much slower than the OpenSSL
|
79
82
|
# based ones. However, these implementation can be used on any Ruby implementation.
|
80
83
|
#
|
84
|
+
# The ARC4 algorithm is deprecated with PDF 2.0 and should not be used when creating new
|
85
|
+
# documents.
|
81
86
|
#
|
82
|
-
# See:
|
87
|
+
# See: PDF2.0 s7.6
|
83
88
|
module Encryption
|
84
89
|
|
85
90
|
autoload(:ARC4, 'hexapdf/encryption/arc4')
|
data/lib/hexapdf/error.rb
CHANGED
@@ -82,4 +82,22 @@ module HexaPDF
|
|
82
82
|
# Raised when the encryption method is not supported.
|
83
83
|
class UnsupportedEncryptionError < EncryptionError; end
|
84
84
|
|
85
|
+
# Raised when a font wrapper implementation should encode a missing glyph.
|
86
|
+
class MissingGlyphError < Error
|
87
|
+
|
88
|
+
# Returns the glyph object that contains the information about the missing glyph.
|
89
|
+
attr_reader :glyph
|
90
|
+
|
91
|
+
# Creates a new MissingGlyphError for the given +glyph+.
|
92
|
+
def initialize(glyph)
|
93
|
+
@glyph = glyph
|
94
|
+
end
|
95
|
+
|
96
|
+
def message # :nodoc:
|
97
|
+
"No glyph for #{glyph.str.inspect} in font '#{glyph.font.full_name}' found. \n\n" \
|
98
|
+
"Use the configuration option 'font.on_missing_glyph' to customize missing glyph handling."
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
85
103
|
end
|
@@ -45,7 +45,7 @@ module HexaPDF
|
|
45
45
|
# This filter module implements the ASCII-85 filter which can encode arbitrary data into an
|
46
46
|
# ASCII compatible format that expands the original data only by a factor of 4:5.
|
47
47
|
#
|
48
|
-
# See: HexaPDF::Filter,
|
48
|
+
# See: HexaPDF::Filter, PDF2.0 s7.4.2
|
49
49
|
module ASCII85Decode
|
50
50
|
|
51
51
|
VALUE_TO_CHAR = {} #:nodoc:
|
@@ -44,7 +44,7 @@ module HexaPDF
|
|
44
44
|
# This filter module implements the ASCII hex decode/encode filter which can encode arbitrary
|
45
45
|
# data into the two byte ASCII hex format that expands the original data by a factor of 1:2.
|
46
46
|
#
|
47
|
-
# See: HexaPDF::Filter,
|
47
|
+
# See: HexaPDF::Filter, PDF2.0 s7.4.2
|
48
48
|
module ASCIIHexDecode
|
49
49
|
|
50
50
|
# See HexaPDF::Filter
|
@@ -48,7 +48,7 @@ module HexaPDF
|
|
48
48
|
# not aligned to byte boundaries, this filter is not as fast as the other filters. If speed is
|
49
49
|
# a concern, the FlateDecode filter should be used instead.
|
50
50
|
#
|
51
|
-
# See: HexaPDF::Filter,
|
51
|
+
# See: HexaPDF::Filter, PDF2.0 s7.4.4
|
52
52
|
module LZWDecode
|
53
53
|
|
54
54
|
CLEAR_TABLE = 256 # :nodoc:
|
@@ -40,7 +40,7 @@ module HexaPDF
|
|
40
40
|
# The PassThrough filter just passes the source on unmodified. This is enough for basic
|
41
41
|
# read-write capabilities but not if the unfiltered bytes are needed.
|
42
42
|
#
|
43
|
-
# See: HexaPDF::Filter,
|
43
|
+
# See: HexaPDF::Filter, PDF2.0 s7.4
|
44
44
|
module PassThrough
|
45
45
|
|
46
46
|
# See HexaPDF::Filter
|
@@ -47,7 +47,7 @@ module HexaPDF
|
|
47
47
|
# Although a predictor isn't a full PDF filter, it is implemented as one in HexaPDF terms to
|
48
48
|
# allow easy chaining of the predictor.
|
49
49
|
#
|
50
|
-
# See:
|
50
|
+
# See: PDF2.0 s7.4.4.3, s7.4.4.4, https://partners.adobe.com/public/developer/en/tiff/TIFF6.pdf
|
51
51
|
# (p64f), http://www.w3.org/TR/PNG-Filters.html
|
52
52
|
#
|
53
53
|
#-- Implemenation notes:
|
data/lib/hexapdf/filter.rb
CHANGED
@@ -48,6 +48,8 @@ module HexaPDF
|
|
48
48
|
attr_reader :length
|
49
49
|
|
50
50
|
# Initializes the Fiber and sets the +length+.
|
51
|
+
#
|
52
|
+
# A +length+ of +nil+ is equal to -1.
|
51
53
|
def initialize(length, &block)
|
52
54
|
super(&block)
|
53
55
|
@length = length || -1
|
@@ -55,6 +57,47 @@ module HexaPDF
|
|
55
57
|
|
56
58
|
end
|
57
59
|
|
60
|
+
# Implements part of the Fiber interface so that it can be used instead of a Fiber by HexaPDF
|
61
|
+
# when only a single string should be returned.
|
62
|
+
class FiberDoubleForString
|
63
|
+
|
64
|
+
# Creates a new FiberDoubleForString instance for the given string +str+ or for the string
|
65
|
+
# returned by invoking the block.
|
66
|
+
def initialize(str = nil, &block)
|
67
|
+
@block = block
|
68
|
+
@str = str
|
69
|
+
@block_used = false
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns the length of the wrapped string.
|
73
|
+
#
|
74
|
+
# May only be called before #resume!
|
75
|
+
def length
|
76
|
+
str.length
|
77
|
+
end
|
78
|
+
|
79
|
+
# Returns +true+ if #resume has not yet been called.
|
80
|
+
def alive?
|
81
|
+
!str.nil?
|
82
|
+
end
|
83
|
+
|
84
|
+
# Returns the wrapped string on the first invocation, +nil+ otherwise.
|
85
|
+
def resume
|
86
|
+
tmp = str
|
87
|
+
@str = nil
|
88
|
+
tmp
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
# Sets the string to the return value of the initially provided block if no string has been
|
94
|
+
# provided.
|
95
|
+
def str
|
96
|
+
@str ||= @block_used || @block.nil? ? nil : (@block_used = true; @block.call)
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
58
101
|
# == Overview
|
59
102
|
#
|
60
103
|
# A stream filter is used to compress a stream or to encode it in an ASCII compatible way; or
|
@@ -83,7 +126,7 @@ module HexaPDF
|
|
83
126
|
#
|
84
127
|
# Such a fiber should *not* return +nil+ unless this signifies that no more data is coming!
|
85
128
|
#
|
86
|
-
# See:
|
129
|
+
# See: PDF2.0 s7.4
|
87
130
|
module Filter
|
88
131
|
|
89
132
|
autoload(:ASCII85Decode, 'hexapdf/filter/ascii85_decode')
|
@@ -99,10 +142,16 @@ module HexaPDF
|
|
99
142
|
|
100
143
|
autoload(:PassThrough, 'hexapdf/filter/pass_through')
|
101
144
|
|
102
|
-
# Returns a
|
103
|
-
#
|
145
|
+
# Returns a FiberDoubleForString that uses the string returned by the provided block and can be
|
146
|
+
# used as a source for decoders/encoders.
|
147
|
+
def self.source_from_proc(&block)
|
148
|
+
FiberDoubleForString.new(&block)
|
149
|
+
end
|
150
|
+
|
151
|
+
# Returns a FiberDoubleForString that returns the given string and can be used as a source for
|
152
|
+
# decoders/encoders.
|
104
153
|
def self.source_from_string(str)
|
105
|
-
|
154
|
+
FiberDoubleForString.new(str.dup)
|
106
155
|
end
|
107
156
|
|
108
157
|
# Returns a Fiber that can be used as a source for decoders/encoders and that reads chunks of
|
@@ -149,7 +198,7 @@ module HexaPDF
|
|
149
198
|
# Note that there will be a problem if the size of the file changes between the invocation of
|
150
199
|
# this method and the actual consumption of the file!
|
151
200
|
#
|
152
|
-
# See ::source_from_io for a description of the
|
201
|
+
# See ::source_from_io for a description of the +pos+, +length+ and +chunk_size+ options.
|
153
202
|
def self.source_from_file(filename, pos: 0, length: -1, chunk_size: 0)
|
154
203
|
fib_length = (length < 0 ? File.stat(filename).size - pos : length)
|
155
204
|
FiberWithLength.new(fib_length) do
|
@@ -165,7 +214,7 @@ module HexaPDF
|
|
165
214
|
# Returns the concatenated string chunks retrieved by resuming the given source Fiber until it
|
166
215
|
# is dead.
|
167
216
|
#
|
168
|
-
# The returned string is always a string with
|
217
|
+
# The returned string is always a string with binary (= +ASCII-8BIT+) encoding.
|
169
218
|
def self.string_from_source(source)
|
170
219
|
str = ''.b
|
171
220
|
while source.alive? && (data = source.resume)
|
@@ -146,10 +146,10 @@ module HexaPDF
|
|
146
146
|
# Parses the "bfrange" operator at the current position.
|
147
147
|
#
|
148
148
|
#--
|
149
|
-
#
|
149
|
+
# PDF2.0 s9.10.3 and Adobe Technical Note #5411 have different views as to how "bfrange"
|
150
150
|
# operators of the form "startCode endCode codePoint" should be handled.
|
151
151
|
#
|
152
|
-
#
|
152
|
+
# PDF2.0 mentions that the last byte of "codePoint" should be incremented, up to a maximum
|
153
153
|
# of 255. However #5411 has the range "<1379> <137B> <90FE>" as example which contradicts
|
154
154
|
# this.
|
155
155
|
#
|
data/lib/hexapdf/font/cmap.rb
CHANGED
@@ -43,7 +43,7 @@ module HexaPDF
|
|
43
43
|
# Represents a CMap, a mapping from character codes to CIDs (character IDs) or to their Unicode
|
44
44
|
# value.
|
45
45
|
#
|
46
|
-
# See:
|
46
|
+
# See: PDF2.0 s9.7.5, s9.10.3; Adobe Technical Notes #5014 and #5411
|
47
47
|
class CMap
|
48
48
|
|
49
49
|
autoload(:Parser, 'hexapdf/font/cmap/parser')
|
@@ -42,7 +42,7 @@ module HexaPDF
|
|
42
42
|
|
43
43
|
# The Mac Roman standard encoding for Latin texts.
|
44
44
|
#
|
45
|
-
# See:
|
45
|
+
# See: PDF2.0 sD.1, sD.2
|
46
46
|
class MacRomanEncoding < Base
|
47
47
|
|
48
48
|
def initialize #:nodoc:
|
@@ -256,7 +256,7 @@ module HexaPDF
|
|
256
256
|
0264 => :yen,
|
257
257
|
0172 => :z,
|
258
258
|
0060 => :zero,
|
259
|
-
# additions due to
|
259
|
+
# additions due to PDF2.0 sD.2 footnote 6
|
260
260
|
0312 => :space,
|
261
261
|
}
|
262
262
|
end
|
@@ -42,7 +42,7 @@ module HexaPDF
|
|
42
42
|
|
43
43
|
# The Windows Code Page 1252, the standard Windows encoding for Latin texts.
|
44
44
|
#
|
45
|
-
# See:
|
45
|
+
# See: PDF2.0 sD.1, sD.2
|
46
46
|
class WinAnsiEncoding < Base
|
47
47
|
|
48
48
|
def initialize #:nodoc:
|
@@ -265,11 +265,11 @@ module HexaPDF
|
|
265
265
|
0172 => :z,
|
266
266
|
0236 => :zcaron,
|
267
267
|
0060 => :zero,
|
268
|
-
# additions due to
|
268
|
+
# additions due to PDF2.0 sD.2 footnote 5,6
|
269
269
|
0240 => :space,
|
270
270
|
0255 => :hyphen,
|
271
271
|
}
|
272
|
-
# additions due to
|
272
|
+
# additions due to PDF2.0 sD.2 footnote 3
|
273
273
|
041.upto(255) do |i|
|
274
274
|
next if @code_to_name.key?(i)
|
275
275
|
@code_to_name[i] = :bullet
|
@@ -51,7 +51,7 @@ module HexaPDF
|
|
51
51
|
#
|
52
52
|
# * By using a composite font more than 256 characters can be encoded with one font object.
|
53
53
|
# * Fonts for vertical writing can potentially be used.
|
54
|
-
# * The PDF specification recommends using a composite font (see
|
54
|
+
# * The PDF specification recommends using a composite font (see PDF2.0 s9.9.1 at the end).
|
55
55
|
#
|
56
56
|
# Additionally, TrueType fonts are *always* embedded.
|
57
57
|
class TrueTypeWrapper
|
@@ -59,6 +59,9 @@ module HexaPDF
|
|
59
59
|
# Represents a single glyph of the wrapped font.
|
60
60
|
class Glyph
|
61
61
|
|
62
|
+
# The associated font object.
|
63
|
+
attr_reader :font
|
64
|
+
|
62
65
|
# The glyph ID.
|
63
66
|
attr_reader :id
|
64
67
|
|
@@ -171,6 +174,18 @@ module HexaPDF
|
|
171
174
|
end
|
172
175
|
end
|
173
176
|
|
177
|
+
# Returns a custom Glyph object which represents the given +string+ via the given glyph +id+.
|
178
|
+
#
|
179
|
+
# This functionality can be used to associate a single glyph id with multiple, different
|
180
|
+
# strings for replacement glyph purposes. When used in such a way, the used glyph id is often
|
181
|
+
# 0 which represents the missing glyph.
|
182
|
+
def custom_glyph(id, string)
|
183
|
+
if id < 0 || id >= @wrapped_font[:maxp].num_glyphs
|
184
|
+
raise HexaPDF::Error, "Glyph ID #{id} is invalid for font '#{@wrapped_font.full_name}'"
|
185
|
+
end
|
186
|
+
Glyph.new(@wrapped_font, id, string)
|
187
|
+
end
|
188
|
+
|
174
189
|
# Returns an array of glyph objects representing the characters in the UTF-8 encoded string.
|
175
190
|
def decode_utf8(str)
|
176
191
|
str.codepoints.map! do |c|
|
@@ -187,9 +202,7 @@ module HexaPDF
|
|
187
202
|
def encode(glyph)
|
188
203
|
(@encoded_glyphs[glyph.id] ||=
|
189
204
|
begin
|
190
|
-
if glyph.kind_of?(InvalidGlyph)
|
191
|
-
raise HexaPDF::Error, "Glyph for #{glyph.str.inspect} missing"
|
192
|
-
end
|
205
|
+
raise HexaPDF::MissingGlyphError.new(glyph) if glyph.kind_of?(InvalidGlyph)
|
193
206
|
if @subsetter
|
194
207
|
[[@subsetter.use_glyph(glyph.id)].pack('n'), glyph]
|
195
208
|
else
|