initial commit

This commit is contained in:
2026-09-25 23:32:29 +02:00
commit fff8b677f8
114 changed files with 84233 additions and 0 deletions
+90
View File
@@ -0,0 +1,90 @@
/*
Package pdf is a self-contained PDF reader and renderer.
It parses PDF files (ISO 32000-2 / PDF 2.0, and every earlier revision in
practice), decodes their content, rasterises pages to RGBA8 bitmaps and
extracts their text. It depends only on Odin's core collection -- no C
libraries, no FreeType, no libjpeg -- so it can be dropped into any Odin
project as-is.
Reading a file and walking its pages works today:
doc, err := pdf.open_file("thesis.pdf")
if err != .None {
fmt.eprintln(pdf.error_string(err))
return
}
defer pdf.close(doc)
for i in 0 ..< pdf.page_count(doc) {
page, perr := pdf.page_get(doc, i) // Page is a value, not a pointer
if perr != .None { continue }
fmt.println(i, page.media_box, page.rotate)
}
`open_bytes` opens a file already in memory, and `Open_Options` carries the
password for an encrypted document and `skip_page_tree` for a tool that only
wants to inspect objects. Below the page tree, `get_object`, `get_dict`,
`get_stream`, `resolve`, the `dict_*` accessors and `stream_data` read the
object graph directly.
The document's navigation model is readable today too. `outline_load` returns
the bookmark tree as `Outline_Item`s, `dest_resolve` turns a destination --
written out in full, named, or carried by a /GoTo action -- into a
`Destination` naming a page index, and `page_label` gives the label a viewer
shows for a page, "iv" rather than "4". Unlike everything above they hand back
memory the caller owns: each takes an `allocator`, and the outline is freed
with `outline_destroy`.
Rendering and text extraction are the other half:
page, _ := pdf.page_get(doc, 0)
bmp, rerr := pdf.render_page(&page, {ctm = pdf.scale_matrix(2, 2)})
if rerr == .None {
defer pdf.bitmap_destroy(&bmp) // RGBA8, straight alpha
upload_texture(bmp.width, bmp.height, bmp.pixels)
}
text, _ := pdf.extract_text(&page)
defer delete(text)
`Render_Options` carries the transform, the background colour, whether the page
keeps an alpha channel and whether annotations are drawn. `render_page_to`
renders into a bitmap the caller already has, which is how a large page is
rendered in tiles. `page_base_matrix` is the transform all of them share -- PDF
user space to device pixels, with the crop box, /Rotate and /UserUnit folded in
-- and inverting it is what turns a pixel a user clicked on back into a point
on the page. Below those, `run_page` runs a page into a `Device` of the
caller's own and `page_display_list` records one for replay. A `Bitmap`'s
pixels are reachable directly -- it is a plain struct -- and `bitmap_row`,
`bitmap_make`, `bitmap_destroy` and `bitmap_to_rgba` are the accessors for
callers that would rather not compute `stride` themselves.
Memory model: a Document owns a growing arena. Every object, stream and page
parsed out of the file lives in that arena and is freed in one shot by
`close`. Nothing the library hands back out of a Document needs to be freed
individually -- a `Page`, a `^Dict` and the bytes from `stream_data` are all
borrowed and die with the Document. The exceptions are the few procedures that
take an explicit `allocator` parameter and say so in their doc comment:
`render_page`'s bitmap, `extract_text`'s string, `page_display_list`'s list and
the navigation procedures above. Rendering allocates nothing else that outlives
the call -- per-page scratch lives in an arena that is destroyed when the call
returns, so a 900-page loop peaks at one page's working set.
Thread safety: a Document is not internally synchronised. Parsing mutates
caches, so one Document should be driven by one thread at a time. Two
Documents are fully independent.
*/
package pdf
// The PDF version this library targets. Files declaring a newer version are
// still parsed -- PDF readers are required to be forward tolerant, and a
// version number in a header is not a promise about the bytes that follow.
SPEC_VERSION_MAJOR :: 2
SPEC_VERSION_MINOR :: 0
// Library version, for callers that want to report it.
VERSION_MAJOR :: 0
VERSION_MINOR :: 1
VERSION_PATCH :: 0