docs/plans/p5a: 01 kind+fetch limits, 02 pdftext child + runner, 03 dcache PDF tables, 04 pdfpool, 05 pdfdoc render (map/pages/grep), 06 api, 07 digest citations, metrics, CLI, skill, unit, README. Given tests compile against a panic-only skeleton and fail there for the intended reasons; PDFs for tests are built by internal/pdftest (given), plus two qpdf-encrypted fixtures. Owner changes for the run: vendor go-pdfkit/extract v0.3.0 + reader v0.6.0 (pinned via tools.go until task 02); AGENTS.md, tools/run-plan.sh (enforces a Co-Authored-By trailer for the model), docs/implementer-log.md, `make gate`; gofmt (whitespace) on clean.go and guard; TestUnsupportedType uses a zip now that PDFs are supported; requestDeadline 90→210 s and writeTimeout 120→240 s for the PDF path's worst case. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
224 lines
6.0 KiB
Go
224 lines
6.0 KiB
Go
package pdffont
|
|
|
|
import "github.com/go-pdfkit/reader"
|
|
|
|
// readSimple reads a font addressed one byte at a time: what each code is
|
|
// called, and how wide it is.
|
|
func (f *Font) readSimple() {
|
|
f.descriptor, _ = f.doc.GetDict(f.dict, "FontDescriptor")
|
|
f.symbolic = symbolicFlag(f.doc, f.descriptor)
|
|
f.names = f.readEncoding()
|
|
f.readSimpleWidths()
|
|
}
|
|
|
|
// readType3 reads a font whose glyphs are little drawings.
|
|
func (f *Font) readType3() {
|
|
f.descriptor, _ = f.doc.GetDict(f.dict, "FontDescriptor")
|
|
f.names = f.readEncoding()
|
|
f.charProcs, _ = f.doc.GetDict(f.dict, "CharProcs")
|
|
f.t3Res, _ = f.doc.GetDict(f.dict, "Resources")
|
|
if m := floatArray(f.doc, f.dict.Get("FontMatrix")); len(m) >= 6 {
|
|
copy(f.fontMatrix[:], m[:6])
|
|
}
|
|
f.readSimpleWidths()
|
|
// A Type 3 font's widths are in its own glyph space, which the font
|
|
// matrix maps onto text space.
|
|
for code, w := range f.widths {
|
|
f.widths[code] = w * 1000 * f.fontMatrix[0]
|
|
}
|
|
f.defaultW = 0
|
|
}
|
|
|
|
// symbolicFlag reads whether a font says it is addressed through its own
|
|
// character map rather than through an encoding of names.
|
|
func symbolicFlag(d *reader.Document, descriptor reader.Dict) bool {
|
|
flags, ok := reader.ToInt(resolve(d, descriptor.Get("Flags")))
|
|
if !ok {
|
|
return false
|
|
}
|
|
const symbolic, nonSymbolic = 1 << 2, 1 << 5
|
|
return flags&symbolic != 0 && flags&nonSymbolic == 0
|
|
}
|
|
|
|
// readEncoding works out what each code of a simple font is called: a base
|
|
// table, and then whatever differences the font names on top of it.
|
|
func (f *Font) readEncoding() map[int]string {
|
|
base := StandardEncoding
|
|
out := map[int]string{}
|
|
f.chosen = map[int]bool{}
|
|
enc := resolve(f.doc, f.dict.Get("Encoding"))
|
|
if name, ok := reader.ToName(enc); ok {
|
|
table := NamedEncoding(name, base)
|
|
fillNames(out, table)
|
|
// A named encoding is the document's own word on what its codes are,
|
|
// symbolic font or not.
|
|
for code, n := range table {
|
|
if n != "" {
|
|
f.chosen[code] = true
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
encDict, ok := reader.ToDict(enc)
|
|
if !ok {
|
|
fillNames(out, base)
|
|
return out
|
|
}
|
|
if name, ok := reader.ToName(resolve(f.doc, encDict.Get("BaseEncoding"))); ok {
|
|
base = NamedEncoding(name, base)
|
|
}
|
|
fillNames(out, base)
|
|
f.applyDifferences(out, encDict)
|
|
return out
|
|
}
|
|
|
|
// NamedEncoding is the table a name stands for, or the fallback for a name
|
|
// that is not one of them.
|
|
func NamedEncoding(name reader.Name, fallback [256]string) [256]string {
|
|
switch name {
|
|
case "WinAnsiEncoding":
|
|
return WinAnsiEncoding
|
|
case "StandardEncoding", "MacRomanEncoding", "MacExpertEncoding":
|
|
// Mac Roman differs from the standard encoding above code 127, where
|
|
// almost nothing in a Latin document lives; using the standard table
|
|
// is closer than using none.
|
|
return StandardEncoding
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
// fillNames copies a table into the map the rest of this works from.
|
|
func fillNames(out map[int]string, table [256]string) {
|
|
for code, name := range table {
|
|
if name != "" {
|
|
out[code] = name
|
|
}
|
|
}
|
|
}
|
|
|
|
// applyDifferences reads the array that names glyphs a code at a time: a
|
|
// number, then the names of the codes from there on.
|
|
func (f *Font) applyDifferences(out map[int]string, encDict reader.Dict) {
|
|
arr, ok := reader.ToArray(resolve(f.doc, encDict.Get("Differences")))
|
|
if !ok {
|
|
return
|
|
}
|
|
code := 0
|
|
for _, e := range arr {
|
|
v := resolve(f.doc, e)
|
|
if n, ok := reader.ToInt(v); ok {
|
|
code = int(n)
|
|
continue
|
|
}
|
|
if name, ok := reader.ToName(v); ok {
|
|
if code >= 0 && code < 256 {
|
|
out[code] = string(name)
|
|
f.chosen[code] = true
|
|
}
|
|
code++
|
|
}
|
|
}
|
|
}
|
|
|
|
// readSimpleWidths reads /Widths, which gives one width per code from
|
|
// /FirstChar on.
|
|
func (f *Font) readSimpleWidths() {
|
|
first, ok := reader.ToInt(resolve(f.doc, f.dict.Get("FirstChar")))
|
|
if !ok {
|
|
return
|
|
}
|
|
arr, ok := reader.ToArray(resolve(f.doc, f.dict.Get("Widths")))
|
|
if !ok {
|
|
return
|
|
}
|
|
for i, e := range arr {
|
|
if v, ok := reader.ToFloat(resolve(f.doc, e)); ok {
|
|
f.widths[int(first)+i] = v / 1000
|
|
}
|
|
}
|
|
if mw := floatOf(resolve(f.doc, f.descriptor.Get("MissingWidth")), -1); mw >= 0 {
|
|
f.defaultW = mw / 1000
|
|
}
|
|
}
|
|
|
|
// readComposite reads a font addressed by character identifier: its descendant
|
|
// holds the widths, the descriptor and the map from identifier to glyph.
|
|
func (f *Font) readComposite() {
|
|
f.defaultW = 1
|
|
arr, ok := reader.ToArray(resolve(f.doc, f.dict.Get("DescendantFonts")))
|
|
if !ok || len(arr) == 0 {
|
|
return
|
|
}
|
|
kid, ok := reader.ToDict(resolve(f.doc, arr[0]))
|
|
if !ok {
|
|
return
|
|
}
|
|
if v, ok := reader.ToFloat(resolve(f.doc, kid.Get("DW"))); ok {
|
|
f.defaultW = v / 1000
|
|
}
|
|
f.readCIDWidths(kid)
|
|
f.descriptor, _ = f.doc.GetDict(kid, "FontDescriptor")
|
|
f.symbolic = symbolicFlag(f.doc, f.descriptor)
|
|
if stream, ok := reader.ToStream(resolve(f.doc, kid.Get("CIDToGIDMap"))); ok {
|
|
if data, img, err := f.doc.DecodeStream(stream); err == nil && img == "" {
|
|
f.cidToGID = data
|
|
}
|
|
}
|
|
}
|
|
|
|
// readCIDWidths reads the /W array, which names widths one identifier at a
|
|
// time or a run at a time.
|
|
func (f *Font) readCIDWidths(kid reader.Dict) {
|
|
arr, ok := reader.ToArray(resolve(f.doc, kid.Get("W")))
|
|
if !ok {
|
|
return
|
|
}
|
|
for i := 0; i < len(arr); {
|
|
first, ok := reader.ToInt(resolve(f.doc, arr[i]))
|
|
if !ok || i+1 >= len(arr) {
|
|
return
|
|
}
|
|
next := resolve(f.doc, arr[i+1])
|
|
if list, ok := reader.ToArray(next); ok {
|
|
for k, e := range list {
|
|
if v, ok := reader.ToFloat(resolve(f.doc, e)); ok {
|
|
f.widths[int(first)+k] = v / 1000
|
|
}
|
|
}
|
|
i += 2
|
|
continue
|
|
}
|
|
last, ok := reader.ToInt(next)
|
|
if !ok || i+2 >= len(arr) {
|
|
return
|
|
}
|
|
w, ok := reader.ToFloat(resolve(f.doc, arr[i+2]))
|
|
if !ok {
|
|
return
|
|
}
|
|
if last >= first && last-first < 1<<20 {
|
|
for c := first; c <= last; c++ {
|
|
f.widths[int(c)] = w / 1000
|
|
}
|
|
}
|
|
i += 3
|
|
}
|
|
}
|
|
|
|
// floatArray reads an array of numbers, or nothing.
|
|
func floatArray(d *reader.Document, o reader.Object) []float64 {
|
|
arr, ok := reader.ToArray(resolve(d, o))
|
|
if !ok {
|
|
return nil
|
|
}
|
|
out := make([]float64, 0, len(arr))
|
|
for _, e := range arr {
|
|
v, ok := reader.ToFloat(resolve(d, e))
|
|
if !ok {
|
|
return nil
|
|
}
|
|
out = append(out, v)
|
|
}
|
|
return out
|
|
}
|