@strav/pdf 0.4.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +79 -0
- package/package.json +51 -0
- package/src/color/cie.ts +61 -0
- package/src/color/color.ts +77 -0
- package/src/color/conversion.ts +26 -0
- package/src/color/device.ts +37 -0
- package/src/color/devicen.ts +74 -0
- package/src/color/icc.ts +103 -0
- package/src/color/index.ts +15 -0
- package/src/color/separation.ts +94 -0
- package/src/color/space.ts +47 -0
- package/src/content/content_stream.ts +373 -0
- package/src/content/graphics_state.ts +64 -0
- package/src/content/index.ts +16 -0
- package/src/content/operators.ts +70 -0
- package/src/content/path.ts +51 -0
- package/src/content/resources.ts +119 -0
- package/src/content/text_object.ts +140 -0
- package/src/document/catalog.ts +16 -0
- package/src/document/index.ts +13 -0
- package/src/document/object_table.ts +67 -0
- package/src/document/page.ts +74 -0
- package/src/document/page_tree.ts +78 -0
- package/src/document/pdf_document.ts +310 -0
- package/src/document/types.ts +65 -0
- package/src/document/xref.ts +68 -0
- package/src/ext-gstate/ext_gstate.ts +69 -0
- package/src/ext-gstate/index.ts +2 -0
- package/src/fonts/cff.ts +123 -0
- package/src/fonts/cid_encoding.ts +45 -0
- package/src/fonts/cmap_table.ts +180 -0
- package/src/fonts/font.ts +342 -0
- package/src/fonts/glyf.ts +59 -0
- package/src/fonts/hmtx.ts +21 -0
- package/src/fonts/index.ts +20 -0
- package/src/fonts/name_table.ts +50 -0
- package/src/fonts/os2.ts +41 -0
- package/src/fonts/sfnt.ts +224 -0
- package/src/fonts/standard_14.ts +132 -0
- package/src/fonts/subset.ts +221 -0
- package/src/fonts/to_unicode.ts +82 -0
- package/src/fonts/win_ansi.ts +69 -0
- package/src/images/image.ts +111 -0
- package/src/images/index.ts +6 -0
- package/src/images/jpeg.ts +103 -0
- package/src/images/png.ts +239 -0
- package/src/images/smask.ts +24 -0
- package/src/index.ts +57 -0
- package/src/metadata/index.ts +3 -0
- package/src/metadata/info_dict.ts +28 -0
- package/src/metadata/xmp.ts +110 -0
- package/src/objects/encode.ts +77 -0
- package/src/objects/index.ts +43 -0
- package/src/objects/indirect_ref.ts +17 -0
- package/src/objects/name.ts +50 -0
- package/src/objects/number.ts +43 -0
- package/src/objects/string.ts +136 -0
- package/src/objects/types.ts +86 -0
- package/src/output/buffer_sink.ts +40 -0
- package/src/output/byte_sink.ts +12 -0
- package/src/output/index.ts +3 -0
- package/src/output/stream_sink.ts +62 -0
- package/src/patterns/index.ts +10 -0
- package/src/patterns/shading.ts +162 -0
- package/src/patterns/tiling_pattern.ts +68 -0
- package/src/standards/context.ts +10 -0
- package/src/standards/index.ts +23 -0
- package/src/standards/pdf_a.ts +23 -0
- package/src/standards/pdf_x.ts +31 -0
- package/src/streams/ascii85.ts +61 -0
- package/src/streams/ascii_hex.ts +33 -0
- package/src/streams/flate.ts +17 -0
- package/src/streams/index.ts +9 -0
- package/src/streams/stream.ts +66 -0
- package/src/util/ascii.ts +63 -0
- package/src/util/binary.ts +71 -0
- package/src/util/errors.ts +61 -0
- package/src/util/index.ts +10 -0
- package/src/util/units.ts +24 -0
- package/tsconfig.json +5 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `PdfFont` (spec §10, §16). The public font handle.
|
|
3
|
+
*
|
|
4
|
+
* - `PdfFont.standard(name)` — a Standard-14 referenced font (never embedded).
|
|
5
|
+
* - `PdfFont.fromTrueType(bytes)` — a fully-embedded TrueType font emitted as
|
|
6
|
+
* a Type0 / CIDFontType2 with Identity-H encoding and a ToUnicode CMap
|
|
7
|
+
* (selectable, copy/pasteable text). Subsetting is milestone 6.
|
|
8
|
+
*
|
|
9
|
+
* A font registers itself into the object table via `register(table)`, which
|
|
10
|
+
* adds however many indirect objects it needs and returns the `/Font` ref to
|
|
11
|
+
* place in the page resource dictionary.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { PdfGenError, UnsupportedFontError } from '../util/errors.ts'
|
|
15
|
+
import { ascii } from '../util/ascii.ts'
|
|
16
|
+
import { arr, dict, name, num } from '../objects/types.ts'
|
|
17
|
+
import type { IndirectRef } from '../objects/types.ts'
|
|
18
|
+
import type { ObjectTable } from '../document/object_table.ts'
|
|
19
|
+
import { makeStream } from '../streams/stream.ts'
|
|
20
|
+
import { encodeWinAnsi } from './win_ansi.ts'
|
|
21
|
+
import {
|
|
22
|
+
type StandardFontName,
|
|
23
|
+
isStandardFontName,
|
|
24
|
+
standardGlyphWidth,
|
|
25
|
+
usesWinAnsi,
|
|
26
|
+
} from './standard_14.ts'
|
|
27
|
+
import { SfntFont } from './sfnt.ts'
|
|
28
|
+
import { parseCmap, type CmapLookup } from './cmap_table.ts'
|
|
29
|
+
import { Hmtx } from './hmtx.ts'
|
|
30
|
+
import { parseName } from './name_table.ts'
|
|
31
|
+
import { parseOs2, type Os2Metrics } from './os2.ts'
|
|
32
|
+
import { encodeIdentityH, buildWidthsArray } from './cid_encoding.ts'
|
|
33
|
+
import { buildToUnicode } from './to_unicode.ts'
|
|
34
|
+
import { subsetTrueType } from './subset.ts'
|
|
35
|
+
import { parseCff } from './cff.ts'
|
|
36
|
+
|
|
37
|
+
export type { StandardFontName }
|
|
38
|
+
|
|
39
|
+
export interface TrueTypeOptions {
|
|
40
|
+
/** Face to select from a `.ttc` collection (default 0). */
|
|
41
|
+
faceIndex?: number
|
|
42
|
+
/** Subset to the glyphs actually used (default true). */
|
|
43
|
+
subset?: boolean
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export abstract class PdfFont {
|
|
47
|
+
abstract readonly id: string
|
|
48
|
+
abstract readonly baseFont: string
|
|
49
|
+
abstract readonly isStandard14: boolean
|
|
50
|
+
|
|
51
|
+
/** Encode a string into the bytes that go inside a `Tj`/`TJ` string. */
|
|
52
|
+
abstract encode(text: string): Uint8Array
|
|
53
|
+
|
|
54
|
+
/** Rendered width of `text` in points at `sizePt`. */
|
|
55
|
+
abstract widthOfText(text: string, sizePt: number): number
|
|
56
|
+
|
|
57
|
+
/** Add the font's objects to the table; return the `/Font` reference. */
|
|
58
|
+
abstract register(table: ObjectTable): IndirectRef
|
|
59
|
+
|
|
60
|
+
/** Reference one of the Standard-14 fonts (never embedded, spec §10.1). */
|
|
61
|
+
static standard(fontName: StandardFontName): PdfFont {
|
|
62
|
+
return new Standard14Font(fontName)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Embed an SFNT font from its `.ttf`/`.otf`/`.ttc` bytes. TrueType (`glyf`)
|
|
67
|
+
* is subsetted by default; OpenType/CFF (`OTTO`) is embedded whole as a
|
|
68
|
+
* CIDFontType0 (CFF subsetting is deferred — `subset` is ignored for CFF).
|
|
69
|
+
*/
|
|
70
|
+
static fromTrueType(bytes: Uint8Array, opts: TrueTypeOptions = {}): PdfFont {
|
|
71
|
+
return new EmbeddedTrueTypeFont(bytes, opts.faceIndex ?? 0, opts.subset !== false)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Alias of {@link fromTrueType} — clearer when embedding an `.otf`. */
|
|
75
|
+
static fromOpenType(bytes: Uint8Array, opts: TrueTypeOptions = {}): PdfFont {
|
|
76
|
+
return PdfFont.fromTrueType(bytes, opts)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ── Standard-14 (referenced, never embedded) ──────────────────────────────
|
|
81
|
+
|
|
82
|
+
class Standard14Font extends PdfFont {
|
|
83
|
+
readonly id: string
|
|
84
|
+
readonly baseFont: string
|
|
85
|
+
readonly isStandard14 = true
|
|
86
|
+
private readonly winAnsi: boolean
|
|
87
|
+
|
|
88
|
+
constructor(private readonly fontName: StandardFontName) {
|
|
89
|
+
super()
|
|
90
|
+
if (!isStandardFontName(fontName)) {
|
|
91
|
+
throw new PdfGenError('PDF_UNSUPPORTED_FONT', `Unknown Standard-14 font: ${fontName}`)
|
|
92
|
+
}
|
|
93
|
+
this.baseFont = fontName
|
|
94
|
+
this.id = `std14:${fontName}`
|
|
95
|
+
this.winAnsi = usesWinAnsi(fontName)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
encode(text: string): Uint8Array {
|
|
99
|
+
if (this.winAnsi) return encodeWinAnsi(text)
|
|
100
|
+
const out = new Uint8Array(text.length)
|
|
101
|
+
for (let i = 0; i < text.length; i++) {
|
|
102
|
+
const c = text.charCodeAt(i)
|
|
103
|
+
if (c > 0xff) {
|
|
104
|
+
throw new PdfGenError(
|
|
105
|
+
'PDF_TEXT_ENCODING',
|
|
106
|
+
`${this.baseFont} uses a built-in 8-bit encoding; char U+${c.toString(16)} is out of range`
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
out[i] = c
|
|
110
|
+
}
|
|
111
|
+
return out
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
widthOfText(text: string, sizePt: number): number {
|
|
115
|
+
let units = 0
|
|
116
|
+
for (const byte of this.encode(text)) units += standardGlyphWidth(this.fontName, byte)
|
|
117
|
+
return (units * sizePt) / 1000
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
register(table: ObjectTable): IndirectRef {
|
|
121
|
+
const d = dict({
|
|
122
|
+
Type: name('Font'),
|
|
123
|
+
Subtype: name('Type1'),
|
|
124
|
+
BaseFont: name(this.baseFont),
|
|
125
|
+
})
|
|
126
|
+
if (this.winAnsi) d.entries.set('Encoding', name('WinAnsiEncoding'))
|
|
127
|
+
return table.add(d)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ── Embedded TrueType → Type0 / CIDFontType2 (Identity-H) ──────────────────
|
|
132
|
+
|
|
133
|
+
class EmbeddedTrueTypeFont extends PdfFont {
|
|
134
|
+
readonly id: string
|
|
135
|
+
readonly baseFont: string
|
|
136
|
+
readonly isStandard14 = false
|
|
137
|
+
|
|
138
|
+
private readonly sfnt: SfntFont
|
|
139
|
+
private readonly cmap: CmapLookup
|
|
140
|
+
private readonly hmtx: Hmtx
|
|
141
|
+
private readonly os2: Os2Metrics | null
|
|
142
|
+
private readonly italicAngle: number
|
|
143
|
+
private readonly fixedPitch: boolean
|
|
144
|
+
/** OpenType/CFF (embedded whole as FontFile3 / CIDFontType0). */
|
|
145
|
+
private readonly isCFF: boolean
|
|
146
|
+
|
|
147
|
+
/** Glyphs referenced so far (drives `/W`); gid → code points for ToUnicode. */
|
|
148
|
+
private readonly usedGids = new Set<number>()
|
|
149
|
+
private readonly gidToCps = new Map<number, number[]>()
|
|
150
|
+
|
|
151
|
+
private static counter = 0
|
|
152
|
+
|
|
153
|
+
constructor(
|
|
154
|
+
bytes: Uint8Array,
|
|
155
|
+
faceIndex: number,
|
|
156
|
+
private readonly doSubset: boolean
|
|
157
|
+
) {
|
|
158
|
+
super()
|
|
159
|
+
this.sfnt = new SfntFont(bytes, faceIndex)
|
|
160
|
+
|
|
161
|
+
const cmap = this.sfnt.table('cmap')
|
|
162
|
+
if (!cmap) throw new UnsupportedFontError("Font is missing the required 'cmap' table")
|
|
163
|
+
this.cmap = parseCmap(cmap)
|
|
164
|
+
|
|
165
|
+
const hmtx = this.sfnt.table('hmtx')
|
|
166
|
+
if (!hmtx) throw new UnsupportedFontError("Font is missing the required 'hmtx' table")
|
|
167
|
+
this.hmtx = new Hmtx(hmtx, this.sfnt.hhea.numberOfHMetrics, this.sfnt.numGlyphs)
|
|
168
|
+
|
|
169
|
+
this.os2 = parseOs2(this.sfnt.table('OS/2'))
|
|
170
|
+
|
|
171
|
+
this.isCFF = this.sfnt.isCFF
|
|
172
|
+
const cffTable = this.isCFF ? this.sfnt.table('CFF ') : undefined
|
|
173
|
+
const cffInfo = cffTable ? parseCff(cffTable) : null
|
|
174
|
+
|
|
175
|
+
const nameTable = this.sfnt.table('name')
|
|
176
|
+
const names = nameTable ? parseName(nameTable) : { postScriptName: null, family: null }
|
|
177
|
+
const ps = (
|
|
178
|
+
names.postScriptName ||
|
|
179
|
+
cffInfo?.name ||
|
|
180
|
+
names.family ||
|
|
181
|
+
'EmbeddedFont'
|
|
182
|
+
).replace(/[\s()<>[\]{}/%]/g, '')
|
|
183
|
+
this.baseFont = ps
|
|
184
|
+
this.id = `${this.isCFF ? 'otf' : 'ttf'}:${ps}:${(EmbeddedTrueTypeFont.counter++).toString()}`
|
|
185
|
+
|
|
186
|
+
const post = this.sfnt.table('post')
|
|
187
|
+
if (post && post.length >= 16) {
|
|
188
|
+
// italicAngle: Fixed 16.16 at offset 4; isFixedPitch: uint32 at 12.
|
|
189
|
+
const raw = (post[4]! << 24) | (post[5]! << 16) | (post[6]! << 8) | post[7]!
|
|
190
|
+
this.italicAngle = raw / 65536
|
|
191
|
+
this.fixedPitch =
|
|
192
|
+
((post[12]! << 24) | (post[13]! << 16) | (post[14]! << 8) | post[15]!) !== 0
|
|
193
|
+
} else {
|
|
194
|
+
this.italicAngle = 0
|
|
195
|
+
this.fixedPitch = false
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private gid(cp: number): number {
|
|
200
|
+
return this.cmap.gidFor(cp)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
encode(text: string): Uint8Array {
|
|
204
|
+
const gids: number[] = []
|
|
205
|
+
for (const ch of text) {
|
|
206
|
+
const cp = ch.codePointAt(0)!
|
|
207
|
+
const g = this.gid(cp)
|
|
208
|
+
gids.push(g)
|
|
209
|
+
this.usedGids.add(g)
|
|
210
|
+
if (g !== 0) {
|
|
211
|
+
const existing = this.gidToCps.get(g)
|
|
212
|
+
if (!existing) this.gidToCps.set(g, [cp])
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return encodeIdentityH(gids)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
widthOfText(text: string, sizePt: number): number {
|
|
219
|
+
const upm = this.sfnt.head.unitsPerEm
|
|
220
|
+
let units1000 = 0
|
|
221
|
+
for (const ch of text) {
|
|
222
|
+
const g = this.gid(ch.codePointAt(0)!)
|
|
223
|
+
units1000 += (this.hmtx.advance(g) * 1000) / upm
|
|
224
|
+
}
|
|
225
|
+
return (units1000 * sizePt) / 1000
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
private get isItalic(): boolean {
|
|
229
|
+
return (
|
|
230
|
+
(this.os2 != null && (this.os2.fsSelection & 0x01) !== 0) ||
|
|
231
|
+
(this.sfnt.head.macStyle & 0x02) !== 0 ||
|
|
232
|
+
this.italicAngle !== 0
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** PDF FontDescriptor /Flags (spec Table 121). */
|
|
237
|
+
private flags(): number {
|
|
238
|
+
let f = 1 << 5 // Nonsymbolic
|
|
239
|
+
if (this.fixedPitch) f |= 1 << 0 // FixedPitch
|
|
240
|
+
const fam = this.os2 ? (this.os2.sFamilyClass >> 8) & 0xff : 0
|
|
241
|
+
if (fam >= 1 && fam <= 7 && fam !== 8) f |= 1 << 1 // Serif
|
|
242
|
+
if (this.isItalic) f |= 1 << 6 // Italic
|
|
243
|
+
return f
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
register(table: ObjectTable): IndirectRef {
|
|
247
|
+
const upm = this.sfnt.head.unitsPerEm
|
|
248
|
+
const scale = 1000 / upm
|
|
249
|
+
const s = (v: number) => Math.round(v * scale)
|
|
250
|
+
|
|
251
|
+
// Font program. TrueType: FontFile2 (subset by default, `TAG+` name).
|
|
252
|
+
// OpenType/CFF: the bare `CFF ` table as FontFile3 /CIDFontType0C,
|
|
253
|
+
// embedded whole — CFF subsetting is deferred (spec §10.3, §23).
|
|
254
|
+
let program: Uint8Array
|
|
255
|
+
let fontName: string
|
|
256
|
+
let fontFileRef: IndirectRef
|
|
257
|
+
let fontFileKey: 'FontFile2' | 'FontFile3'
|
|
258
|
+
|
|
259
|
+
if (this.isCFF) {
|
|
260
|
+
program = this.sfnt.table('CFF ')!
|
|
261
|
+
fontName = this.baseFont
|
|
262
|
+
fontFileKey = 'FontFile3'
|
|
263
|
+
fontFileRef = table.add(
|
|
264
|
+
makeStream(program, {
|
|
265
|
+
filter: 'FlateDecode',
|
|
266
|
+
extra: { Subtype: name('CIDFontType0C') },
|
|
267
|
+
})
|
|
268
|
+
)
|
|
269
|
+
} else {
|
|
270
|
+
if (this.doSubset) {
|
|
271
|
+
const { bytes, tag } = subsetTrueType(this.sfnt, this.usedGids)
|
|
272
|
+
program = bytes
|
|
273
|
+
fontName = `${tag}+${this.baseFont}`
|
|
274
|
+
} else {
|
|
275
|
+
program = this.sfnt.programBytes
|
|
276
|
+
fontName = this.baseFont
|
|
277
|
+
}
|
|
278
|
+
fontFileKey = 'FontFile2'
|
|
279
|
+
fontFileRef = table.add(
|
|
280
|
+
makeStream(program, { filter: 'FlateDecode', extra: { Length1: num(program.length) } })
|
|
281
|
+
)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const ascent = this.os2 && this.os2.typoAscender ? this.os2.typoAscender : this.sfnt.hhea.ascent
|
|
285
|
+
const descent =
|
|
286
|
+
this.os2 && this.os2.typoDescender ? this.os2.typoDescender : this.sfnt.hhea.descent
|
|
287
|
+
const capHeight = this.os2 && this.os2.capHeight ? this.os2.capHeight : Math.round(ascent * 0.7)
|
|
288
|
+
const stemV = this.os2 && this.os2.weightClass >= 600 ? 120 : 80
|
|
289
|
+
|
|
290
|
+
const descriptorRef = table.add(
|
|
291
|
+
dict({
|
|
292
|
+
Type: name('FontDescriptor'),
|
|
293
|
+
FontName: name(fontName),
|
|
294
|
+
Flags: num(this.flags()),
|
|
295
|
+
FontBBox: arr(
|
|
296
|
+
[this.sfnt.head.xMin, this.sfnt.head.yMin, this.sfnt.head.xMax, this.sfnt.head.yMax]
|
|
297
|
+
.map(s)
|
|
298
|
+
.map(num)
|
|
299
|
+
),
|
|
300
|
+
ItalicAngle: num(this.italicAngle),
|
|
301
|
+
Ascent: num(s(ascent)),
|
|
302
|
+
Descent: num(s(descent)),
|
|
303
|
+
CapHeight: num(s(capHeight)),
|
|
304
|
+
StemV: num(stemV),
|
|
305
|
+
[fontFileKey]: fontFileRef,
|
|
306
|
+
})
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
const usedGids = [...this.usedGids]
|
|
310
|
+
const cidFont = dict({
|
|
311
|
+
Type: name('Font'),
|
|
312
|
+
Subtype: name(this.isCFF ? 'CIDFontType0' : 'CIDFontType2'),
|
|
313
|
+
BaseFont: name(fontName),
|
|
314
|
+
CIDSystemInfo: dict({
|
|
315
|
+
Registry: { kind: 'str', value: ascii('Adobe'), encoding: 'literal' },
|
|
316
|
+
Ordering: { kind: 'str', value: ascii('Identity'), encoding: 'literal' },
|
|
317
|
+
Supplement: num(0),
|
|
318
|
+
}),
|
|
319
|
+
FontDescriptor: descriptorRef,
|
|
320
|
+
DW: num(Math.round(this.hmtx.advance(0) * scale) || 1000),
|
|
321
|
+
W: buildWidthsArray(usedGids, g => this.hmtx.advance(g), upm),
|
|
322
|
+
})
|
|
323
|
+
// CIDToGIDMap applies only to CIDFontType2 (TrueType).
|
|
324
|
+
if (!this.isCFF) cidFont.entries.set('CIDToGIDMap', name('Identity'))
|
|
325
|
+
const cidFontRef = table.add(cidFont)
|
|
326
|
+
|
|
327
|
+
const toUnicodeRef = table.add(
|
|
328
|
+
makeStream(ascii(buildToUnicode(this.gidToCps)), { filter: 'FlateDecode' })
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
return table.add(
|
|
332
|
+
dict({
|
|
333
|
+
Type: name('Font'),
|
|
334
|
+
Subtype: name('Type0'),
|
|
335
|
+
BaseFont: name(fontName),
|
|
336
|
+
Encoding: name('Identity-H'),
|
|
337
|
+
DescendantFonts: arr([cidFontRef]),
|
|
338
|
+
ToUnicode: toUnicodeRef,
|
|
339
|
+
})
|
|
340
|
+
)
|
|
341
|
+
}
|
|
342
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `loca` + `glyf` reading (spec §10.2). Milestone 5 embeds the whole font, so
|
|
3
|
+
* this isn't on the embed path yet — but it provides the loca offsets and
|
|
4
|
+
* composite-glyph component closure that milestone-6 subsetting builds on.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { BinaryReader } from '../util/binary.ts'
|
|
8
|
+
|
|
9
|
+
const ARG_1_AND_2_ARE_WORDS = 0x0001
|
|
10
|
+
const WE_HAVE_A_SCALE = 0x0008
|
|
11
|
+
const MORE_COMPONENTS = 0x0020
|
|
12
|
+
const WE_HAVE_AN_X_AND_Y_SCALE = 0x0040
|
|
13
|
+
const WE_HAVE_A_TWO_BY_TWO = 0x0080
|
|
14
|
+
|
|
15
|
+
export class GlyfTable {
|
|
16
|
+
/** Glyph data offsets, length numGlyphs+1 (offset[i+1]-offset[i] = size). */
|
|
17
|
+
readonly loca: number[] = []
|
|
18
|
+
|
|
19
|
+
constructor(
|
|
20
|
+
loca: Uint8Array,
|
|
21
|
+
private readonly glyf: Uint8Array,
|
|
22
|
+
numGlyphs: number,
|
|
23
|
+
longLoca: boolean
|
|
24
|
+
) {
|
|
25
|
+
const r = new BinaryReader(loca)
|
|
26
|
+
for (let i = 0; i <= numGlyphs; i++) {
|
|
27
|
+
this.loca.push(longLoca ? r.u32() : r.u16() * 2)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Raw glyph bytes for `gid` (empty for whitespace/.notdef). */
|
|
32
|
+
glyphData(gid: number): Uint8Array {
|
|
33
|
+
const start = this.loca[gid] ?? 0
|
|
34
|
+
const end = this.loca[gid + 1] ?? start
|
|
35
|
+
return this.glyf.subarray(start, end)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Component glyph indices referenced by a composite glyph (else []). */
|
|
39
|
+
componentGids(gid: number): number[] {
|
|
40
|
+
const data = this.glyphData(gid)
|
|
41
|
+
if (data.length < 10) return []
|
|
42
|
+
const r = new BinaryReader(data)
|
|
43
|
+
const numberOfContours = r.i16()
|
|
44
|
+
if (numberOfContours >= 0) return [] // simple glyph
|
|
45
|
+
r.seek(10) // skip bbox
|
|
46
|
+
const out: number[] = []
|
|
47
|
+
for (;;) {
|
|
48
|
+
const flags = r.u16()
|
|
49
|
+
out.push(r.u16())
|
|
50
|
+
let skip = flags & ARG_1_AND_2_ARE_WORDS ? 4 : 2
|
|
51
|
+
if (flags & WE_HAVE_A_SCALE) skip += 2
|
|
52
|
+
else if (flags & WE_HAVE_AN_X_AND_Y_SCALE) skip += 4
|
|
53
|
+
else if (flags & WE_HAVE_A_TWO_BY_TWO) skip += 8
|
|
54
|
+
r.seek(r.position + skip)
|
|
55
|
+
if (!(flags & MORE_COMPONENTS)) break
|
|
56
|
+
}
|
|
57
|
+
return out
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `hmtx` table (spec §10.2): per-glyph advance widths in font units. The last
|
|
3
|
+
* `numberOfHMetrics` entry's advance applies to all trailing glyphs (which
|
|
4
|
+
* carry only a left-side bearing).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export class Hmtx {
|
|
8
|
+
constructor(
|
|
9
|
+
private readonly hmtx: Uint8Array,
|
|
10
|
+
private readonly numberOfHMetrics: number,
|
|
11
|
+
private readonly numGlyphs: number
|
|
12
|
+
) {}
|
|
13
|
+
|
|
14
|
+
/** Advance width of a glyph in font units. */
|
|
15
|
+
advance(gid: number): number {
|
|
16
|
+
if (gid < 0 || gid >= this.numGlyphs) return 0
|
|
17
|
+
const i = gid < this.numberOfHMetrics ? gid : this.numberOfHMetrics - 1
|
|
18
|
+
const o = i * 4
|
|
19
|
+
return (this.hmtx[o]! << 8) | this.hmtx[o + 1]!
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export { PdfFont } from './font.ts'
|
|
2
|
+
export type { StandardFontName, TrueTypeOptions } from './font.ts'
|
|
3
|
+
export { isStandardFontName, usesWinAnsi, standardGlyphWidth } from './standard_14.ts'
|
|
4
|
+
export { encodeWinAnsi, winAnsiByte } from './win_ansi.ts'
|
|
5
|
+
export { SfntFont, tableChecksum } from './sfnt.ts'
|
|
6
|
+
export type { TableRecord, HeadTable, HheaTable } from './sfnt.ts'
|
|
7
|
+
export { parseCmap } from './cmap_table.ts'
|
|
8
|
+
export type { CmapLookup } from './cmap_table.ts'
|
|
9
|
+
export { Hmtx } from './hmtx.ts'
|
|
10
|
+
export { parseName } from './name_table.ts'
|
|
11
|
+
export type { FontNames } from './name_table.ts'
|
|
12
|
+
export { parseOs2 } from './os2.ts'
|
|
13
|
+
export type { Os2Metrics } from './os2.ts'
|
|
14
|
+
export { GlyfTable } from './glyf.ts'
|
|
15
|
+
export { encodeIdentityH, buildWidthsArray } from './cid_encoding.ts'
|
|
16
|
+
export { buildToUnicode } from './to_unicode.ts'
|
|
17
|
+
export { subsetTrueType } from './subset.ts'
|
|
18
|
+
export type { SubsetResult } from './subset.ts'
|
|
19
|
+
export { parseCff } from './cff.ts'
|
|
20
|
+
export type { CffInfo } from './cff.ts'
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `name` table (spec §10.2). We only need the PostScript name (nameID 6) for
|
|
3
|
+
* `/BaseFont`; family (1) is read as a fallback.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { BinaryReader } from '../util/binary.ts'
|
|
7
|
+
|
|
8
|
+
function decode(bytes: Uint8Array, platformId: number): string {
|
|
9
|
+
// Windows (3) and Unicode (0) are UTF-16BE; Mac (1) is ASCII-ish.
|
|
10
|
+
if (platformId === 1) {
|
|
11
|
+
let s = ''
|
|
12
|
+
for (const b of bytes) s += String.fromCharCode(b)
|
|
13
|
+
return s
|
|
14
|
+
}
|
|
15
|
+
let s = ''
|
|
16
|
+
for (let i = 0; i + 1 < bytes.length; i += 2) {
|
|
17
|
+
s += String.fromCharCode((bytes[i]! << 8) | bytes[i + 1]!)
|
|
18
|
+
}
|
|
19
|
+
return s
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface FontNames {
|
|
23
|
+
postScriptName: string | null
|
|
24
|
+
family: string | null
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function parseName(name: Uint8Array): FontNames {
|
|
28
|
+
const r = new BinaryReader(name)
|
|
29
|
+
r.u16() // format
|
|
30
|
+
const count = r.u16()
|
|
31
|
+
const stringOffset = r.u16()
|
|
32
|
+
|
|
33
|
+
let postScriptName: string | null = null
|
|
34
|
+
let family: string | null = null
|
|
35
|
+
|
|
36
|
+
for (let i = 0; i < count; i++) {
|
|
37
|
+
const platformId = r.u16()
|
|
38
|
+
r.u16() // encodingId
|
|
39
|
+
r.u16() // languageId
|
|
40
|
+
const nameId = r.u16()
|
|
41
|
+
const length = r.u16()
|
|
42
|
+
const offset = r.u16()
|
|
43
|
+
if (nameId !== 6 && nameId !== 1) continue
|
|
44
|
+
const start = stringOffset + offset
|
|
45
|
+
const value = decode(name.subarray(start, start + length), platformId)
|
|
46
|
+
if (nameId === 6 && !postScriptName) postScriptName = value
|
|
47
|
+
if (nameId === 1 && !family) family = value
|
|
48
|
+
}
|
|
49
|
+
return { postScriptName, family }
|
|
50
|
+
}
|
package/src/fonts/os2.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `OS/2` table (spec §10.2): the metrics the PDF Font Descriptor needs
|
|
3
|
+
* (cap height, weight, typographic ascender/descender, fsSelection). All
|
|
4
|
+
* fields are optional — callers fall back to `hhea`/derived values.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { BinaryReader } from '../util/binary.ts'
|
|
8
|
+
|
|
9
|
+
export interface Os2Metrics {
|
|
10
|
+
weightClass: number
|
|
11
|
+
fsSelection: number
|
|
12
|
+
sFamilyClass: number
|
|
13
|
+
typoAscender: number
|
|
14
|
+
typoDescender: number
|
|
15
|
+
capHeight: number | null
|
|
16
|
+
xHeight: number | null
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function parseOs2(os2: Uint8Array | undefined): Os2Metrics | null {
|
|
20
|
+
if (!os2 || os2.length < 78) return null
|
|
21
|
+
const r = new BinaryReader(os2)
|
|
22
|
+
const version = r.u16()
|
|
23
|
+
r.seek(4)
|
|
24
|
+
const weightClass = r.u16()
|
|
25
|
+
r.seek(30)
|
|
26
|
+
const sFamilyClass = r.i16()
|
|
27
|
+
r.seek(62)
|
|
28
|
+
const fsSelection = r.u16()
|
|
29
|
+
r.seek(68)
|
|
30
|
+
const typoAscender = r.i16()
|
|
31
|
+
const typoDescender = r.i16()
|
|
32
|
+
|
|
33
|
+
let capHeight: number | null = null
|
|
34
|
+
let xHeight: number | null = null
|
|
35
|
+
if (version >= 2 && os2.length >= 90) {
|
|
36
|
+
r.seek(86)
|
|
37
|
+
xHeight = r.i16()
|
|
38
|
+
capHeight = r.i16()
|
|
39
|
+
}
|
|
40
|
+
return { weightClass, fsSelection, sFamilyClass, typoAscender, typoDescender, capHeight, xHeight }
|
|
41
|
+
}
|