Prawn
by example

Last Update: 2024-03-05
Prawn Version: 2.5.0
git commit: f82783b1

How to read this manualThis manual is a collection of examples categorized by theme and organized from the least
to the most complex. While it covers most of the common use cases it is not acomprehensive guide.
The best way to read it depends on your previous knowledge of Prawn and what you needto accomplish.
If you are beginning with Prawn the first chapter will teach you the most basic concepts andhow to create pdf documents. For an overview of the other features each chapter beyond
the first either has a Basics section (which offer enough insight on the feature without
showing all the advanced stuff you might never use) or is simple enough with only a few
examples.
Once you understand the basics you might want to come back to this manual looking forexamples that accomplish tasks you need.
Advanced users are encouraged to go beyond this manual and read the source code
directly if any doubt is not directly covered on this manual.

Reading the examples
The title of each example is the relative path from the Prawn source manual/ folder.
The first body of text is the introductory text for the example. Generally it is a short
description of the features illustrated by the example.
Next comes the example source code block in fixed width font.
Most of the example snippets illustrate features that alter the page in place. The effect ofthese snippets is shown right below a dashed line. If it doesn't make sense to evaluate thesnippet inline, a box with the link for the example file is shown instead.
Note that the stroke_axis method used throughout the manual is part of standard Prawn. It
is defined in this file:https://github.com/prawnpdf/prawn/blob/master/lib/prawn/graphics.rb

Basic Concepts

This chapter covers the minimum amount of functionality you'll need to start using Prawn.
If you are new to Prawn this is the first chapter to read. Once you are comfortable with theconcepts shown here you might want to check the Basics section of the Graphics, Bounding
Box and Text sections.
The examples show:• How to create new pdf documents in every possible way• Where the origin for the document coordinates is. What are Bounding Boxes andhow they interact with the origin• How the cursor behaves• How to start new pages• What the base unit for measurement and coordinates is and how to use otherconvenient measures• How to build custom view objects that use Prawn's DSL

Creating a PDF Document
basic_concepts/creation.rb

There are three ways to create a PDF Document in Prawn: creating a new Prawn::Document
instance, or using the Prawn::Document.generate method with and without block arguments.
The following snippet showcase each way by creating a simple document with some text
drawn.
When we instantiate the Prawn::Document object the actual pdf document will only be createdafter we call render_file.
The generate method will render the actual pdf object after exiting the block. When we use
it without a block argument the provided block is evaluated in the context of a newly
created Prawn::Document instance. When we use it with a block argument a Prawn::Document
instance is created and passed to the block.
The generate method without block arguments requires less typing and defines and renders
the pdf document in one shot. Almost all of the examples are coded this way.

# Assignment
pdf = Prawn::Document.new
pdf.text('Hello World')
pdf.render_file('assignment.pdf')# Implicit Block
Prawn::Document.generate('implicit.pdf') do
text 'Hello World'end# Explicit Block
Prawn::Document.generate('explicit.pdf') do |pdf|
pdf.text('Hello World')
end

This code snippet was not evaluated inline. You may see its output by runningthe example file located here:
https://github.com/prawnpdf/prawn/tree/master/manual/basic_concepts/
creation.rb

Originbasic_concepts/origin.rb

This is the most important concept you need to learn about Prawn:
PDF documents have the origin [0,0] at the bottom-left corner of the page.
A bounding box is a structure which provides boundaries for inserting content. A boundingbox also has the property of relocating the origin to its relative bottom-left corner. However,be aware that the location specified when creating a bounding box is its top-left corner, notbottom-left (hence the [100, 300] coordinates below).
Even if you never create a bounding box explicitly, each document already comes with one
called the margin box. This initial bounding box is the one responsible for the document
margins.
So practically speaking the origin of a page on a default generated document isn't theabsolute bottom left corner but the bottom left corner of the margin box.
The following snippet strokes a circle on the margin box origin. Then strokes the boundaries
of a bounding box and a circle on its origin.

stroke_circle [0, 0], 10
bounding_box([100, 200], width: 300, height: 100) do
stroke_bounds
stroke_circle [0, 0], 10
end

Example Output
200.0

100.0

100.0

200.0

300.0

400.0

500.0

Cursorbasic_concepts/cursor.rb

We normally write our documents from top to bottom and it is no different with Prawn. Even
if the origin is on the bottom left corner we still fill the page from the top to the bottom. In
other words the cursor for inserting content starts on the top of the page.
Most of the functions that insert content on the page will start at the current cursor positionand proceed to the bottom of the page.
The following snippet shows how the cursor behaves when we add some text to the pageand demonstrates some of the helpers to manage the cursor position. The cursor method
returns the current cursor position.

text "the cursor is here: #{cursor}"
text "now it is here: #{cursor}"
move_down 100
text "on the first move the cursor went down to: #{cursor}"
move_up 50
text "on the second move the cursor went up to: #{cursor}"
move_cursor_to 50
text "on the last move the cursor went directly to: #{cursor}"

Example Output
the cursor is here: 281.15749999999997
now it is here: 267.28549999999996

200.0

on the second move the cursor went up to: 189.54149999999996
on the first move the cursor went down to: 153.41349999999994

100.0

on the last move the cursor went directly to: 50.0

100.0

200.0

300.0

400.0

500.0

Other Cursor Helpers
basic_concepts/other_cursor_helpers.rb

Another group of helpers for changing the cursor position are the pad methods. They accept
a numeric value and a block. pad will use the numeric value to move the cursor down both
before and after the block content. pad_top will only move the cursor before the block whilepad_bottom will only move after.
float is a method for not changing the cursor. Pass it a block and the cursor will remain on
the same place when the block returns.

stroke_horizontal_rule
pad(20) { text 'Text padded both before and after.' }stroke_horizontal_rule
pad_top(20) { text 'Text padded on the top.' }stroke_horizontal_rule
pad_bottom(20) { text 'Text padded on the bottom.' }stroke_horizontal_rule
move_down 30
text 'Text written before the float block.'float domove_down 30
bounding_box([0, cursor], width: 200) do
text 'Text written inside the float block.'stroke_bounds
end
end
text 'Text written after the float block.'Example Output
Text padded both before and after.

Text padded on the top.
Text padded on the bottom.

Text written before the float block.
Text written after the float block.
Text written inside the float block.

Adding Pages
basic_concepts/adding_pages.rb

A PDF document is a collection of pages. When we create a new document be it with
Document.new or on a Document.generate block one initial page is created for us.
Some methods might create new pages automatically like text which will create a new pagewhenever the text string cannot fit on the current page.
But what if you want to go to the next page by yourself? That is easy.
Just use the start_new_page method and a shiny new page will be created for you just like inthe following snippet.

text "We are still on the initial page for this example. Now I'll ask " \'Prawn to gently start a new page. Please follow me to the next page.'start_new_page
text "See. We've left the previous page behind."

Example Output
We are still on the initial page for this example. Now I'll ask Prawn to gently start a new page.
Please follow me to the next page.

Example Output
See. We've left the previous page behind.

Measurement Extensions
basic_concepts/measurement.rb

The base unit in Prawn is the PDF Point. One PDF Point is equal to 1/72 of an inch.
There is no need to waste time converting this measure. Prawn provides helpers forconverting from other measurements to PDF Points.
Just require "prawn/measurement_extensions" and it will mix some helpers onto Numeric forconverting common measurement units to PDF Points.

require 'prawn/measurement_extensions'%i[mm cm dm m in yd ft].each do |measurement|
text "1 #{measurement} in PDF Points: #{1.public_send(measurement)} pt"
move_down 5.mm
end

Example Output
1 mm in PDF Points: 2.834645669291339 pt
1 cm in PDF Points: 28.34645669291339 pt
1 dm in PDF Points: 283.46456692913387 pt
1 m in PDF Points: 2834.645669291339 pt
1 in in PDF Points: 72 pt
1 yd in PDF Points: 2592 pt
1 ft in PDF Points: 864 pt

View
basic_concepts/view.rb

The recommended way to extend Prawn's functionality is to include the Prawn::View mixin inyour own class, which will make all Prawn::Document methods available to your custom
objects.
This approach is preferred over inheriting from Prawn::Document, as your state will be kept
completely separate from Prawn::Document's, thus avoiding accidental method collisions.
Note that Prawn::View lazily instantiates a Prawn::Document with default initialization settings,
such as page size, layout, margins, etc.
By defining your own document method you will be able to override those settings andinitialize a Prawn::Document to your heart's content. This method will be called repeatedly by
Prawn::View, so be sure to memoize the object by assigning it to an instance variable via the||= operator.

class Greeter
include Prawn::View
def initialize(name)@name = nameend
def document@document ||= Prawn::Document.new(page_size: 'A4', margin: 30)
end
def say_hello
font('Courier') dotext("Hello, #{@name}!")
end
end
end
greeter = Greeter.new('Gregory')
greeter.say_hello
greeter.save_as('greetings.pdf')

This code snippet was not evaluated inline. You may see its output by runningthe example file located here:
https://github.com/prawnpdf/prawn/tree/master/manual/basic_concepts/view.rb

Graphics

Here we show all the drawing methods provided by Prawn. Use them to draw the mostbeautiful imaginable things.
Most of the content that you'll add to your pdf document will use the graphics package.
Even text is rendered on a page just like a rectangle so even if you never use any of the
shapes described here you should at least read the basic examples.
The examples show:• All the possible ways that you can fill or stroke shapes on a page
• How to draw all the shapes that Prawn has to offer from a measly line to a mighty
polygon or ellipse• The configuration options for stroking lines and filling shapes• How to apply transformations to your drawing space

Stroke Axis
graphics/helper.rb

To produce this manual we use the stroke_axis helper method within the examples.
stroke_axis prints the x and y axis for the current bounding box with markers in 100
increments. The defaults can be changed with various options.
Note that the examples define a custom :height option so that only the example canvas isused (as seen with the output of the first line of the example code).

stroke_axis
stroke_axis(
at: [70, 70],
height: 200,
step_length: 50,
negative_axes_length: 5,color: '0000FF',)
stroke_axis(
at: [140, 140],
width: 200,
height: Integer(cursor) - 140,
step_length: 20,
negative_axes_length: 40,
color: 'FF0000',)

Example Output

200

100
150

80

200.0

60
40
100
20

20

40

60

80

100

120

140

160

180

200

50
100.0

50.0

100.0

100.0

150.0

200.0

200.0

250.0

300.0

300.0

350.0

400.0

400.0

450.0

500.0

Fill and Strokegraphics/fill_and_stroke.rb

There are two drawing primitives in Prawn: fill and stroke.
These are the methods that actually draw stuff on the document. All the other drawing
shapes like rectangle, circle or line_to define drawing paths. These paths need to be either
stroked or filled to gain form on the document.
Calling these methods without a block will act on the drawing path that has been definedprior to the call.
Calling with a block will act on the drawing path set within the block.
Most of the methods which define drawing paths have methods of the same name startingwith stroke_ and fill_ which create the drawing path and then stroke or fill it.

# No blockline [0, 200], [100, 150]
strokerectangle [0, 100], 100, 100
fill# With block
stroke { line [200, 200], [300, 150] }fill { rectangle [200, 100], 100, 100 }# Method hookstroke_line [400, 200], [500, 150]
fill_rectangle [400, 100], 100, 100

Example Output
200.0

100.0

100.0

200.0

300.0

400.0

500.0

Lines and Curves
graphics/lines_and_curves.rb

Prawn supports drawing both lines and curves starting either at the current position, orfrom a specified starting position.
line_to and curve_to set the drawing path from the current drawing position to the specified
point. The initial drawing position can be set with move_to. They are useful when you want tochain successive calls because the drawing position will be set to the specified point
afterwards.
line and curve set the drawing path between the two specified points.
Both curve methods define a Bezier curve bounded by two aditional points provided as the:bounds param.

# line_to and curve_tostroke do
move_to 0, 0
line_to 100, 100
line_to 0, 100
curve_to [150, 200], bounds: [[20, 150], [120, 150]]
curve_to [200, 0], bounds: [[150, 200], [450, 10]]end
# line and curvestroke doline [300, 200], [400, 50]
curve [500, 0], [400, 100], bounds: [[600, 200], [300, 290]]
end

Example Output
200.0

100.0

100.0

200.0

300.0

400.0

500.0

Common Linesgraphics/common_lines.rb

Prawn provides helpers for drawing some commonly used lines:
vertical_line and horizontal_line do just what their names imply. Specify the start and end
point at a fixed coordinate to define the line.
horizontal_rule draws a horizontal line on the current bounding box from border to border,using the current y position.

stroke_color 'ff0000'
stroke do# just lower the current y position
move_down 50
horizontal_rule
vertical_line 100, 300, at: 50
horizontal_line 200, 500, at: 150
end

Example Output
300.0

200.0

100.0

100.0

200.0

300.0

400.0

500.0

Rectangles
graphics/rectangle.rb

To draw a rectangle, just provide the upper-left corner, width and height to the rectangle
method.
There's also rounded_rectangle. Just provide an additional radius value for the rounded
corners.

stroke dorectangle [100, 300], 100, 200
rounded_rectangle [300, 300], 100, 200, 20
end

Example Output
400.0

300.0

200.0

100.0

100.0

200.0

300.0

400.0

500.0

Polygons
graphics/polygon.rb

Drawing polygons in Prawn is easy, just pass a sequence of points to one of the polygon
family of methods.
Just like rounded_rectangle we also have rounded_polygon. The only difference is the radiusparam comes before the polygon points.

