# -*- encoding: utf-8; frozen_string_literal: true -*- # #-- # This file is part of HexaPDF. # # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby # Copyright (C) 2014-2019 Thomas Leitner # # HexaPDF is free software: you can redistribute it and/or modify it # under the terms of the GNU Affero General Public License version 3 as # published by the Free Software Foundation with the addition of the # following permission added to Section 15 as permitted in Section 7(a): # FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY # THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON # INFRINGEMENT OF THIRD PARTY RIGHTS. # # HexaPDF is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public # License for more details. # # You should have received a copy of the GNU Affero General Public License # along with HexaPDF. If not, see . # # The interactive user interfaces in modified source and object code # versions of HexaPDF must display Appropriate Legal Notices, as required # under Section 5 of the GNU Affero General Public License version 3. # # In accordance with Section 7(b) of the GNU Affero General Public # License, a covered work must retain the producer line in every PDF that # is created or manipulated using HexaPDF. # # If the GNU Affero General Public License doesn't fit your need, # commercial licenses are available at . #++ require 'hexapdf/error' require 'weakref' module HexaPDF # The Importer class manages the process of copying objects from one Document to another. # # It may seem unnecessary using an importer containing state for the task. However, by retaining # some information about the already copied objects we can make sure that already imported # objects don't get imported again. # # Two types of indirect objects are *never* imported from one document to another: the catalog # and page tree nodes. If the catalog was imported, the whole source document would be imported. # And if one page tree node would imported, the whole page tree would be imported. # # See: Document#import class Importer class NullableWeakRef < WeakRef #:nodoc: def __getobj__ #:nodoc: super rescue nil end end # Returns the Importer object for copying objects from the +source+ to the +destination+ # document. def self.for(source:, destination:) @map ||= {} @map.keep_if {|_, v| v.source.weakref_alive? && v.destination.weakref_alive? } source = NullableWeakRef.new(source) destination = NullableWeakRef.new(destination) @map[[source.hash, destination.hash]] ||= new(source: source, destination: destination) end private_class_method :new attr_reader :source, :destination #:nodoc: # Initializes a new importer that can import objects from the +source+ document to the # +destination+ document. def initialize(source:, destination:) @source = source @destination = destination @mapper = {} end # Imports the given +object+ from the source to the destination object and returns the # imported object. # # Note: Indirect objects are automatically added to the destination document but direct or # simple objects are not. # # An error is raised if the object doesn't belong to the +source+ document. def import(object) mapped_object = @mapper[object.data] if object.kind_of?(HexaPDF::Object) if object.kind_of?(HexaPDF::Object) && object.document? && @source != object.document raise HexaPDF::Error, "Import error: Incorrect document object for importer" elsif mapped_object && mapped_object == @destination.object(mapped_object) mapped_object else duplicate(object) end end private # Recursively duplicates the object. # # PDF objects are automatically added to the destination document if they are indirect objects # in the source document. def duplicate(object) case object when Hash object.each_with_object({}) do |(k, v), obj| obj[k] = duplicate(v) end when Array object.map {|v| duplicate(v) } when HexaPDF::Reference import(@source.object(object)) when HexaPDF::Object if object.type == :Catalog || object.type == :Pages @mapper[object.data] = nil else obj = @mapper[object.data] = object.dup obj.document = @destination.__getobj__ obj.instance_variable_set(:@data, obj.data.dup) obj.data.oid = 0 obj.data.gen = 0 @destination.add(obj) if object.indirect? obj.data.stream = obj.data.stream.dup if obj.data.stream.kind_of?(String) obj.data.value = duplicate(obj.data.value) obj.data.value.update(duplicate(object.copy_inherited_values)) if object.type == :Page obj end when String object.dup else object end end end end