prawn-core 0.7.2 → 0.8.4
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +1 -1
- data/examples/general/background.rb +1 -1
- data/examples/general/measurement_units.rb +2 -2
- data/examples/general/outlines.rb +50 -0
- data/examples/general/repeaters.rb +11 -7
- data/examples/general/stamp.rb +6 -6
- data/examples/graphics/basic_images.rb +1 -1
- data/examples/graphics/curves.rb +1 -1
- data/examples/graphics/rounded_polygons.rb +19 -0
- data/examples/graphics/rounded_rectangle.rb +20 -0
- data/examples/graphics/transformations.rb +52 -0
- data/examples/m17n/win_ansi_charset.rb +1 -1
- data/examples/text/font_calculations.rb +3 -3
- data/examples/text/indent_paragraphs.rb +18 -0
- data/examples/text/kerning.rb +4 -4
- data/examples/text/rotated.rb +98 -0
- data/examples/text/simple_text.rb +3 -3
- data/examples/text/simple_text_ttf.rb +1 -1
- data/lib/prawn/byte_string.rb +1 -0
- data/lib/prawn/core.rb +12 -5
- data/lib/prawn/core/object_store.rb +99 -0
- data/lib/prawn/core/page.rb +96 -0
- data/lib/prawn/core/text.rb +75 -0
- data/lib/prawn/document.rb +71 -78
- data/lib/prawn/document/annotations.rb +2 -2
- data/lib/prawn/document/bounding_box.rb +19 -9
- data/lib/prawn/document/column_box.rb +13 -12
- data/lib/prawn/document/graphics_state.rb +49 -0
- data/lib/prawn/document/internals.rb +5 -40
- data/lib/prawn/document/page_geometry.rb +1 -18
- data/lib/prawn/document/snapshot.rb +12 -7
- data/lib/prawn/errors.rb +18 -0
- data/lib/prawn/font.rb +4 -2
- data/lib/prawn/font/afm.rb +8 -0
- data/lib/prawn/font/dfont.rb +12 -4
- data/lib/prawn/font/ttf.rb +9 -0
- data/lib/prawn/graphics.rb +66 -9
- data/lib/prawn/graphics/color.rb +1 -1
- data/lib/prawn/graphics/transformation.rb +156 -0
- data/lib/prawn/graphics/transparency.rb +3 -7
- data/lib/prawn/images.rb +4 -3
- data/lib/prawn/images/png.rb +2 -2
- data/lib/prawn/outline.rb +278 -0
- data/lib/prawn/pdf_object.rb +5 -3
- data/lib/prawn/repeater.rb +25 -13
- data/lib/prawn/stamp.rb +6 -29
- data/lib/prawn/text.rb +139 -121
- data/lib/prawn/text/box.rb +168 -102
- data/spec/bounding_box_spec.rb +7 -2
- data/spec/document_spec.rb +7 -5
- data/spec/font_spec.rb +9 -1
- data/spec/graphics_spec.rb +229 -0
- data/spec/object_store_spec.rb +5 -5
- data/spec/outline_spec.rb +229 -0
- data/spec/repeater_spec.rb +18 -1
- data/spec/snapshot_spec.rb +7 -7
- data/spec/span_spec.rb +6 -2
- data/spec/spec_helper.rb +7 -3
- data/spec/stamp_spec.rb +13 -0
- data/spec/text_at_spec.rb +119 -0
- data/spec/text_box_spec.rb +257 -4
- data/spec/text_spec.rb +278 -180
- data/vendor/pdf-inspector/lib/pdf/inspector/graphics.rb +12 -0
- metadata +16 -3
- data/lib/prawn/object_store.rb +0 -92
data/lib/prawn/graphics.rb
CHANGED
@@ -11,6 +11,7 @@ require "prawn/graphics/dash"
|
|
11
11
|
require "prawn/graphics/cap_style"
|
12
12
|
require "prawn/graphics/join_style"
|
13
13
|
require "prawn/graphics/transparency"
|
14
|
+
require "prawn/graphics/transformation"
|
14
15
|
|
15
16
|
module Prawn
|
16
17
|
|
@@ -27,9 +28,10 @@ module Prawn
|
|
27
28
|
include CapStyle
|
28
29
|
include JoinStyle
|
29
30
|
include Transparency
|
31
|
+
include Transformation
|
30
32
|
|
31
33
|
#######################################################################
|
32
|
-
# Low level drawing operations must
|
34
|
+
# Low level drawing operations must map the point to absolute coords! #
|
33
35
|
#######################################################################
|
34
36
|
|
35
37
|
# Moves the drawing position to a given point. The point can be
|
@@ -39,7 +41,7 @@ module Prawn
|
|
39
41
|
# pdf.move_to(100,50)
|
40
42
|
#
|
41
43
|
def move_to(*point)
|
42
|
-
x,y =
|
44
|
+
x,y = map_to_absolute(point)
|
43
45
|
add_content("%.3f %.3f m" % [ x, y ])
|
44
46
|
end
|
45
47
|
|
@@ -50,7 +52,7 @@ module Prawn
|
|
50
52
|
# pdf.line_to(50,50)
|
51
53
|
#
|
52
54
|
def line_to(*point)
|
53
|
-
x,y =
|
55
|
+
x,y = map_to_absolute(point)
|
54
56
|
add_content("%.3f %.3f l" % [ x, y ])
|
55
57
|
end
|
56
58
|
|
@@ -64,7 +66,7 @@ module Prawn
|
|
64
66
|
"Bounding points for bezier curve must be specified "+
|
65
67
|
"as :bounds => [[x1,y1],[x2,y2]]"
|
66
68
|
|
67
|
-
curve_points = (options[:bounds] << dest).map { |e|
|
69
|
+
curve_points = (options[:bounds] << dest).map { |e| map_to_absolute(e) }
|
68
70
|
add_content("%.3f %.3f %.3f %.3f %.3f %.3f c" %
|
69
71
|
curve_points.flatten )
|
70
72
|
end
|
@@ -75,9 +77,21 @@ module Prawn
|
|
75
77
|
# pdf.rectangle [300,300], 100, 200
|
76
78
|
#
|
77
79
|
def rectangle(point,width,height)
|
78
|
-
x,y =
|
80
|
+
x,y = map_to_absolute(point)
|
79
81
|
add_content("%.3f %.3f %.3f %.3f re" % [ x, y - height, width, height ])
|
80
82
|
end
|
83
|
+
|
84
|
+
# Draws a rounded rectangle given <tt>point</tt>, <tt>width</tt> and
|
85
|
+
# <tt>height</tt> and <tt>radius</tt> for the rounded corner. The rectangle
|
86
|
+
# is bounded by its upper-left corner.
|
87
|
+
#
|
88
|
+
# pdf.rounded_rectangle [300,300], 100, 200, 10
|
89
|
+
#
|
90
|
+
def rounded_rectangle(point,width,height,radius)
|
91
|
+
x, y = point
|
92
|
+
rounded_polygon(radius, point, [x + width, y], [x + width, y - height], [x, y - height])
|
93
|
+
end
|
94
|
+
|
81
95
|
|
82
96
|
###########################################################
|
83
97
|
# Higher level functions: May use relative coords #
|
@@ -221,6 +235,34 @@ module Prawn
|
|
221
235
|
line_to(*point)
|
222
236
|
end
|
223
237
|
end
|
238
|
+
|
239
|
+
# Draws a rounded polygon from specified points using the radius to define bezier curves
|
240
|
+
#
|
241
|
+
# # draws a rounded filled in polygon
|
242
|
+
# pdf.fill_and_stroke_rounded_polygon(10, [100, 250], [200, 300], [300, 250],
|
243
|
+
# [300, 150], [200, 100], [100, 150])
|
244
|
+
def rounded_polygon(radius, *points)
|
245
|
+
move_to point_on_line(radius, points[1], points[0])
|
246
|
+
sides = points.size
|
247
|
+
points << points[0] << points[1]
|
248
|
+
(sides).times do |i|
|
249
|
+
rounded_vertex(radius, points[i], points[i + 1], points[i + 2])
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
|
254
|
+
# Creates a rounded vertex for a line segment used for building a rounded polygon
|
255
|
+
# requires a radius to define bezier curve and three points. The first two points define
|
256
|
+
# the line segment and the third point helps define the curve for the vertex.
|
257
|
+
def rounded_vertex(radius, *points)
|
258
|
+
x0,y0,x1,y1,x2,y2 = points.flatten
|
259
|
+
radial_point_1 = point_on_line(radius, points[0], points[1])
|
260
|
+
bezier_point_1 = point_on_line((radius - radius*KAPPA), points[0], points[1] )
|
261
|
+
radial_point_2 = point_on_line(radius, points[2], points[1])
|
262
|
+
bezier_point_2 = point_on_line((radius - radius*KAPPA), points[2], points[1])
|
263
|
+
line_to(radial_point_1)
|
264
|
+
curve_to(radial_point_2, :bounds => [bezier_point_1, bezier_point_2])
|
265
|
+
end
|
224
266
|
|
225
267
|
# Strokes and closes the current path. See Graphic::Color for color details
|
226
268
|
#
|
@@ -248,17 +290,32 @@ module Prawn
|
|
248
290
|
yield if block_given?
|
249
291
|
add_content "b"
|
250
292
|
end
|
251
|
-
|
293
|
+
|
252
294
|
private
|
253
295
|
|
254
|
-
def
|
296
|
+
def map_to_absolute(*point)
|
255
297
|
x,y = point.flatten
|
256
298
|
[@bounding_box.absolute_left + x, @bounding_box.absolute_bottom + y]
|
257
299
|
end
|
258
300
|
|
259
|
-
def
|
260
|
-
point.replace(
|
301
|
+
def map_to_absolute!(point)
|
302
|
+
point.replace(map_to_absolute(point))
|
261
303
|
end
|
262
304
|
|
305
|
+
def degree_to_rad(angle)
|
306
|
+
angle * Math::PI / 180
|
307
|
+
end
|
308
|
+
|
309
|
+
# Returns the coordinates for a point on a line that is a given distance away from the second
|
310
|
+
# point defining the line segement
|
311
|
+
def point_on_line(distance_from_end, *points)
|
312
|
+
x0,y0,x1,y1 = points.flatten
|
313
|
+
length = Math.sqrt((x1 - x0)**2 + (y1 - y0)**2)
|
314
|
+
p = (length - distance_from_end) / length
|
315
|
+
xr = x0 + p*(x1 - x0)
|
316
|
+
yr = y0 + p*(y1 - y0)
|
317
|
+
[xr, yr]
|
318
|
+
end
|
319
|
+
|
263
320
|
end
|
264
321
|
end
|
data/lib/prawn/graphics/color.rb
CHANGED
@@ -0,0 +1,156 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
# transformation.rb: Implements rotate, translate, skew, scale and a generic
|
4
|
+
# transformation_matrix
|
5
|
+
#
|
6
|
+
# Copyright January 2010, Michael Witrant. All Rights Reserved.
|
7
|
+
#
|
8
|
+
# This is free software. Please see the LICENSE and COPYING files for details.
|
9
|
+
|
10
|
+
module Prawn
|
11
|
+
module Graphics
|
12
|
+
module Transformation
|
13
|
+
|
14
|
+
# Rotate the user space. If a block is not provided, then you must save
|
15
|
+
# and restore the graphics state yourself.
|
16
|
+
#
|
17
|
+
# == Options
|
18
|
+
# <tt>:origin</tt>:: <tt>[number, number]</tt>. The point around which to
|
19
|
+
# rotate. A block must be provided if using the :origin
|
20
|
+
#
|
21
|
+
# raises <tt>Prawn::Errors::BlockRequired</tt> if an :origin option is
|
22
|
+
# provided, but no block is given
|
23
|
+
#
|
24
|
+
# Example without a block:
|
25
|
+
#
|
26
|
+
# save_graphics_state
|
27
|
+
# rotate 30
|
28
|
+
# text "rotated text"
|
29
|
+
# restore_graphics_state
|
30
|
+
#
|
31
|
+
# Example with a block: rotating a rectangle around its upper-left corner
|
32
|
+
#
|
33
|
+
# x = 300
|
34
|
+
# y = 300
|
35
|
+
# width = 150
|
36
|
+
# height = 200
|
37
|
+
# angle = 30
|
38
|
+
# pdf.rotate(angle, :origin => [x, y]) do
|
39
|
+
# pdf.stroke_rectangle([x, y], width, height)
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
def rotate(angle, options={}, &block)
|
43
|
+
Prawn.verify_options(:origin, options)
|
44
|
+
rad = degree_to_rad(angle)
|
45
|
+
cos = Math.cos(rad)
|
46
|
+
sin = Math.sin(rad)
|
47
|
+
if options[:origin].nil?
|
48
|
+
transformation_matrix(cos, sin, -sin, cos, 0, 0, &block)
|
49
|
+
else
|
50
|
+
raise Prawn::Errors::BlockRequired unless block_given?
|
51
|
+
x = options[:origin][0] + bounds.absolute_left
|
52
|
+
y = options[:origin][1] + bounds.absolute_bottom
|
53
|
+
x_prime = x * cos - y * sin
|
54
|
+
y_prime = x * sin + y * cos
|
55
|
+
translate(x - x_prime, y - y_prime) do
|
56
|
+
transformation_matrix(cos, sin, -sin, cos, 0, 0, &block)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Translate the user space. If a block is not provided, then you must save
|
62
|
+
# and restore the graphics state yourself.
|
63
|
+
#
|
64
|
+
# Example without a block: move the text up and over 10
|
65
|
+
#
|
66
|
+
# save_graphics_state
|
67
|
+
# translate(10, 10)
|
68
|
+
# text "scaled text"
|
69
|
+
# restore_graphics_state
|
70
|
+
#
|
71
|
+
# Example with a block: draw a rectangle with its upper-left corner at
|
72
|
+
# x + 10, y + 10
|
73
|
+
#
|
74
|
+
# x = 300
|
75
|
+
# y = 300
|
76
|
+
# width = 150
|
77
|
+
# height = 200
|
78
|
+
# pdf.translate(10, 10) do
|
79
|
+
# pdf.stroke_rectangle([x, y], width, height)
|
80
|
+
# end
|
81
|
+
#
|
82
|
+
def translate(x, y, &block)
|
83
|
+
transformation_matrix(1, 0, 0, 1, x, y, &block)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Scale the user space. If a block is not provided, then you must save
|
87
|
+
# and restore the graphics state yourself.
|
88
|
+
#
|
89
|
+
# == Options
|
90
|
+
# <tt>:origin</tt>:: <tt>[number, number]</tt>. The point from which to
|
91
|
+
# scale. A block must be provided if using the :origin
|
92
|
+
#
|
93
|
+
# raises <tt>Prawn::Errors::BlockRequired</tt> if an :origin option is
|
94
|
+
# provided, but no block is given
|
95
|
+
#
|
96
|
+
# Example without a block:
|
97
|
+
#
|
98
|
+
# save_graphics_state
|
99
|
+
# scale 1.5
|
100
|
+
# text "scaled text"
|
101
|
+
# restore_graphics_state
|
102
|
+
#
|
103
|
+
# Example with a block: scale a rectangle from its upper-left corner
|
104
|
+
#
|
105
|
+
# x = 300
|
106
|
+
# y = 300
|
107
|
+
# width = 150
|
108
|
+
# height = 200
|
109
|
+
# factor = 1.5
|
110
|
+
# pdf.scale(angle, :origin => [x, y]) do
|
111
|
+
# pdf.stroke_rectangle([x, y], width, height)
|
112
|
+
# end
|
113
|
+
#
|
114
|
+
def scale(factor, options={}, &block)
|
115
|
+
Prawn.verify_options(:origin, options)
|
116
|
+
if options[:origin].nil?
|
117
|
+
transformation_matrix(factor, 0, 0, factor, 0, 0, &block)
|
118
|
+
else
|
119
|
+
raise Prawn::Errors::BlockRequired unless block_given?
|
120
|
+
x = options[:origin][0] + bounds.absolute_left
|
121
|
+
y = options[:origin][1] + bounds.absolute_bottom
|
122
|
+
x_prime = factor * x
|
123
|
+
y_prime = factor * y
|
124
|
+
translate(x - x_prime, y - y_prime) do
|
125
|
+
transformation_matrix(factor, 0, 0, factor, 0, 0, &block)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# The following definition of skew would only work in a clearly
|
131
|
+
# predicatable manner when if the document had no margin. don't provide
|
132
|
+
# this shortcut until it behaves in a clearly understood manner
|
133
|
+
#
|
134
|
+
# def skew(a, b, &block)
|
135
|
+
# transformation_matrix(1,
|
136
|
+
# Math.tan(degree_to_rad(a)),
|
137
|
+
# Math.tan(degree_to_rad(b)),
|
138
|
+
# 1, 0, 0, &block)
|
139
|
+
# end
|
140
|
+
|
141
|
+
# Transform the user space (see notes for rotate regarding graphics state)
|
142
|
+
# Generally, one would use the rotate, scale, translate, and skew
|
143
|
+
# convenience methods instead of calling transformation_matrix directly
|
144
|
+
def transformation_matrix(a, b, c, d, e, f)
|
145
|
+
values = [a, b, c, d, e, f].map { |x| "%.5f" % x }.join(" ")
|
146
|
+
save_graphics_state if block_given?
|
147
|
+
add_content "#{values} cm"
|
148
|
+
if block_given?
|
149
|
+
yield
|
150
|
+
restore_graphics_state
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
@@ -57,14 +57,10 @@ module Prawn
|
|
57
57
|
opacity = [[opacity, 0.0].max, 1.0].min
|
58
58
|
stroke_opacity = [[stroke_opacity, 0.0].max, 1.0].min
|
59
59
|
|
60
|
-
|
61
|
-
add_content "q"
|
60
|
+
save_graphics_state
|
62
61
|
add_content "/#{opacity_dictionary_name(opacity, stroke_opacity)} gs"
|
63
|
-
|
64
62
|
yield if block_given?
|
65
|
-
|
66
|
-
# restore the previous graphics context
|
67
|
-
add_content "Q"
|
63
|
+
restore_graphics_state
|
68
64
|
end
|
69
65
|
|
70
66
|
private
|
@@ -94,7 +90,7 @@ module Prawn
|
|
94
90
|
:obj => dictionary }
|
95
91
|
end
|
96
92
|
|
97
|
-
|
93
|
+
page.ext_gstates.merge!(dictionary_name => dictionary)
|
98
94
|
dictionary_name
|
99
95
|
end
|
100
96
|
|
data/lib/prawn/images.rb
CHANGED
@@ -91,7 +91,7 @@ module Prawn
|
|
91
91
|
w,h = calc_image_dimensions(info, options)
|
92
92
|
|
93
93
|
if options[:at]
|
94
|
-
x,y =
|
94
|
+
x,y = map_to_absolute(options[:at])
|
95
95
|
else
|
96
96
|
x,y = image_position(w,h,options)
|
97
97
|
move_text_position h
|
@@ -100,7 +100,7 @@ module Prawn
|
|
100
100
|
# add a reference to the image object to the current page
|
101
101
|
# resource list and give it a label
|
102
102
|
label = "I#{next_image_id}"
|
103
|
-
|
103
|
+
page.xobjects.merge!( label => image_obj )
|
104
104
|
|
105
105
|
# add the image to the current page
|
106
106
|
instruct = "\nq\n%.3f 0 0 %.3f %.3f %.3f cm\n/%s Do\nQ"
|
@@ -251,7 +251,7 @@ module Prawn
|
|
251
251
|
# - An array with N elements, where N is two times the number of color
|
252
252
|
# components.
|
253
253
|
rgb = png.transparency[:rgb]
|
254
|
-
obj.data[:Mask] = rgb.collect { |
|
254
|
+
obj.data[:Mask] = rgb.collect { |x| [x,x] }.flatten
|
255
255
|
elsif png.transparency[:indexed]
|
256
256
|
# TODO: broken. I was attempting to us Color Key Masking, but I think
|
257
257
|
# we need to construct an SMask i think. Maybe do it inside
|
@@ -263,6 +263,7 @@ module Prawn
|
|
263
263
|
# channel mixed in with the main image data. The PNG class seperates
|
264
264
|
# it out for us and makes it available via the alpha_channel attribute
|
265
265
|
if png.alpha_channel
|
266
|
+
min_version 1.4
|
266
267
|
smask_obj = ref!(:Type => :XObject,
|
267
268
|
:Subtype => :Image,
|
268
269
|
:Height => png.height,
|
data/lib/prawn/images/png.rb
CHANGED
@@ -203,8 +203,8 @@ module Prawn
|
|
203
203
|
# convert the pixel data to seperate strings for colours and alpha
|
204
204
|
color_byte_size = self.colors * self.bits / 8
|
205
205
|
alpha_byte_size = self.bits / 8
|
206
|
-
pixels.each do |
|
207
|
-
|
206
|
+
pixels.each do |this_row|
|
207
|
+
this_row.each do |pixel|
|
208
208
|
@img_data << pixel[0, color_byte_size].pack("C*")
|
209
209
|
@alpha_channel << pixel[color_byte_size, alpha_byte_size].pack("C*")
|
210
210
|
end
|
@@ -0,0 +1,278 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
# generates outline dictionary and items for document
|
4
|
+
#
|
5
|
+
# Author Jonathan Greenberg
|
6
|
+
|
7
|
+
require 'forwardable'
|
8
|
+
|
9
|
+
module Prawn
|
10
|
+
|
11
|
+
class Document
|
12
|
+
|
13
|
+
# See Outline#define below for documentation
|
14
|
+
def define_outline(&block)
|
15
|
+
outline.define(&block)
|
16
|
+
end
|
17
|
+
|
18
|
+
# The Outline dictionary (12.3.3) for this document. It is
|
19
|
+
# lazily initialized, so that documents that do not have an outline
|
20
|
+
# do not incur the additional overhead.
|
21
|
+
def outline_root(outline_root)
|
22
|
+
@store.root.data[:Outlines] ||= ref!(outline_root)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Lazily instantiates an Outline object for document. This is used as point of entry
|
26
|
+
# to methods to build the outline tree.
|
27
|
+
def outline
|
28
|
+
@outline ||= Outline.new(self)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
# The Outline class organizes the outline tree items for the document.
|
34
|
+
# Note that the prev and parent instance variables are adjusted while navigating
|
35
|
+
# through the nested blocks. These variables along with the presence or absense
|
36
|
+
# of blocks are the primary means by which the relations for the various
|
37
|
+
# OutlineItems and the OutlineRoot are set. Unfortunately, the best way to
|
38
|
+
# understand how this works is to follow the method calls through a real example.
|
39
|
+
#
|
40
|
+
# Some ideas for the organization of this class were gleaned from name_tree. In
|
41
|
+
# particular the way in which the OutlineItems are finally rendered into document
|
42
|
+
# objects in PdfObject through a hash.
|
43
|
+
#
|
44
|
+
class Outline
|
45
|
+
|
46
|
+
extend Forwardable
|
47
|
+
def_delegator :@document, :page_number
|
48
|
+
|
49
|
+
attr_accessor :parent
|
50
|
+
attr_accessor :prev
|
51
|
+
attr_accessor :document
|
52
|
+
attr_accessor :outline_root
|
53
|
+
attr_accessor :items
|
54
|
+
|
55
|
+
def initialize(document)
|
56
|
+
@document = document
|
57
|
+
@outline_root = document.outline_root(OutlineRoot.new)
|
58
|
+
@parent = outline_root
|
59
|
+
@prev = nil
|
60
|
+
@items = {}
|
61
|
+
end
|
62
|
+
|
63
|
+
# Defines an outline for the document.
|
64
|
+
# The outline is an optional nested index that appears on the side of a PDF
|
65
|
+
# document usually with direct links to pages. The outline DSL is defined by nested
|
66
|
+
# blocks involving two methods: section and page.
|
67
|
+
#
|
68
|
+
# section(title, options{}, &block)
|
69
|
+
# title: the outline text that appears for the section.
|
70
|
+
# options: page - optional integer defining the page number for a destination link.
|
71
|
+
# - currently only :FIT destination supported with link to top of page.
|
72
|
+
# closed - whether the section should show its nested outline elements.
|
73
|
+
# - defaults to false.
|
74
|
+
# page(page, options{})
|
75
|
+
# page: integer defining the page number for the destination link.
|
76
|
+
# currently only :FIT destination supported with link to top of page.
|
77
|
+
# set to nil if destination link is not desired.
|
78
|
+
# options: title - the outline text that appears for the section.
|
79
|
+
# closed - whether the section should show its nested outline elements.
|
80
|
+
# - defaults to false.
|
81
|
+
#
|
82
|
+
# The syntax is best illustrated with an example:
|
83
|
+
#
|
84
|
+
# Prawn::Document.generate(outlined document) do
|
85
|
+
# text "Page 1. This is the first Chapter. "
|
86
|
+
# start_new_page
|
87
|
+
# text "Page 2. More in the first Chapter. "
|
88
|
+
# start_new_page
|
89
|
+
# define_outline do
|
90
|
+
# section 'Chapter 1', :page => 1, :closed => true do
|
91
|
+
# page 1, :title => 'Page 1'
|
92
|
+
# page 2, :title => 'Page 2'
|
93
|
+
# end
|
94
|
+
# end
|
95
|
+
# end
|
96
|
+
#
|
97
|
+
# It should be noted that not defining a title for a page element will raise
|
98
|
+
# a RequiredOption error
|
99
|
+
#
|
100
|
+
def define(&block)
|
101
|
+
if block
|
102
|
+
block.arity < 1 ? instance_eval(&block) : block[self]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Adds an outine section to the outline tree (see define_outline).
|
107
|
+
# Although you will probably choose to exclusively use define_outline so
|
108
|
+
# that your outline tree is contained and easy to manage, this method
|
109
|
+
# gives you the option to add sections to the outline tree at any point
|
110
|
+
# during document generation. Note that the section will be added at the
|
111
|
+
# top level at the end of the outline. For more a more flexible API try
|
112
|
+
# using outline.insert_section_after.
|
113
|
+
#
|
114
|
+
# block uses the same DSL syntax as define_outline, for example:
|
115
|
+
#
|
116
|
+
# outline.add_section do
|
117
|
+
# section 'Added Section', :page => 3 do
|
118
|
+
# page 3, :title => 'Page 3'
|
119
|
+
# end
|
120
|
+
# end
|
121
|
+
def add_section(&block)
|
122
|
+
@parent = outline_root
|
123
|
+
@prev = outline_root.data.last
|
124
|
+
if block
|
125
|
+
block.arity < 1 ? instance_eval(&block) : block[self]
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Inserts an outline section to the outline tree (see define_outline).
|
130
|
+
# Although you will probably choose to exclusively use define_outline so
|
131
|
+
# that your outline tree is contained and easy to manage, this method
|
132
|
+
# gives you the option to insert sections to the outline tree at any point
|
133
|
+
# during document generation. Unlike outline.add_section, this method allows
|
134
|
+
# you to enter a section after any other item at any level in the outline tree.
|
135
|
+
# Currently the only way to locate the place of entry is with the title for the
|
136
|
+
# item. If your titles names are not unique consider using define_outline.
|
137
|
+
#
|
138
|
+
# block uses the same DSL syntax as define_outline, for example:
|
139
|
+
#
|
140
|
+
# go_to_page 2
|
141
|
+
# start_new_page
|
142
|
+
# text "Inserted Page"
|
143
|
+
# outline.insert_section_after :title => 'Page 2' do
|
144
|
+
# page page_number, :title => "Inserted Page"
|
145
|
+
# end
|
146
|
+
#
|
147
|
+
def insert_section_after(title, &block)
|
148
|
+
@prev = items[title]
|
149
|
+
if @prev
|
150
|
+
@parent = @prev.data.parent
|
151
|
+
nxt = @prev.data.next
|
152
|
+
if block
|
153
|
+
block.arity < 1 ? instance_eval(&block) : block[self]
|
154
|
+
end
|
155
|
+
adjust_relations(nxt)
|
156
|
+
else
|
157
|
+
raise Prawn::Errors::UnknownOutlineTitle,
|
158
|
+
"\n No outline item with title: '#{title}' exists in the outline tree"
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
private
|
163
|
+
|
164
|
+
def section(title, options = {}, &block)
|
165
|
+
add_outline_item(title, options, &block)
|
166
|
+
end
|
167
|
+
|
168
|
+
def page(page = nil, options = {})
|
169
|
+
if options[:title]
|
170
|
+
title = options[:title]
|
171
|
+
options[:page] = page
|
172
|
+
else
|
173
|
+
raise Prawn::Errors::RequiredOption,
|
174
|
+
"\nTitle is a required option for page"
|
175
|
+
end
|
176
|
+
add_outline_item(title, options)
|
177
|
+
end
|
178
|
+
|
179
|
+
def add_outline_item(title, options, &block)
|
180
|
+
outline_item = create_outline_item(title, options)
|
181
|
+
set_relations(outline_item)
|
182
|
+
increase_count
|
183
|
+
set_variables_for_block(outline_item, block)
|
184
|
+
block.call if block
|
185
|
+
reset_parent(outline_item)
|
186
|
+
end
|
187
|
+
|
188
|
+
def create_outline_item(title, options)
|
189
|
+
outline_item = OutlineItem.new(title, parent, options)
|
190
|
+
|
191
|
+
if options[:page]
|
192
|
+
page_index = options[:page] - 1
|
193
|
+
outline_item.dest = [document.pages[page_index].dictionary, :Fit]
|
194
|
+
end
|
195
|
+
|
196
|
+
outline_item.prev = prev if @prev
|
197
|
+
items[title] = document.ref!(outline_item)
|
198
|
+
end
|
199
|
+
|
200
|
+
def set_relations(outline_item)
|
201
|
+
prev.data.next = outline_item if prev
|
202
|
+
parent.data.first = outline_item unless prev
|
203
|
+
parent.data.last = outline_item
|
204
|
+
end
|
205
|
+
|
206
|
+
def increase_count
|
207
|
+
counting_parent = parent
|
208
|
+
while counting_parent
|
209
|
+
counting_parent.data.count += 1
|
210
|
+
if counting_parent == outline_root
|
211
|
+
counting_parent = nil
|
212
|
+
else
|
213
|
+
counting_parent = counting_parent.data.parent
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def set_variables_for_block(outline_item, block)
|
219
|
+
self.prev = block ? nil : outline_item
|
220
|
+
self.parent = outline_item if block
|
221
|
+
end
|
222
|
+
|
223
|
+
def reset_parent(outline_item)
|
224
|
+
if parent == outline_item
|
225
|
+
self.prev = outline_item
|
226
|
+
self.parent = outline_item.data.parent
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def adjust_relations(nxt)
|
231
|
+
if nxt
|
232
|
+
nxt.data.prev = @prev
|
233
|
+
@prev.data.next = nxt
|
234
|
+
@parent.data.last = nxt
|
235
|
+
else
|
236
|
+
@parent.data.last = @prev
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
end
|
241
|
+
|
242
|
+
class OutlineRoot #:nodoc:
|
243
|
+
attr_accessor :count, :first, :last
|
244
|
+
|
245
|
+
def initialize
|
246
|
+
@count = 0
|
247
|
+
end
|
248
|
+
|
249
|
+
def to_hash
|
250
|
+
{:Type => :Outlines, :Count => count, :First => first, :Last => last}
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
class OutlineItem #:nodoc:
|
255
|
+
attr_accessor :count, :first, :last, :next, :prev, :parent, :title, :dest, :closed
|
256
|
+
|
257
|
+
def initialize(title, parent, options)
|
258
|
+
@closed = options[:closed]
|
259
|
+
@title = title
|
260
|
+
@parent = parent
|
261
|
+
@count = 0
|
262
|
+
end
|
263
|
+
|
264
|
+
def to_hash
|
265
|
+
hash = { :Title => Prawn::LiteralString.new(title),
|
266
|
+
:Parent => parent,
|
267
|
+
:Count => closed ? -count : count }
|
268
|
+
[{:First => first}, {:Last => last}, {:Next => @next},
|
269
|
+
{:Prev => prev}, {:Dest => dest}].each do |h|
|
270
|
+
unless h.values.first.nil?
|
271
|
+
hash.merge!(h)
|
272
|
+
end
|
273
|
+
end
|
274
|
+
hash
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|