# Triangle
stroke_polygon [50, 200], [50, 300], [150, 300]
# Hexagon
fill_polygon [50, 150], [150, 200], [250, 150], [250, 50], [150, 0], [50, 50]# Pentagram
pentagon_points = [500, 100], [430, 5], [319, 41], [319, 159], [430, 195]
pentagram_points = [0, 2, 4, 1, 3].map { |i| pentagon_points[i] }stroke_rounded_polygon(20, *pentagram_points)

Example Output
300.0

200.0

100.0

100.0

200.0

300.0

400.0

500.0

Circles and Ellipses
graphics/circle_and_ellipse.rb

To define a circle all you need is the center point and the radius.
To define an ellipse you provide the center point and two radii (or axes) values. If thesecond radius value is omitted, both radii will be equal and you will end up drawing a circle.

stroke_circle [100, 300], 100
fill_ellipse [200, 100], 100, 50
fill_ellipse [400, 100], 50

Example Output
400.0

300.0

200.0

100.0

100.0

200.0

300.0

400.0

500.0

Line Widthgraphics/line_width.rb

The line_width= method sets the stroke width for subsequent stroke calls.
Since Ruby assumes that an unknown variable on the left hand side of an assignment is alocal temporary, rather than a setter method, if you are using the block call to
Prawn::Document.generate without passing params you will need to call line_width on self.

y = 225
3.times do |i|case iwhen 0 then line_width = 10 # This call will have no effectwhen 1 then self.line_width = 10
when 2 then self.line_width = 25
endstroke dohorizontal_line 25, 75, at: yrectangle [225, y + 25], 50, 50
circle [450, y], 25
endy -= 90
end

Example Output

200.0

100.0

100.0

200.0

300.0

400.0

500.0

Stroke Capgraphics/stroke_cap.rb

The cap style defines how the edge of a line or curve will be drawn. There are three types::butt (the default), :round and :projecting_square.
The difference is better seen with thicker lines. With :butt lines are drawn starting andending at the exact points provided. With both :round and :projecting_square the line isprojected beyond the start and end points.
Just like line_width= the cap_style= method needs an explicit receiver to work.

self.line_width = 25%i[butt round projecting_square].each_with_index do |cap, i|self.cap_style = cap
y = 250 - (i * 100)
stroke_horizontal_line 100, 300, at: ystroke_circle [400, y], 15
end

Example Output
300.0

200.0

100.0

100.0

200.0

300.0

400.0

500.0

Stroke Joingraphics/stroke_join.rb

The join style defines how the intersection between two lines is drawn. There are three
types: :miter (the default), :round and :bevel.
Just like cap_style, the difference between styles is better seen with thicker lines.

self.line_width = 25
%i[miter round bevel].each_with_index do |style, i|self.join_style = styley = 200 - (i * 100)
stroke do
move_to(100, y)line_to(200, y + 100)
line_to(300, y)
end
stroke_rectangle [400, y + 75], 50, 50
end

Example Output

Stroke Dash Patterngraphics/stroke_dash.rb

This sets the dashed pattern for lines and curves. The (dash) length defines how long eachdash will be.
The :space option defines the length of the space between the dashes.
The :phase option defines the start point of the sequence of dashes and spaces.
Complex dash patterns can be specified by using an array with alternating dash/gap lengths
for the first parameter (note that the :space option is ignored in this case).

move_down 20
dash([1, 2, 3, 2, 1, 5], phase: 6)
stroke_horizontal_line 50, 500
move_down 10dash([1, 2, 3, 4, 5, 6, 7, 8])
stroke_horizontal_line 50, 500
base_y = cursor - 10
24.times do |i|length = (i / 4) + 1
space = length # space between dashes same length as dashphase = 0 # start with dashcase i % 4when 0 then base_y -= 10
when 1 then phase = length # start with space between dashes
when 2 then space = length * 0.5 # space between dashes half as long as dashwhen 3space = length * 0.5 # space between dashes half as long as dashphase = length # start with space between dashes
endbase_y -= 10dash(length, space: space, phase: phase)stroke_horizontal_line 50, 500, at: base_y - (2 * i)endExample Output

Colorgraphics/color.rb

We can change the stroke and fill colors providing an HTML rgb 6 digit color code string("AB1234") or 4 values for CMYK.

# Fill with Orange using RGB (Unlike css, there is no leading #)fill_color 'FF8844'
fill_polygon [50, 150], [150, 200], [250, 150], [250, 50], [150, 0], [50, 50]# Stroke with Purple using CMYK
stroke_color 50, 100, 0, 0
stroke_rectangle [300, 300], 200, 100# Both together
fill_and_stroke_circle [400, 100], 50

Example Output

Gradientsgraphics/gradients.rb

Note that because of the way PDF renders radial gradients in order to get solid fill your start
circle must be fully inside your end circle. Otherwise you will get triangle fill like illustrated
in the example below.

self.line_width = 10
# Linear Gradientsfill_gradient [0, 250], [100, 150], 'ff0000', '0000ff'
fill_rectangle [0, 250], 100, 100
stroke_gradient [150, 150], [250, 250], '00ffff', 'ffff00'
stroke_rectangle [150, 250], 100, 100
fill_gradient [300, 250], [400, 150], 'ff0000', '0000ff'
stroke_gradient [300, 150], [400, 250], '00ffff', 'ffff00'
fill_and_stroke_rectangle [300, 250], 100, 100
rotate 45, origin: [500, 200] do
stops = { 0 => 'ff0000', 0.6 => '999900', 0.8 => '00cc00', 1 => '4444ff' }fill_gradient from: [460, 240], to: [540, 160], stops: stops
fill_rectangle [460, 240], 80, 80
end# Radial gradientsfill_gradient [50, 50], 0, [50, 50], 70.71, 'ff0000', '0000ff'
fill_rectangle [0, 100], 100, 100
stroke_gradient [200, 50], 45, [200, 50], 70.71, '00ffff', 'ffff00'
stroke_rectangle [150, 100], 100, 100
stroke_gradient [350, 50], 45, [350, 50], 70.71, '00ffff', 'ffff00'
fill_gradient [350, 50], 0, [350, 50], 70.71, 'ff0000', '0000ff'
fill_and_stroke_rectangle [300, 100], 100, 100
fill_gradient [500, 100], 50, [500, 0], 0, 'ff0000', '0000ff'
fill_rectangle [450, 100], 100, 100

Example Output

Transparency
graphics/transparency.rb

Although the name of the method is transparency, what we are actually setting is the opacity
for fill and stroke. So 0 means completely transparent and 1.0 means completely opaque.
You may call it providing one or two values. The first value sets fill opacity and the second
value sets stroke opacity. If the second value is omitted fill and stroke will have the same
opacity.

self.line_width = 5
fill_color 'ff0000'
fill_rectangle [0, 100], 500, 100
fill_color '000000'
stroke_color 'ffffff'
base_x = 100[[0.5, 1], 0.5, [1, 0.5]].each do |args|
transparent(*args) do
fill_circle [base_x, 100], 50
stroke_rectangle [base_x - 20, 100], 40, 80
end
base_x += 150
end

Example Output

Soft Masks
graphics/soft_masks.rb

Soft masks are used for more complex alpha channel manipulations. You can use arbitrarydrawing functions for creation of soft masks. The resulting alpha channel is made ofgreyscale version of the drawing (luminosity channel to be precise). So while you can use
any combination of colors for soft masks it's easier to use greyscales. Black will result in fulltransparency and white will make region fully opaque.
Soft mask is a part of page graphic state. So if you want to apply soft mask only to a part ofpage you need to enclose drawing instructions in save_graphics_state block.

save_graphics_state do
soft_mask do
0.upto(15) do |i|fill_color 0, 0, 0, 100.0 / 16.0 * (15 - i)fill_circle [75 + (i * 25), 100], 60
end
end%w[009ddc 963d97 e03a3e f5821f fdb827 61bb46].each_with_index do |color, i|fill_color color
fill_rectangle [0, 60 + (i * 20)], 600, 20
end
end

Example Output

Blend Modesgraphics/blend_mode.rb

Blend modes can be used to change the way two layers (images, graphics, text, etc.) are
blended together. The blend_mode method accepts a single blend mode or an array of blend
modes. PDF viewers should blend the layers based on the first recognized blend mode.
Valid blend modes in v1.4 of the PDF spec include :Normal, :Multiply, :Screen, :Overlay,:Darken, :Lighten, :ColorDodge, :ColorBurn, :HardLight, :SoftLight, :Difference, :Exclusion, :Hue,:Saturation, :Color, and :Luminosity.

# https://commons.wikimedia.org/wiki/File:Blend_modes_2.-bottomlayer.jpg#/media/File:Blend_modes_2.-bottom-layer.jpg
bottom_layer = "#{Prawn::DATADIR}/images/blend_modes_bottom_layer.jpg"# https://commons.wikimedia.org/wiki/File:Blend_modes_1.-top-layer.jpg#/media/File:Blend_modes_1.top-layer.jpg
top_layer = "#{Prawn::DATADIR}/images/blend_modes_top_layer.jpg"
blend_modes = %i[
Normal Multiply Screen Overlay Darken Lighten ColorDodge
ColorBurn HardLight SoftLight Difference Exclusion Hue
Saturation Color Luminosity]
blend_modes.each_with_index do |blend_mode, index|
x = 5 + (index % 4 * 130)y = cursor - (index / 4 * 195) - 5
image bottom_layer, at: [x, y], fit: [120, 120]
blend_mode(blend_mode) do
image top_layer, at: [x, y], fit: [120, 120]end
y -= 130
fill_color '009ddc'
fill_rectangle [x, y], 75, 25
blend_mode(blend_mode) do
fill_color 'fdb827'
fill_rectangle [x + 50, y], 70, 25
end
y -= 30
fill_color '000000'
text_box blend_mode.to_s, at: [x, y]endExample Output

NormalMultiply

ScreenOverlay

DarkenLightenColorDodge

ColorBurn

HardLight

SoftLight

Difference

Exclusion

Hue

Saturation

Color

LuminosityFill Rules
graphics/fill_rules.rb

Prawn's fill operators (fill and fill_and_stroke both accept a :fill_rule option. These rules
determine which parts of the page are counted as "inside" vs. "outside" the path. There are
two fill rules:• :nonzero_winding_number (default): a point is inside the path if a ray from that point
to infinity crosses a nonzero "net number" of path segments, where pathsegments intersecting in one direction are counted as positive and those in theother direction negative.

• :even_odd: A point is inside the path if a ray from that point to infinity crosses an
odd number of path segments, regardless of direction.
The differences between the fill rules only come into play with complex paths; they are
identical for simple shapes.

pentagram = [[181, 95], [0, 36], [111, 190], [111, 0], [0, 154]]
stroke_color 'ff0000'
line_width 2
text_box 'Nonzero Winding Number', at: [10, 200]
polygon(*pentagram.map { |x, y| [x + 50, y] })fill_and_stroke
text_box 'Even-Odd', at: [330, 200]
polygon(*pentagram.map { |x, y| [x + 330, y] })fill_and_stroke(fill_rule: :even_odd)

Example Output
200.0

Nonzero Winding Number

Even-Odd

100.0

100.0

200.0

300.0

400.0

500.0

Rotation
graphics/rotate.rb

This transformation is used to rotate the user space. Give it an angle and an :origin point
about which to rotate and a block. Everything inside the block will be drawn with therotated coordinates.
The angle is in degrees.
If you omit the :origin option the page origin will be used.

fill_circle [270, 180], 2
12.times do |i|rotate(i * 30, origin: [270, 180]) do
stroke_rectangle [350, 200], 80, 40
text_box "Rotated #{i * 30}°", size: 10, at: [360, 185]
end
end

°
60
ed
tat
Ro

Rotated 90°

Ro

tat

e

2
d1

0°

Example Output

°

Ro

ed
tat

30

Ro

Rotated 0°

tat
ed
1
50
°
Rotated 180°

Ro

tat
e

Ro

d3

30

°

tat
Ro
°
00

3
ed

Rotated 270°

e

tat

Ro
°

d2
40

2
ed
tat

°
10

Translationgraphics/translate.rb

This transformation is used to translate the user space. Just provide the x and y coordinates
for the new origin.

1.upto(3) do |i|
x = i * 50
y = i * 100
translate(x, y) do# Draw a point on the new origin
fill_circle [0, 0], 2
draw_text "New origin after translation to [#{x}, #{y}]", at: [5, -3]
stroke_rectangle [100, 50], 200, 30
text_box 'Top left corner at [100, 50]', at: [110, 40], width: 180
end
end

Example Output
Top left corner at [100, 50]
New origin after translation to [150, 300]

300.0

Top left corner at [100, 50]
200.0

New origin after translation to [100, 200]

Top left corner at [100, 50]
100.0

New origin after translation to [50, 100]

100.0

200.0

300.0

400.0

500.0

Scaling
graphics/scale.rb

This transformation is used to scale the user space. Give it an scale factor and an :origin
point and everything inside the block will be scaled using the origin point as reference.
If you omit the :origin option the page origin will be used.

width = 100
height = 50
y = 190x = 50stroke_rectangle [x, y], width, heighttext_box 'reference rectangle', at: [x + 10, y - 10], width: width - 20
scale(2, origin: [x, y]) do
stroke_rectangle [x, y], width, heighttext_box 'rectangle scaled from upper-left corner', at: [x, y - height - 5], width: widthend
x = 350
stroke_rectangle [x, y], width, heighttext_box 'reference rectangle', at: [x + 10, y - 10], width: width - 20
scale(2, origin: [x + (width / 2), y - (height / 2)]) do
stroke_rectangle [x, y], width, heighttext_box 'rectangle scaled from center', at: [x, y - height - 5], width: width
end

