File: stamp.rb

package info (click to toggle)
ruby-prawn 2.3.0%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 4,380 kB
  • sloc: ruby: 15,820; sh: 43; makefile: 20
file content (151 lines) | stat: -rw-r--r-- 4,555 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# 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 <tt>name</tt> to the page
    # raises <tt>Prawn::Errors::InvalidName</tt> if name.empty?
    # raises <tt>Prawn::Errors::UndefinedObjectName</tt> 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 <tt>name</tt> 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 <tt>name</tt>
    #
    # raises <tt>Prawn::Errors::NameTaken</tt> if a stamp already
    # exists in this document with this name
    # raises <tt>Prawn::Errors::InvalidName</tt> 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