diff --git a/lib/prawn.rb b/lib/prawn.rb index 05f4aeb78..06b441a3d 100644 --- a/lib/prawn.rb +++ b/lib/prawn.rb @@ -61,6 +61,7 @@ def verify_options(accepted, actual) require_relative 'prawn/images/image' require_relative 'prawn/images/jpg' require_relative 'prawn/images/png' +require_relative 'prawn/embedded_files' require_relative 'prawn/stamp' require_relative 'prawn/soft_mask' require_relative 'prawn/security' diff --git a/lib/prawn/document.rb b/lib/prawn/document.rb index 439667d5a..03cd472b7 100644 --- a/lib/prawn/document.rb +++ b/lib/prawn/document.rb @@ -55,6 +55,7 @@ class Document include Prawn::Text include Prawn::Graphics include Prawn::Images + include Prawn::EmbeddedFiles include Prawn::Stamp include Prawn::SoftMask include Prawn::TransformationStack diff --git a/lib/prawn/embedded_files.rb b/lib/prawn/embedded_files.rb new file mode 100644 index 000000000..01af76343 --- /dev/null +++ b/lib/prawn/embedded_files.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +module Prawn + module EmbeddedFiles + # include PDF::Core::EmbeddedFiles + # Link to PR (https://github.com/prawnpdf/pdf-core/pull/47) + + # Add the file's data from a source to the document. Any kind of data with + # a string representation can be embedded. + # + # Arguments: + # src:: path to file, string or an object that responds to #to_str + # and #length. + # + # Options: + # :name:: explicit default filename override. + # :creation_date:: date when the file was created. + # :modification_date:: date when the file was last modified. + # :description:: file description. + # :hidden:: if true, prevents the file from appearing in the + # catalog. (default false) + # + # Prawn::Document.generate("file1.pdf") do + # dice = "#{Prawn::DATADIR}/images/dice.png" + # file dice, description: 'Example of an embedded image file' + # end + # + # This method returns an instance of PDF::Core::NameTree::Value + # corresponding to the file in the embedded files catalog entry node. If + # hidden, then nil is returned. + # + def file(src, options = {}) + path = Pathname.new(src) + mut_opts = options.dup + + if path.directory? + raise ArgumentError, 'Data source can\'t be a directory' + elsif path.file? + data = path.read + mut_opts[:name] ||= src + mut_opts[:creation_date] ||= path.birthtime + mut_opts[:modification_date] ||= path.mtime + else + data = src + end + + file = EmbeddedFile.new(data, mut_opts) + file_obj = file_registry[file.checksum] + + if file_obj.nil? + file_obj = file.build_pdf_object(self) + file_registry[file.checksum] = file_obj + end + + filespec = Filespec.new(file_obj, mut_opts) + filespec_obj = filespec.build_pdf_object(self) + + unless filespec.hidden? + # Wait for pdf-core PR + + # attach_file(filespec.file_name, filespec_obj) + end + end + + private + + def file_registry + @file_registry ||= {} + end + end +end + +require_relative 'embedded_files/embedded_file' +require_relative 'embedded_files/filespec' diff --git a/lib/prawn/embedded_files/embedded_file.rb b/lib/prawn/embedded_files/embedded_file.rb new file mode 100644 index 000000000..524092071 --- /dev/null +++ b/lib/prawn/embedded_files/embedded_file.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require 'digest' + +module Prawn + module EmbeddedFiles + class EmbeddedFile + attr_reader :checksum + + def initialize(data, options = {}) + @creation_date = options[:creation_date] + unless @creation_date.is_a?(Time) + @creation_date = Time.now.utc + end + + @modification_date = options[:modification_date] + unless @modification_date.is_a?(Time) + @modification_date = Time.now.utc + end + + @checksum = Digest::MD5.digest(data) + @data = data + end + + def build_pdf_object(document) + obj = document.ref!( + Type: :EmbeddedFile, + Params: { + CreationDate: creation_date, + ModDate: modification_date, + CheckSum: PDF::Core::LiteralString.new(checksum), + Size: data.length + } + ) + + obj << data + obj.stream.compress! if document.compression_enabled? + obj + end + + private + + attr_reader :data, :creation_date, :modification_date + end + end +end diff --git a/lib/prawn/embedded_files/filespec.rb b/lib/prawn/embedded_files/filespec.rb new file mode 100644 index 000000000..07b51337b --- /dev/null +++ b/lib/prawn/embedded_files/filespec.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require 'digest' + +module Prawn + module EmbeddedFiles + class Filespec + attr_reader :file_name + + def initialize(file, options = {}) + hexdigest = Digest.hexencode(file.data[:Params][:CheckSum]) + name = options[:name] || hexdigest + + @file_name = PDF::Core::LiteralString.new(name) + + if options[:description] + desc_str = options[:description].to_s + @description = PDF::Core::LiteralString.new(desc_str) + end + + @hidden = options[:hidden] + @file = file + end + + def hidden? + @hidden + end + + def build_pdf_object(document) + obj = document.ref!( + Type: :Filespec, + F: file_name, + EF: { F: file, UF: file }, + UF: file_name + ) + + obj.data[:Desc] = description if description + obj + end + + private + + attr_reader :file, :description + end + end +end