Files
sift/vendor/github.com/go-pdfkit/pdffont/read.go
T
kyleandClaude Opus 5.5 cfde35397a P5a plan: PDF documents — seven Ornith tasks with acceptance tests first
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>
2026-09-25 20:47:38 -07:00

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
}