Example Output
reference
rectangle

rectangle scaled
from upper-leftcornerreference
rectangle

rectangle scaled
from centerText

This is probably the feature people will use the most. There is no shortage of options whenit comes to text. You'll be hard pressed to find a use case that is not covered by one of thetext methods and configurable options.
The examples show:• Text that flows from page to page automatically starting new pages whennecessary• How to use text boxes and place them on specific positions• What to do when a text box is too small to fit its content• Flowing text in columns• How to change the text style configuring font, size, alignment and many othersettings• How to style specific portions of a text with inline styling and formatted text
• How to define formatted callbacks to reuse common styling definitions• How to use the different rendering modes available for the text methods• How to create your custom text box extensions• How to use external fonts on your pdfs
• What happens when rendering text in different languages

Free Flowing Text
text/free_flowing_text.rb

Text rendering can be as simple or as complex as you want.
This example covers the most basic method: text. It is meant for free flowing text. The
provided string will flow according to the current bounding box width and height. It will also
flow onto the next page if the bottom of the bounding box is reached.
The text will start being rendered on the current cursor position. When it finishes rendering,the cursor is left directly below the text.
This example also shows text flowing across pages following the margin box and otherbounding boxes.

text 'This text will flow to the next page. ' * 20
y_position = cursor
bounding_box([0, y_position], width: 200, height: 150) do
transparent(0.5) { stroke_bounds }text 'This text will flow along this bounding box we created for it. ' * 5endbounding_box([300, y_position], width: 200, height: 150) do
transparent(0.5) { stroke_bounds } # This will stroke on one page
text 'Now look what happens when the free flowing text reaches the end of a bounding box ' \"that is narrower than the margin box.#{' . ' * 200}It continues on the next page as if " \'the previous bounding box was cloned. If we want it to have the same border as the one on ' \'the previous page we will need to stroke the boundaries again.'transparent(0.5) { stroke_bounds } # And this will stroke on the nextend
move_cursor_to 200
span(350, position: :center) do
text 'Span is a different kind of bounding box as it lets the text flow gracefully onto the ' \"next page. It doesn't matter if the text started on the middle of the previous page, when " \"it flows to the next page it will start at the beginning.#{' _ ' * 500}" \'I told you it would start on the beginning of this page.'end

Example Output
This text will flow to the next page. This text will flow to the next page. This text will flow to the next
page. This text will flow to the next page. This text will flow to the next page. This text will flow to
the next page. This text will flow to the next page. This text will flow to the next page. This text willflow to the next page. This text will flow to the next page. This text will flow to the next page. ThisExample Output
text will flow to the next page. This text will flow to the next page. This text will flow to the next
page. This text will flow to the next page. This text will flow to the next page. This text will flow to
the next page. This text will flow to the next page. This text will flow to the next page. This text willflow to the next page.
This text will flow along this bounding
Now look what happens when thebox we created for it. This text will
free flowing text reaches the end of aflow along this bounding box webounding box that is narrower thancreated for it. This text will flow along
the margin box. . . . . . . . . . . . .
this bounding box we created for it.
. . . . . . . . . . . . . . . . . . . .
This text will flow along this bounding. . . . . . . . . . . . . . . . . . . .
box we created for it. This text will
. . . . . . . . . . . . . . . . . . . .
flow along this bounding box we. . . . . . . . . . . . . . . . . . . .
created for it.
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .

Example Output. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . It
continues on the next page as if the previous bounding box was cloned. If we want it to have thesame border as the one on the previous page we will need to stroke the boundaries again.

Span is a different kind of bounding box as it lets the text flow
gracefully onto the next page. It doesn't matter if the text started
on the middle of the previous page, when it flows to the next page
it will start at the beginning. _ _ _ _ _ _ _ _ _ _ _ _ _ _ __ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ __ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ __ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ __ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ __ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ __ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ __ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ __ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ __ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ __ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _Example Output_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ __ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ __ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ __ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ __ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ __ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ I told you it would starton the beginning of this page.

Positioned Text
text/positioned_text.rb

Sometimes we want the text on a specific position on the page. The text method just won't
help us.
There are two other methods for this task: draw_text and text_box.
draw_text is very simple. It will render text starting at the position provided to the :at option.
It won't flow to a new line even if it hits the document boundaries so it is best suited forshort text.
text_box gives us much more control over the output. Just provide :width and :height options
and the text will flow accordingly. Even if you don't provide a :width option the text will flow
to a new line if it reaches the right border.
Given that, text_box is the better option available.

draw_text "This draw_text line is absolute positioned. However don't " \'expect it to flow even if it hits the document border',at: [200, 170]
text_box 'This is a text box, you can control where it will flow by ' \'specifying the :height and :width options',
at: [50, 150],
height: 100,
width: 100
text_box 'Another text box with no :width option passed, so it will ' \'flow to a new line whenever it reaches the right margin. ',at: [200, 100]

Example Output

This draw_text line is absolute positioned. However don't expect it toThis is a text box,
you can control
where it will flowby specifying the:height and :width
options

Another text box with no :width option passed, so it will flow
to a new line whenever it reaches the right margin.

Text Box Overflow
text/text_box_overflow.rb

The text_box method accepts both :width and :height options. So what happens if the text
doesn't fit the box?
The default behavior is to truncate the text but this can be changed with the :overflow
option. Available modes are :expand (the box will increase to fit the text) and :shrink_to_fit(the text font size will be shrunk to fit).
If :shrink_to_fit mode is used with the :min_font_size option set, the font size will not be
reduced to less than the value provided even if it means truncating some text.
If the :disable_wrap_by_char is set to true then any text wrapping done while using the:shrink_to_fit mode will not break up the middle of words.

string = 'This is the sample text used for the text boxes. See how it ' \'behave with the various overflow options used.'text stringy_position = cursor - 20%i[truncate expand shrink_to_fit].each_with_index do |mode, i|
text_box string,at: [i * 150, y_position],width: 100,height: 50,
overflow: modeend
string = 'If the box is too small for the text, :shrink_to_fit ' \'can render the text in a really small font size.'move_down 120
text stringy_position = cursor - 20
[nil, 8, 10, 12].each_with_index do |value, index|
text_box string,at: [index * 150, y_position],width: 50,
height: 50,
overflow: :shrink_to_fit,
min_font_size: valueendExample Output
This is the sample text used for the text boxes. See how it behave with the various overflow options
used.
This is the sample text
This is the sample
used for the text boxes.
text used for the
See how it behave withtext boxes. See
the various overflow
options used.
how it behave withthe various
overflow options
used.
If the box is too small for the text, :shrink_to_fit can render the text in a really small font size.

This is the sample
text used for the
text boxes. See

If the box is too
small for the text,:shrink_to_fit canrender the text ina really small
font size.

If the box is
too small forthe text,:shrink_to_fit
can render

If the box
is toosmall forthe text,

If the box
is toosmall for

Text Box Excess
text/text_box_excess.rb

Whenever the text_box method truncates text, this truncated bit is not lost, it is the method
return value and we can take advantage of that.
We just need to take some precautions.
This example renders as much of the text as will fit in a larger font inside one text_box andthen proceeds to render the remaining text in the default size in a second text_box.

string = 'This is the beginning of the text. It will be cut somewhere and ' \'the rest of the text will proceed to be rendered this time by ' \'calling another method.' + (' . ' * 50)
y_position = cursor - 20
excess_text = text_box(
string,width: 300,
height: 50,
overflow: :truncate,
at: [100, y_position],size: 18,)
text_box(
excess_text,
width: 300,
at: [100, y_position - 100],)

Example Output
This is the beginning of the text. It will
be cut somewhere and the rest of thetext will proceed to be rendered this time by calling
another method. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Column Box
text/column_box.rb

The column_box method allows you to define columns that flow their contents from one
section to the next. You can have a number of columns on the page, and only when the lastcolumn overflows will a new page be created.

move_down 30
text 'The Prince', align: :center, size: 18
text 'Niccolò Machiavelli', align: :center, size: 14
move_down 30
column_box([0, cursor], columns: 2, width: bounds.width) dotext("#{<<~TEXT.gsub(/\s+/, ' ')}\n\n" * 5)
All the States and Governments by which men are or ever have been ruled,have been and are either Republics or Princedoms. Princedoms are either
hereditary, in which the sovereignty is derived through an ancient lineof ancestors, or they are new. New Princedoms are either wholly new, asthat of Milan to Francesco Sforza; or they are like limbs joined on to
the hereditary possessions of the Prince who acquires them, as theKingdom of Naples to the dominions of the King of Spain. The States thus
acquired have either been used to live under a Prince or have been free;and he who acquires them does so either by his own arms or by the arms ofothers, and either by good fortune or by merit.
TEXT
end

Example Output
The Prince
Niccolò Machiavelli
All the States and Governments by which men
are or ever have been ruled, have been and areeither Republics or Princedoms. Princedoms
are either hereditary, in which the sovereignty isderived through an ancient line of ancestors, orthey are new. New Princedoms are either wholly
new, as that of Milan to Francesco Sforza; orthey are like limbs joined on to the hereditary
possessions of the Prince who acquires them,as the Kingdom of Naples to the dominions of
the King of Spain. The States thus acquired
have either been used to live under a Prince or
have been free; and he who acquires themdoes so either by his own arms or by the armsof others, and either by good fortune or by
merit.
All the States and Governments by which men
are or ever have been ruled, have been and areeither Republics or Princedoms. Princedoms
are either hereditary, in which the sovereignty isderived through an ancient line of ancestors, orthey are new. New Princedoms are either wholly
new, as that of Milan to Francesco Sforza; orthey are like limbs joined on to the hereditary
possessions of the Prince who acquires them,as the Kingdom of Naples to the dominions of
the King of Spain. The States thus acquired
have either been used to live under a Prince or
have been free; and he who acquires themdoes so either by his own arms or by the armsof others, and either by good fortune or by
merit.
All the States and Governments by which men
are or ever have been ruled, have been and areeither Republics or Princedoms. Princedoms
are either hereditary, in which the sovereignty isderived through an ancient line of ancestors, orthey are new. New Princedoms are either wholly
new, as that of Milan to Francesco Sforza; orthey are like limbs joined on to the hereditary
possessions of the Prince who acquires them,as the Kingdom of Naples to the dominions of
the King of Spain. The States thus acquired
have either been used to live under a Prince or
have been free; and he who acquires themdoes so either by his own arms or by the armsof others, and either by good fortune or by
merit.
All the States and Governments by which men
are or ever have been ruled, have been and areeither Republics or Princedoms. Princedoms
are either hereditary, in which the sovereignty isderived through an ancient line of ancestors, orthey are new. New Princedoms are either wholly
new, as that of Milan to Francesco Sforza; orthey are like limbs joined on to the hereditary
possessions of the Prince who acquires them,as the Kingdom of Naples to the dominions of
the King of Spain. The States thus acquired
have either been used to live under a Prince or
have been free; and he who acquires themdoes so either by his own arms or by the armsof others, and either by good fortune or by
merit.
All the States and Governments by which men
are or ever have been ruled, have been and areeither Republics or Princedoms. Princedoms
are either hereditary, in which the sovereignty isderived through an ancient line of ancestors, orthey are new. New Princedoms are either wholly
new, as that of Milan to Francesco Sforza; orthey are like limbs joined on to the hereditary
possessions of the Prince who acquires them,as the Kingdom of Naples to the dominions of
the King of Spain. The States thus acquired
have either been used to live under a Prince or
have been free; and he who acquires themdoes so either by his own arms or by the armsof others, and either by good fortune or by
merit.

Fontstext/font.rb

The font method can be used in three different ways.
If we don't pass it any arguments it will return the current font being used to render text.
If we just pass it a font name it will use that font for rendering text through the rest of the
document.
It can also be used by passing a font name and a block. In this case the specified font will
only be used to render text inside the block.
The default font is Helvetica.

text "Let's see which font we are using: #{font.inspect}"font 'Times-Roman'text 'Written in Times.'font('Courier') do
text 'Written in Courier because we are inside the block.'end
text 'Written in Times again as we left the previous block.'text "Let's see which font we are using again: #{font.inspect}"font 'Helvetica'
text 'Back to normal.'Example Output
Let's see which font we are using: Prawn::Fonts::AFM< Helvetica: 12 >Written in Times.
Written in Courier because we are inside the block.
Written in Times again as we left the previous block.
Let's see which font we are using again: Prawn::Fonts::AFM< Times-Roman: 12 >Back to normal.

Font Size
text/font_size.rb

The font_size method works just like the font method.
In fact we can even use font with the :size option to declare which size we want.
Another way to change the font size is by supplying the :size option to the text methods.
The default font size is 12.

text "Let's see which is the current font_size: #{font_size.inspect}"font_size 16
text 'Yeah, something bigger!'
font_size(25) { text 'Even bigger!' }text 'Back to 16 again.'text 'Single line on 20 using the :size option.', size: 20
text 'Back to 16 once more.'font('Courier', size: 10) do
text 'Yeah, using Courier 10 courtesy of the font method.'endfont('Helvetica', size: 12)
text 'Back to normal'

Example Output
Let's see which is the current font_size: 12

Yeah, something bigger!

Even bigger!
Back to 16 again.

