diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..b038473 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "[pdf]": { + "files.trimTrailingWhitespace": false, + } +} diff --git a/lib/rubrik/document.rb b/lib/rubrik/document.rb index f300bdb..e629bd1 100644 --- a/lib/rubrik/document.rb +++ b/lib/rubrik/document.rb @@ -17,19 +17,25 @@ class Document sig {returns(PDF::Reader::ObjectHash)} attr_accessor :objects + sig {returns(Integer)} + attr_accessor :first_free_object_id + sig {returns(T::Array[{id: PDF::Reader::Reference, value: T.untyped}])} attr_accessor :modified_objects sig {returns(Integer)} attr_accessor :last_object_id - private :io=, :objects=, :modified_objects=, :last_object_id= + private :io=, :objects=, :first_free_object_id=, :modified_objects=, :last_object_id= sig {params(input: T.any(File, Tempfile, StringIO)).void} def initialize(input) self.io = input self.objects = PDF::Reader::ObjectHash.new(input) - self.last_object_id = objects.size + + self.last_object_id = objects.trailer[:Size] - 1 + self.first_free_object_id = find_first_free_object_id + self.modified_objects = [] fetch_or_create_interactive_form! @@ -87,6 +93,16 @@ def interactive_form T.must(modified_objects.first).fetch(:value) end + sig{returns(Integer)} + def find_first_free_object_id + return 0 if last_object_id == objects.size + + xref = objects.send(:xref).instance_variable_get(:@xref) + missing_ids = (1..last_object_id).to_a - xref.keys + + T.must(missing_ids.min) + end + sig {void} def fetch_or_create_interactive_form! root_ref = objects.trailer[:Root] diff --git a/lib/rubrik/document/increment.rb b/lib/rubrik/document/increment.rb index 831e865..10b9819 100644 --- a/lib/rubrik/document/increment.rb +++ b/lib/rubrik/document/increment.rb @@ -46,11 +46,11 @@ def call(document, io:) io << "#{starting_id} #{length}\n" if starting_id.zero? - io << "0000000000 65535 f\n" + io << "#{format("%010d", document.first_free_object_id)} 65535 f \n" subsection.shift end - subsection.each { |entry| io << "#{format("%010d", entry[:offset])} 00000 n\n" } + subsection.each { |entry| io << "#{format("%010d", entry[:offset])} 00000 n \n" } end io << "trailer\n" diff --git a/test/rubrik/document_test.rb b/test/rubrik/document_test.rb index f46d377..032886d 100644 --- a/test/rubrik/document_test.rb +++ b/test/rubrik/document_test.rb @@ -15,7 +15,7 @@ def test_initialize_document_without_interactive_form # Assert assert_equal(input, document.send(:io)) # FACT: the interactive form was created - assert_equal(5, document.last_object_id) + assert_equal(6, document.last_object_id) assert_kind_of(PDF::Reader::ObjectHash, document.objects) acro_form = document.modified_objects.find { |obj| obj.dig(:value, :Type) == :Catalog } diff --git a/test/support/with_interactive_form.expected.pdf b/test/support/with_interactive_form.expected.pdf index 12db41a..d8cfbdf 100644 Binary files a/test/support/with_interactive_form.expected.pdf and b/test/support/with_interactive_form.expected.pdf differ diff --git a/test/support/with_interactive_form.pdf b/test/support/with_interactive_form.pdf index 97a0b15..91f4e34 100644 Binary files a/test/support/with_interactive_form.pdf and b/test/support/with_interactive_form.pdf differ diff --git a/test/support/without_interactive_form.expected.pdf b/test/support/without_interactive_form.expected.pdf index 22b5732..2b1a42b 100644 Binary files a/test/support/without_interactive_form.expected.pdf and b/test/support/without_interactive_form.expected.pdf differ diff --git a/test/support/without_interactive_form.pdf b/test/support/without_interactive_form.pdf index 29e95d6..544bfe2 100644 Binary files a/test/support/without_interactive_form.pdf and b/test/support/without_interactive_form.pdf differ