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
@@ -11,6 +11,7 @@ require 'prawn/name_tree'
|
|
11
11
|
module Prawn
|
12
12
|
class Document
|
13
13
|
module Destinations
|
14
|
+
|
14
15
|
# The maximum number of children to fit into a single node in the Dests tree.
|
15
16
|
NAME_TREE_CHILDREN_LIMIT = 20 #:nodoc:
|
16
17
|
|
@@ -19,7 +20,7 @@ module Prawn
|
|
19
20
|
# (For more on name trees, see section 3.8.4 in the PDF spec.)
|
20
21
|
#
|
21
22
|
def dests
|
22
|
-
names.data[:Dests] ||= ref(Prawn::NameTree::Node.new(self, NAME_TREE_CHILDREN_LIMIT))
|
23
|
+
names.data[:Dests] ||= ref!(Prawn::NameTree::Node.new(self, NAME_TREE_CHILDREN_LIMIT))
|
23
24
|
end
|
24
25
|
|
25
26
|
# Adds a new destination to the dests name tree (see #dests). The
|
@@ -27,63 +28,63 @@ module Prawn
|
|
27
28
|
# it is not already one.
|
28
29
|
#
|
29
30
|
def add_dest(name, reference)
|
30
|
-
reference = ref(reference) unless reference.is_a?(Prawn::Reference)
|
31
|
+
reference = ref!(reference) unless reference.is_a?(Prawn::Reference)
|
31
32
|
dests.data.add(name, reference)
|
32
33
|
end
|
33
34
|
|
34
35
|
# Return a Dest specification for a specific location (and optional zoom
|
35
36
|
# level).
|
36
37
|
#
|
37
|
-
def dest_xyz(left, top, zoom=nil, page
|
38
|
+
def dest_xyz(left, top, zoom=nil, page=current_page)
|
38
39
|
[page, :XYZ, left, top, zoom]
|
39
40
|
end
|
40
41
|
|
41
42
|
# Return a Dest specification that will fit the given page into the
|
42
43
|
# viewport.
|
43
44
|
#
|
44
|
-
def dest_fit(page
|
45
|
+
def dest_fit(page=current_page)
|
45
46
|
[page, :Fit]
|
46
47
|
end
|
47
48
|
|
48
49
|
# Return a Dest specification that will fit the given page horizontally
|
49
50
|
# into the viewport, aligned vertically at the given top coordinate.
|
50
51
|
#
|
51
|
-
def dest_fit_horizontally(top, page
|
52
|
+
def dest_fit_horizontally(top, page=current_page)
|
52
53
|
[page, :FitH, top]
|
53
54
|
end
|
54
55
|
|
55
56
|
# Return a Dest specification that will fit the given page vertically
|
56
57
|
# into the viewport, aligned horizontally at the given left coordinate.
|
57
58
|
#
|
58
|
-
def dest_fit_vertically(left, page
|
59
|
+
def dest_fit_vertically(left, page=current_page)
|
59
60
|
[page, :FitV, left]
|
60
61
|
end
|
61
62
|
|
62
63
|
# Return a Dest specification that will fit the given rectangle into the
|
63
64
|
# viewport, for the given page.
|
64
65
|
#
|
65
|
-
def dest_fit_rect(left, bottom, right, top, page
|
66
|
+
def dest_fit_rect(left, bottom, right, top, page=current_page)
|
66
67
|
[page, :FitR, left, bottom, right, top]
|
67
68
|
end
|
68
69
|
|
69
70
|
# Return a Dest specfication that will fit the given page's bounding box
|
70
71
|
# into the viewport.
|
71
72
|
#
|
72
|
-
def dest_fit_bounds(page
|
73
|
+
def dest_fit_bounds(page=current_page)
|
73
74
|
[page, :FitB]
|
74
75
|
end
|
75
76
|
|
76
77
|
# Same as #dest_fit_horizontally, but works on the page's bounding box
|
77
78
|
# instead of the entire page.
|
78
79
|
#
|
79
|
-
def dest_fit_bounds_horizontally(top, page
|
80
|
+
def dest_fit_bounds_horizontally(top, page=current_page)
|
80
81
|
[page, :FitBH, top]
|
81
82
|
end
|
82
83
|
|
83
84
|
# Same as #dest_fit_vertically, but works on the page's bounding box
|
84
85
|
# instead of the entire page.
|
85
86
|
#
|
86
|
-
def dest_fit_bounds_vertically(left, page
|
87
|
+
def dest_fit_bounds_vertically(left, page=current_page)
|
87
88
|
[page, :FitBV, left]
|
88
89
|
end
|
89
90
|
end
|
@@ -17,7 +17,9 @@ module Prawn
|
|
17
17
|
module Internals
|
18
18
|
|
19
19
|
# Creates a new Prawn::Reference and adds it to the Document's object
|
20
|
-
# list. The +data+ argument is anything that Prawn::PdfObject() can convert.
|
20
|
+
# list. The +data+ argument is anything that Prawn::PdfObject() can convert.
|
21
|
+
#
|
22
|
+
# Returns the identifier which points to the reference in the ObjectStore
|
21
23
|
#
|
22
24
|
# If a block is given, it will be invoked just before the object is written
|
23
25
|
# out to the PDF document stream. This allows you to do deferred processing
|
@@ -25,8 +27,40 @@ module Prawn
|
|
25
27
|
# about until the last page of the document is finished).
|
26
28
|
#
|
27
29
|
def ref(data, &block)
|
28
|
-
|
30
|
+
ref!(data, &block).identifier
|
29
31
|
end
|
32
|
+
|
33
|
+
# Like ref, but returns the actual reference instead of its identifier.
|
34
|
+
#
|
35
|
+
# While you can use this to build up nested references within the object
|
36
|
+
# tree, it is recommended to persist only identifiers, and them provide
|
37
|
+
# helper methods to look up the actual references in the ObjectStore
|
38
|
+
# if needed. If you take this approach, Prawn::Document::Snapshot
|
39
|
+
# will probably work with your extension
|
40
|
+
#
|
41
|
+
def ref!(data, &block)
|
42
|
+
@store.ref(data, &block)
|
43
|
+
end
|
44
|
+
|
45
|
+
def page_content
|
46
|
+
@store[@page_content]
|
47
|
+
end
|
48
|
+
|
49
|
+
def current_page
|
50
|
+
@store[@current_page]
|
51
|
+
end
|
52
|
+
|
53
|
+
# Grabs the reference for the current page content
|
54
|
+
#
|
55
|
+
def page_content
|
56
|
+
@active_stamp_stream || @store[@page_content]
|
57
|
+
end
|
58
|
+
|
59
|
+
# Grabs the reference for the current page
|
60
|
+
#
|
61
|
+
def current_page
|
62
|
+
@active_stamp_dictionary || @store[@current_page]
|
63
|
+
end
|
30
64
|
|
31
65
|
# Appends a raw string to the current page content.
|
32
66
|
#
|
@@ -37,20 +71,20 @@ module Prawn
|
|
37
71
|
# pdf.add_content("S") # stroke
|
38
72
|
#
|
39
73
|
def add_content(str)
|
40
|
-
|
74
|
+
page_content << str << "\n"
|
41
75
|
end
|
42
76
|
|
43
77
|
# Add a new type to the current pages ProcSet
|
44
78
|
#
|
45
79
|
def proc_set(*types)
|
46
|
-
|
47
|
-
|
80
|
+
current_page.data[:ProcSet] ||= ref!([])
|
81
|
+
current_page.data[:ProcSet].data |= types
|
48
82
|
end
|
49
83
|
|
50
84
|
# The Resources dictionary for the current page
|
51
85
|
#
|
52
86
|
def page_resources
|
53
|
-
|
87
|
+
current_page.data[:Resources] ||= {}
|
54
88
|
end
|
55
89
|
|
56
90
|
# The Font dictionary for the current page
|
@@ -63,24 +97,28 @@ module Prawn
|
|
63
97
|
#
|
64
98
|
def page_xobjects
|
65
99
|
page_resources[:XObject] ||= {}
|
66
|
-
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def page_ext_gstates
|
103
|
+
page_resources[:ExtGState] ||= {}
|
104
|
+
end
|
67
105
|
|
68
106
|
# The Name dictionary (PDF spec 3.6.3) for this document. It is
|
69
107
|
# lazily initialized, so that documents that do not need a name
|
70
108
|
# dictionary do not incur the additional overhead.
|
71
109
|
#
|
72
110
|
def names
|
73
|
-
@root.data[:Names] ||= ref(:Type => :Names)
|
111
|
+
@store.root.data[:Names] ||= ref!(:Type => :Names)
|
74
112
|
end
|
75
113
|
|
76
114
|
private
|
77
115
|
|
78
116
|
def finish_page_content
|
79
|
-
@header.draw if @header
|
80
|
-
@footer.draw if @footer
|
117
|
+
@header.draw if defined?(@header) and @header
|
118
|
+
@footer.draw if defined?(@footer) and @footer
|
81
119
|
add_content "Q"
|
82
|
-
|
83
|
-
|
120
|
+
page_content.compress_stream if compression_enabled?
|
121
|
+
page_content.data[:Length] = page_content.stream.size
|
84
122
|
end
|
85
123
|
|
86
124
|
# raise the PDF version of the file we're going to generate.
|
@@ -91,39 +129,44 @@ module Prawn
|
|
91
129
|
end
|
92
130
|
|
93
131
|
# Write out the PDF Header, as per spec 3.4.1
|
132
|
+
#
|
94
133
|
def render_header(output)
|
95
134
|
# pdf version
|
96
135
|
output << "%PDF-#{@version}\n"
|
97
136
|
|
98
137
|
# 4 binary chars, as recommended by the spec
|
99
|
-
output << "
|
138
|
+
output << "%\xFF\xFF\xFF\xFF\n"
|
100
139
|
end
|
101
140
|
|
102
141
|
# Write out the PDF Body, as per spec 3.4.2
|
142
|
+
#
|
103
143
|
def render_body(output)
|
104
|
-
@
|
144
|
+
@store.each do |ref|
|
105
145
|
ref.offset = output.size
|
106
146
|
output << ref.object
|
107
147
|
end
|
108
148
|
end
|
109
149
|
|
110
150
|
# Write out the PDF Cross Reference Table, as per spec 3.4.3
|
151
|
+
#
|
111
152
|
def render_xref(output)
|
112
153
|
@xref_offset = output.size
|
113
154
|
output << "xref\n"
|
114
|
-
output << "0 #{@
|
155
|
+
output << "0 #{@store.size + 1}\n"
|
115
156
|
output << "0000000000 65535 f \n"
|
116
|
-
@
|
157
|
+
@store.each do |ref|
|
117
158
|
output.printf("%010d", ref.offset)
|
118
159
|
output << " 00000 n \n"
|
119
160
|
end
|
120
161
|
end
|
121
162
|
|
122
163
|
# Write out the PDF Trailer, as per spec 3.4.4
|
164
|
+
#
|
123
165
|
def render_trailer(output)
|
124
|
-
trailer_hash = {:Size => @
|
125
|
-
:Root => @root,
|
126
|
-
:Info => @info}
|
166
|
+
trailer_hash = {:Size => @store.size + 1,
|
167
|
+
:Root => @store.root,
|
168
|
+
:Info => @store.info}
|
169
|
+
trailer_hash.merge!(@trailer) if @trailer
|
127
170
|
|
128
171
|
output << "trailer\n"
|
129
172
|
output << Prawn::PdfObject(trailer_hash) << "\n"
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# snapshot.rb : Implements transactional rendering 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
|
+
require 'delegate'
|
9
|
+
|
10
|
+
module Prawn
|
11
|
+
class Document
|
12
|
+
module Snapshot
|
13
|
+
|
14
|
+
RollbackTransaction = Class.new(StandardError)
|
15
|
+
|
16
|
+
# Call this within a +transaction+ block to roll back the transaction and
|
17
|
+
# prevent any of its data from being rendered. You must reset the
|
18
|
+
# y-position yourself if you have performed any drawing operations that
|
19
|
+
# modify it.
|
20
|
+
def rollback
|
21
|
+
raise RollbackTransaction
|
22
|
+
end
|
23
|
+
|
24
|
+
# Run a block of drawing operations, to be completed atomically. If
|
25
|
+
# +rollback+ is called or a RollbackTransaction exception is raised
|
26
|
+
# inside the block, all actions taken inside the block will be rolled
|
27
|
+
# back (with the exception of y-position, which you must restore
|
28
|
+
# yourself).
|
29
|
+
#
|
30
|
+
# Returns true on success, or false if the transaction was rolled back.
|
31
|
+
def transaction
|
32
|
+
snap = take_snapshot
|
33
|
+
yield
|
34
|
+
true
|
35
|
+
rescue RollbackTransaction
|
36
|
+
restore_snapshot(snap)
|
37
|
+
false
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
# Takes a current snapshot of the document's state, sufficient to
|
43
|
+
# reconstruct it after it was amended.
|
44
|
+
def take_snapshot
|
45
|
+
{:page_content => Marshal.load(Marshal.dump(page_content)),
|
46
|
+
:current_page => Marshal.load(Marshal.dump(current_page)),
|
47
|
+
:page_kids => @store.pages.data[:Kids].map{|kid| kid.identifier},
|
48
|
+
:dests => Marshal.load(Marshal.dump(names.data[:Dests]))}
|
49
|
+
end
|
50
|
+
|
51
|
+
# Rolls the page state back to the state of the given snapshot.
|
52
|
+
def restore_snapshot(shot)
|
53
|
+
# Because these objects are referenced by identifier from the Pages
|
54
|
+
# dictionary, we can't just restore them over the current refs in
|
55
|
+
# page_content and current_page. We have to restore them over the old
|
56
|
+
# ones.
|
57
|
+
@page_content = shot[:page_content].identifier
|
58
|
+
page_content.replace shot[:page_content]
|
59
|
+
|
60
|
+
@current_page = shot[:current_page].identifier
|
61
|
+
current_page.replace shot[:current_page]
|
62
|
+
|
63
|
+
@store.pages.data[:Kids] = shot[:page_kids].map{|id| @store[id]}
|
64
|
+
@store.pages.data[:Count] = shot[:page_kids].size
|
65
|
+
|
66
|
+
names.data[:Dests] = shot[:dests]
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -23,6 +23,8 @@ module Prawn
|
|
23
23
|
end
|
24
24
|
|
25
25
|
module Text
|
26
|
+
# FIXME: requires documentation
|
27
|
+
#
|
26
28
|
class Box #:nodoc:
|
27
29
|
def initialize(text,options={})
|
28
30
|
@document = options[:for]
|
@@ -54,6 +56,8 @@ module Prawn
|
|
54
56
|
unless @overflow == :expand
|
55
57
|
@document.y = y + @document.bounds.absolute_bottom - @height
|
56
58
|
end
|
59
|
+
|
60
|
+
@excess_text
|
57
61
|
end
|
58
62
|
|
59
63
|
private
|
@@ -71,6 +75,9 @@ module Prawn
|
|
71
75
|
when :ellipses
|
72
76
|
@text[-3..-1] = "..." if @text.size > 3
|
73
77
|
end
|
78
|
+
@excess_text = @document.naive_unwrap(lines[max_lines..-1].join)
|
79
|
+
else
|
80
|
+
@excess_text = ""
|
74
81
|
end
|
75
82
|
end
|
76
83
|
|
data/lib/prawn/document/text.rb
CHANGED
@@ -43,8 +43,10 @@ module Prawn
|
|
43
43
|
#
|
44
44
|
# === Text Positioning Details:
|
45
45
|
#
|
46
|
-
# When using the :at parameter, Prawn will position your text by
|
47
|
-
# baseline, and flow along a single line.
|
46
|
+
# When using the :at parameter, Prawn will position your text by the
|
47
|
+
# left-most edge of its baseline, and flow along a single line. (This
|
48
|
+
# means that :align will not work)
|
49
|
+
#
|
48
50
|
#
|
49
51
|
# Otherwise, the text is positioned at font.ascender below the baseline,
|
50
52
|
# making it easy to use this method within bounding boxes and spans.
|
@@ -79,6 +81,11 @@ module Prawn
|
|
79
81
|
font.normalize_encoding!(text) unless @skip_encoding
|
80
82
|
|
81
83
|
if options[:at]
|
84
|
+
|
85
|
+
if options[:align]
|
86
|
+
raise ArgumentError, "The :align option does not work with :at"
|
87
|
+
end
|
88
|
+
|
82
89
|
x,y = translate(options[:at])
|
83
90
|
font_size(options[:size]) { add_text_content(text,x,y,options) }
|
84
91
|
else
|
data/lib/prawn/document.rb
CHANGED
@@ -15,6 +15,7 @@ require "prawn/document/span"
|
|
15
15
|
require "prawn/document/text"
|
16
16
|
require "prawn/document/annotations"
|
17
17
|
require "prawn/document/destinations"
|
18
|
+
require "prawn/document/snapshot"
|
18
19
|
|
19
20
|
module Prawn
|
20
21
|
|
@@ -58,13 +59,20 @@ module Prawn
|
|
58
59
|
include Internals
|
59
60
|
include Annotations
|
60
61
|
include Destinations
|
62
|
+
include Snapshot
|
61
63
|
include Prawn::Graphics
|
62
64
|
include Prawn::Images
|
65
|
+
include Prawn::Stamp
|
63
66
|
|
64
|
-
attr_accessor :
|
65
|
-
attr_reader :margins, :page_size, :page_layout
|
67
|
+
attr_accessor :margin_box
|
68
|
+
attr_reader :margins, :page_size, :page_layout, :y
|
66
69
|
attr_writer :font_size
|
67
70
|
|
71
|
+
|
72
|
+
def self.extensions
|
73
|
+
@extensions ||= []
|
74
|
+
end
|
75
|
+
|
68
76
|
# Creates and renders a PDF document.
|
69
77
|
#
|
70
78
|
# When using the implicit block form, Prawn will evaluate the block
|
@@ -102,6 +110,7 @@ module Prawn
|
|
102
110
|
#
|
103
111
|
# <tt>:page_size</tt>:: One of the Document::PageGeometry sizes [LETTER]
|
104
112
|
# <tt>:page_layout</tt>:: Either <tt>:portrait</tt> or <tt>:landscape</tt>
|
113
|
+
# <tt>:margin</tt>:: Sets the margin on all sides in points [0.5 inch]
|
105
114
|
# <tt>:left_margin</tt>:: Sets the left margin in points [0.5 inch]
|
106
115
|
# <tt>:right_margin</tt>:: Sets the right margin in points [0.5 inch]
|
107
116
|
# <tt>:top_margin</tt>:: Sets the top margin in points [0.5 inch]
|
@@ -111,7 +120,19 @@ module Prawn
|
|
111
120
|
# <tt>:background</tt>:: An image path to be used as background on all pages [nil]
|
112
121
|
# <tt>:info</tt>:: Generic hash allowing for custom metadata properties [nil]
|
113
122
|
# <tt>:text_options</tt>:: A set of default options to be handed to text(). Be careful with this.
|
114
|
-
|
123
|
+
#
|
124
|
+
# Setting e.g. the :margin to 100 points and the :left_margin to 50 will result in margins
|
125
|
+
# of 100 points on every side except for the left, where it will be 50.
|
126
|
+
#
|
127
|
+
# The :margin can also be an array much like CSS shorthand:
|
128
|
+
#
|
129
|
+
# # Top and bottom are 20, left and right are 100.
|
130
|
+
# :margin => [20, 100]
|
131
|
+
# # Top is 50, left and right are 100, bottom is 20.
|
132
|
+
# :margin => [50, 100, 20]
|
133
|
+
# # Top is 10, right is 20, bottom is 30, left is 40.
|
134
|
+
# :margin => [10, 20, 30, 40]
|
135
|
+
#
|
115
136
|
# Additionally, :page_size can be specified as a simple two value array giving
|
116
137
|
# the width and height of the document you need in PDF Points.
|
117
138
|
#
|
@@ -130,9 +151,11 @@ module Prawn
|
|
130
151
|
# pdf = Prawn::Document.new(:background => "#{Prawn::BASEDIR}/data/images/pigs.jpg")
|
131
152
|
#
|
132
153
|
def initialize(options={},&block)
|
133
|
-
Prawn.verify_options [:page_size, :page_layout, :left_margin,
|
154
|
+
Prawn.verify_options [:page_size, :page_layout, :margin, :left_margin,
|
134
155
|
:right_margin, :top_margin, :bottom_margin, :skip_page_creation,
|
135
156
|
:compress, :skip_encoding, :text_options, :background, :info], options
|
157
|
+
|
158
|
+
self.class.extensions.reverse_each { |e| extend e }
|
136
159
|
|
137
160
|
options[:info] ||= {}
|
138
161
|
options[:info][:Creator] ||= "Prawn"
|
@@ -145,23 +168,28 @@ module Prawn
|
|
145
168
|
end
|
146
169
|
|
147
170
|
@version = 1.3
|
148
|
-
@
|
149
|
-
@
|
150
|
-
|
151
|
-
@
|
152
|
-
@
|
153
|
-
@
|
154
|
-
@
|
155
|
-
@
|
156
|
-
@
|
157
|
-
@
|
171
|
+
@store = ObjectStore.new(options[:info])
|
172
|
+
@trailer = {}
|
173
|
+
|
174
|
+
@page_size = options[:page_size] || "LETTER"
|
175
|
+
@page_layout = options[:page_layout] || :portrait
|
176
|
+
@compress = options[:compress] || false
|
177
|
+
@skip_encoding = options[:skip_encoding]
|
178
|
+
@background = options[:background]
|
179
|
+
@font_size = 12
|
180
|
+
@page_content = nil
|
181
|
+
@bounding_box = nil
|
182
|
+
@margin_box = nil
|
158
183
|
|
159
184
|
@text_options = options[:text_options] || {}
|
185
|
+
|
186
|
+
apply_margin_option(options) if options[:margin]
|
160
187
|
|
161
|
-
|
162
|
-
|
163
|
-
:
|
164
|
-
:
|
188
|
+
default_margin = 36 # 0.5 inch
|
189
|
+
@margins = { :left => options[:left_margin] || default_margin,
|
190
|
+
:right => options[:right_margin] || default_margin,
|
191
|
+
:top => options[:top_margin] || default_margin,
|
192
|
+
:bottom => options[:bottom_margin] || default_margin }
|
165
193
|
|
166
194
|
generate_margin_box
|
167
195
|
|
@@ -182,22 +210,25 @@ module Prawn
|
|
182
210
|
# pdf.start_new_page #=> Starts new page keeping current values
|
183
211
|
# pdf.start_new_page(:size => "LEGAL", :layout => :landscape)
|
184
212
|
# pdf.start_new_page(:left_margin => 50, :right_margin => 50)
|
213
|
+
# pdf.start_new_page(:margin => 100)
|
185
214
|
#
|
186
215
|
def start_new_page(options = {})
|
187
216
|
@page_size = options[:size] if options[:size]
|
188
217
|
@page_layout = options[:layout] if options[:layout]
|
218
|
+
|
219
|
+
apply_margin_option(options) if options[:margin]
|
189
220
|
|
190
221
|
[:left,:right,:top,:bottom].each do |side|
|
191
|
-
if options[:"#{side}_margin"]
|
192
|
-
@margins[side] =
|
222
|
+
if margin = options[:"#{side}_margin"]
|
223
|
+
@margins[side] = margin
|
193
224
|
end
|
194
225
|
end
|
195
226
|
|
196
227
|
finish_page_content if @page_content
|
197
228
|
build_new_page_content
|
198
229
|
|
199
|
-
@pages.data[:Kids] <<
|
200
|
-
@pages.data[:Count] += 1
|
230
|
+
@store.pages.data[:Kids] << current_page
|
231
|
+
@store.pages.data[:Count] += 1
|
201
232
|
|
202
233
|
add_content "q"
|
203
234
|
|
@@ -214,7 +245,12 @@ module Prawn
|
|
214
245
|
# pdf.page_count #=> 4
|
215
246
|
#
|
216
247
|
def page_count
|
217
|
-
@pages.data[:Count]
|
248
|
+
@store.pages.data[:Count]
|
249
|
+
end
|
250
|
+
|
251
|
+
def y=(new_y)
|
252
|
+
@y = new_y
|
253
|
+
bounds.update_height
|
218
254
|
end
|
219
255
|
|
220
256
|
# The current y drawing position relative to the innermost bounding box,
|
@@ -224,6 +260,13 @@ module Prawn
|
|
224
260
|
y - bounds.absolute_bottom
|
225
261
|
end
|
226
262
|
|
263
|
+
|
264
|
+
# Moves to the specified y position in relative terms to the bottom margin.
|
265
|
+
#
|
266
|
+
def move_cursor_to(new_y)
|
267
|
+
self.y = new_y + bounds.absolute_bottom
|
268
|
+
end
|
269
|
+
|
227
270
|
# Renders the PDF document to string, useful for example in a Rails
|
228
271
|
# application where you want to stream out the PDF to a web browser:
|
229
272
|
#
|
@@ -376,6 +419,67 @@ module Prawn
|
|
376
419
|
fields.each { |f| send("#{f}=", stored[f]) }
|
377
420
|
end
|
378
421
|
|
422
|
+
# Raised if group() is called with a block that is too big to be
|
423
|
+
# rendered in the current context.
|
424
|
+
#
|
425
|
+
CannotGroup = Class.new(StandardError)
|
426
|
+
|
427
|
+
# Attempts to group the given block vertically within the current context.
|
428
|
+
# First attempts to render it in the current position on the current page.
|
429
|
+
# If that attempt overflows, it is tried anew after starting a new context
|
430
|
+
# (page or column).
|
431
|
+
#
|
432
|
+
# Raises CannotGroup if the provided content is too large to fit alone in
|
433
|
+
# the current page or column.
|
434
|
+
#
|
435
|
+
def group(second_attempt=false)
|
436
|
+
old_bounding_box = @bounding_box
|
437
|
+
@bounding_box = SimpleDelegator.new(@bounding_box)
|
438
|
+
|
439
|
+
def @bounding_box.move_past_bottom
|
440
|
+
raise RollbackTransaction
|
441
|
+
end
|
442
|
+
|
443
|
+
success = transaction { yield }
|
444
|
+
|
445
|
+
unless success
|
446
|
+
raise CannotGroup if second_attempt
|
447
|
+
old_bounding_box.move_past_bottom
|
448
|
+
group(second_attempt=true) { yield }
|
449
|
+
end
|
450
|
+
|
451
|
+
@bounding_box = old_bounding_box
|
452
|
+
end
|
453
|
+
|
454
|
+
# Specify a template for page numbering. This should be called
|
455
|
+
# towards the end of document creation, after all your content is already in
|
456
|
+
# place. In your template string, <page> refers to the current page, and
|
457
|
+
# <total> refers to the total amount of pages in the doucment.
|
458
|
+
#
|
459
|
+
# Example:
|
460
|
+
#
|
461
|
+
# Prawn::Document.generate("page_with_numbering.pdf") do
|
462
|
+
# text "Hai"
|
463
|
+
# start_new_page
|
464
|
+
# text "bai"
|
465
|
+
# start_new_page
|
466
|
+
# text "-- Hai again"
|
467
|
+
# number_pages "<page> in a total of <total>", [bounds.right - 50, 0]
|
468
|
+
# end
|
469
|
+
def number_pages(string, position)
|
470
|
+
page_count.times do |i|
|
471
|
+
go_to_page(i)
|
472
|
+
str = string.gsub("<page>","#{i+1}").gsub("<total>","#{page_count}")
|
473
|
+
text str, :at => position
|
474
|
+
end
|
475
|
+
end
|
476
|
+
|
477
|
+
def go_to_page(k) # :nodoc:
|
478
|
+
jump_to = @store.pages.data[:Kids][k]
|
479
|
+
@current_page = jump_to.identifier
|
480
|
+
@page_content = jump_to.data[:Contents].identifier
|
481
|
+
end
|
482
|
+
|
379
483
|
# Returns true if content streams will be compressed before rendering,
|
380
484
|
# false otherwise
|
381
485
|
#
|
@@ -392,10 +496,11 @@ module Prawn
|
|
392
496
|
@page_content = ref(:Length => 0)
|
393
497
|
|
394
498
|
@current_page = ref(:Type => :Page,
|
395
|
-
:Parent => @pages,
|
499
|
+
:Parent => @store.pages,
|
396
500
|
:MediaBox => page_dimensions,
|
397
|
-
:Contents =>
|
501
|
+
:Contents => page_content)
|
398
502
|
update_colors
|
503
|
+
undash if dashed?
|
399
504
|
end
|
400
505
|
|
401
506
|
def generate_margin_box
|
@@ -413,6 +518,17 @@ module Prawn
|
|
413
518
|
# when the bounding box exits.
|
414
519
|
@bounding_box = @margin_box if old_margin_box == @bounding_box
|
415
520
|
end
|
521
|
+
|
522
|
+
def apply_margin_option(options)
|
523
|
+
# Treat :margin as CSS shorthand with 1-4 values.
|
524
|
+
margin = Array(options[:margin])
|
525
|
+
positions = { 4 => [0,1,2,3], 3 => [0,1,2,1],
|
526
|
+
2 => [0,1,0,1], 1 => [0,0,0,0] }[margin.length]
|
527
|
+
|
528
|
+
[:top, :right, :bottom, :left].zip(positions).each do |p,i|
|
529
|
+
options[:"#{p}_margin"] ||= margin[i]
|
530
|
+
end
|
531
|
+
end
|
416
532
|
|
417
533
|
end
|
418
534
|
end
|
data/lib/prawn/errors.rb
CHANGED
@@ -45,5 +45,17 @@ module Prawn
|
|
45
45
|
# type. This can either a completely unsupported format, or a dialect of a
|
46
46
|
# supported format (ie. some types of PNG)
|
47
47
|
UnsupportedImageType = Class.new(StandardError)
|
48
|
+
|
49
|
+
# This error is raised when a named element has alredy been
|
50
|
+
# created. For example, in the stamp module, stamps must have
|
51
|
+
# unique names within a document
|
52
|
+
NameTaken = Class.new(StandardError)
|
53
|
+
|
54
|
+
# This error is raised when a name is not a valid format
|
55
|
+
InvalidName = Class.new(StandardError)
|
56
|
+
|
57
|
+
# This error is raised when an object is attempted to be
|
58
|
+
# referenced by name, but no such name is associated with an object
|
59
|
+
UndefinedObjectName = Class.new(StandardError)
|
48
60
|
end
|
49
61
|
end
|