-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathprogram.go
More file actions
175 lines (167 loc) · 5.83 KB
/
Copy pathprogram.go
File metadata and controls
175 lines (167 loc) · 5.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
package extract
import (
"github.com/go-opentype/opentype"
"github.com/go-pdfkit/pdffont"
"github.com/go-pdfkit/reader"
)
// attachProgram gives a font a last resort for the codes the document says
// nothing about: what its own embedded program calls them.
//
// A font that says it is symbolic is addressed through its own character map,
// and an assumed encoding is a bad guess at it — a mathematical font puts a
// capital gamma where the standard encoding puts an inverted exclamation mark.
// The program knows, and this is how it is asked.
//
// There are two ways to ask, and a program answers at most one of them. A
// PostScript program names its glyphs and carries an encoding from code to
// name, so the name is the answer. A TrueType program usually does neither —
// a subset of one names nothing worth reading and has no code-to-name
// encoding at all — but it does carry character maps, and walking through one
// to the glyph and back out of another says which character the glyph is for.
func attachProgram(f *pdffont.Font) {
if f.Kind() == pdffont.Composite {
return
}
key, data, ok := f.Program()
if !ok {
return
}
program, err := readProgram(key, data)
if err != nil {
return
}
maps := chooseCharacterMaps(program)
// Only a simple font reaches here, and its codes are bytes.
f.SetFallback(func(code int) (string, bool) {
if r, ok := runeByName(program, code); ok {
return string(r), true
}
if r, ok := runeByCharacterMap(program, maps, code); ok {
return string(r), true
}
return "", false
})
}
// runeByName asks the program's own encoding what it calls a code, and reads
// the character out of that name. Only a PostScript program — a Type 1 one, or
// the CFF outlines of an OpenType font — carries such an encoding.
func runeByName(program *opentype.Font, code int) (rune, bool) {
gid, ok := program.GlyphIndexByCode(byte(code))
if !ok {
return 0, false
}
// A glyph the program does not name comes back as no name at all,
// which names no character either.
name, _ := program.GlyphName(gid)
return pdffont.RuneOfGlyphName(name)
}
// characterMaps says which of a program's cmap subtables are worth addressing,
// by index, or -1 for one the program does not carry.
type characterMaps struct {
// symbol is the Microsoft Symbol subtable, platform 3 encoding 0: a font's
// own codes, conventionally written at 0xF000 + code.
symbol int
// mac is the Macintosh Roman subtable, platform 1 encoding 0, indexed by
// single bytes.
mac int
// unicode is a Unicode subtable — Microsoft Unicode, platform 3 encoding 1
// or 10, or anything on platform 0 — indexed by codepoint. This is the one
// that is inverted, because it is the only one whose codes are characters.
unicode int
}
// chooseCharacterMaps picks out the subtables a font is addressed through and
// the one that says which character a glyph is for.
//
// A font can carry several subtables of a kind; the first of each is taken,
// which is what a font that repeats one means by repeating it.
func chooseCharacterMaps(program *opentype.Font) characterMaps {
m := characterMaps{symbol: -1, mac: -1, unicode: -1}
for i := range program.NumCharacterMaps() {
platform, encoding, _, _ := program.CharacterMap(i)
switch {
case platform == 3 && encoding == 0:
if m.symbol < 0 {
m.symbol = i
}
case platform == 1 && encoding == 0:
if m.mac < 0 {
m.mac = i
}
case platform == 0, platform == 3 && (encoding == 1 || encoding == 10):
if m.unicode < 0 {
m.unicode = i
}
}
}
return m
}
// runeByCharacterMap walks a code through the font's own character map to a
// glyph, and back out of its Unicode character map to the character that glyph
// stands for.
//
// The way in is the one poppler uses for a font the document gave no encoding:
// the Microsoft Symbol subtable if there is one, else the Macintosh Roman one,
// addressed by the raw code and then, failing that, by 0xF000 + code, which is
// where such subtables are conventionally written.
//
// The way out is the Unicode subtable, inverted. Without one there is no way
// out: a glyph on its own says nothing about which character it is, and a
// guess would be exactly the wrong letter the caller's guard exists to refuse.
func runeByCharacterMap(program *opentype.Font, m characterMaps, code int) (rune, bool) {
if m.unicode < 0 {
return 0, false
}
in := m.symbol
if in < 0 {
in = m.mac
}
if in < 0 {
return 0, false
}
gid, ok := program.GlyphIndexInMap(in, rune(code))
if !ok {
gid, ok = program.GlyphIndexInMap(in, rune(0xF000|code))
}
if !ok {
return 0, false
}
r, ok := program.RuneOfGlyphInMap(m.unicode, gid)
if !ok || !readableRune(r) {
return 0, false
}
return r, true
}
// readableRune reports whether a character recovered this way is worth
// reporting as text.
//
// A private-use codepoint is not: it means whatever the font decided it means
// and nothing outside the font can read it, so a page full of them searches no
// better than a page of nothing and looks, wrongly, like it was read. Control
// characters are refused for the same reason — a page does not say them.
func readableRune(r rune) bool {
switch {
case r < 0x20, r >= 0x7F && r <= 0x9F:
return false
case r >= 0xE000 && r <= 0xF8FF:
return false
case r >= 0xF0000:
return false
}
return true
}
// readProgram decodes an embedded font program. Which key it arrived under
// says what it is: FontFile2 is TrueType, FontFile a PostScript Type 1
// program, and FontFile3 either a bare CFF one or a whole OpenType font — the
// key alone does not settle that one, so both are tried.
func readProgram(key reader.Name, data []byte) (*opentype.Font, error) {
switch key {
case "FontFile":
return opentype.ParseType1(data)
case "FontFile3":
if f, err := opentype.Parse(data); err == nil {
return f, nil
}
return opentype.ParseCFF(data)
}
return opentype.Parse(data)
}