-
Notifications
You must be signed in to change notification settings - Fork 687
/
stamp.rb
147 lines (130 loc) · 4.34 KB
/
stamp.rb
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
# frozen_string_literal: true
module Prawn
# This 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:
# * Faster document creation.
# * Smaller final document.
# * 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.
#
# @example
# pdf.create_stamp("my_stamp") {
# pdf.fill_circle([10, 15], 5)
# pdf.text("hello world", at: [20, 10])
# }
# pdf.stamp("my_stamp")
#
# @param name [String]
# @return [void]
# @raise [Prawn::Errors::InvalidName] if name is empty.
# @raise [Prawn::Errors::UndefinedObjectName] if no stamp has been created
# with this name.
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 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])
#
# @param name [String]
# @param point [Array(Number, Number)]
# @return [void]
# @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.
#
# @example
# pdf.create_stamp("my_stamp") {
# pdf.fill_circle([10, 15], 5)
# pdf.draw_text("hello world", at: [20, 10])
# }
#
# @param name [String] Stamp name.
# @yield Stamp content.
# @return [void]
# @raise [Prawn::Errors::NameTaken]
# if a stamp already exists in this document with this name.
# @raise [Prawn::Errors::InvalidName] if name is empty.
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