diff --git a/lib/pdf/core.rb b/lib/pdf/core.rb index 33ee64c..fbc3445 100644 --- a/lib/pdf/core.rb +++ b/lib/pdf/core.rb @@ -4,6 +4,7 @@ require_relative 'core/annotations' require_relative 'core/byte_string' require_relative 'core/destinations' +require_relative 'core/embedded_files' require_relative 'core/filters' require_relative 'core/stream' require_relative 'core/reference' diff --git a/lib/pdf/core/embedded_files.rb b/lib/pdf/core/embedded_files.rb new file mode 100644 index 0000000..91929bf --- /dev/null +++ b/lib/pdf/core/embedded_files.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +module PDF + module Core + module EmbeddedFiles #:nodoc: + # The maximum number of children to fit into a single node in the + # EmbeddedFiles tree. + NAME_TREE_CHILDREN_LIMIT = 20 #:nodoc: + + # The EmbeddedFiles name tree in the Name dictionary (see + # Prawn::Document::Internal#names). This name tree is used to store named + # embedded files (PDF spec 3.10.3). (For more on name trees, see section + # 3.8.4 in the PDF spec.) + # + def embedded_files + bump_min_version + + names.data[:EmbeddedFiles] ||= ref!( + PDF::Core::NameTree::Node.new(self, NAME_TREE_CHILDREN_LIMIT) + ) + end + + # Adds a new embedded file to the EmbeddedFiles name tree + # (see #embedded_files). The +reference+ parameter will be converted into + # a PDF::Core::Reference if it is not already one. + # + def add_embedded_file(name, reference) + reference = ref!(reference) unless reference.is_a?(PDF::Core::Reference) + embedded_files.data.add(name, reference) + end + + # Friendly method alias to attach file specifications in the catalog + alias attach_file add_embedded_file + + private + + def bump_min_version + renderer.min_version(1.4) + end + end + end +end diff --git a/spec/pdf/core/embedded_files_spec.rb b/spec/pdf/core/embedded_files_spec.rb new file mode 100644 index 0000000..1e73966 --- /dev/null +++ b/spec/pdf/core/embedded_files_spec.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require 'spec_helper' + +class EmbeddedFiles + include PDF::Core::EmbeddedFiles + + class InnerRenderer; def min_version(value); end; end + + attr_reader :renderer + + def initialize + @size = 0 + @root = ref!(Type: :Catalog) + @renderer = InnerRenderer.new + end + + def names + @root.data[:Names] ||= ref!(Type: :Names) + end + + def ref!(data) + @size += 1 + + PDF::Core::Reference.new(@size, data) + end +end + +RSpec.describe EmbeddedFiles do + it 'has an empty catalog object' do + t = described_class.new + pdf_object = "2 0 obj\n<< /Type /Names\n>>\nendobj\n" + expect(t.names.object).to eq pdf_object + end + + it 'has an embedded files object with no names' do + t = described_class.new + pdf_object = "3 0 obj\n<< /Names []\n>>\nendobj\n" + expect(t.embedded_files.object).to eq pdf_object + end + + it 'has a catalog object with an embedded files reference' do + t = described_class.new + t.embedded_files + + pdf_object = "2 0 obj\n<< /Type /Names\n/EmbeddedFiles 3 0 R\n>>\nendobj\n" + expect(t.names.object).to eq pdf_object + end + + it 'has an embedded files object with one name reference' do + t = described_class.new + file_name = PDF::Core::LiteralString.new('my_file') + + t.add_embedded_file(file_name, t.ref!(Type: :Filespec)) + + pdf_object = "4 0 obj\n<< /Names [(my_file) 2 0 R]\n>>\nendobj\n" + expect(t.embedded_files.data.size).to eq 1 + expect(t.embedded_files.object).to eq pdf_object + end +end