Single line on 20 using the :size option.
Back to 16 once more.
Yeah, using Courier 10 courtesy of the font method.

Back to normal

Font Style
text/font_style.rb

Most font families come with some styles other than normal. Most common are bold, italic
and bold_italic.
The style can be set the using the :style option, with either the font method which will set
the font and style for rest of the document, or with the inline text methods.

fonts = %w[Courier Helvetica Times-Roman]styles = %i[bold bold_italic italic normal]
fonts.each do |example_font|move_down 20
styles.each do |style|font example_font, style: styletext "I'm writing in #{example_font} (#{style})"end
end

Example Output
I'm writing in Courier (bold)
I'm writing in Courier (bold_italic)
I'm writing in Courier (italic)
I'm writing in Courier (normal)
I'm writing in Helvetica (bold)
I'm writing in Helvetica (bold_italic)
I'm writing in Helvetica (italic)
I'm writing in Helvetica (normal)
I'm writing in Times-Roman (bold)
I'm writing in Times-Roman (bold_italic)
I'm writing in Times-Roman (italic)
I'm writing in Times-Roman (normal)

Colortext/color.rb

The :color attribute can give a block of text a default color, in RGB hex format or 4-value
CMYK.

text 'Default color is black'move_down 25
text 'Changed to red', color: 'FF0000'
move_down 25
text 'CMYK color', color: [22, 55, 79, 30]
move_down 25
text("Also works with <color rgb='ff0000'>inline</color> formatting",
color: '0000FF',
inline_format: true,)

Example Output
Default color is black
Changed to red
CMYK color
Also works with inline formatting

Alignment
text/alignment.rb

Horizontal text alignment can be achieved by supplying the :align option to the text
methods. Available options are :left (default), :right, :center, and :justify.
Vertical text alignment can be achieved using the :valign option with the text methods.
Available options are :top (default), :center, and :bottom.
Both forms of alignment will be evaluated in the context of the current bounding_box.

text 'This text should be left aligned'
text 'This text should be centered', align: :centertext 'This text should be right aligned', align: :righty = cursor - 20
bounding_box([0, y], width: 250, height: 220) do
text 'This text is flowing from the left. ' * 4
move_down 15
text 'This text is flowing from the center. ' * 3, align: :center
move_down 15
text 'This text is flowing from the right. ' * 4, align: :rightmove_down 15
text 'This text is justified. ' * 6, align: :justify
transparent(0.5) { stroke_bounds }endbounding_box([270, y], width: 250, height: 220) do
text 'This text should be vertically top aligned'
text 'This text should be vertically centered', valign: :centertext 'This text should be vertically bottom aligned', valign: :bottom
transparent(0.5) { stroke_bounds }end

Example Output
This text should be left aligned
This text should be centered
This text should be right aligned
This text is flowing from the left. This text is
flowing from the left. This text is flowing fromthe left. This text is flowing from the left.

This text should be vertically top aligned

This text is flowing from the center. This text is
flowing from the center. This text is flowing
from the center.
This text is flowing from the right. This text is
flowing from the right. This text is flowing fromthe right. This text is flowing from the right.

This text should be vertically centered

This text is justified. This text is justified. This
text is justified. This text is justified. This text isjustified. This text is justified.
This text should be vertically bottom aligned

Leading
text/leading.rb

Leading is the additional space between lines of text.
The leading can be set using the default_leading method which applies to the rest of the
document or until it is changed, or inline in the text methods with the :leading option.
The default leading is 0.

string = 'Hey, what did you do with the space between my lines? ' * 8
text string, leading: 0
move_down 20
default_leading 5
text string
move_down 20
text string, leading: 10

Example Output
Hey, what did you do with the space between my lines? Hey, what did you do with the space
between my lines? Hey, what did you do with the space between my lines? Hey, what did you dowith the space between my lines? Hey, what did you do with the space between my lines? Hey,
what did you do with the space between my lines? Hey, what did you do with the space between
my lines? Hey, what did you do with the space between my lines?
Hey, what did you do with the space between my lines? Hey, what did you do with the space
between my lines? Hey, what did you do with the space between my lines? Hey, what did you dowith the space between my lines? Hey, what did you do with the space between my lines? Hey,
what did you do with the space between my lines? Hey, what did you do with the space between
my lines? Hey, what did you do with the space between my lines?
Hey, what did you do with the space between my lines? Hey, what did you do with the space
between my lines? Hey, what did you do with the space between my lines? Hey, what did you dowith the space between my lines? Hey, what did you do with the space between my lines? Hey,
what did you do with the space between my lines? Hey, what did you do with the space between
my lines? Hey, what did you do with the space between my lines?

Kerning and Character Spacing
text/kerning_and_character_spacing.rb

Kerning is the process of adjusting the spacing between characters in a proportional font. It
is usually done with specific letter pairs. We can switch it on and off if it is available with thecurrent font. Just pass a boolean value to the :kerning option of the text methods.
Character Spacing is the space between characters. It can be increased or decreased andwill have effect on the whole text. Just pass a number to the :character_spacing option fromthe text methods.

font_size(30) do
text_box 'With kerning:', kerning: true, at: [0, y - 40]
text_box 'Without kerning:', kerning: false, at: [0, y - 80]
text_box 'Tomato', kerning: true, at: [230, y - 40]
text_box 'Tomato', kerning: false, at: [230, y - 80]
text_box 'WAR', kerning: true, at: [360, y - 40]
text_box 'WAR', kerning: false, at: [360, y - 80]
text_box 'F.', kerning: true, at: [470, y - 40]
text_box 'F.', kerning: false, at: [470, y - 80]
end
move_down 80
string = 'What have you done to the space between the characters?'[-2, -1, 0, 0.5, 1, 2].each do |spacing|move_down 20
text "#{string} (character_spacing: #{spacing})",character_spacing: spacingendExample Output

With kerning:
Tomato WAR
Without kerning: Tomato WAR

F.
F.

Whathaveyoudonetothespacebetweenthecharacters?(character_spacing:-2)
What have you done to the space between the characters? (character_spacing: -1)
What have you done to the space between the characters? (character_spacing: 0)
What have you done to the space between the characters? (character_spacing: 0.5)
What have you done to the space between the characters? (character_spacing: 1)
What have you done to the space between the characters?
(character_spacing: 2)

Paragraph Indentation
text/paragraph_indentation.rb

Prawn strips all whitespace from the beginning and the end of strings so there are two ways
to indent paragraphs:
One is to use non-breaking spaces which Prawn won't strip. One shortcut to using them isthe Prawn::Text::NBSP.
The other is to use the :indent_paragraphs option with the text methods. Just pass a number
with the space to indent the first line in each paragraph.

# Using non-breaking spaces
text (' ' * 10) + ("This paragraph won't be indented. " * 10) +"\n#{Prawn::Text::NBSP * 10}" + ('This one will with NBSP. ' * 10)
move_down 20
text "#{'This paragraph will be indented. ' * 10}\n#{'This one will too. ' * 10}",indent_paragraphs: 60
move_down 20
text 'FROM RIGHT TO LEFT:'
text "#{'This paragraph will be indented. ' * 10}\n#{'This one will too. ' * 10}",indent_paragraphs: 60,
direction: :rtl

Example Output
This paragraph won't be indented. This paragraph won't be indented. This paragraph won't be
indented. This paragraph won't be indented. This paragraph won't be indented. This paragraphwon't be indented. This paragraph won't be indented. This paragraph won't be indented. This
paragraph won't be indented. This paragraph won't be indented.
This one will with NBSP. This one will with NBSP. This one will with NBSP. This one will with
NBSP. This one will with NBSP. This one will with NBSP. This one will with NBSP. This one will with
NBSP. This one will with NBSP. This one will with NBSP.
This paragraph will be indented. This paragraph will be indented. This paragraph will be
indented. This paragraph will be indented. This paragraph will be indented. This paragraph will be
indented. This paragraph will be indented. This paragraph will be indented. This paragraph will be
indented. This paragraph will be indented.
This one will too. This one will too. This one will too. This one will too. This one will too.
This one will too. This one will too. This one will too. This one will too. This one will too.
FROM RIGHT TO LEFT:
eb lliw hpargarap sihT .detnedni eb lliw hpargarap sihT .detnedni eb lliw hpargarap sihT
eb lliw hpargarap sihT .detnedni eb lliw hpargarap sihT .detnedni eb lliw hpargarap sihT .detnedni
eb lliw hpargarap sihT .detnedni eb lliw hpargarap sihT .detnedni eb lliw hpargarap sihT .detnedni.detnedni eb lliw hpargarap sihT .detnedni.oot lliw eno sihT .oot lliw eno sihT .oot lliw eno sihT .oot lliw eno sihT .oot lliw eno sihT.oot lliw eno sihT .oot lliw eno sihT .oot lliw eno sihT .oot lliw eno sihT .oot lliw eno sihT

Rotationtext/rotation.rb

Rotating text is best avoided on free flowing text, so this example will only use the text_box
method as we can have much more control over its output.
To rotate text all we need to do is use the :rotate option passing an angle in degrees and an
optional :rotate_around to indicate the origin of the rotation (the default is :upper_left).

width = 100
height = 60
angle = 30
x = 200
y = cursor - 30
stroke_rectangle [0, y], width, heighttext_box('This text was not rotated',
at: [0, y],width: width,height: height,)
stroke_rectangle [0, y - 100], width, heighttext_box('This text was rotated around the center',at: [0, y - 100],
width: width,height: height,
rotate: angle,
rotate_around: :center,)%i[lower_left upper_left lower_right upper_right].each_with_index do |corner, index|
y -= 100 if index == 2
stroke_rectangle [x + ((index % 2) * 200), y], width, heighttext_box("This text was rotated around the #{corner} corner.",
at: [x + ((index % 2) * 200), y],width: width,height: height,
rotate: angle,
rotate_around: corner,)end

Example Output
This text was not
rotatedshewa nd tt
ex ou
is t d arhT ate
rot nterce

as d thewtn.
tex arou rnersio
Th ated eft crot er_llowshewa nd t er.
tex ou rn
is t d ar t cohT ate ighrot er_r
low

as d thewtnr.
tex arou ornesi
Th ated left crot per_upshewa nd tt
ex ou
is t d ar hthT ate rig
rot per_up rner.
coInline Formatting
text/inline.rb

Inline formatting gives you the option to format specific portions of a text. It uses HTMLesque syntax inside the text string. Supported tags are: b (bold), i (italic), u (underline),
strikethrough, sub (subscript), sup (superscript).
The following tags accept specific attributes: font accepts size, name, and character_spacing;color accepts rgb and set of c, m, y, and k; link accepts href for external links.

%w[b i u strikethrough sub sup].each do |tag|text "Just your regular text <#{tag}>except this portion</#{tag}> " \"is using the #{tag} tag",
inline_format: true# move_down 10
endtext "This <font size='18'>line</font> uses " \"<font name='Courier'>all the font tag</font> attributes in " \"<font character_spacing='2'>a single line</font>. ",inline_format: true# move_down 10
text "Coloring in <color rgb='FF00FF'>both RGB</color> " \"<color c='100' m='0' y='0' k='0'>and CMYK</color>",inline_format: true# move_down 10
text 'This an external link to the ' \"<u><link href='https://prawnpdf.org/'>Prawn home page" \'</link></u>',inline_format: true

Example Output
Just your regular text except this portion is using the b tag
Just your regular text except this portion is using the i tag
Just your regular text except this portion is using the u tag
Just your regular text except this portion is using the strikethrough tag
Just your regular text except this portion is using the sub tag
Just your regular text except this portion is using the sup tag
This line uses all the font tag attributes in a s i n g l e l i n e.
Coloring in both RGB and CMYKThis an external link to the Prawn home pageFormatted Text
text/formatted_text.rb

There are two other text methods available: formatted_text and formatted_text_box.
These are useful when the provided text has numerous portions that need to be formatted
differently. As you might imply from their names the first should be used for free flowing
text just like the text method and the last should be used for positioned text just like
text_box.
The main difference between these methods and the text and text_box methods is how the
text is provided. The formatted_text and formatted_text_box methods accept an array ofhashes. Each hash must provide a :text option which is the text string and may provide thefollowing options: :styles (an array of symbols), :size (the font size), :character_spacing(additional space between the characters), :font (the name of a registered font), :color (thesame input accepted by fill_color and stroke_color), :link (an URL to create a link), and:local (a link to a local file).

formatted_text [{ text: 'Some bold. ', styles: [:bold] },{ text: 'Some italic. ', styles: [:italic] },{ text: 'Bold italic. ', styles: %i[bold italic] },{ text: 'Bigger Text. ', size: 20 },{ text: 'More spacing. ', character_spacing: 3 },{ text: 'Different Font. ', font: 'Courier' },{ text: 'Some coloring. ', color: 'FF00FF' },{ text: 'Link to the home page. ', color: '0000FF', link: 'https://prawnpdf.org/' },{ text: 'Link to a local file. ', color: '0000FF', local: 'README.md' },]formatted_text_box([{ text: 'Just your regular' },{ text: ' text_box ', font: 'Courier' },{text: 'with some additional formatting options added to the mix.',
color: [50, 100, 0, 0],styles: [:italic],},],
at: [100, 100],
width: 200,
height: 100,)

Example Output
Bigger Text.

Some bold. Some italic. Bold italic.
M o r e s p a c i n g . Different
Font. Some coloring. Link to the home page. Link to a local file.

Just your regular text_box withsome additional formatting options
added to the mix.

