prawn-core 0.5.1 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/HACKING +46 -0
- data/README +9 -3
- data/Rakefile +7 -6
- data/examples/bounding_box/stretched_nesting.rb +67 -0
- data/examples/general/margin.rb +36 -0
- data/examples/general/multi_page_layout.rb +3 -1
- data/examples/general/page_numbering.rb +15 -0
- data/examples/general/stamp.rb +45 -0
- data/examples/graphics/stroke_cap_and_join.rb +45 -0
- data/examples/graphics/stroke_dash.rb +42 -0
- data/examples/graphics/transparency.rb +26 -0
- data/examples/text/text_box_returning_excess.rb +51 -0
- data/lib/prawn/byte_string.rb +7 -0
- data/lib/prawn/core.rb +7 -8
- data/lib/prawn/document/annotations.rb +3 -2
- data/lib/prawn/document/bounding_box.rb +15 -10
- data/lib/prawn/document/column_box.rb +1 -3
- data/lib/prawn/document/destinations.rb +11 -10
- data/lib/prawn/document/internals.rb +62 -19
- data/lib/prawn/document/snapshot.rb +71 -0
- data/lib/prawn/document/text/box.rb +7 -0
- data/lib/prawn/document/text/wrapping.rb +3 -0
- data/lib/prawn/document/text.rb +9 -2
- data/lib/prawn/document.rb +141 -25
- data/lib/prawn/errors.rb +12 -0
- data/lib/prawn/font/afm.rb +1 -1
- data/lib/prawn/font/ttf.rb +5 -5
- data/lib/prawn/font.rb +8 -5
- data/lib/prawn/graphics/cap_style.rb +35 -0
- data/lib/prawn/graphics/dash.rb +69 -0
- data/lib/prawn/graphics/join_style.rb +35 -0
- data/lib/prawn/graphics/transparency.rb +56 -0
- data/lib/prawn/graphics.rb +9 -1
- data/lib/prawn/images.rb +4 -4
- data/lib/prawn/name_tree.rb +2 -1
- data/lib/prawn/object_store.rb +63 -0
- data/lib/prawn/pdf_object.rb +4 -0
- data/lib/prawn/reference.rb +18 -5
- data/lib/prawn/stamp.rb +87 -0
- data/spec/bounding_box_spec.rb +9 -0
- data/spec/document_spec.rb +58 -5
- data/spec/images_spec.rb +1 -1
- data/spec/name_tree_spec.rb +14 -5
- data/spec/object_store_spec.rb +42 -0
- data/spec/pdf_object_spec.rb +5 -0
- data/spec/reference_spec.rb +40 -0
- data/spec/snapshot_spec.rb +115 -0
- data/spec/spec_helper.rb +1 -4
- data/spec/stamp_spec.rb +98 -0
- data/spec/stroke_styles_spec.rb +152 -0
- data/spec/text_box_spec.rb +26 -0
- data/spec/text_spec.rb +8 -1
- data/spec/transparency_spec.rb +61 -0
- data/vendor/pdf-inspector/lib/pdf/inspector/extgstate.rb +18 -0
- data/vendor/pdf-inspector/lib/pdf/inspector/graphics.rb +40 -1
- data/vendor/pdf-inspector/lib/pdf/inspector/page.rb +12 -3
- data/vendor/pdf-inspector/lib/pdf/inspector.rb +2 -1
- metadata +26 -2
data/lib/prawn/font/ttf.rb
CHANGED
@@ -222,7 +222,7 @@ module Prawn
|
|
222
222
|
|
223
223
|
def register(subset)
|
224
224
|
temp_name = @ttf.name.postscript_name.gsub("\0","").to_sym
|
225
|
-
@document.ref(:Type => :Font, :BaseFont => temp_name) { |ref| embed(ref, subset) }
|
225
|
+
@document.ref!(:Type => :Font, :BaseFont => temp_name) { |ref| embed(ref, subset) }
|
226
226
|
end
|
227
227
|
|
228
228
|
def embed(reference, subset)
|
@@ -241,12 +241,12 @@ module Prawn
|
|
241
241
|
|
242
242
|
compressed_font = Zlib::Deflate.deflate(font_content)
|
243
243
|
|
244
|
-
fontfile = @document.ref(:Length => compressed_font.size,
|
244
|
+
fontfile = @document.ref!(:Length => compressed_font.size,
|
245
245
|
:Length1 => font_content.size,
|
246
246
|
:Filter => :FlateDecode )
|
247
247
|
fontfile << compressed_font
|
248
248
|
|
249
|
-
descriptor = @document.ref(:Type => :FontDescriptor,
|
249
|
+
descriptor = @document.ref!(:Type => :FontDescriptor,
|
250
250
|
:FontName => basename.to_sym,
|
251
251
|
:FontFile2 => fontfile,
|
252
252
|
:FontBBox => bbox,
|
@@ -287,7 +287,7 @@ module Prawn
|
|
287
287
|
|
288
288
|
to_unicode_cmap = UNICODE_CMAP_TEMPLATE % range_blocks.strip
|
289
289
|
|
290
|
-
cmap = @document.ref({})
|
290
|
+
cmap = @document.ref!({})
|
291
291
|
cmap << to_unicode_cmap
|
292
292
|
cmap.compress_stream
|
293
293
|
|
@@ -296,7 +296,7 @@ module Prawn
|
|
296
296
|
:FontDescriptor => descriptor,
|
297
297
|
:FirstChar => 32,
|
298
298
|
:LastChar => 255,
|
299
|
-
:Widths => @document.ref(widths),
|
299
|
+
:Widths => @document.ref!(widths),
|
300
300
|
:ToUnicode => cmap)
|
301
301
|
end
|
302
302
|
|
data/lib/prawn/font.rb
CHANGED
@@ -28,9 +28,9 @@ module Prawn
|
|
28
28
|
# make it more portable.
|
29
29
|
#
|
30
30
|
def font(name=nil, options={})
|
31
|
-
return @font || font("Helvetica") if name.nil?
|
31
|
+
return((defined?(@font) && @font) || font("Helvetica")) if name.nil?
|
32
32
|
|
33
|
-
raise Errors::NotOnPage unless @current_page
|
33
|
+
raise Errors::NotOnPage unless defined?(@current_page) && @current_page
|
34
34
|
new_font = find_font(name, options)
|
35
35
|
|
36
36
|
if block_given?
|
@@ -252,13 +252,16 @@ module Prawn
|
|
252
252
|
"#{self.class.name}< #{name}: #{size} >"
|
253
253
|
end
|
254
254
|
|
255
|
-
# Normalizes the encoding of the string to an encoding supported by the
|
256
|
-
# The string is expected to be UTF-8 going in
|
257
|
-
#
|
255
|
+
# Normalizes the encoding of the string to an encoding supported by the
|
256
|
+
# font. The string is expected to be UTF-8 going in. It will be re-encoded
|
257
|
+
# and the new string will be returned. For an in-place (destructive)
|
258
|
+
# version, see normalize_encoding!.
|
258
259
|
def normalize_encoding(string)
|
259
260
|
raise NotImplementedError, "subclasses of Prawn::Font must implement #normalize_encoding"
|
260
261
|
end
|
261
262
|
|
263
|
+
# Destructive version of normalize_encoding; normalizes the encoding of a
|
264
|
+
# string in place.
|
262
265
|
def normalize_encoding!(str)
|
263
266
|
str.replace(normalize_encoding(str))
|
264
267
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# cap_style.rb : Implements stroke cap styling
|
4
|
+
#
|
5
|
+
# Contributed by Daniel Nelson. October, 2009
|
6
|
+
#
|
7
|
+
# This is free software. Please see the LICENSE and COPYING files for details.
|
8
|
+
#
|
9
|
+
module Prawn
|
10
|
+
module Graphics
|
11
|
+
module CapStyle
|
12
|
+
# Sets the cap_style for stroked lines and curves
|
13
|
+
#
|
14
|
+
|
15
|
+
CAP_STYLES = { :butt => 0, :round => 1, :projecting_square => 2 }
|
16
|
+
|
17
|
+
# style is one of :butt, :round, or :projecting_square
|
18
|
+
def cap_style(style=nil)
|
19
|
+
return @cap_style || :butt if style.nil?
|
20
|
+
|
21
|
+
@cap_style = style
|
22
|
+
|
23
|
+
write_stroke_cap_style
|
24
|
+
end
|
25
|
+
|
26
|
+
alias_method :cap_style=, :cap_style
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def write_stroke_cap_style
|
31
|
+
add_content "#{CAP_STYLES[@cap_style]} J"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# dash.rb : Implements stroke dashing
|
4
|
+
#
|
5
|
+
# Contributed by Daniel Nelson. October, 2009
|
6
|
+
#
|
7
|
+
# This is free software. Please see the LICENSE and COPYING files for details.
|
8
|
+
#
|
9
|
+
module Prawn
|
10
|
+
module Graphics
|
11
|
+
module Dash
|
12
|
+
|
13
|
+
# Sets the dash pattern for stroked lines and curves
|
14
|
+
#
|
15
|
+
# length is the length of the dash. If options is not present,
|
16
|
+
# or options[:space] is nil, then length is also the length of
|
17
|
+
# the space between dashes
|
18
|
+
#
|
19
|
+
# options may contain :space and :phase
|
20
|
+
# :space is the space between the dashes
|
21
|
+
# :phase is where in the cycle to begin dashing. For
|
22
|
+
# example, a phase of 0 starts at the beginning of
|
23
|
+
# the dash; whereas, if the phase is equal to the
|
24
|
+
# length of the dash, then stroking will begin at
|
25
|
+
# the beginning of the space. Default is 0
|
26
|
+
#
|
27
|
+
# integers or floats may be used for length and the options
|
28
|
+
#
|
29
|
+
# dash units are in PDF points ( 1/72 in )
|
30
|
+
#
|
31
|
+
def dash(length=nil, options={})
|
32
|
+
return @dash || undash_hash if length.nil?
|
33
|
+
|
34
|
+
@dash = { :dash => length,
|
35
|
+
:space => options[:space] || length,
|
36
|
+
:phase => options[:phase] || 0 }
|
37
|
+
|
38
|
+
write_stroke_dash
|
39
|
+
end
|
40
|
+
|
41
|
+
alias_method :dash=, :dash
|
42
|
+
|
43
|
+
# Restores solid stroking
|
44
|
+
def undash
|
45
|
+
@dash = undash_hash
|
46
|
+
write_stroke_dash
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns true iff the stroke is dashed
|
50
|
+
def dashed?
|
51
|
+
dash != undash_hash
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def undash_hash
|
57
|
+
{ :dash => nil, :space => nil, :phase => 0 }
|
58
|
+
end
|
59
|
+
|
60
|
+
def write_stroke_dash
|
61
|
+
if @dash[:dash].nil?
|
62
|
+
add_content "[] 0 d"
|
63
|
+
return
|
64
|
+
end
|
65
|
+
add_content "[#{@dash[:dash]} #{@dash[:space]}] #{@dash[:phase]} d"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# join_style.rb : Implements stroke join styling
|
4
|
+
#
|
5
|
+
# Contributed by Daniel Nelson. October, 2009
|
6
|
+
#
|
7
|
+
# This is free software. Please see the LICENSE and COPYING files for details.
|
8
|
+
#
|
9
|
+
module Prawn
|
10
|
+
module Graphics
|
11
|
+
module JoinStyle
|
12
|
+
# Sets the join_style for stroked lines and curves
|
13
|
+
#
|
14
|
+
|
15
|
+
JOIN_STYLES = { :miter => 0, :round => 1, :bevel => 2 }
|
16
|
+
|
17
|
+
# style is one of :miter, :round, or :bevel
|
18
|
+
def join_style(style=nil)
|
19
|
+
return @join_style || :miter if style.nil?
|
20
|
+
|
21
|
+
@join_style = style
|
22
|
+
|
23
|
+
write_stroke_join_style
|
24
|
+
end
|
25
|
+
|
26
|
+
alias_method :join_style=, :join_style
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def write_stroke_join_style
|
31
|
+
add_content "#{JOIN_STYLES[@join_style]} j"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
# transparency.rb : Implements transparency
|
4
|
+
#
|
5
|
+
# Copyright October 2009, Daniel Nelson. All Rights Reserved.
|
6
|
+
#
|
7
|
+
# This is free software. Please see the LICENSE and COPYING files for details.
|
8
|
+
#
|
9
|
+
|
10
|
+
module Prawn
|
11
|
+
module Graphics
|
12
|
+
module Transparency
|
13
|
+
|
14
|
+
def transparent(opacity, stroke_opacity=opacity, &block)
|
15
|
+
min_version(1.4)
|
16
|
+
|
17
|
+
key = "#{opacity}_#{stroke_opacity}"
|
18
|
+
|
19
|
+
if opacity_dictionary_registry[key]
|
20
|
+
opacity_dictionary = opacity_dictionary_registry[key][:obj]
|
21
|
+
opacity_dictionary_name = opacity_dictionary_registry[key][:name]
|
22
|
+
else
|
23
|
+
opacity_dictionary = ref!(:Type => :ExtGState,
|
24
|
+
:CA => stroke_opacity,
|
25
|
+
:ca => opacity
|
26
|
+
)
|
27
|
+
|
28
|
+
opacity_dictionary_name = "Tr#{next_opacity_dictionary_id}"
|
29
|
+
opacity_dictionary_registry[key] = { :name => opacity_dictionary_name,
|
30
|
+
:obj => opacity_dictionary }
|
31
|
+
end
|
32
|
+
|
33
|
+
page_ext_gstates.merge!(opacity_dictionary_name => opacity_dictionary)
|
34
|
+
|
35
|
+
# push a new graphics context onto the graphics context stack
|
36
|
+
add_content "q"
|
37
|
+
add_content "/#{opacity_dictionary_name} gs"
|
38
|
+
|
39
|
+
yield if block_given?
|
40
|
+
|
41
|
+
add_content "Q"
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def opacity_dictionary_registry
|
47
|
+
@opacity_dictionary_registry ||= {}
|
48
|
+
end
|
49
|
+
|
50
|
+
def next_opacity_dictionary_id
|
51
|
+
opacity_dictionary_registry.length + 1
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/prawn/graphics.rb
CHANGED
@@ -7,6 +7,10 @@
|
|
7
7
|
# This is free software. Please see the LICENSE and COPYING files for details.
|
8
8
|
|
9
9
|
require "prawn/graphics/color"
|
10
|
+
require "prawn/graphics/dash"
|
11
|
+
require "prawn/graphics/cap_style"
|
12
|
+
require "prawn/graphics/join_style"
|
13
|
+
require "prawn/graphics/transparency"
|
10
14
|
|
11
15
|
module Prawn
|
12
16
|
|
@@ -19,6 +23,10 @@ module Prawn
|
|
19
23
|
module Graphics
|
20
24
|
|
21
25
|
include Color
|
26
|
+
include Dash
|
27
|
+
include CapStyle
|
28
|
+
include JoinStyle
|
29
|
+
include Transparency
|
22
30
|
|
23
31
|
#######################################################################
|
24
32
|
# Low level drawing operations must translate to absolute coords! #
|
@@ -94,7 +102,7 @@ module Prawn
|
|
94
102
|
if width
|
95
103
|
self.line_width = width
|
96
104
|
else
|
97
|
-
@line_width || 1
|
105
|
+
(defined?(@line_width) && @line_width) || 1
|
98
106
|
end
|
99
107
|
end
|
100
108
|
|
data/lib/prawn/images.rb
CHANGED
@@ -154,7 +154,7 @@ module Prawn
|
|
154
154
|
else
|
155
155
|
raise ArgumentError, 'JPG uses an unsupported number of channels'
|
156
156
|
end
|
157
|
-
obj = ref(:Type => :XObject,
|
157
|
+
obj = ref!(:Type => :XObject,
|
158
158
|
:Subtype => :Image,
|
159
159
|
:Filter => :DCTDecode,
|
160
160
|
:ColorSpace => color_space,
|
@@ -202,7 +202,7 @@ module Prawn
|
|
202
202
|
end
|
203
203
|
|
204
204
|
# build the image dict
|
205
|
-
obj = ref(:Type => :XObject,
|
205
|
+
obj = ref!(:Type => :XObject,
|
206
206
|
:Subtype => :Image,
|
207
207
|
:Height => png.height,
|
208
208
|
:Width => png.width,
|
@@ -226,7 +226,7 @@ module Prawn
|
|
226
226
|
obj.data[:ColorSpace] = color
|
227
227
|
else
|
228
228
|
# embed the colour palette in the PDF as a object stream
|
229
|
-
palette_obj = ref(:Length => png.palette.size)
|
229
|
+
palette_obj = ref!(:Length => png.palette.size)
|
230
230
|
palette_obj << png.palette
|
231
231
|
|
232
232
|
# build the color space array for the image
|
@@ -266,7 +266,7 @@ module Prawn
|
|
266
266
|
# channel mixed in with the main image data. The PNG class seperates
|
267
267
|
# it out for us and makes it available via the alpha_channel attribute
|
268
268
|
if png.alpha_channel
|
269
|
-
smask_obj = ref(:Type => :XObject,
|
269
|
+
smask_obj = ref!(:Type => :XObject,
|
270
270
|
:Subtype => :Image,
|
271
271
|
:Height => png.height,
|
272
272
|
:Width => png.width,
|
data/lib/prawn/name_tree.rb
CHANGED
@@ -79,6 +79,7 @@ module Prawn
|
|
79
79
|
split! if children.length > limit
|
80
80
|
else
|
81
81
|
fit = children.detect { |child| child >= value }
|
82
|
+
fit = children.last unless fit
|
82
83
|
fit << value
|
83
84
|
end
|
84
85
|
|
@@ -113,7 +114,7 @@ module Prawn
|
|
113
114
|
|
114
115
|
def new_node(parent=nil)
|
115
116
|
node = Node.new(document, limit, parent)
|
116
|
-
node.ref = document.ref(node)
|
117
|
+
node.ref = document.ref!(node)
|
117
118
|
return node
|
118
119
|
end
|
119
120
|
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# object_store.rb : Implements PDF object repository for Prawn
|
4
|
+
#
|
5
|
+
# Copyright August 2008, Brad Ediger. All Rights Reserved.
|
6
|
+
#
|
7
|
+
# This is free software. Please see the LICENSE and COPYING files for details.
|
8
|
+
module Prawn
|
9
|
+
class ObjectStore
|
10
|
+
include Enumerable
|
11
|
+
|
12
|
+
def initialize(info={})
|
13
|
+
@objects = {}
|
14
|
+
@identifiers = []
|
15
|
+
|
16
|
+
# Create required PDF roots
|
17
|
+
@info = ref(info).identifier
|
18
|
+
@pages = ref(:Type => :Pages, :Count => 0, :Kids => []).identifier
|
19
|
+
@root = ref(:Type => :Catalog, :Pages => pages).identifier
|
20
|
+
end
|
21
|
+
|
22
|
+
def ref(data, &block)
|
23
|
+
push(size + 1, data, &block)
|
24
|
+
end
|
25
|
+
|
26
|
+
%w[info pages root].each do |name|
|
27
|
+
define_method(name) do
|
28
|
+
@objects[instance_variable_get("@#{name}")]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Adds the given reference to the store and returns the reference object.
|
33
|
+
# If the object provided is not a Prawn::Reference, one is created from the
|
34
|
+
# arguments provided.
|
35
|
+
def push(*args, &block)
|
36
|
+
reference = if args.first.is_a?(Prawn::Reference)
|
37
|
+
args.first
|
38
|
+
else
|
39
|
+
Prawn::Reference.new(*args, &block)
|
40
|
+
end
|
41
|
+
@objects[reference.identifier] = reference
|
42
|
+
@identifiers << reference.identifier
|
43
|
+
reference
|
44
|
+
end
|
45
|
+
alias_method :<<, :push
|
46
|
+
|
47
|
+
def each
|
48
|
+
@identifiers.each do |id|
|
49
|
+
yield @objects[id]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def [](id)
|
54
|
+
@objects[id]
|
55
|
+
end
|
56
|
+
|
57
|
+
def size
|
58
|
+
@identifiers.size
|
59
|
+
end
|
60
|
+
alias_method :length, :size
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
data/lib/prawn/pdf_object.rb
CHANGED
@@ -6,6 +6,8 @@
|
|
6
6
|
#
|
7
7
|
# This is free software. Please see the LICENSE and COPYING files for details.
|
8
8
|
|
9
|
+
require 'prawn/byte_string'
|
10
|
+
|
9
11
|
# Top level Module
|
10
12
|
#
|
11
13
|
module Prawn
|
@@ -41,6 +43,8 @@ module Prawn
|
|
41
43
|
obj = obj.strftime("D:%Y%m%d%H%M%S%z").chop.chop + "'00'"
|
42
44
|
obj = obj.gsub(/[\\\n\(\)]/) { |m| "\\#{m}" }
|
43
45
|
"(#{obj})"
|
46
|
+
when Prawn::ByteString
|
47
|
+
"<" << obj.unpack("H*").first << ">"
|
44
48
|
when String
|
45
49
|
obj = "\xFE\xFF" + obj.unpack("U*").pack("n*") unless in_content_stream
|
46
50
|
"<" << obj.unpack("H*").first << ">"
|
data/lib/prawn/reference.rb
CHANGED
@@ -12,15 +12,16 @@ module Prawn
|
|
12
12
|
|
13
13
|
class Reference #:nodoc:
|
14
14
|
|
15
|
-
attr_accessor :gen, :data, :offset
|
16
|
-
attr_reader :identifier
|
15
|
+
attr_accessor :gen, :data, :offset, :stream
|
16
|
+
attr_reader :identifier
|
17
17
|
|
18
18
|
def initialize(id, data, &block)
|
19
19
|
@identifier = id
|
20
|
-
@gen
|
21
|
-
@data
|
20
|
+
@gen = 0
|
21
|
+
@data = data
|
22
22
|
@compressed = false
|
23
|
-
@on_encode
|
23
|
+
@on_encode = block
|
24
|
+
@stream = nil
|
24
25
|
end
|
25
26
|
|
26
27
|
def object
|
@@ -48,6 +49,18 @@ module Prawn
|
|
48
49
|
@data[:Length] ||= @stream.length
|
49
50
|
@compressed = true
|
50
51
|
end
|
52
|
+
|
53
|
+
def compressed?
|
54
|
+
@compressed
|
55
|
+
end
|
56
|
+
|
57
|
+
# Replaces the data and stream with that of other_ref. Preserves compressed
|
58
|
+
# status.
|
59
|
+
def replace(other_ref)
|
60
|
+
@data = other_ref.data
|
61
|
+
@stream = other_ref.stream
|
62
|
+
@compressed = other_ref.compressed?
|
63
|
+
end
|
51
64
|
end
|
52
65
|
|
53
66
|
module_function
|
data/lib/prawn/stamp.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
# stamp.rb : Implements a repeatable stamp
|
4
|
+
#
|
5
|
+
# Copyright October 2009, Daniel Nelson. All Rights Reserved.
|
6
|
+
#
|
7
|
+
# This is free software. Please see the LICENSE and COPYING files for details.
|
8
|
+
#
|
9
|
+
|
10
|
+
module Prawn
|
11
|
+
module Stamp
|
12
|
+
|
13
|
+
def stamp(user_defined_name)
|
14
|
+
stamp_at(user_defined_name, [0, 0])
|
15
|
+
end
|
16
|
+
|
17
|
+
def stamp_at(user_defined_name, point)
|
18
|
+
raise Prawn::Errors::InvalidName if user_defined_name.empty?
|
19
|
+
unless stamp_dictionary_registry[user_defined_name]
|
20
|
+
raise Prawn::Errors::UndefinedObjectName
|
21
|
+
end
|
22
|
+
|
23
|
+
add_content "q"
|
24
|
+
|
25
|
+
x,y = point
|
26
|
+
translate_position = "1 0 0 1 %.3f %.3f cm" % [x, y]
|
27
|
+
add_content translate_position
|
28
|
+
|
29
|
+
dict = stamp_dictionary_registry[user_defined_name]
|
30
|
+
|
31
|
+
stamp_dictionary_name = dict[:stamp_dictionary_name]
|
32
|
+
stamp_dictionary = dict[:stamp_dictionary]
|
33
|
+
|
34
|
+
add_content "/#{stamp_dictionary_name} Do"
|
35
|
+
add_content "Q"
|
36
|
+
|
37
|
+
page_xobjects.merge!(stamp_dictionary_name => stamp_dictionary)
|
38
|
+
end
|
39
|
+
|
40
|
+
def create_stamp(user_defined_name="", &block)
|
41
|
+
raise Prawn::Errors::InvalidName if user_defined_name.empty?
|
42
|
+
|
43
|
+
if stamp_dictionary_registry[user_defined_name]
|
44
|
+
raise Prawn::Errors::NameTaken
|
45
|
+
end
|
46
|
+
|
47
|
+
stamp_dictionary = ref!(:Type => :XObject,
|
48
|
+
:Subtype => :Form,
|
49
|
+
:BBox => [0, 0, bounds.width, bounds.height])
|
50
|
+
|
51
|
+
stamp_dictionary_name = "Stamp#{next_stamp_dictionary_id}"
|
52
|
+
|
53
|
+
stamp_dictionary_registry[user_defined_name] =
|
54
|
+
{ :stamp_dictionary_name => stamp_dictionary_name,
|
55
|
+
:stamp_dictionary => stamp_dictionary}
|
56
|
+
|
57
|
+
|
58
|
+
@active_stamp_stream = ""
|
59
|
+
@active_stamp_dictionary = stamp_dictionary
|
60
|
+
|
61
|
+
yield if block_given?
|
62
|
+
|
63
|
+
stamp_dictionary.data[:Length] = @active_stamp_stream.length + 1
|
64
|
+
stamp_dictionary << @active_stamp_stream
|
65
|
+
|
66
|
+
@active_stamp_stream = nil
|
67
|
+
# The ProcSet needs to be assigned at the page level
|
68
|
+
procs = @active_stamp_dictionary.data[:ProcSet]
|
69
|
+
@active_stamp_dictionary.data.delete(:ProcSet)
|
70
|
+
@active_stamp_dictionary = nil
|
71
|
+
|
72
|
+
# The ProcSet needs to be assigned at the page level
|
73
|
+
proc_set(procs) if procs
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def stamp_dictionary_registry
|
79
|
+
@stamp_dictionary_registry ||= {}
|
80
|
+
end
|
81
|
+
|
82
|
+
def next_stamp_dictionary_id
|
83
|
+
stamp_dictionary_registry.length + 1
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
data/spec/bounding_box_spec.rb
CHANGED
@@ -125,6 +125,15 @@ describe "drawing bounding boxes" do
|
|
125
125
|
|
126
126
|
@pdf.y.should.be.close 458.384, 0.001
|
127
127
|
end
|
128
|
+
|
129
|
+
it "should keep track of the max height the box was stretched to" do
|
130
|
+
box = @pdf.bounding_box(@pdf.bounds.top_left, :width => 100) do
|
131
|
+
@pdf.move_down 100
|
132
|
+
@pdf.move_up 15
|
133
|
+
end
|
134
|
+
|
135
|
+
assert_equal 100, box.height
|
136
|
+
end
|
128
137
|
|
129
138
|
end
|
130
139
|
|
data/spec/document_spec.rb
CHANGED
@@ -11,6 +11,14 @@ describe "The cursor" do
|
|
11
11
|
pdf.y = 300
|
12
12
|
pdf.cursor.should == pdf.y - pdf.bounds.absolute_bottom
|
13
13
|
end
|
14
|
+
|
15
|
+
it "should be able to move relative to the bottom margin" do
|
16
|
+
pdf = Prawn::Document.new
|
17
|
+
pdf.move_cursor_to(10)
|
18
|
+
|
19
|
+
pdf.cursor.should == 10
|
20
|
+
pdf.y.should == pdf.cursor + pdf.bounds.absolute_bottom
|
21
|
+
end
|
14
22
|
end
|
15
23
|
|
16
24
|
describe "when generating a document from a subclass" do
|
@@ -18,7 +26,7 @@ describe "when generating a document from a subclass" do
|
|
18
26
|
custom_document = Class.new(Prawn::Document)
|
19
27
|
custom_document.generate(Tempfile.new("generate_test").path) do |e|
|
20
28
|
e.class.should == custom_document
|
21
|
-
|
29
|
+
e.should.be.kind_of(Prawn::Document)
|
22
30
|
end
|
23
31
|
end
|
24
32
|
end
|
@@ -71,11 +79,11 @@ describe "When ending each page" do
|
|
71
79
|
it "should not compress the page content stream if compression is disabled" do
|
72
80
|
|
73
81
|
pdf = Prawn::Document.new(:compress => false)
|
74
|
-
content_stub = pdf.ref({})
|
82
|
+
content_stub = pdf.ref!({})
|
75
83
|
content_stub.stubs(:compress_stream).returns(true)
|
76
84
|
content_stub.expects(:compress_stream).never
|
77
85
|
|
78
|
-
pdf.instance_variable_set("@page_content", content_stub)
|
86
|
+
pdf.instance_variable_set("@page_content", content_stub.identifier)
|
79
87
|
pdf.text "Hi There" * 20
|
80
88
|
pdf.render
|
81
89
|
end
|
@@ -83,11 +91,11 @@ describe "When ending each page" do
|
|
83
91
|
it "should compress the page content stream if compression is enabled" do
|
84
92
|
|
85
93
|
pdf = Prawn::Document.new(:compress => true)
|
86
|
-
content_stub = pdf.ref({})
|
94
|
+
content_stub = pdf.ref!({})
|
87
95
|
content_stub.stubs(:compress_stream).returns(true)
|
88
96
|
content_stub.expects(:compress_stream).once
|
89
97
|
|
90
|
-
pdf.instance_variable_set("@page_content", content_stub)
|
98
|
+
pdf.instance_variable_set("@page_content", content_stub.identifier)
|
91
99
|
pdf.text "Hi There" * 20
|
92
100
|
pdf.render
|
93
101
|
end
|
@@ -151,6 +159,51 @@ describe "The mask() feature" do
|
|
151
159
|
end
|
152
160
|
end
|
153
161
|
|
162
|
+
describe "The group() feature" do
|
163
|
+
it "should group a simple block on a single page" do
|
164
|
+
pdf = Prawn::Document.new do
|
165
|
+
self.y = 50
|
166
|
+
group do
|
167
|
+
text "Hello"
|
168
|
+
text "World"
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
pages = PDF::Inspector::Page.analyze(pdf.render).pages
|
173
|
+
pages.size.should == 2
|
174
|
+
pages[0][:strings].should == []
|
175
|
+
pages[1][:strings].should == ["Hello", "World"]
|
176
|
+
end
|
177
|
+
|
178
|
+
it "should raise CannotGroup if the content is too tall" do
|
179
|
+
lambda {
|
180
|
+
Prawn::Document.new do
|
181
|
+
group do
|
182
|
+
100.times { text "Too long" }
|
183
|
+
end
|
184
|
+
end.render
|
185
|
+
}.should.raise(Prawn::Document::CannotGroup)
|
186
|
+
end
|
187
|
+
|
188
|
+
it "should group within individual column boxes" do
|
189
|
+
pdf = Prawn::Document.new do
|
190
|
+
# Set up columns with grouped blocks of 0..49. 0 to 49 is slightly short
|
191
|
+
# of the height of one page / column, so each column should get its own
|
192
|
+
# group (every column should start with zero).
|
193
|
+
column_box([0, bounds.top], :width => bounds.width, :columns => 7) do
|
194
|
+
10.times do
|
195
|
+
group { 50.times { |i| text(i.to_s) } }
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
# Second page should start with a 0 because it's a new group.
|
201
|
+
pages = PDF::Inspector::Page.analyze(pdf.render).pages
|
202
|
+
pages.size.should == 2
|
203
|
+
pages[1][:strings].first.should == '0'
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
154
207
|
describe "The render() feature" do
|
155
208
|
if "spec".respond_to?(:encode!)
|
156
209
|
it "should return a 8 bit encoded string on a m17n aware VM" do
|