hexapdf 0.14.3 → 0.15.3

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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +85 -0
  3. data/lib/hexapdf/cli/form.rb +30 -8
  4. data/lib/hexapdf/configuration.rb +19 -4
  5. data/lib/hexapdf/content/canvas.rb +1 -0
  6. data/lib/hexapdf/encryption/security_handler.rb +7 -2
  7. data/lib/hexapdf/encryption/standard_security_handler.rb +16 -0
  8. data/lib/hexapdf/error.rb +4 -3
  9. data/lib/hexapdf/filter.rb +1 -0
  10. data/lib/hexapdf/filter/crypt.rb +60 -0
  11. data/lib/hexapdf/font/type1/afm_parser.rb +2 -1
  12. data/lib/hexapdf/parser.rb +35 -11
  13. data/lib/hexapdf/revision.rb +16 -0
  14. data/lib/hexapdf/serializer.rb +7 -1
  15. data/lib/hexapdf/tokenizer.rb +22 -3
  16. data/lib/hexapdf/type/acro_form.rb +1 -0
  17. data/lib/hexapdf/type/acro_form/appearance_generator.rb +29 -17
  18. data/lib/hexapdf/type/acro_form/button_field.rb +8 -4
  19. data/lib/hexapdf/type/acro_form/field.rb +1 -0
  20. data/lib/hexapdf/type/acro_form/form.rb +37 -0
  21. data/lib/hexapdf/type/acro_form/signature_field.rb +223 -0
  22. data/lib/hexapdf/type/annotation.rb +13 -9
  23. data/lib/hexapdf/type/annotations/widget.rb +3 -1
  24. data/lib/hexapdf/type/font_descriptor.rb +9 -2
  25. data/lib/hexapdf/type/page.rb +81 -0
  26. data/lib/hexapdf/type/xref_stream.rb +7 -0
  27. data/lib/hexapdf/utils/graphics_helpers.rb +4 -4
  28. data/lib/hexapdf/version.rb +1 -1
  29. data/test/hexapdf/content/test_canvas.rb +21 -0
  30. data/test/hexapdf/encryption/test_security_handler.rb +15 -0
  31. data/test/hexapdf/encryption/test_standard_security_handler.rb +27 -0
  32. data/test/hexapdf/filter/test_crypt.rb +21 -0
  33. data/test/hexapdf/font/type1/test_afm_parser.rb +5 -0
  34. data/test/hexapdf/test_parser.rb +47 -3
  35. data/test/hexapdf/test_revision.rb +21 -0
  36. data/test/hexapdf/test_serializer.rb +3 -0
  37. data/test/hexapdf/test_tokenizer.rb +22 -0
  38. data/test/hexapdf/test_writer.rb +2 -2
  39. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +21 -2
  40. data/test/hexapdf/type/acro_form/test_button_field.rb +13 -7
  41. data/test/hexapdf/type/acro_form/test_field.rb +5 -0
  42. data/test/hexapdf/type/acro_form/test_form.rb +46 -2
  43. data/test/hexapdf/type/acro_form/test_signature_field.rb +38 -0
  44. data/test/hexapdf/type/annotations/test_widget.rb +2 -0
  45. data/test/hexapdf/type/test_annotation.rb +20 -10
  46. data/test/hexapdf/type/test_font_descriptor.rb +7 -0
  47. data/test/hexapdf/type/test_page.rb +187 -49
  48. data/test/hexapdf/type/test_xref_stream.rb +7 -0
  49. data/test/hexapdf/utils/test_graphics_helpers.rb +8 -0
  50. metadata +6 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c43d8e9e117db1717ddfee73a54e4384743b8aa35863ab5bd19ffe57b8ce5674