Formatted Text Callbacks
text/formatted_callbacks.rb

The :callback option is also available for the formatted text methods.
This option accepts an object (or array of objects) on which two methods will be called if
defined: render_behind and render_in_front. They are called before and after rendering thetext fragment and are passed the fragment as an argument.
This example defines two new callback classes and provide callback objects for theformatted_text.

class HighlightCallback
def initialize(options)@color, @document = options.values_at(:color, :document)
end
def render_behind(fragment)
original_color = @document.fill_color@document.fill_color = @color@document.fill_rectangle(fragment.top_left, fragment.width, fragment.height)@document.fill_color = original_color
end
endclass ConnectedBorderCallback
def initialize(options)@radius, @document = options.values_at(:radius, :document)
end
def render_in_front(fragment)
points = [fragment.top_left, fragment.top_right, fragment.bottom_right, fragment.bottom_left]@document.stroke_polygon(*points)
points.each { |point| @document.fill_circle(point, @radius) }end
end
highlight = HighlightCallback.new(color: 'ffff00', document: self)border = ConnectedBorderCallback.new(radius: 2.5, document: self)
formatted_text([{ text: 'hello', callback: highlight },{ text: '' },{ text: 'world', callback: border },{ text: '' },{ text: 'hello world', callback: [highlight, border] },],size: 20,)

Example Output
hello

world

hello world

Text Rendering Modes
text/rendering_and_color.rb

You have already seen how to set the text color using both inline formatting and the format
text methods. There is another way by using the graphics methods fill_color andstroke_color.
When reading the graphics reference you learned about fill and stroke. If you haven't read it
before, read it now before continuing.
Text can be rendered by being filled (the default mode) or just stroked, or both filled andstroked. This can be set using the text_rendering_mode method or the :mode option on the text
methods.

fill_color '00ff00'
stroke_color '0000ff'
font_size(40) do# normal rendering mode: filltext 'This text is filled with green.'# inline rendering mode: stroke
text 'This text is stroked with blue', mode: :stroke# block rendering mode: fill and stroke
text_rendering_mode(:fill_stroke) do
text 'This text is filled with green and stroked with blue'
end
end

Example Output

This text is filled with green.
This text is stroked with blue
This text is filled with green
and stroked with blue

Text Box Extensions
text/text_box_extensions.rb

We've already seen one way of using text boxes with the text_box method. Turns out thismethod is just a convenience for using the Prawn::Text::Box class as it creates a new object
and call render on it.
Knowing that any extensions we add to Prawn::Text::Box will take effect when we use thetext_box

method.

To

add

an

extensionall

Prawn::Text::Box.extensions array with a module.

module TriangleBox
def available_widthheight + 25
end
end
y_position = cursor
width = 100
height = 100
Prawn::Text::Box.extensions << TriangleBox
stroke_rectangle([0, y_position], width, height)
text_box('A' * 100,
at: [0, y_position],width: width,height: height,)
Prawn::Text::Formatted::Box.extensions << TriangleBox
stroke_rectangle([200, y_position], width, height)
formatted_text_box([{ text: 'A' * 100, color: '009900' }],at: [200, y_position],width: width,height: height,)# Here we clear the extensions array
Prawn::Text::Box.extensions.clear
Prawn::Text::Formatted::Box.extensions.clear

we

need

to

do

is

appendtheExample Output
AAA
AAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAA

AAA
AAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAASingle Usage Fonts
text/single_usage.rb

The PDF format has some built-in font support. If you want to use other fonts in Prawn you
need to embed the font file.
Doing this for a single font is extremely simple. Remember the Styling font example?
Another use of the font method is to provide a font file path and the font will be embeddedin the document and set as the current font.
This is reasonable if a font is used only once, but, if a font used several times, providing thepath each time it is used becomes cumbersome. The example on the next page shows abetter way to deal with fonts which are used several times in a document.

# Using a TTF font file
font("#{Prawn::ManualBuilder::DATADIR}/fonts/DejaVuSans.ttf") do
text 'Written with the DejaVu Sans TTF font.'
end
move_down 20
text 'Written with the default font.'
move_down 20# Using an DFONT font file
font("#{Prawn::ManualBuilder::DATADIR}/fonts/Panic+Sans.dfont") do
text 'Written with the Panic Sans DFONT font'end
move_down 20
text 'Written with the default font once more.'

Example Output
Written with the DejaVu Sans TTF font.
Written with the default font.
Written with the Panic Sans DFONT font
Written with the default font once more.

Registering Font Families
text/registering_families.rb

Registering font families will help you when you want to use a font over and over or if you
would like to take advantage of the :style option of the text methods and the b and i tags
when using inline formatting.
To register a font family update the font_families hash with the font path for each style youwant to use.

# Registering a single external font
font_families.update('DejaVu Sans' => {normal: "#{Prawn::ManualBuilder::DATADIR}/fonts/DejaVuSans.ttf",},)
font('DejaVu Sans') do
text 'Using the DejaVu Sans font providing only its name to the font method'end
move_down 20# Registering a DFONT package
font_path = "#{Prawn::ManualBuilder::DATADIR}/fonts/Panic+Sans.dfont"
font_families.update('Panic Sans' => {normal: { file: font_path, font: 'PanicSans' },italic: { file: font_path, font: 'PanicSans-Italic' },bold: { file: font_path, font: 'PanicSans-Bold' },bold_italic: { file: font_path, font: 'PanicSans-BoldItalic' },},)font 'Panic Sans'
text 'Also using Panic Sans by providing only its name'move_down 20
text 'Taking <b>advantage</b> of the <i>inline formatting</i>',inline_format: true
move_down 20%i[bold bold_italic italic normal].each do |style|text "Using the #{style} style option.", style: stylemove_down 10
endExample Output
Using the DejaVu Sans font providing only its name to the font method
Also using Panic Sans by providing only its nameTaking advantage of the inline formatting
Using the bold style option.
Using the bold_italic style option.
Using the italic style option.
Using the normal style option.

UTF-8text/utf8.rb

Multilingualization isn't much of a problem on Prawn as its default encoding is UTF-8. The
only thing you need to worry about is if the font support the glyphs of your language.

text 'Take this example, a simple Euro sign:'
text '€', size: 32
move_down 20
text 'This works, because €is one of the few ' \'non-ASCII glyphs supported in PDF built-in fonts.'
move_down 20
text 'For full internationalized text support, we need to use external fonts:'
move_down 20
font("#{Prawn::ManualBuilder::DATADIR}/fonts/DejaVuSans.ttf") do
text 'ὕαλον ϕαγεῖν δύναμαι· τοῦτο οὔ με βλάπτει.'text 'There you go.'end

Example Output
Take this example, a simple Euro sign:€This works, because € is one of the few non-ASCII glyphs supported in PDF built-in fonts.
For full internationalized text support, we need to use external fonts:ὕαλον ϕαγεῖν δύναμαι· τοῦτο οὔ με βλάπτει.
There you go.

Line Wrapping
text/line_wrapping.rb

Line wrapping happens on white space or hyphens. Soft hyphens can be used to indicate
where words can be hyphenated. Non-breaking spaces can be used to display space
without allowing for a break.
For writing styles that do not make use of spaces, the zero width space serves to mark wordboundaries. Zero width spaces are available only with external fonts.

text "Hard hyphens:\n" \'Slip-sliding away, slip sliding awaaaay. You know the ' \"nearer your destination the more you're slip-sliding away."
move_down 20
shy = Prawn::Text::SHY
text "Soft hyphens:\n" \"Slip slid#{shy}ing away, slip slid#{shy}ing away. You know the " \"nearer your destinat#{shy}ion the more you're slip slid#{shy}ing away."
move_down 20
nbsp = Prawn::Text::NBSP
text "Non-breaking spaces:\n" \"Slip#{nbsp}sliding away, slip#{nbsp}sliding awaaaay. You know the " \"nearer your destination the more you're slip#{nbsp}sliding away."
move_down 20
font_families.update('Jigmo' => { normal: "#{Prawn::ManualBuilder::DATADIR}/fonts/Jigmo.ttf" })font('Jigmo', size: 16) dotext "No word boundaries:\n更可怕的是、同质化竞争对手可以按照URL中后面这个ID来遍历" \'您的DB中的内容、写个小爬虫把你的页面上的关键信息顺次爬下来也不是什么难事、这样的话、' \'你就非常被动了。更可怕的是，同质化竞争对手可以按照URL中后面这个ID来遍历您的DB中的内容、' \'写个小爬虫把你的页面上的关键信息顺次爬下来也不是什么难事、这样的话、你就非常被动了。'
move_down 20
zwsp = Prawn::Text::ZWSP
text "Invisible word boundaries:\n更#{zwsp}可怕的#{zwsp}是、#{zwsp}同质化#{zwsp}竞争" \"#{zwsp}对#{zwsp}手#{zwsp}可以#{zwsp}按照#{zwsp}URL#{zwsp}中#{zwsp}后面#{zwsp}这个" \"#{zwsp}ID#{zwsp}来#{zwsp}遍历#{zwsp}您的#{zwsp}DB#{zwsp}中的#{zwsp}内容、#{zwsp}写个" \"#{zwsp}小爬虫#{zwsp}把#{zwsp}你的#{zwsp}页面#{zwsp}上的#{zwsp}关#{zwsp}键#{zwsp}信" \"#{zwsp}息顺#{zwsp}次#{zwsp}爬#{zwsp}下来#{zwsp}也#{zwsp}不是#{zwsp}什么#{zwsp}难事、" \"#{zwsp}这样的话，#{zwsp}你#{zwsp}就#{zwsp}非常#{zwsp}被动了。#{zwsp}更#{zwsp}可怕的" \"#{zwsp}是、#{zwsp}同质化#{zwsp}竞争#{zwsp}对#{zwsp}手#{zwsp}可以#{zwsp}按照#{zwsp}URL" \"#{zwsp}中#{zwsp}后面#{zwsp}这个#{zwsp}ID#{zwsp}来#{zwsp}遍历#{zwsp}您的#{zwsp}DB#{zwsp}" \"中的#{zwsp}内容、#{zwsp}写个#{zwsp}小爬虫#{zwsp}把#{zwsp}你的#{zwsp}页面#{zwsp}上的" \"#{zwsp}关#{zwsp}键#{zwsp}信#{zwsp}息顺#{zwsp}次#{zwsp}爬#{zwsp}下来#{zwsp}也#{zwsp}不是" \"#{zwsp}什么#{zwsp}难事、#{zwsp}这样的话、#{zwsp}你#{zwsp}就#{zwsp}非常#{zwsp}被动了。"
endExample Output
Hard hyphens:
Slip-sliding away, slip sliding awaaaay. You know the nearer your destination the more you're slipsliding away.
Soft hyphens:
Slip sliding away, slip sliding away. You know the nearer your destination the more you're slip sliding away.
Non-breaking spaces:
Slip sliding away, slip sliding awaaaay. You know the nearer your destination the more you're
slip sliding away.

No word boundaries:
更可怕的是、同质化竞争对手可以按照URL中后面这个ID来遍历您的DB中的
内容、写个小爬虫把你的页面上的关键信息顺次爬下来也不是什么难事、这样的话、你就非常被动了。更可怕的是，同质化竞争对手可以按照URL中后面这个ID来遍历您的DB中的内容、写个小爬虫把你的页面上的关键信息
顺次爬下来也不是什么难事、这样的话、你就非常被动了。
Invisible word boundaries:
更可怕的是、同质化竞争对手可以按照URL中后面这个ID来遍历您的DB中的
内容、写个小爬虫把你的页面上的关键信息顺次爬下来也不是什么难事、
这样的话，你就非常被动了。更可怕的是、同质化竞争对手可以按照URL中后面这个ID来遍历您的DB中的内容、写个小爬虫把你的页面上的关键信
息顺次爬下来也不是什么难事、这样的话、你就非常被动了。

Right-to-Left Text
text/right_to_left_text.rb

Prawn can be used with right-to-left text. The direction can be set document-wide, on
particular text, or on a text-box. Setting the direction to :rtl automatically changes thedefault alignment to :right.
You can even override direction on an individual fragment. The one caveat is that two
fragments going against the main direction cannot be placed next to each other without
appearing in the wrong order.
Writing bidirectional text that combines both left-to-right and right-to-left languages is easyusing the bidi Ruby Gem and its render_visual function. See https://github.com/elad/rubybidi for instructions and an example using Prawn.

# set the direction document-wide
self.text_direction = :rtl
font("#{Prawn::ManualBuilder::DATADIR}/fonts/Jigmo.ttf", size: 16) do
long_text = '写个小爬虫把你的页面上的关键信息顺次爬下来也不是什么难事写个小爬虫把你的页面上的' \'关键信息顺次爬下来也不是什么难事写个小爬虫把你的页面上的关键信息顺次爬下来也不是什么难事写' \'个小'text long_text
move_down 20
text 'You can override the document direction.', direction: :ltr
move_down 20
formatted_text [{ text: '更可怕的是，同质化竞争对手可以按照' },{ text: 'URL', direction: :ltr },{ text: '中后面这个' },{ text: 'ID', direction: :ltr },{ text: '来遍历您的' },{ text: 'DB', direction: :ltr },{text: '中的内容、写个小爬虫把你的页面上的关键信息顺次爬下来也不是什么难事、这样的话、' \'你就非常被动了。',},]
move_down 20
formatted_text [{ text: '更可怕的是、同质化竞争对手可以按照' },{ text: 'this', direction: :ltr },{ text: "won't", direction: :ltr, size: 24 },{ text: 'work', direction: :ltr },{ text: '中的内容、写个小爬虫把你的页面上的关键信息顺次爬下来也不是什么难事' },]endExample Output
爬小个写事难么什是不也来下爬次顺息信键关的上面页的你把虫爬小个写的你把虫爬小个写事难么什是不也来下爬次顺息信键关的上面页的你把虫
小个写事难么什是不也来下爬次顺息信键关的上面页
You can override the document direction.
的中DB的您历遍来ID个这面后中URL照按以可手对争竞化质同，是的怕可更、事难么什是不也来下爬次顺息信键关的上面页的你把虫爬小个写、容内。了动被常非就你、话的样这个写、容内的中workwon'tthis照按以可手对争竞化质同、是的怕可更事难么什是不也来下爬次顺息信键关的上面页的你把虫爬小Fallback Fonts
text/fallback_fonts.rb

