Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

pdfwriter - generate simple PDF documents

Import with import "pdfwriter.j" as pdf;. Build a Document of Pages with value-semantic builders - text, lines, rectangles - then render() writes the PDF object / xref structure by hand (no stdlib PDF) as bytes, the way htmlwriter / label generate their formats. Content streams are FlateDecode-compressed via compress. Pure Jennifer; runs on both binaries.

import "pdfwriter.j" as pdf;
use fs;

def p as pdf.Page init pdf.page(612, 792);
$p = pdf.text($p, 72, 720, "Helvetica", 24, "Hello, PDF");
def doc as pdf.Document init pdf.addPage(pdf.document(), $p);
fs.writeBytes("out.pdf", pdf.render($doc));

Runnable: examples/modules/pdfwriter_demo.j.

Coordinates and units

Coordinates are in PDF points (1/72 inch), with the origin at the bottom-left and y increasing upward. All coordinates and sizes are integers. Common page sizes: US Letter 612 x 792, A4 595 x 842. Colours are 0-255 RGB integers.

Building

Every builder is value-semantic - it returns a fresh copy and never mutates its argument, so you thread them ($p = pdf.text($p, ...)).

CallReturns
pdf.document()Documentan empty document
pdf.page(width, height)Pagea blank page of the given size
pdf.text(pg, x, y, font, size, str)Pagedraw text at (x, y)
pdf.line(pg, fromX, fromY, toX, toY)Pagedraw a stroked line
pdf.rect(pg, x, y, width, height, filled)Pagedraw a rectangle (fill or stroke)
pdf.color(pg, red, green, blue)Pageset fill + stroke colour for what follows
pdf.addPage(doc, pg)Documentappend a page
pdf.render(doc)bytesthe finished PDF

color sets the drawing colour for subsequent operations on that page (both fill and stroke), so order matters: set the colour, then draw. rect’s filled flag fills the rectangle when true, otherwise strokes its outline.

Fonts

text takes one of the standard-14 base fonts every PDF viewer provides; any other name throws Error{kind: "pdfwriter"}:

Helvetica  Helvetica-Bold  Helvetica-Oblique  Helvetica-BoldOblique
Times-Roman  Times-Bold  Times-Italic  Times-BoldItalic
Courier  Courier-Bold  Courier-Oblique  Courier-BoldOblique
Symbol  ZapfDingbats

Each distinct font used becomes one shared Type1 font object (WinAnsiEncoding). Text is escaped for the PDF literal-string syntax (\, (, ), and line breaks), so any ASCII / Latin-1 string is safe to pass.

Metadata

Set document metadata - the PDF Info dictionary shown in a viewer’s “Document Properties” - with pdf.info(doc, key, value). key is a PDF Info key:

Key
Title / Author / Subject / Keywordsthe descriptive fields
Creatorthe app that authored the source
Producerthe app that wrote the PDF (defaults to "Jennifer pdfwriter")
CreationDate / ModDatePDF date strings (see pdfDate below)
def doc as pdf.Document init pdf.document();
$doc = pdf.info($doc, "Title", "Q3 Report");
$doc = pdf.info($doc, "Author", "Ada Lovelace");
$doc = pdf.info($doc, "Keywords", "report, finance, q3");

document() presets Producer to "Jennifer pdfwriter"; every other field is unset until you set it. Any custom key works too. Dates use the PDF date syntax, which pdf.pdfDate(t) builds from a time.Time:

use time;
$doc = pdf.info($doc, "CreationDate", pdf.pdfDate(time.utc()));   # D:20260714160000+00'00'

Rendering

render(doc) produces a complete PDF 1.7 file as bytes: a catalog, a page tree, one page dict + one FlateDecode-compressed content stream per page, the shared font objects, an Info dictionary when any metadata is set, a cross-reference table with correct byte offsets, and the trailer. Write it with fs.writeBytes, return it from an httpd handler, or attach it via mime. It validates clean under qpdf --check.

Byte-identical output. The same document always renders to the exact same bytes - on either binary, run to run. This is deliberate: pdfwriter never auto-stamps a CreationDate or any other timestamp (you opt into one explicitly via info + pdfDate), so nothing varies with wall-clock time. That makes the output safe to assert against a golden file in an automated test, and reproducible for content-addressed builds.

Scope

  • Text, lines, rectangles. The standard-14 fonts, solid fills / strokes, and RGB colour. No curves / paths beyond rectangles, no clipping, no transparency.
  • No embedded fonts or images yet - a follow-on. Only the built-in base fonts, so no font file is embedded and non-Latin text is out of scope.
  • A writer, not a reader. It generates PDFs; it does not parse them.

See also