101 lines
3.2 KiB
Odin
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)
|
|
}
|
|
}
|