// 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 [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) } }