Prawn enables the declaration of fallback fonts for those glyphs that may not be present in
the desired font. Use the :fallback_fonts option with any of the text or text box methods, or
set fallback_fonts document-wide.

jigmo_file = "#{Prawn::ManualBuilder::DATADIR}/fonts/Jigmo.ttf"
font_families['Jigmo'] = { normal: { file: jigmo_file, font: 'Jigmo' } }panic_sans_file = "#{Prawn::ManualBuilder::DATADIR}/fonts/Panic+Sans.dfont"
font_families['Panic Sans'] = { normal: { file: panic_sans_file, font: 'PanicSans' } }font('Panic Sans') do
text('When fallback fonts are included, each glyph will be rendered ' \'using the first font that includes the glyph, starting with the ' \'current font and then moving through the fallback fonts from left ' \'to right.' \"\n\n" \"hello ƒ 你好\n再见 ƒ goodbye",
fallback_fonts: %w[Times-Roman Jigmo],)
end
move_down 20
formatted_text([{ text: 'Fallback fonts can even override' },{ text: 'fragment fonts (你好)', font: 'Times-Roman' },],fallback_fonts: %w[Times-Roman Jigmo],)

Example Output
When fallback fonts are included, each glyph will be rendered using thefirst font that includes the glyph, starting with the current font andthen moving through the fallback fonts from left to right.
hello ƒ 你好
再见 ƒ goodbye
Fallback fonts can even overridefragment fonts (你好)Windown-1252 Charset
text/win_ansi_charset.rb

Here's a list of all of the glyphs that can be rendered by Adobe's built in fonts, along with
their character widths and WinAnsi codes. Be sure to pass these glyphs as UTF-8, andPrawn will transcode them for you.

font 'Helvetica', size: 10
x = 0
y = bounds.top
fields = [[20, :right], [8, :left], [12, :center], [30, :right], [8, :left], [0, :left]]Prawn::Encoding::WinAnsi::CHARACTERS.each_with_index do |name, index|
next if name == '.notdef'
y -= font_size
if y < font_size
y = bounds.top - font_size
x += 170
end
code = format('%<index>d.', index: index)
char = index.chr.force_encoding(::Encoding::Windows_1252)
width = 1000 * width_of(char, size: font_size) / font_size
size = format('%<width>d', width: width)
data = [code, nil, char, size, nil, name]dx = xfields.zip(data).each do |(total_width, align), field|
if fieldwidth = width_of(field, size: font_size)case alignwhen :left then offset = 0
when :right then offset = total_width - width
when :center then offset = (total_width - width) / 2
end
text_box(field.dup.force_encoding('windows-1252').encode('UTF-8'), at: [dx + offset, y])end
dx += total_width
end
endExample Output
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.

