prawn-core 0.5.1 → 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|