4
- data.tar.gz: 1020c8a3de8fcdf201500c1c0d22dfb99ed27daebac7baac92748f8127efc992
3
+ metadata.gz: 592ea8ae7648df43e92ba50effdf3f8f34163e4acf7fb9567c3b38db46eb598e
4
+ data.tar.gz: 6c3b7d32a1499f2e2133fbafdf46b9d3cd4d1df41b9ae308c0c32ea39aefff2d
5
5
  SHA512:
6
- metadata.gz: e19eea4e88077afb7e8532fa6fe9ab2a03ffc5588749b72277462a971ebcec877ee72868d0ab698744117d46566be98e65c10225649d3bd1b4cd6e64e9625767
7
- data.tar.gz: 6626a9feba0af0b46f293c1069a0d53b458a0dc29d08b82253f14f9bb98a878b914042faccc433b73f2f0e35d4da47c58a1bdebd2f3dee2fefb24c076a4e6bb3
6
+ metadata.gz: fdf9edf53c0443d459008634ddbff7cd80fc1422fa558df41db04af0d9eeb512ea050d5b4a10987b824c675203e39bc851d1b2a68d0178f2cd12fada66b31245
7
+ data.tar.gz: 8e6a7b91da0ed2b63f7bc6d52c3993553f439edf986253b3508d0510310195c2a6f3721c2cfed735afc3d60dacacedc8207a6fac361bedc354bc6bd779207eac
data/CHANGELOG.md CHANGED
@@ -1,3 +1,88 @@
1
+ ## 0.15.3 - 2021-05-01
2
+
3
+ ### Fixed
4
+
5
+ * Handling of general (not document-level), unencrypted metadata streams
6
+
7
+
8
+ ## 0.15.2 - 2021-05-01
9
+
10
+ ### Fixed
11
+
12
+ * Handling of unencrypted metadata streams
13
+
14
+
15
+ ## 0.15.1 - 2021-04-15
16
+
17
+ ### Fixed
18
+
19
+ * Potential division by zero when calculating the scaling for XObjects
20
+ * Handling of XObjects with a width or height of zero when drawing on canvas
21
+
22
+
23
+ ## 0.15.0 - 2021-04-12
24
+
25
+ ### Added
26
+
27
+ * [HexaPDF::Type::Page#flatten_annotations] for flattening the annotations of a
28
+ page
29
+ * [HexaPDF::Type::AcroForm::Form#flatten] for flattening interactive forms
30
+ * [HexaPDF::Revision#update] for updating the stored wrapper class of a PDF
31
+ object
32
+ * [HexaPDF::Type::AcroForm::SignatureField] for working with AcroForm signature
33
+ fields
34
+ * Support for form field flattening to the `hexapdf form` CLI command
35
+
36
+ ### Changed
37
+
38
+ * **Breaking change**: Overhauled the interface for accessing appearances of
39
+ annotations to make it more convenient
40
+ * Validation of [HexaPDF::Type::FontDescriptor] to delete invalid `/FontWeight`
41
+ value
42
+ * [HexaPDF::MalformedPDFError#pos] an accessor instead of a reader and update
43
+ the exception message
44
+ * Configuration option 'acro_form.fallback_font' to allow a callable object for
45
+ more advanced fallback font handling
46
+
47
+ ### Fixed
48
+
49
+ * [HexaPDF::Type::Annotations::Widget#background_color] to correctly handle
50
+ empty background color arrays
51
+ * [HexaPDF::Type::AcroForm::Field#delete_widget] to update the wrapper object
52
+ stored in the document in case the widget is embedded
53
+ * Processing of invalid PDF files containing a space,CR,LF combination after
54
+ the 'stream' keyword
55
+ * Cross-reference stream reconstruction with respect to detection of linearized
56
+ files
57
+ * Detection of existing appearances for AcroForm push button fields when
58
+ creating appearances
59
+
60
+
61
+ ## 0.14.4 - 2021-02-27
62
+
63
+ ### Added
64
+
65
+ * Support for the Crypt filters
66
+
67
+ ### Changed
68
+
69
+ * [HexaPDF::MalformedPDFError] to make the `pos` argument optional
70
+
71
+ ### Fixed
72
+
73
+ * Handling of invalid floating point numbers NaN, Inf and -Inf when serializing
74
+ * Processing of invalid PDF files containing NaN and Inf instead of numbers
75
+ * Bug in Type1 font AFM parser that occured if the file doesn't end with a new
76
+ line character
77
+ * Cross-reference table reconstruction to handle the case of an entry specifying
78
+ a non-existent indirect object
79
+ * Cross-reference table reconstruction to handle trailers specified by cross-
80
+ reference streams
81
+ * Cross-reference table reconstruction to use the set security handle for
82
+ decrypting indirect objects
83
+ * Parsing of cross-reference streams where data is missing
84
+
85
+
1
86
  ## 0.14.3 - 2021-02-16
2
87
 
3
88
  ### Fixed
@@ -52,18 +52,26 @@ module HexaPDF
52
52
  If the the output file name is not given, all form fields are listed in page order. Use
53
53
  the global --verbose option to show additional information like field type and location.
54
54
 
55
- If the output file name is given, the fields can be interactively filled out. By
56
- additionally using the --template option, the data for the fields is read from the given
57
- template file instead of the standard input.
55
+ If the output file name is given, the fields can be filled out interactively, via a
56
+ template or just flattened by using the respective options. Form field flattening can also
57
+ be activated in addition to filling out the form. If neither --fill, --template nor
58
+ --flatten is specified, --fill is implied.
58
59
  EOF
59
60
 
60
61
  options.on("--password PASSWORD", "-p", String,
61
62
  "The password for decryption. Use - for reading from standard input.") do |pwd|
62
63
  @password = (pwd == '-' ? read_password : pwd)
63
64
  end
65
+ options.on("--fill", "Fill out the form") do
66
+ @fill = true
67
+ end
64
68
  options.on("--template TEMPLATE_FILE", "-t TEMPLATE_FILE",
65
- "Use the template file for the field values") do |template|
69
+ "Use the template file for the field values (implies --fill)") do |template|
66
70
  @template = template
71
+ @fill = true
72
+ end
73
+ options.on('--flatten', 'Flatten the form fields') do
74
+ @flatten = true
67
75
  end
68
76
  options.on("--[no-]viewer-override", "Let the PDF viewer override the visual " \
69
77
  "appearance. Default: use setting from input PDF") do |need_appearances|
@@ -75,6 +83,8 @@ module HexaPDF
75
83
  end
76
84
 
77
85
  @password = nil
86
+ @fill = false
87
+ @flatten = false
78
88
  @template = nil
79
89
  @need_appearances = nil
80
90
  @incremental = true
@@ -82,16 +92,28 @@ module HexaPDF
82
92
 
83
93
  def execute(in_file, out_file = nil) #:nodoc:
84
94
  maybe_raise_on_existing_file(out_file) if out_file
95
+ if (@fill || @flatten) && !out_file
96
+ raise "Output file missing"
97
+ end
85
98
  with_document(in_file, password: @password, out_file: out_file,
86
99
  incremental: @incremental) do |doc|
87
100
  if !doc.acro_form
88
101
  raise "This PDF doesn't contain an interactive form"
89
102
  elsif out_file
90
103
  doc.acro_form[:NeedAppearances] = @need_appearances unless @need_appearances.nil?
91
- if @template
92
- fill_form_with_template(doc)
93
- else
94
- fill_form(doc)
104
+ if @fill || !@flatten
105
+ if @template
106
+ fill_form_with_template(doc)
107
+ else
108
+ fill_form(doc)
109
+ end
110
+ end
111
+ if @flatten
112
+ unless doc.acro_form.flatten.empty?
113
+ $stderr.puts "Warning: Not all form fields could be flattened"
114
+ doc.catalog.delete(:AcroForm)
115
+ doc.delete(doc.acro_form)
116
+ end
95
117
  end
96
118
  else
97
119
  list_form_fields(doc)
@@ -164,9 +164,20 @@ module HexaPDF
164
164
  # acro_form.fallback_font::
165
165
  # The font that should be used when a variable text field references a font that cannot be used.
166
166
  #
167
- # Can either be the name of a font, like 'Helvetica', or an array consisting of the font name
168
- # and a hash of font options, like ['Helvetica', variant: :italic]. If set to +nil+, the use of
169
- # the fallback font is disabled.
167
+ # Can be one of the following:
168
+ #
169
+ # * The name of a font, like 'Helvetica'.
170
+ #
171
+ # * An array consisting of the font name and a hash of font options, like ['Helvetica',
172
+ # variant: :italic].
173
+ #
174
+ # * A callable object receiving the field and the font object (or +nil+ if no valid font object
175
+ # was found) and which has to return either a font name or an array consisting of the font
176
+ # name and a hash of font options. This way the response can be different depending on the
177
+ # original font and it would also allow e.g. modifying the configured fonts to add custom
178
+ # ones.
179
+ #
180
+ # If set to +nil+, the use of the fallback font is disabled.
170
181
  #
171
182
  # Default is 'Helvetica'.
172
183
  #
@@ -393,7 +404,7 @@ module HexaPDF
393
404
  DCTDecode: 'HexaPDF::Filter::PassThrough',
394
405
  DCT: 'HexaPDF::Filter::PassThrough',
395
406
  JPXDecode: 'HexaPDF::Filter::PassThrough',
396
- Crypt: nil,
407
+ Crypt: 'HexaPDF::Filter::Crypt',
397
408
  Encryption: 'HexaPDF::Filter::Encryption',
398
409
  },
399
410
  'font.map' => {},
@@ -516,6 +527,9 @@ module HexaPDF
516
527
  XXAcroFormField: 'HexaPDF::Type::AcroForm::Field',
517
528
  XXAppearanceDictionary: 'HexaPDF::Type::Annotation::AppearanceDictionary',
518
529
  Border: 'HexaPDF::Type::Annotation::Border',
530
+ SigFieldLock: 'HexaPDF::Type::AcroForm::SignatureField::LockDictionary',
531
+ SV: 'HexaPDF::Type::AcroForm::SignatureField::SeedValueDictionary',
532
+ SVCert: 'HexaPDF::Type::AcroForm::SignatureField::CertificateSeedValueDictionary',
519
533
  },
520
534
  'object.subtype_map' => {
521
535
  nil => {
@@ -561,6 +575,7 @@ module HexaPDF
561
575
  Tx: 'HexaPDF::Type::AcroForm::TextField',
562
576
  Btn: 'HexaPDF::Type::AcroForm::ButtonField',
563
577
  Ch: 'HexaPDF::Type::AcroForm::ChoiceField',
578
+ Sig: 'HexaPDF::Type::AcroForm::SignatureField',
564
579
  },
565
580
  })
566
581
 
@@ -1260,6 +1260,7 @@ module HexaPDF
1260
1260
  unless obj.kind_of?(HexaPDF::Stream)
1261
1261
  obj = context.document.images.add(obj)
1262
1262
  end
1263
+ return obj if obj.width == 0 || obj.height == 0
1263
1264
 
1264
1265
  width, height = calculate_dimensions(obj.width, obj.height,
1265
1266
  rwidth: width, rheight: height)
@@ -268,7 +268,7 @@ module HexaPDF
268
268
  str.replace(string_algorithm.decrypt(key, str))
269
269
  end
270
270
 
271
- if obj.kind_of?(HexaPDF::Stream)
271
+ if obj.kind_of?(HexaPDF::Stream) && obj.raw_stream.filter[0] != :Crypt
272
272
  unless string_algorithm == stream_algorithm
273
273
  key = object_key(obj.oid, obj.gen, stream_algorithm)
274
274
  end
@@ -300,7 +300,12 @@ module HexaPDF
300
300
  obj.raw_stream.key == key && obj.raw_stream.algorithm == stream_algorithm
301
301
  obj.raw_stream.undecrypted_fiber
302
302
  else
303
- stream_algorithm.encryption_fiber(key, result)
303
+ filter = obj[:Filter]
304
+ if filter == :Crypt || (filter.kind_of?(PDFArray) && filter[0] == :Crypt)
305
+ result
306
+ else
307
+ stream_algorithm.encryption_fiber(key, result)
308
+ end
304
309
  end
305
310
  end
306
311
 
@@ -240,6 +240,22 @@ module HexaPDF
240
240
  end
241
241
  end
242
242
 
243
+ def decrypt(obj) #:nodoc:
244
+ if dict[:V] >= 4 && obj.type == :Metadata && obj[:Subtype] == :XML && !dict[:EncryptMetadata]
245
+ obj
246
+ else
247
+ super
248
+ end
249
+ end
250
+
251
+ def encrypt_stream(obj) #:nodoc
252
+ if dict[:V] >= 4 && obj.type == :Metadata && obj[:Subtype] == :XML && !dict[:EncryptMetadata]
253
+ obj.stream_encoder
254
+ else
255
+ super
256
+ end
257
+ end
258
+
243
259
  private
244
260
 
245
261
  # Prepares the security handler for use in encrypting the document.
data/lib/hexapdf/error.rb CHANGED
@@ -43,12 +43,13 @@ module HexaPDF
43
43
  class MalformedPDFError < Error
44
44
 
45
45
  # The byte position in the PDF file where the error occured.
46
- attr_reader :pos
46
+ attr_accessor :pos
47
47
 
48
48
  # Creates a new malformed PDF error object for the given exception message.
49
49
  #
50
- # The byte position where the error occured has to be given via the +pos+ argument.
51
- def initialize(message, pos:)
50
+ # The byte position where the error occured can either be given via the +pos+ argument or later
51
+ # via the #pos accessor but must be set before the exception message is retrieved.
52
+ def initialize(message, pos: nil)
52
53
  super(message)
53
54
  @pos = pos
54
55
  end
@@ -95,6 +95,7 @@ module HexaPDF
95
95
  autoload(:Predictor, 'hexapdf/filter/predictor')
96
96
 
97
97
  autoload(:Encryption, 'hexapdf/filter/encryption')
98
+ autoload(:Crypt, 'hexapdf/filter/crypt')
98
99
 
99
100
  autoload(:PassThrough, 'hexapdf/filter/pass_through')
100
101
 
@@ -0,0 +1,60 @@
1
+ # -*- encoding: utf-8; frozen_string_literal: true -*-
2
+ #
3
+ #--
4
+ # This file is part of HexaPDF.
5
+ #
6
+ # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
+ # Copyright (C) 2014-2020 Thomas Leitner
8
+ #
9
+ # HexaPDF is free software: you can redistribute it and/or modify it
10
+ # under the terms of the GNU Affero General Public License version 3 as
11
+ # published by the Free Software Foundation with the addition of the
12
+ # following permission added to Section 15 as permitted in Section 7(a):
13
+ # FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
14
+ # THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
15
+ # INFRINGEMENT OF THIRD PARTY RIGHTS.
16
+ #
17
+ # HexaPDF is distributed in the hope that it will be useful, but WITHOUT
18
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
20
+ # License for more details.
21
+ #
22
+ # You should have received a copy of the GNU Affero General Public License
23
+ # along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
24
+ #
25
+ # The interactive user interfaces in modified source and object code
26
+ # versions of HexaPDF must display Appropriate Legal Notices, as required
27
+ # under Section 5 of the GNU Affero General Public License version 3.
28
+ #
29
+ # In accordance with Section 7(b) of the GNU Affero General Public
30
+ # License, a covered work must retain the producer line in every PDF that
31
+ # is created or manipulated using HexaPDF.
32
+ #
33
+ # If the GNU Affero General Public License doesn't fit your need,
34
+ # commercial licenses are available at <https://gettalong.at/hexapdf/>.
35
+ #++
36
+
37
+ require 'hexapdf/error'
38
+
39
+ module HexaPDF
40
+ module Filter
41
+
42
+ # This filter module implements the Crypt filter. The only supported part is using the Identity
43
+ # filter.
44
+ module Crypt
45
+
46
+ # See HexaPDF::Filter
47
+ def self.decoder(source, options)
48
+ if !options || !options.key?(:Name) || options[:Name] == :Identity
49
+ source
50
+ else
51
+ raise FilterError, "Handling of Crypt filters besides Identity is not implemented"
52
+ end
53
+ end
54
+
55
+ singleton_class.send(:alias_method, :encoder, :decoder)
56
+
57
+ end
58
+
59
+ end
60
+ end
@@ -207,7 +207,8 @@ module HexaPDF
207
207
 
208
208
  # Returns the rest of the line, with whitespace stripped.
209
209
  def parse_string
210
- line = @line.strip!
210
+ @line.strip!
211
+ line = @line
211
212
  @line = ''
212
213
  line
213
214
  end
@@ -56,10 +56,12 @@ module HexaPDF
56
56
  # PDF references are resolved using the associated Document object.
57
57
  def initialize(io, document)
58
58
  @io = io
59
- @tokenizer = Tokenizer.new(io)
59
+ on_correctable_error = document.config['parser.on_correctable_error'].curry[document]
60
+ @tokenizer = Tokenizer.new(io, on_correctable_error: on_correctable_error)
60
61
  @document = document
61
62
  @object_stream_data = {}
62
63
  @reconstructed_revision = nil
64
+ @in_reconstruct_revision = false
63
65
  retrieve_pdf_header_offset_and_version
64
66
  end
65
67
 
@@ -94,7 +96,8 @@ module HexaPDF
94
96
 
95
97
  @document.wrap(obj, oid: oid, gen: gen, stream: stream)
96
98
  rescue HexaPDF::MalformedPDFError
97
- reconstructed_revision.object(xref_entry)
99
+ reconstructed_revision.object(xref_entry) ||
100
+ @document.wrap(nil, oid: xref_entry.oid, gen: xref_entry.gen)
98
101
  end
99
102
 
100
103
  # Parses the indirect object at the specified offset.
@@ -137,11 +140,13 @@ module HexaPDF
137
140
  raise_malformed("A stream needs a dictionary, not a(n) #{object.class}", pos: offset)
138
141
  end
139
142
  tok1 = @tokenizer.next_byte
140
- tok2 = @tokenizer.next_byte if tok1 == 13 # 13=CR, 10=LF
143
+ if tok1 == 32 # space
144
+ maybe_raise("Keyword stream followed by space instead of LF or CR/LF", pos: @tokenizer.pos)
145
+ tok1 = @tokenizer.next_byte
146
+ end
147
+ tok2 = @tokenizer.next_byte if tok1 == 13 # CR
141
148
  if tok1 != 10 && tok1 != 13
142
- tok2 = @tokenizer.next_byte
143
- maybe_raise("Keyword stream must be followed by LF or CR/LF", pos: @tokenizer.pos,
144
- force: tok1 != 32 || (tok2 != 10 && tok2 != 13)) # 32=space
149
+ raise_malformed("Keyword stream must be followed by LF or CR/LF", pos: @tokenizer.pos)
145
150
  elsif tok1 == 13 && tok2 != 10
146
151
  maybe_raise("Keyword stream must be followed by LF or CR/LF, not CR alone",
147
152
  pos: @tokenizer.pos)
@@ -211,7 +216,12 @@ module HexaPDF
211
216
  unless obj.respond_to?(:xref_section)
212
217
  raise_malformed("Object is not a cross-reference stream", pos: pos)
213
218
  end
214
- xref_section = obj.xref_section
219
+ begin
220
+ xref_section = obj.xref_section
221
+ rescue MalformedPDFError => e
222
+ e.pos = pos
223
+ raise
224
+ end
215
225
  trailer = obj.trailer
216
226
  unless xref_section.entry?(obj.oid, obj.gen)
217
227
  maybe_raise("Cross-reference stream doesn't contain entry for itself", pos: pos)
@@ -389,12 +399,16 @@ module HexaPDF
389
399
  # If the file contains multiple cross-reference sections, all objects will be put into a single
390
400
  # cross-reference table, later objects overwriting prior ones.
391
401
  def reconstruct_revision
402
+ return if @in_reconstruct_revision
403
+ @in_reconstruct_revision = true
404
+
392
405
  raise unless @document.config['parser.try_xref_reconstruction']
393
406
  msg = "#{$!} - trying cross-reference table reconstruction"
394
407
  @document.config['parser.on_correctable_error'].call(@document, msg, @tokenizer.pos)
395
408
 
396
409
  xref = XRefSection.new
397
410
  @tokenizer.pos = 0
411
+ linearized = nil
398
412
  while true
399
413
  @tokenizer.skip_whitespace
400
414
  pos = @tokenizer.pos
@@ -410,13 +424,17 @@ module HexaPDF
410
424
  @tokenizer.pos = next_new_line_pos
411
425
  elsif gen.kind_of?(Integer) && tok.kind_of?(Tokenizer::Token) && tok == 'obj'
412
426
  xref.add_in_use_entry(token, gen, pos)
427
+ if linearized.nil?
428
+ obj = @tokenizer.next_object rescue nil
429
+ linearized = obj.kind_of?(Hash) && obj.key?(:Linearized)
430
+ end
413
431
  @tokenizer.scan_until(/(?:\n|\r\n?)endobj\b/)
414
432
  end
415
433
  elsif token.kind_of?(Tokenizer::Token) && token == 'trailer'
416
434
  obj = @tokenizer.next_object rescue nil
417
435
  # Use last trailer found in case of multiple revisions but use first trailer in case of
418
436
  # linearized file.
419
- trailer = obj if obj.kind_of?(Hash) && (obj.key?(:Prev) || trailer.nil?)
437
+ trailer = obj if obj.kind_of?(Hash) && (!linearized || trailer.nil?)
420
438
  elsif token == Tokenizer::NO_MORE_TOKENS
421
439
  break
422
440
  else
@@ -424,16 +442,22 @@ module HexaPDF
424
442
  end
425
443
  end
426
444
 
427
- trailer&.delete(:Prev) # no need for this and may wreak havoc
428
445
  if !trailer || trailer.empty?
429
- raise_malformed("Could not reconstruct malformed PDF because trailer was not found", pos: 0)
446
+ _, trailer = load_revision(startxref_offset) rescue nil
447
+ unless trailer
448
+ @in_reconstruct_revision = false
449
+ raise_malformed("Could not reconstruct malformed PDF because trailer was not found", pos: 0)
450
+ end
430
451
  end
452
+ trailer&.delete(:Prev) # no need for this and may wreak havoc
431
453
 
432
454
  loader = lambda do |xref_entry|
433
455
  obj, oid, gen, stream = parse_indirect_object(xref_entry.pos)
434
- @document.wrap(obj, oid: oid, gen: gen, stream: stream)
456
+ obj = @document.wrap(obj, oid: oid, gen: gen, stream: stream)
457
+ @document.security_handler ? @document.security_handler.decrypt(obj) : obj
435
458
  end
436
459
 
460
+ @in_reconstruct_revision = false
437
461
  Revision.new(@document.wrap(trailer, type: :XXTrailer), xref_section: xref,
438
462
  loader: loader)
439
463
  end