pdf.cr

Native Crystal PDF generation library

pdf.cr

CI standard-readme compliant

A pure Crystal library for PDF generation and parsing.

PDF.cr is a high-performance, pure-Crystal implementation of the PDF 1.7 (ISO 32000-1) specification. It is designed to be a reliable, easy-to-use alternative to C-based libraries like libharu, providing a native Crystal experience for generating and (eventually) parsing PDF documents.

Table of Contents

Background

The motivation behind pdf.cr was to create a PDF generation library for the Crystal ecosystem that doesn't rely on external C dependencies. While libraries like libharu are powerful, they can be difficult to manage in containerized environments or cross-platform builds. By being written in pure Crystal, pdf.cr ensures maximum portability and ease of installation, while leveraging Crystal's performance and type safety.

Install

Add the dependency to your shard.yml:

dependencies:
  pdf:
    github: watzon/pdf

Then run shards install.

Usage

Quick Start

require "pdf"

# Create a new document
pdf = PDF::Document.new
pdf.title = "My Document"
pdf.author = "Crystal Developer"

# Add a page with content
pdf.page do |page|
  page.font "Helvetica-Bold", size: 24
  page.text "Hello, World!", at: {72, 720}
  
  page.font "Helvetica", size: 12
  page.text "This PDF was generated with pdf.cr", at: {72, 690}
end

# Save to file
pdf.save("output.pdf")

Page Sizes

# Default (US Letter: 612 x 792 points)
pdf.page { |page| }

# Named sizes
pdf.page(size: :a4) { |page| }
pdf.page(size: :legal) { |page| }
pdf.page(size: :tabloid) { |page| }

# Custom size (width x height in points)
pdf.page(width: 400, height: 600) { |page| }

# Landscape orientation
pdf.page(size: :letter, orientation: :landscape) { |page| }

Text

pdf.page do |page|
  # Set font (required before drawing text)
  page.font "Helvetica", size: 12
  
  # Draw text at position (x, y from bottom-left)
  page.text "Hello!", at: {72, 720}
  
  # Change font
  page.font "Times-Bold", size: 18
  page.text "Bold Times", at: {72, 680}
end

Standard Fonts (Type1)

All 14 standard PDF fonts are built-in:

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

TrueType Fonts

Load any TrueType (.ttf) font for full Unicode support:

pdf = PDF::Document.new

# Load a TrueType font
my_font = pdf.load_font("path/to/DejaVuSans.ttf")

pdf.page do |page|
  # Use the TrueType font
  page.font my_font, size: 16
  page.text "English: Hello, World!", at: {72, 720}
  page.text "Russian: Привет, мир!", at: {72, 690}
  page.text "Greek: Γειά σου, Κόσμε!", at: {72, 660}
  page.text "Symbols: © ® ™ € £ ¥ → ←", at: {72, 630}
  
  # You can mix TrueType and Type1 fonts
  page.font "Helvetica", size: 12
  page.text "Back to Helvetica", at: {72, 590}
end

pdf.save("unicode_text.pdf")

Features:

  • Full Unicode support (BMP and supplementary planes)
  • Automatic font subsetting (only used glyphs are embedded)
  • Proper glyph metrics for accurate text positioning
  • ToUnicode mapping for text extraction from generated PDFs

Graphics

pdf.page do |page|
  # Set colors (RGB, values 0.0 to 1.0)
  page.fill_color(1.0, 0.0, 0.0)    # Red fill
  page.stroke_color(0.0, 0.0, 1.0)  # Blue stroke
  
  # Line width
  page.line_width(2)
  
  # Draw a rectangle
  page.rectangle(72, 600, 200, 100)  # x, y, width, height
  page.fill_stroke
  
  # Draw lines
  page.move_to(72, 500)
  page.line_to(272, 500)
  page.stroke
  
  # Graphics state (save/restore)
  page.save_graphics_state do
    page.fill_color(0.0, 1.0, 0.0)
    page.rectangle(100, 400, 50, 50)
    page.fill
  end
  # Original colors are restored here
end

Multiple Pages

pdf = PDF::Document.new

3.times do |i|
  pdf.page do |page|
    page.font "Helvetica", size: 18
    page.text "Page #{i + 1}", at: {72, 720}
  end
end

pdf.save("multi_page.pdf")

Getting PDF as Bytes

pdf = PDF::Document.new
pdf.page { |page| page.font("Helvetica", size: 12) }

# Get as Bytes for HTTP response, etc.
bytes = pdf.to_slice

API

PDF::Document

The PDF::Document class is the main entry point for creating and managing PDF files. It handles the document-wide resources, metadata, and page management.

  • initialize: Creates a new document.
  • page(size, orientation, &block): Adds a new page to the document and yields a PDF::Page object.
  • load_font(path): Loads a TrueType font for use in the document.
  • save(path): Serializes the document and writes it to a file.
  • to_slice: Returns the serialized document as a Bytes slice.

PDF::Page

The PDF::Page class represents a single page and provides a high-level DSL for drawing content.

  • font(name_or_font, size): Sets the current font and size.
  • text(string, at): Draws text at the specified coordinates.
  • rectangle(x, y, width, height): Defines a rectangular path.
  • move_to(x, y) / line_to(x, y): Path construction methods.
  • stroke, fill, fill_stroke: Path painting operations.
  • save_graphics_state(&block): Saves the current graphics state, executes the block, and restores it.

Maintainers

Contributing

PRs accepted.

Small note: If editing the README, please conform to the standard-readme specification.

  1. Fork it (https://github.com/watzon/pdf/fork)
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

License

MIT © Chris Watson

Repository

pdf.cr

Owner
Statistic
  • 1
  • 0
  • 0
  • 0
  • 3
  • about 2 hours ago
  • January 19, 2026
License

MIT License

Links
Synced at

Mon, 19 Jan 2026 03:24:38 GMT

Languages