gpui-pdf
A PDF viewer for GPUI, rendered in pure Rust with
hayro. There are no native libraries and no
system-font dependency, so it builds and behaves the same on macOS, Linux, and
Windows.
Use the ready-made PdfView, which handles loading, scrolling, rendering, and
memory on its own, or build your own viewer on the lower-level functions
(parse, page_dims, render_page, keep_window). Text search, highlights,
and fillable forms are optional features.
The complete API reference is in API.md.
Overview
Section titled “Overview”- Bounded memory.
PdfViewis page-virtualized: every page gets a correctly sized slot up front (so the scrollbar reflects the whole document), but only the pages near the viewport are rasterized. Pages scrolled away are freed — CPU pixel buffer and GPU atlas texture — so an 800-page document stays as light as a one-pager. - Zoom & navigation, no flicker. Built-in zoom and page navigation (header controls, a click-to-edit page counter, keyboard shortcuts, a scroll-to-top button). On a zoom or quality change the page never blanks — the current bitmap stays on screen, rescaled, until the crisp re-render lands.
- Off-thread everything. The file is read, parsed once, and measured on a background thread; pages rasterize on the background executor and paint as they land. The UI never blocks.
- DPI-aware, host-settable quality. Pages rasterize at the display’s pixel ratio × zoom × a quality multiplier the host supplies — read reactively, like the theme, so a settings slider re-renders every open viewer automatically.
- Password-protected PDFs. An encrypted file doesn’t fail to load —
PdfViewenters a locked state and emits an event so the host can render its own password prompt;unlock(password)retries (RC4 / AES-128 / AES-256 via hayro’s standard security handler — the exact table is in API.md). - Outline & links. A table-of-contents side panel from the document’s outline,
and clickable link annotations (internal → jump to page, external → open URL),
both also exposed as plain functions (
outline,page_links) for custom UIs. - Theme-reactive. Colors come from a closure read at paint time, so the viewer follows live theme changes (and can differ per window) with no push from the host.
Adding the dependency
Section titled “Adding the dependency”Published on crates.io:
[dependencies]gpui-pdf = "0.6"
# Optional features:# markup — text layer + quote-anchored highlights (adds only `kurbo`)# search — find-in-PDF bar; implies markup# forms — AcroForm values/checkboxes display correctly (adds `lopdf`)gpui-pdf = { version = "0.6", features = ["search", "forms"] }gpui version: the crate depends on GPUI as published on crates.io — the
gpui-prefamily, consumed under the namegpui(gpui = { package = "gpui-pre", version = "0.3" }). Everygpui-prerelease is a different Zed snapshot, so your app must resolve to the samegpui-preversion as this crate (one gpui graph); pin it in yourCargo.lockand move both together.
Quick start
Section titled “Quick start”use std::rc::Rc;use std::path::PathBuf;use gpui_pdf::{PdfView, PdfStyle};
// Create the viewer (kicks off the off-thread load):let view = cx.new(|cx| { PdfView::new( path, // PathBuf to a local .pdf Rc::new(|| PdfStyle { // map your theme onto the chrome bg: my_theme::bg(), border: my_theme::border(), placeholder_bg: my_theme::muted_bg(), placeholder_fg: my_theme::muted_fg(), header_fg: my_theme::text(), header_muted: my_theme::muted_fg(), }), Rc::new(|| 1.0), // render-quality multiplier (1.0 = native DPI) cx, )});
// Render it like any child view:div().child(view.clone())
// Free its GPU textures before dropping it (e.g. when its tab closes):view.update(cx, |v, cx| v.release(window, cx));The exact contracts (locked-state flow, texture lifetime, zoom clamps, the low-level primitives) are in API.md.
Markup & search (optional features)
Section titled “Markup & search (optional features)”markup adds a text layer — extracted by a custom hayro Device, no heavyweight
PDF dependency — and quote-anchored highlights on top of it: the host stores quotes
however it likes, hands them to the viewer to locate and draw, and gets callbacks
for clicks and drag-to-create (with a color picker). Coordinates are normalized, so
highlights track zoom and DPI for free. search builds a browser-style find-in-PDF
bar (⌘F, match highlighting, next/prev) on the same text layer.
Forms (optional feature)
Section titled “Forms (optional feature)”forms makes AcroForm PDFs display correctly: filled values, checked
checkboxes, and radio states render read-only. hayro already composites
annotation appearance streams; this feature normalizes the two shapes it can’t
draw — checkbox/radio state-dictionary appearances (resolved through /AS) and
valued text fields with no appearance at all (the NeedAppearances case, given
a synthesized one) — by rewriting the bytes with lopdf before parsing. It
happens automatically inside parse.
The same feature makes forms fillable through the viewer: every widget
renders as a hoverable click target, and a click emits
PdfEvent::FieldClicked with the field’s description + window bounds — the
host toggles the checkbox or seats its own text input there (the same
host-owns-the-input pattern as the password prompt), writes through
set_form_value (value + regenerated appearance, so the file renders in any
viewer), and hot-swaps the document with PdfView::replace_bytes (scroll and
zoom kept, no blanking). form_fields / reveal_field drive Tab-through-the-
form navigation. A forms_check example reports any PDF’s widget shapes.
Status
Section titled “Status”Early, but solid for scroll-to-read viewing. Password-protected PDFs open behind a host-rendered prompt. Markup, find-in-PDF, and forms (display + filling) are available behind their features. Roadmap: area highlights for pages with no text layer; choice-field dropdowns.
License
Section titled “License”MIT. (The Zorite app itself is GPL-3.0-or-later.)