Files
pdf-odin-llm/examples/pdfinfo/main.odin
T
2026-09-25 23:32:29 +02:00

101 lines
3.2 KiB
Odin

// pdfinfo prints the structure of a PDF: version, page count, metadata, page
// geometry, and how much content each page carries.
//
// It exists as much to keep the library's public surface honest as to be
// useful. Everything here is reachable from outside the package, so if a
// procedure a consumer needs is accidentally @(private), this example stops
// compiling and scripts/check.sh catches it.
//
// odin run examples/pdfinfo -- file.pdf
package main
import "core:fmt"
import "core:os"
import pdf "../.."
main :: proc() {
if len(os.args) < 2 {
fmt.eprintfln("usage: %s <file.pdf> [more.pdf ...]", os.args[0])
os.exit(2)
}
failures := 0
for path in os.args[1:] {
if !report(path) {
failures += 1
}
}
if failures > 0 {
os.exit(1)
}
}
report :: proc(path: string) -> bool {
doc, err := pdf.open_file(path)
if err != .None {
fmt.eprintfln("%s: %s", path, pdf.error_string(err))
return false
}
defer pdf.close(doc)
major, minor := pdf.document_version(doc)
fmt.printfln("%s", path)
fmt.printfln(" PDF %d.%d, %d pages, %d objects", major, minor,
pdf.page_count(doc), pdf.object_count(doc))
print_info(doc)
total := 0
for i in 0 ..< pdf.page_count(doc) {
page, perr := pdf.page_get(doc, i)
if perr != .None {
fmt.printfln(" page %d: %s", i + 1, pdf.error_string(perr))
continue
}
content, cerr := pdf.page_content(&page)
defer delete(content)
if cerr == .None {
total += len(content)
}
// Only the first few, or a thousand-page standard buries everything.
if i < 5 {
b := pdf.page_bounds(&page)
w, h := pdf.page_size(&page, 1)
fmt.printfln(" page %d: %.1f x %.1f pt, rotate %d, %d x %d px, %d bytes of content",
i + 1, pdf.rect_width(b), pdf.rect_height(b), page.rotate, w, h, len(content))
}
}
fmt.printfln(" %d bytes of content stream decoded, %d KB parsed",
total, pdf.document_memory_used(doc) / 1024)
// A file that needed repairs still opened; say what was worked around, so
// the difference between "clean" and "damaged but readable" is visible.
if doc.is_damaged {
fmt.printfln(" %d repairs:", len(doc.repairs))
shown := 0
for r in doc.repairs {
if shown >= 5 { break }
fmt.printfln(" %s", pdf.repair_string(r.kind))
shown += 1
}
}
return true
}
// Every value in the document information dictionary is a text string, so it
// has to go through decode_text_string: the common encodings are UTF-16BE with
// a byte-order mark and PDFDocEncoding, and neither is UTF-8.
print_info :: proc(doc: ^pdf.Document) {
info := pdf.dict_dict(doc, doc.trailer, "Info")
if info == nil { return }
for key in ([]string{"Title", "Author", "Subject", "Creator", "Producer", "CreationDate", "ModDate"}) {
raw := pdf.dict_bytes(doc, info, key)
if len(raw) == 0 { continue }
text := pdf.decode_text_string(raw)
defer delete(text)
fmt.printfln(" %-13s %s", key, text)
}
}