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
@@ -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
|