initial commit
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
// pdftext prints the text of a page, or of a whole document.
|
||||
//
|
||||
// The text comes off the same interpreter that draws the page, so what it
|
||||
// prints is what a renderer would have painted: invisible OCR text under a
|
||||
// scan comes out, text drawn as vector paths does not, and the reading order
|
||||
// is the order the glyphs were placed in. Like the other examples it is
|
||||
// compiled by scripts/check.sh, which is what keeps the public API honest.
|
||||
//
|
||||
// odin run examples/pdftext -- file.pdf # page 1
|
||||
// odin run examples/pdftext -- file.pdf -page=all # every page
|
||||
// odin run examples/pdftext -- file.pdf -page=3 -stats
|
||||
//
|
||||
// Options:
|
||||
// -page=N|all 1-based page number, or every page (default 1)
|
||||
// -stats print per-page character counts instead of the text
|
||||
package main
|
||||
|
||||
import "core:fmt"
|
||||
import "core:os"
|
||||
import "core:strconv"
|
||||
import "core:strings"
|
||||
import "core:unicode/utf8"
|
||||
import pdf "../.."
|
||||
|
||||
main :: proc() {
|
||||
path, page, stats, ok := parse_args()
|
||||
if !ok {
|
||||
fmt.eprintfln("usage: %s <file.pdf> [-page=N|all] [-stats]",
|
||||
len(os.args) > 0 ? os.args[0] : "pdftext")
|
||||
os.exit(2)
|
||||
}
|
||||
|
||||
doc, err := pdf.open_file(path)
|
||||
if err != .None {
|
||||
fmt.eprintfln("%s: %s", path, pdf.error_string(err))
|
||||
os.exit(1)
|
||||
}
|
||||
defer pdf.close(doc)
|
||||
|
||||
n := pdf.page_count(doc)
|
||||
first, last := page, page
|
||||
if page == ALL_PAGES {
|
||||
first, last = 0, n - 1
|
||||
}
|
||||
if first < 0 || last >= n {
|
||||
fmt.eprintfln("%s: page %d of %d", path, first + 1, n)
|
||||
os.exit(1)
|
||||
}
|
||||
|
||||
total, with_text, replacements := 0, 0, 0
|
||||
for i in first ..= last {
|
||||
p, perr := pdf.page_get(doc, i)
|
||||
if perr != .None {
|
||||
fmt.eprintfln("page %d: %s", i + 1, pdf.error_string(perr))
|
||||
continue
|
||||
}
|
||||
|
||||
// extract_text hands back a string the caller owns.
|
||||
text, terr := pdf.extract_text(&p)
|
||||
defer delete(text)
|
||||
if terr != .None {
|
||||
fmt.eprintfln("page %d: %s", i + 1, pdf.error_string(terr))
|
||||
}
|
||||
|
||||
total += len(text)
|
||||
if len(text) > 0 { with_text += 1 }
|
||||
replacements += count_replacements(text)
|
||||
|
||||
if stats {
|
||||
fmt.printfln("page %d: %d bytes, %d runes, %d U+FFFD",
|
||||
i + 1, len(text), utf8.rune_count(text), count_replacements(text))
|
||||
continue
|
||||
}
|
||||
if first != last {
|
||||
fmt.printfln("--- page %d ---", i + 1)
|
||||
}
|
||||
fmt.println(text)
|
||||
}
|
||||
|
||||
if stats || first != last {
|
||||
fmt.printfln("%d of %d pages yielded text, %d bytes, %d U+FFFD",
|
||||
with_text, last - first + 1, total, replacements)
|
||||
}
|
||||
}
|
||||
|
||||
ALL_PAGES :: -1
|
||||
|
||||
parse_args :: proc() -> (path: string, page: int, stats: bool, ok: bool) {
|
||||
for arg in os.args[1:] {
|
||||
switch {
|
||||
case arg == "-page=all":
|
||||
page = ALL_PAGES
|
||||
case strings.has_prefix(arg, "-page="):
|
||||
n, nok := strconv.parse_int(arg[len("-page="):])
|
||||
if !nok || n < 1 { return "", 0, false, false }
|
||||
page = n - 1
|
||||
case arg == "-stats":
|
||||
stats = true
|
||||
case strings.has_prefix(arg, "-"):
|
||||
return "", 0, false, false
|
||||
case:
|
||||
if path != "" { return "", 0, false, false }
|
||||
path = arg
|
||||
}
|
||||
}
|
||||
return path, page, stats, path != ""
|
||||
}
|
||||
|
||||
// A code the font could not map to Unicode extracts as U+FFFD, so counting
|
||||
// them is the cheapest measure of how well the /ToUnicode and encoding chain
|
||||
// did on a file. A page that is mostly replacement characters has text on it
|
||||
// that nothing can search or copy.
|
||||
count_replacements :: proc(s: string) -> int {
|
||||
n := 0
|
||||
for r in s {
|
||||
if r == utf8.RUNE_ERROR { n += 1 }
|
||||
}
|
||||
return n
|
||||
}
|
||||
Reference in New Issue
Block a user