@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.
Files changed (80) hide show
  1. package/README.md +79 -0
  2. package/package.json +51 -0
  3. package/src/color/cie.ts +61 -0
  4. package/src/color/color.ts +77 -0
  5. package/src/color/conversion.ts +26 -0
  6. package/src/color/device.ts +37 -0
  7. package/src/color/devicen.ts +74 -0
  8. package/src/color/icc.ts +103 -0
  9. package/src/color/index.ts +15 -0
  10. package/src/color/separation.ts +94 -0
  11. package/src/color/space.ts +47 -0
  12. package/src/content/content_stream.ts +373 -0
  13. package/src/content/graphics_state.ts +64 -0
  14. package/src/content/index.ts +16 -0
  15. package/src/content/operators.ts +70 -0
  16. package/src/content/path.ts +51 -0
  17. package/src/content/resources.ts +119 -0
  18. package/src/content/text_object.ts +140 -0
  19. package/src/document/catalog.ts +16 -0
  20. package/src/document/index.ts +13 -0
  21. package/src/document/object_table.ts +67 -0
  22. package/src/document/page.ts +74 -0
  23. package/src/document/page_tree.ts +78 -0
  24. package/src/document/pdf_document.ts +310 -0
  25. package/src/document/types.ts +65 -0
  26. package/src/document/xref.ts +68 -0
  27. package/src/ext-gstate/ext_gstate.ts +69 -0
  28. package/src/ext-gstate/index.ts +2 -0
  29. package/src/fonts/cff.ts +123 -0
  30. package/src/fonts/cid_encoding.ts +45 -0
  31. package/src/fonts/cmap_table.ts +180 -0
  32. package/src/fonts/font.ts +342 -0
  33. package/src/fonts/glyf.ts +59 -0
  34. package/src/fonts/hmtx.ts +21 -0
  35. package/src/fonts/index.ts +20 -0
  36. package/src/fonts/name_table.ts +50 -0
  37. package/src/fonts/os2.ts +41 -0
  38. package/src/fonts/sfnt.ts +224 -0
  39. package/src/fonts/standard_14.ts +132 -0
  40. package/src/fonts/subset.ts +221 -0
  41. package/src/fonts/to_unicode.ts +82 -0
  42. package/src/fonts/win_ansi.ts +69 -0
  43. package/src/images/image.ts +111 -0
  44. package/src/images/index.ts +6 -0
  45. package/src/images/jpeg.ts +103 -0
  46. package/src/images/png.ts +239 -0
  47. package/src/images/smask.ts +24 -0
  48. package/src/index.ts +57 -0
  49. package/src/metadata/index.ts +3 -0
  50. package/src/metadata/info_dict.ts +28 -0
  51. package/src/metadata/xmp.ts +110 -0
  52. package/src/objects/encode.ts +77 -0
  53. package/src/objects/index.ts +43 -0
  54. package/src/objects/indirect_ref.ts +17 -0
  55. package/src/objects/name.ts +50 -0
  56. package/src/objects/number.ts +43 -0
  57. package/src/objects/string.ts +136 -0
  58. package/src/objects/types.ts +86 -0
  59. package/src/output/buffer_sink.ts +40 -0
  60. package/src/output/byte_sink.ts +12 -0
  61. package/src/output/index.ts +3 -0
  62. package/src/output/stream_sink.ts +62 -0
  63. package/src/patterns/index.ts +10 -0
  64. package/src/patterns/shading.ts +162 -0
  65. package/src/patterns/tiling_pattern.ts +68 -0
  66. package/src/standards/context.ts +10 -0
  67. package/src/standards/index.ts +23 -0
  68. package/src/standards/pdf_a.ts +23 -0
  69. package/src/standards/pdf_x.ts +31 -0
  70. package/src/streams/ascii85.ts +61 -0
  71. package/src/streams/ascii_hex.ts +33 -0
  72. package/src/streams/flate.ts +17 -0
  73. package/src/streams/index.ts +9 -0
  74. package/src/streams/stream.ts +66 -0
  75. package/src/util/ascii.ts +63 -0
  76. package/src/util/binary.ts +71 -0
  77. package/src/util/errors.ts +61 -0
  78. package/src/util/index.ts +10 -0
  79. package/src/util/units.ts +24 -0
  80. 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
+ }
@@ -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
+ }