Text and navigation
Place text, wrap paragraphs, and connect pages with links and outlines.
The examples on this page build on a document, a font, and a page:
require "okab"
font = Okab::Font.load("/path/to/font.ttf")
document = Okab::Document.new(title: "Report", author: "Your team")
page = document.page(width: 595, height: 842)
On this page
Place and style text
Page#text places one run at a baseline. Use a valid UTF-8 Ruby string and a
font containing its glyphs. Font size and tracking use points; RGB color
components range from 0 to 1.
page.text("Quarterly report", x: 48, y: 790,
font: font, size: 24, color: [0.1, 0.2, 0.4])
page.text("Draft", x: 48, y: 754,
font: font, size: 12, tracking: 1, bold: true, italic: true)
bold: true thickens the glyph outlines; italic: true slants them. These
are synthetic styles using the same font. Load a separate bold or italic
font file when you need that typeface’s designed style.
To write Japanese, load a font with Japanese glyphs and pass a UTF-8 string:
japanese_font = Okab::Font.load("/path/to/japanese-font.ttf")
page.text("四半期報告", x: 48, y: 710, font: japanese_font, size: 18)
Fonts are subsetted and reused within a document. Unicode mappings are written alongside the glyphs so PDF viewers can search and extract text. Direct text placement maps characters to glyphs without complex-script shaping or automatic font fallback.
Measure text
Font#measure returns the sum of the font’s glyph advance widths at the
requested size, in points. Use it for simple alignment:
label = "Page 1"
width = font.measure(label, size: 10)
page.text(label, x: page.width - 48 - width, y: 32, font: font, size: 10)
Measurement does not include tracking, synthetic style effects, kerning,
or shaping. Account for those separately when designing a precise layout.
Wrap paragraphs
Page#text_block wraps text to a width. Explicit newlines start new paragraphs,
and wrapped lines move down from y by line_height points:
page.text_block("The report contains searchable text and embedded fonts. " \
"Long paragraphs wrap within the specified width.",
x: 48, y: 660, width: 400, font: font, size: 12,
line_height: 18, align: :left, color: [0.2, 0.2, 0.2])
Available alignment values are :left, :center, :right, and :justify.
Justification adjusts character tracking on all but the final line of the
block. Width calculations use glyph advances, so keep custom
tracking small when wrapping.
Wrapping prefers whitespace and splits oversized tokens at grapheme
boundaries. It does not hyphenate words, apply Japanese line-breaking rules,
shape scripts, or paginate. A grapheme wider than the block can overflow it.
text_block returns the page, without reporting the block’s height.
Supply shaped glyphs
When another text engine has already shaped a run, Page#glyph accepts the
glyph ID and its Unicode source text. Position each glyph yourself:
glyph_id = font.face.glyph_id("A".ord)
page.glyph(glyph_id, x: 48, y: 580, font: font, size: 18, unicode: "A")
Pass the glyph ID from the same font face. unicode may contain multiple
characters for a ligature or cluster. The
Zaniah bridge performs this mapping for recorded glyph runs.
Add pages and outlines
Create pages in reading order. Each page can have its own dimensions, and
Document#page can yield the new page to a block:
details = document.page(width: 595, height: 842) do |p|
p.text("Details", x: 48, y: 790, font: font, size: 24)
end
document.outline("Report", page: page)
document.outline("Details", page: details, level: 1)
Outlines are PDF bookmarks. The first entry must have level 0; subsequent
entries can move deeper by one level at a time. Destination pages must belong
to the same document.
Make links
Links cover an explicit rectangle [x, y, width, height] and do not draw a
label or visible border. Draw the text or artwork yourself:
page.text("Project website", x: 48, y: 520, font: font, size: 12)
page.link([48, 516, 100, 18], uri: "https://noxdea.github.io/okab/")
page.text("Read the details", x: 48, y: 488, font: font, size: 12)
page.link([48, 484, 100, 18], page: details)
Provide exactly one of uri: or page:. URI links accept http, https,
and mailto. Page links require a page from the same document.
Write or render a document
Document#write writes a binary PDF to a path and returns that path:
document.write("report.pdf")
Document#render returns the PDF as a binary Ruby string for an HTTP response
or other storage:
pdf_bytes = document.render
File.binwrite("report.pdf", pdf_bytes)
Create at least one page before rendering. Parent directories must already
exist, and writing to an existing path overwrites it. Metadata accepts
title:, author:, and creator: (default: "Okab").
The same font and image bytes, metadata, and drawing operations in the same order produce deterministic output. Okab does not add creation timestamps. For input restrictions and errors, see formats and limits.