278 space!
278 exclam"
355 quotedbl#
556 numbersign$
556 dollar% 889 percent&
667 ampersand'
191 quotesingle(
333 parenleft)
333 parenright*389 asterisk+
584 plus,
278 comma
333 hyphen.
278 period/
278 slash0556 zero1556 one2
556 two
3
556 three4556 four5
556 five
6
556 six
7556 seven8
556 eight
9556 nine:
278 colon;
278 semicolon<
584 less=
584 equal>
584 greater?
556 question@ 1015 at
A
667 A
B
667 B
C
722 C
D
722 D
E
667 E
F
611 F
G
778 G
H
722 H
I
278 I
J
500 J
K
667 K
L
556 L
M
833 M
N
722 N
O
778 O
P
667 P
Q
778 Q
R
722 R
S
667 S
T
611 T
U
722 U
V
667 V
W 944 W
X
667 X
Y
667 Y
Z
611 Z
[
278 bracketleft\
278 backslash]
278 bracketright^469 asciicircum_
556 underscore`
333 grave
a
556 a
b
556 b
c
500 c
d
556 d
e
556 e
f
278 f
g
556 g
h
556 h
i
222 i
j
222 j

107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
128.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
142.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
183.
184.
185.
186.
187.

k
l
m
n
o
p
q
r
s
t
u
v
w
x
y
z
{
|
}
~
€
‚
ƒ
„
…
†
‡
ˆ
‰
Š
‹
Œ
Ž
‘
’
“
”
•
–
—
˜
™
š
›
œ
ž
Ÿ
¡
¢
£
¤
¥
¦
§
¨
©
ª
«
¬
®
¯
°
±
²
³
´
µ
¶
·
¸
¹
º
»

500
222
833
556
556
556
556
333
500
278
556
500
722
500
500
500
334
260
334
584
556
222
556
333
1000
556
556
333
1000
667
333
1000
611
222
222
333
333
350
556
1000
333
1000
500
333
944
500
500
278
333
556
556
556
556
260
556
333
737
370
556
584
333
737
333
400
584
333
333
333
556
537
278
333
333
365
556

k
l
m
n
o
p
q
r
s
t
u
v
w
x
y
z
braceleftbar
bracerightasciitildeEuroquotesinglbase
florinquotedblbase
ellipsis
daggerdaggerdbl
circumflex
perthousand
Scaronguilsinglleft
OE
Zcaronquoteleft
quoterightquotedblleft
quotedblrightbulletendashemdashtilde
trademarkscaronguilsinglrightoezcaronydieresis
spaceexclamdown
centsterling
currencyyenbrokenbar
sectiondieresis
copyright
ordfeminine
guillemotleft
logicalnot
hyphenregistered
macrondegreeplusminus
twosuperior
threesuperior
acutemuparagraph
periodcenteredcedillaonesuperior
ordmasculine
guillemotright

188.
189.
190.
191.
192.
193.
194.
195.
196.
197.
198.
199.
200.
201.
202.
203.
204.
205.
206.
207.
208.
209.
210.
211.
212.
213.
214.
215.
216.
217.
218.
219.
220.
221.
222.
223.
224.
225.
226.
227.
228.
229.
230.
231.
232.
233.
234.
235.
236.
237.
238.
239.
240.
241.
242.
243.
244.
245.
246.
247.
248.
249.
250.
251.
252.
253.
254.
255.

¼
834 onequarter½
834 onehalf¾
834 threequarters¿
611 questiondownÀ
667 AgraveÁ
667 AacuteÂ
667 Acircumflex
Ã
667 AtildeÄ
667 Adieresis
Å
667 AringÆ 1000 AE
Ç
722 CcedillaÈ
667 Egrave
É
667 EacuteÊ
667 Ecircumflex
Ë
667 EdieresisÌ
278 IgraveÍ
278 IacuteÎ
278 Icircumflex
Ï
278 Idieresis
Ð
722 EthÑ
722 NtildeÒ
778 OgraveÓ
778 OacuteÔ
778 Ocircumflex
Õ
778 OtildeÖ
778 Odieresis
×
584 multiplyØ
778 OslashÙ
722 UgraveÚ
722 UacuteÛ
722 Ucircumflex
Ü
722 Udieresis
Ý
667 YacuteÞ
667 Thornß
611 germandbls
à
556 agraveá
556 aacuteâ
556 acircumflex
ã
556 atildeä
556 adieresis
å
556 aringæ 889 aeç
500 ccedillaè
556 egraveé556 eacuteê
556 ecircumflex
ë
556 edieresis
ì
278 igraveí
278 iacuteî
278 icircumflex
ï
278 idieresis
ð556 ethñ
556 ntildeò
556 ograveó
556 oacuteô
556 ocircumflex
õ
556 otildeö
556 odieresis
÷584 divideø
611 oslashù
556 ugraveú
556 uacuteû
556 ucircumflex
ü
556 udieresis
ý
500 yacuteþ
556 thornÿ
500 ydieresis

Bounding Box

Bounding boxes are the basic containers for structuring the content flow. Even being low
level building blocks sometimes their simplicity is very welcome.
The examples show:• How to create bounding boxes with specific dimensions• How to inspect the current bounding box for its coordinates• Stretchy bounding boxes• Nested bounding boxes• Indent blocks

Bounding Box Creation
bounding_box/creation.rb

If you've read the basic concepts examples you probably know that the origin of a page ison the bottom left corner and that the content flows from top to bottom.
You also know that a Bounding Box is a structure for helping the content flow.
A bounding box can be created with the bounding_box method. Just provide the top left
corner, a required :width option and an optional :height.

bounding_box([200, cursor - 100], width: 200, height: 100) do
text 'Just your regular bounding box'
transparent(0.5) { stroke_bounds }end

Example Output

Just your regular bounding box

Bounding Box Creation
bounding_box/bounds.rb

The bounds method returns the current bounding box. This is useful because thePrawn::BoundingBox exposes some nice boundary helpers.
top, bottom, left and right methods return the bounding box boundaries relative to its
translated origin. top_left, top_right, bottom_left and bottom_right return those boundaries
pairs inside arrays.
All these methods have an "absolute" version like absolute_right. The absolute version
returns the same boundary relative to the page absolute coordinates.
The following snippet shows the boundaries for the margin box side by side with theboundaries for a custom bounding box.

def print_coordinates
text("top: #{bounds.top}")text("bottom: #{bounds.bottom}")text("left: #{bounds.left}")text("right: #{bounds.right}")
move_down(10)text("absolute top: #{Float(bounds.absolute_top).round(2)}")text("absolute bottom: #{Float(bounds.absolute_bottom).round(2)}")text("absolute left: #{Float(bounds.absolute_left).round(2)}")text("absolute right: #{Float(bounds.absolute_right).round(2)}")end
move_down 20
text 'Margin box bounds:'
move_down 5
print_coordinates
bounding_box([250, cursor + 140], width: 200, height: 150) do
text 'This bounding box bounds:'
move_down 5
print_coordinatestransparent(0.5) { stroke_bounds }end

Example Output
Margin box bounds:

This bounding box bounds:

top: 769.89
bottom: 0
left: 0right: 523.28

top: 150
bottom: 0
left: 0right: 200

absolute top: 805.89
absolute bottom: 36.0
absolute left: 36.0
absolute right: 559.28

absolute top: 786.04
absolute bottom: 636.04
absolute left: 286.0
absolute right: 486.0

Stretchy Bounding Box
bounding_box/stretchy.rb

Bounding Boxes accept an optional :height parameter. Unless it is provided the boundingbox will be stretchy. It will expand the height to fit all content generated inside it.

y_position = cursor
bounding_box([0, y_position], width: 200, height: 100) do
text 'This bounding box has a height of 100. If this text gets too large ' \'it will flow to the next page.'transparent(0.5) { stroke_bounds }endbounding_box([300, y_position], width: 200) do
text 'This bounding box has variable height. No matter how much text is ' \'written here, the height will expand to fit.'
text ' _' * 100
text ' *' * 100
transparent(0.5) { stroke_bounds }end

Example Output
This bounding box has a height of100. If this text gets too large it willflow to the next page.

This bounding box has variableheight. No matter how much text iswritten here, the height will expand tofit.
____________________________________________________________________________________________________****************************************************************************************************

Nesting Bounding Boxes
bounding_box/nesting.rb

Normally when we provide the top left corner of a bounding box we express the coordinates
relative to the margin box. This is not the case when we have nested bounding boxes. Once
nested the inner bounding box coordinates are relative to the outer bounding box.
This example shows some nested bounding boxes with fixed and stretchy heights. Note how
the cursor method returns coordinates relative to the current bounding box.

def box_content(string)text(string)transparent(0.5) { stroke_bounds }end
gap = 20
bounding_box([50, cursor], width: 400, height: 200) do
box_content('Fixed height')
bounding_box([gap, cursor - gap], width: 300) do
text 'Stretchy height'
bounding_box([gap, bounds.top - gap], width: 100) do
text 'Stretchy height'
transparent(0.5) dodash(1)stroke_bounds
undashend
endbounding_box([gap * 7, bounds.top - gap], width: 100, height: 50) do
box_content('Fixed height')
endtransparent(0.5) dodash(1)stroke_bounds
undashend
endbounding_box([gap, cursor - gap], width: 300, height: 50) do
box_content('Fixed height')
end
endExample Output
Fixed height
Stretchy height
Stretchy height

Fixed height

Fixed heightBounding Box Indentation
bounding_box/indentation.rb

Sometimes you just need to indent a portion of the contents of a bounding box, and using anested bounding box is pure overkill. The indent method is what you might need.
Just provide a number for it to indent all content generated inside the block.

text 'No indentation on the margin box.'
indent(20) do
text 'Some indentation inside an indent block.'end
move_down 20
bounding_box([50, cursor], width: 400, height: cursor) dotransparent(0.5) { stroke_bounds }move_down 10
text 'No indentation inside this bounding box.'
indent(40) do
text 'Inside an indent block. And so is this horizontal line:'stroke_horizontal_rule
end
move_down 10
text 'No indentation'
move_down 20
indent(60) do
text 'Another indent block.'bounding_box([0, cursor], width: 200) do
text 'Note that this bounding box coordinates are relative to the indent block'transparent(0.5) { stroke_bounds }
end
end
end

Example Output
No indentation on the margin box.
Some indentation inside an indent block.

No indentation inside this bounding box.
Inside an indent block. And so is this horizontal line:
No indentation
Another indent block.
Note that this bounding box
coordinates are relative to the indentblockCanvasbounding_box/canvas.rb

The origin example already mentions that a new document already comes with a marginbox whose bottom left corner is used as the origin for calculating coordinates.
What has not been told is that there is one helper for "bypassing" the margin box: canvas.
This method is a shortcut for creating a bounding box mapped to the absolute coordinatesand evaluating the code inside it.
The following snippet draws a circle on each of the four absolute corners.

canvas do
fill_circle [bounds.left, bounds.top], 30
fill_circle [bounds.right, bounds.top], 30
fill_circle [bounds.right, bounds.bottom], 30
fill_circle [0, 0], 30
end

Example Output

Recursive Boxes
bounding_box/recursive_boxes.rb

This example is mostly just for fun, and shows how nested bounding boxes can simplify
calculations. See the "Bounding Box" section of the manual for more basic uses.

def combine(horizontal_span, vertical_span)vertical_span.flat_map do |y|horizontal_span.zip([y] * horizontal_span.size)
end
end
def recurse_bounding_box(max_depth = 4, depth = 1)
width = (bounds.width - 15) / 2
height = (bounds.height - 15) / 2
left_top_corners = combine([5, bounds.right - width - 5], [bounds.top - 5, height + 5])
left_top_corners.each do |lt|bounding_box(lt, width: width, height: height) do
stroke_bounds
recurse_bounding_box(max_depth, depth + 1) if depth < max_depth
end
end
end
recurse_bounding_box

Example Output

Layout

Prawn has support for two-dimensional grid based layouts out of the box.
The examples show:• How to define the document grid
• How to configure the grid rows and columns gutters• How to create boxes according to the gridSimple Gridlayout/simple_grid.rb

The document grid on Prawn is just a table-like structure with a defined number of rows andcolumns. There are some helpers to create boxes of content based on the grid coordinates.
define_grid accepts the following options which are pretty much self-explanatory: :rows,:columns, :gutter, :row_gutter, :column_gutter.

# The grid only need to be defined once, but since all the examples should be# able to run alone we are repeating it on every example
define_grid(columns: 5, rows: 8, gutter: 10)
text 'We defined the grid, roll over to the next page to see its outline'
start_new_page
grid.show_all

Example Output
We defined the grid, roll over to the next page to see its outline

Example Output
0,0

0,1

0,2

0,3

0,4

1,0

1,1

1,2

1,3

1,4

2,0

2,1

2,2

2,3

2,4

3,0

3,1

3,2

3,3

3,4

4,0

4,1

4,2

4,3

4,4

5,0

5,1

5,2

5,3

5,4

6,0

6,1

6,2

6,3

6,4

7,0

7,1

7,2

7,3

7,4

Boxeslayout/boxes.rb

After defined the grid is there but nothing happens. To start taking effect we need to use
the grid boxes.
grid has three different return values based on the arguments received. With no arguments
it will return the grid itself. With integers it will return the grid box at those indices. With twoarrays it will return a multi-box spanning the region of the two grid boxes at the arrays
indices.

# The grid only need to be defined once, but since all the examples should be# able to run alone we are repeating it on every example
define_grid(columns: 5, rows: 4, gutter: 10)
grid(0, 0).show
grid(1, 1).show
grid([2, 2], [3, 3]).show
grid([0, 4], [3, 4]).show
grid([3, 0], [3, 1]).show

Example Output
0,0

0,4:3,4

1,1

2,2:3,3

3,0:3,1

Contentlayout/content.rb

Now that we know how to access the boxes we might as well add some content to them.
This can be done by taping into the bounding box for a given grid box or multi-box with thebounding_box method.

# The grid only need to be defined once, but since all the examples should be# able to run alone we are repeating it on every example
define_grid(rows: 4, columns: 5, gutter: 10)
grid([1, 0], [3, 1]).bounding_box dotext "Adding some content to this multi_box.\n#{' _ ' * 200}"end
grid(2, 3).bounding_box dotext "Just a little snippet here.\n#{' _ ' * 10}"end

Example Output

Adding some content to thismulti_box.
_ _ _ _ _ _ _ _ _ _ _ _ _ _ __ _ _ _ _ _ _ _ _ _ _ _ _ _ __ _ _ _ _ _ _ _ _ _ _ _ _ _ __ _ _ _ _ _ _ _ _ _ _ _ _ _ __ _ _ _ _ _ _ _ _ _ _ _ _ _ __ _ _ _ _ _ _ _ _ _ _ _ _ _ __ _ _ _ _ _ _ _ _ _ _ _ _ _ __ _ _ _ _ _ _ _ _ _ _ _ _ _ __ _ _ _ _ _ _ _ _ _ _ _ _ _ __ _ _ _ _ _ _ _ _ _ _ _ _ _ __ _ _ _ _ _ _ _ _ _ _ _ _ _ __ _ _ _ _ _ _ _ _ _ _ _ _ _ __ _ _ _ _ _ _ _ _ _ _ _ _ _ __ _ _ _ _Just a little
snippet here.
_ _ _ _ _ _ __ _ _Prawn::Table

As of Prawn 1.2.0, Prawn::Table has been extracted into its own semi-officially supported
gem.
Please see https://github.com/prawnpdf/prawn-table for more details.

This code snippet was not evaluated inline. You may see its output by runningthe example file located here:
https://github.com/prawnpdf/prawn/tree/master/manual/table.rb

Images

Embedding images on PDF documents is fairly easy. Prawn supports both JPG and PNG
images.
The examples show:• How to add an image to a page
• How place the image on a specific position• How to configure the image dimensions by setting the width and height or by
scaling it

Plain Image
images/plain_image.rb

To embed images onto your PDF file use the image method. It accepts the file path of theimage to be loaded and some optional arguments.
If only the image path is provided the image will be rendered starting on the cursor
position. No manipulation is done with the image even if it doesn't fit entirely on the page
like the following snippet.

text 'The image will go right below this line of text.'
image "#{Prawn::DATADIR}/images/pigs.jpg"

Example Output
The image will go right below this line of text.

Absolute Positioning
images/absolute_position.rb

One of the options that the image method accepts is :at. If you've read some of the graphics
examples you are probably already familiar with it. Just provide it the upper-left corner
where you want the image placed.
While sometimes useful this option won't be practical. Notice that the cursor won't be
moved after the image is rendered and there is nothing forbidding the text to overlap with
the image.

y_position = cursor
text "The image won't go below this line of text."
image "#{Prawn::DATADIR}/images/fractal.jpg", at: [200, y_position]text 'And this line of text will go just below the previous one.'

Example Output
The image won't go below this line of text.
And this line of text will go just below the previous one.

Horizontal Positioning
images/horizontal.rb

The image may be positioned relatively to the current bounding box. The horizontal position
may be set with the :position option.
It may be :left, :center, :right or a number representing an x-offset from the left boundary.

bounding_box([50, cursor], width: 400, height: 450) do
stroke_bounds%i[left center right].each do |position|text "Image aligned to the #{position}."image "#{Prawn::DATADIR}/images/stef.jpg", position: positionendtext 'The next image has a 50 point offset from the left boundary'image "#{Prawn::DATADIR}/images/stef.jpg", position: 50
endExample Output
Image aligned to the left.

Image aligned to the center.

Image aligned to the right.

The next image has a 50 point offset from the left boundary

Vertical Positioning
images/vertical.rb

To set the vertical position of an image use the :vposition option.
It may be :top, :center, :bottom or a number representing the y-offset from the top
boundary.

bounding_box([0, cursor], width: 500, height: 450) do
stroke_bounds%i[top center bottom].each do |vposition|text "Image vertically aligned to the #{vposition}.", valign: vposition
image "#{Prawn::DATADIR}/images/stef.jpg",
position: 220,vposition: vpositionend
text_box 'The next image has a 100 point offset from the top boundary',
at: [bounds.width - 110, bounds.top - 10],width: 100
image "#{Prawn::DATADIR}/images/stef.jpg",
position: :right,
vposition: 100
endExample Output
Image vertically aligned to the top.

Image vertically aligned to the center.

Image vertically aligned to the bottom.

The next image
has a 100 point
offset from the top
boundary

Width and Height
images/width_and_height.rb

The image size can be set with the :width and :height options.
If only one of those is provided, the image will be scaled proportionally. When both areprovided, the image will be stretched to fit the dimensions without maintaining the aspectratio.

text 'Scale by setting only the width'
image "#{Prawn::DATADIR}/images/pigs.jpg", width: 150
move_down 10
text 'Scale by setting only the height'
image "#{Prawn::DATADIR}/images/pigs.jpg", height: 80
move_down 10
text 'Stretch to fit the width and height provided'
image "#{Prawn::DATADIR}/images/pigs.jpg", width: 500, height: 100

Example Output
Scale by setting only the width

Scale by setting only the height

Stretch to fit the width and height provided

Scaling Pmages
images/scale.rb

To scale an image use the :scale option.
It scales the image proportionally given the provided value.

text 'Normal size'
image "#{Prawn::DATADIR}/images/stef.jpg"
move_down 10
text 'Scaled to 50%'
image "#{Prawn::DATADIR}/images/stef.jpg", scale: 0.5
move_down 10
text 'Scaled to 200%'
image "#{Prawn::DATADIR}/images/stef.jpg", scale: 2

Example Output
Normal size

Scaled to 50%

Scaled to 200%

Fiting
images/fit.rb:fit option is useful when you want the image to have the maximum size within a container
preserving the aspect ratio without overlapping.
Just provide the container width and height pair.

size = 300
text 'Using the fit option'bounding_box([0, cursor], width: size, height: size) do
image "#{Prawn::DATADIR}/images/pigs.jpg", fit: [size, size]
stroke_bounds
end

Example Output
Using the fit option

Document and Page Options

So far we've already seen how to create new documents and start new pages. This chapter
expands on the previous examples by showing other options avialable. Some of the options
are only available when creating new documents.
The examples show:• How to configure page size
• How to configure page margins• How to use a background image• How to add metadata to the generated PDF

Page Size
document_and_page_options/page_size.rb

Prawn comes with support for most of the common page sizes so you'll only need to provide
specific values if your intended format is not supported. To see a list with all supported sizes
take a look at PDF::Core::PageGeometry.
To define the size use :page_size when creating new documents and :size when starting
new pages. The default page size for new documents is LETTER (612.00 x 792.00).
You may also define the orientation of the page to be either portrait (default) or landscape.
Use :page_layout when creating new documents and :layout when starting new pages.

Prawn::Document.generate('example.pdf',
page_size: 'EXECUTIVE',
page_layout: :landscape,) dotext 'EXECUTIVE landscape page.'custom_size = [275, 326]['A4', 'TABLOID', 'B7', custom_size].each do |size|
start_new_page(size: size, layout: :portrait)
text "#{size} portrait page."start_new_page(size: size, layout: :landscape)text "#{size} landscape page."end
endThis code snippet was not evaluated inline. You may see its output by runningthe example file located here:
https://github.com/prawnpdf/prawn/tree/master/manual/
document_and_page_options/page_size.rb

Page Margins
document_and_page_options/page_margins.rb

The default margin for pages is 0.5 inch but you can change that with the :margin option or
if you'd like to have different margins you can use the :left_margin, :right_margin,:top_margin, :bottom_margin options.
These options are available both for starting new pages and creating new documents.

Prawn::Document.generate('example.pdf', margin: 100) do
text '100 pts margins.'
stroke_bounds
start_new_page(left_margin: 300)
text '300 pts margin on the left.'stroke_bounds
start_new_page(top_margin: 300)
text '300 pts margin both on the top and on the left. Notice that whenever ' \'you set an option for a new page it will remain the default for the ' \'following pages.'stroke_bounds
start_new_page(margin: 50)
text '50 pts margins. Using the margin option will reset previous specific ' \'calls to left, right, top and bottom margins.'
stroke_bounds
start_new_page(margin: [50, 100, 150, 200])
text 'There is also the shorthand CSS like syntax used here.'stroke_bounds
endThis code snippet was not evaluated inline. You may see its output by runningthe example file located here:
https://github.com/prawnpdf/prawn/tree/master/manual/
document_and_page_options/page_margins.rb

Backgrounddocument_and_page_options/background.rb

Pass an image path to the :background option and it will be used as the background for all
pages.
This option can only be used on document creation.

img = "#{Prawn::DATADIR}/images/letterhead.jpg"
Prawn::Document.generate('example.pdf', background: img, margin: 100) do
text 'My report caption', size: 18, align: :rightmove_down font.height * 2
text 'Here is my text explaining this report. ' * 20,
size: 12,align: :left,
leading: 2
move_down font.heighttext "I'm using a soft background. " * 40,
size: 12,align: :left,
leading: 2
endThis code snippet was not evaluated inline. You may see its output by runningthe example file located here:
https://github.com/prawnpdf/prawn/tree/master/manual/
document_and_page_options/background.rb

Document Metadata
document_and_page_options/metadata.rb

To set the document metadata just pass a hash to the :info option when creating new
documents.
The keys in the example below are arbitrary, so you may add whatever keys you want.

info = {Title: 'My title',
Author: 'John Doe',
Subject: 'My Subject',
Keywords: 'test metadata ruby pdf dry',Creator: 'ACME Soft App',
Producer: 'Prawn',
CreationDate: Time.now,}
Prawn::Document.generate('example.pdf', info: info) do
text 'This is a test of setting metadata properties via the info option.'text 'While the keys are arbitrary, the above example sets common attributes.'
endThis code snippet was not evaluated inline. You may see its output by runningthe example file located here:
https://github.com/prawnpdf/prawn/tree/master/manual/
document_and_page_options/metadata.rb

Print Scaling
document_and_page_options/print_scaling.rb(Optional; PDF 1.6) The page scaling option to be selected when a print dialog is displayed
for this document. Valid values are None, which indicates that the print dialog should reflect
no page scaling, and AppDefault, which indicates that applications should use the current
print scaling. If this entry has an unrecognized value, applications should use the current
print scaling. Default value: AppDefault.
Note: If the print dialog is suppressed and its parameters are provided directly by theapplication, the value of this entry should still be used.

Prawn::Document.generate('example.pdf',
page_layout: :landscape,print_scaling: :none,) dotext 'When you print this document, the scale to fit in print preview ' \'should be disabled by default.'
endThis code snippet was not evaluated inline. You may see its output by runningthe example file located here:
https://github.com/prawnpdf/prawn/tree/master/manual/
document_and_page_options/print_scaling.rb

Outline

The outline of a PDF document is the table of contents tab you see to the right or left ofyour PDF viewer.
The examples include:• How to define sections and pages• How to insert sections and/or pages to a previously defined outline structureSections and Pages
outline/sections_and_pages.rb

The document outline tree is the set of links used to navigate through the various
document sections and pages.
To define the document outline we first use the outline method to lazily instantiate an
outline object. Then we use the define method with a block to start the outline tree.
The basic methods for creating outline nodes are section and page. The only difference
between the two is that page doesn't accept a block and will only create leaf nodes while
section accepts a block to create nested nodes.
section accepts the title of the section and two options: :destination - a page number to link
and :closed - a boolean value that defines if the nested outline nodes are shown when the
document is open (defaults to true).
page is very similar to section. It requires a :title option to be set and accepts a:destination.
section and page may also be used without the define method but they will need to
instantiate the outline object every time.

# First we create 10 pages just to have something to link to(1..10).each do |index|
text "Page #{index}"
start_new_page
end
outline.define do
section('Section 1', destination: 1) do
page title: 'Page 2', destination: 2
page title: 'Page 3', destination: 3
endsection('Section 2', destination: 4) do
page title: 'Page 5', destination: 5
section('Subsection 2.1', destination: 6, closed: true) do
page title: 'Page 7', destination: 7
end
end
end
# Outside of the define block
outline.section('Section 3', destination: 8) dooutline.page(title: 'Page 9', destination: 9)
endoutline.page(title: 'Page 10', destination: 10)# Section and Pages without links. While a section without a link may be# useful to group some pages, a page without a link is uselessoutline.update do # update is an alias to define
section('Section without link') do
page title: 'Page without link'end
endThis code snippet was not evaluated inline. You may see its output by runningthe example file located here:
https://github.com/prawnpdf/prawn/tree/master/manual/outline/
sections_and_pages.rb

Adding a Subsection to the Outline Tree
outline/add_subsection_to.rb

We have already seen how to define an outline tree sequentially.
If you'd like to add nodes to the middle of an outline tree the add_subsection_to may helpyou.
It allows you to insert sections to the outline tree at any point. Just provide the title of theparent section, the position you want the new subsection to be inserted :first or :last(defaults to :last) and a block to declare the subsection.
The add_subsection_to block doesn't necessarily create new sections, it may also create newpages.
If the parent title provided is the title of a page. The page will be converted into a section toreceive the subsection created.

# First we create 10 pages and some default outline(1..10).each do |index|
text "Page #{index}"
start_new_page
end
outline.define do
section('Section 1', destination: 1) do
page title: 'Page 2', destination: 2
page title: 'Page 3', destination: 3
endend# Now we will start adding nodes to the previous outline
outline.add_subsection_to('Section 1', :first) dooutline.section('Added later - first position') dooutline.page(title: 'Page 4', destination: 4)
outline.page(title: 'Page 5', destination: 5)
end
end
outline.add_subsection_to('Section 1') dooutline.page(title: 'Added later - last position', destination: 6)
endoutline.add_subsection_to('Added later - first position') dooutline.page(title: 'Another page added later', destination: 7)
end# The title provided is for a page which will be converted into a section
outline.add_subsection_to('Page 3') dooutline.page(title: 'Last page added', destination: 8)
end

This code snippet was not evaluated inline. You may see its output by runningthe example file located here:
https://github.com/prawnpdf/prawn/tree/master/manual/outline/
add_subsection_to.rb

Insert Section Afteroutline/insert_section_after.rb

Another way to insert nodes into an existing outline is the insert_section_after method.
It accepts the title of the node that the new section will go after and a block declaring the
new section.
As is the case with add_subsection_to the section added doesn't need to be a section, it may
be just a page.

# First we create 10 pages and some default outline(1..10).each do |index|
text "Page #{index}"
start_new_page
end
outline.define do
section('Section 1', destination: 1) do
page title: 'Page 2', destination: 2
page title: 'Page 3', destination: 3
endend# Now we will start adding nodes to the previous outline
outline.insert_section_after('Page 2') dooutline.section('Section after Page 2') dooutline.page(title: 'Page 4', destination: 4)
end
end
outline.insert_section_after('Section 1') dooutline.section('Section after Section 1') dooutline.page(title: 'Page 5', destination: 5)
endend# Adding just a page
outline.insert_section_after('Page 3') dooutline.page(title: 'Page after Page 3', destination: 6)
end

This code snippet was not evaluated inline. You may see its output by runningthe example file located here:
https://github.com/prawnpdf/prawn/tree/master/manual/outline/
insert_section_after.rb

Repeatable Content

Prawn offers two ways to handle repeatable content blocks. Repeater is useful for content
that gets repeated at well defined intervals while Stamp is more appropriate if you needbetter control of when to repeat it.
There is also one very specific helper for numbering pages.
The examples show:• How to repeat content on several pages with a single invocation• How to create a new Stamp• How to "stamp" the content block on the page
• How to number the document pages with one simple call

Repeater
repeatable_content/repeater.rb

The repeat method is quite versatile when it comes to define the intervals at which thecontent block should repeat.
The interval may be a symbol (:all, :odd, :even), an array listing the pages, a range or a Proc
that receives the page number as an argument and should return true if the content is to berepeated on the given page.
You may also pass an option :dynamic to reevaluate the code block on every call which isuseful when the content changes based on the page number.
It is also important to say that no matter where you define the repeater it will be applied to
all matching pages.

repeat(:all) do
draw_text 'All pages', at: bounds.top_left
end
repeat(:odd) do
draw_text 'Only odd pages', at: [0, 0]
end
repeat(:even) do
draw_text 'Only even pages', at: [0, 0]
end
repeat([1, 3, 7]) do
draw_text 'Only on pages 1, 3 and 7', at: [100, 0]
end
repeat(2..4) do
draw_text 'From the 2nd to the 4th page', at: [300, 0]
end
repeat(->(pg) { (pg % 3).zero? }) do
draw_text 'Every third page', at: [250, 20]
end
repeat(:all, dynamic: true) do
draw_text page_number, at: [500, 0]
end
10.times dostart_new_page
draw_text 'A wonderful page', at: [400, 400]
endThis code snippet was not evaluated inline. You may see its output by runningthe example file located here:
https://github.com/prawnpdf/prawn/tree/master/manual/repeatable_content/
repeater.rb

Stamprepeatable_content/stamp.rb

Stamps should be used when you have content that will be included multiple times in adocument. Its advantages over creating the content anew each time are:1. Faster document creation
2. Smaller final document
3. Faster display on subsequent displays of the repeated element because theviewer application can cache the rendered results
The create_stamp method does just what it says. Pass it a block with the content that shouldbe generated and the stamp will be created.
There are two methods to render the stamp on a page stamp and stamp_at. The first willrender the stamp as is while the second accepts a point to serve as an offset to the stamp
content.

create_stamp('approved') do
rotate(30, origin: [-5, -5]) do
stroke_color 'FF3333'
stroke_ellipse [0, 0], 29, 15
stroke_color '000000'
fill_color '993333'
font('Times-Roman') do
draw_text 'Approved', at: [-23, -3]
end
fill_color '000000'
end
end
stamp 'approved'
stamp_at 'approved', [200, 100]

Example Output
d
ove
r
p
p

A

A

d
ove
r
p
p

Page Numbering
repeatable_content/page_numbering.rb

The number_pages method is a simple way to number the pages of your document. It should
be called towards the end of the document since pages created after the call won't benumbered.
It accepts a string and a hash of options:• start_count_at is the value from which to start numbering pages• total_pages If provided, will replace total with the value given. Useful foroverriding the total number of pages when using the start_count_at option.

• page_filter, which is one of: :all, :odd, :even, an array, a range, or a Proc thatreceives the page number as an argument and should return true if the page
number should be printed on that page.

• color which accepts the same values as fill_color• As well as any option accepted by text_box
text 'This is the first page!'
10.times dostart_new_page
text 'Here comes yet another page.'end
string = 'page <page> of <total>'# Green page numbers 1 to 7
options = {at: [bounds.right - 150, 0],
width: 150,align: :right,
page_filter: (1..7),
start_count_at: 1,
color: '007700',}
number_pages string, options# Gray page numbers from 8 on up
options[:page_filter] = ->(pg) { pg > 7 }options[:start_count_at] = 8
options[:color] = '333333'
number_pages string, options
start_new_page
text "See. This page isn't numbered and doesn't count towards the total."This code snippet was not evaluated inline. You may see its output by runningthe example file located here:
https://github.com/prawnpdf/prawn/tree/master/manual/repeatable_content/
page_numbering.rb

Alternating Page Numbering
repeatable_content/alternate_page_numbering.rb

Below is the code to generate page numbers that alternate being rendered on the right andleft side of the page. The first page will have a "1" in the bottom right corner. The second
page will have a "2" in the bottom left corner of the page. The third a "3" in the bottom
right, etc.

text 'This is the first page!'
10.times dostart_new_page
text 'Here comes yet another page.'end
string = '<page>'
odd_options = {at: [bounds.right - 150, 0],
width: 150,align: :right,
page_filter: :odd,
start_count_at: 1,}
even_options = {at: [0, bounds.left],
width: 150,align: :left,
page_filter: :even,start_count_at: 2,}
number_pages string, odd_options
number_pages string, even_options

This code snippet was not evaluated inline. You may see its output by runningthe example file located here:
https://github.com/prawnpdf/prawn/tree/master/manual/repeatable_content/
alternate_page_numbering.rb

Security

Security lets you control who can read the document by defining a password.
The examples include:• How to encrypt the document without the need for a password• How to configure the regular user permissions• How to require a password for the regular user
• How to set a owner password that bypass the document permissions

Encryption
security/encryption.rb

The encrypt_document method, as you might have already guessed, is used to encrypt the
PDF document.
Once encrypted whoever is using the document will need the user password to read the
document. This password can be set with the :user_password option. If this is not set the
document will be encrypted but a password will not be needed to read the document.
There are some caveats when encrypting your PDFs. Be sure to read the source
documentation (you can find it here: https://github.com/prawnpdf/prawn/blob/master/lib/
prawn/security.rb) before using this for anything super serious.

# Bare encryption. No password needed.
Prawn::ManualBuilder::Example.generate('bare_encryption.pdf') do
text 'See, no password was asked but the document is still encrypted.'encrypt_document
end# Simple password. All permissions granted.
Prawn::ManualBuilder::Example.generate('simple_password.pdf') do
text 'You was asked for a password.'encrypt_document(user_password: 'foo', owner_password: 'bar')
end

This code snippet was not evaluated inline. You may see its output by runningthe example file located here:
https://github.com/prawnpdf/prawn/tree/master/manual/security/encryption.rb

Permissions
security/permissions.rb

Some permissions may be set for the regular user with the following options::print_document, :modify_contents, :copy_contents, :modify_annotations. All this options default
to true, so if you'd like to revoke just set them to false.
A user may bypass all permissions if he provides the owner password which may be set
with the :owner_password option. This option may be set to :random so that users will never beable to bypass permissions.
There are some caveats when encrypting your PDFs. Be sure to read the source
documentation (you can find it here: https://github.com/prawnpdf/prawn/blob/master/lib/
prawn/security.rb) before using this for anything super serious.

# User cannot print the document.
Prawn::Document.generate('cannot_print.pdf') do
text "If you used the user password you won't be able to print the doc."
encrypt_document(
user_password: 'foo',
owner_password: 'bar',permissions: { print_document: false },)end# All permissions revoked and owner password set to random
Prawn::Document.generate('no_permissions.pdf') do
text "You may only view this and won't be able to use the owner password."encrypt_document(
user_password: 'foo',
owner_password: :random,
permissions: {print_document: false,
modify_contents: false,
copy_contents: false,
modify_annotations: false,
},
)
end

This code snippet was not evaluated inline. You may see its output by runningthe example file located here:
https://github.com/prawnpdf/prawn/tree/master/manual/security/permissions.rb