# frozen_string_literal: true # stamp.rb : Implements a repeatable stamp # # Copyright October 2009, Daniel Nelson. All Rights Reserved. # # This is free software. Please see the LICENSE and COPYING files for details. # module Prawn # The Prawn::Stamp module is used to create content that will be # included multiple times in a document. Using a stamp has three # advantages over creating content anew each time it is placed on # the page: # i. faster document creation # ii. smaller final document # iii. faster display on subsequent displays of the repeated # element because the viewer application can cache the rendered # results # # Example: # pdf.create_stamp("my_stamp") { # pdf.fill_circle([10, 15], 5) # pdf.draw_text("hello world", :at => [20, 10]) # } # pdf.stamp("my_stamp") # module Stamp # @group Stable API # Renders the stamp named name to the page # raises Prawn::Errors::InvalidName if name.empty? # raises Prawn::Errors::UndefinedObjectName if no stamp # has been created with this name # # Example: # pdf.create_stamp("my_stamp") { # pdf.fill_circle([10, 15], 5) # pdf.text("hello world", :at => [20, 10]) # } # pdf.stamp("my_stamp") # def stamp(name) dictionary_name, dictionary = stamp_dictionary(name) renderer.add_content "/#{dictionary_name} Do" update_annotation_references dictionary.data[:Annots] state.page.xobjects.merge!(dictionary_name => dictionary) end # Renders the stamp named name at a position offset from # the initial coords at which the elements of the stamp was # created # # Example: # pdf.create_stamp("circle") do # pdf.fill_circle([0, 0], 25) # end # # draws a circle at 100, 100 # pdf.stamp_at("circle", [100, 100]) # # See stamp() for exceptions that might be raised # def stamp_at(name, point) translate(point[0], point[1]) { stamp(name) } end # Creates a re-usable stamp named name # # raises Prawn::Errors::NameTaken if a stamp already # exists in this document with this name # raises Prawn::Errors::InvalidName if name.empty? # # Example: # pdf.create_stamp("my_stamp") { # pdf.fill_circle([10, 15], 5) # pdf.draw_text("hello world", :at => [20, 10]) # } # def create_stamp(name, &block) dictionary = create_stamp_dictionary(name) state.page.stamp_stream(dictionary, &block) end private def stamp_dictionary_registry @stamp_dictionary_registry ||= {} end def next_stamp_dictionary_id stamp_dictionary_registry.length + 1 end def stamp_dictionary(name) raise Prawn::Errors::InvalidName if name.empty? if stamp_dictionary_registry[name].nil? raise Prawn::Errors::UndefinedObjectName end dict = stamp_dictionary_registry[name] dictionary_name = dict[:stamp_dictionary_name] dictionary = dict[:stamp_dictionary] [dictionary_name, dictionary] end def create_stamp_dictionary(name) raise Prawn::Errors::InvalidName if name.empty? raise Prawn::Errors::NameTaken unless stamp_dictionary_registry[name].nil? # BBox origin is the lower left margin of the page, so we need # it to be the full dimension of the page, or else things that # should appear near the top or right margin are invisible dictionary = ref!( Type: :XObject, Subtype: :Form, BBox: [ 0, 0, state.page.dimensions[2], state.page.dimensions[3] ] ) dictionary_name = "Stamp#{next_stamp_dictionary_id}" stamp_dictionary_registry[name] = { stamp_dictionary_name: dictionary_name, stamp_dictionary: dictionary } dictionary end # Referencing annotations from a stamp XObject doesn't result # in a working link. Instead, the references must be appended # to the /Annot dictionary of the object that contains the # call to the stamp object. def update_annotation_references(annots) if annots&.any? state.page.dictionary.data[:Annots] ||= [] state.page.dictionary.data[:Annots] |= annots end end def freeze_stamp_graphics update_colors write_line_width write_stroke_cap_style write_stroke_join_style write_stroke_dash end end end