@chr33s/pdf-standard-fonts 5.0.0

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 (116) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +41 -0
  3. package/dist/all-encodings.compressed.json +1 -0
  4. package/dist/courier-bold-oblique.compressed.json +1 -0
  5. package/dist/courier-bold.compressed.json +1 -0
  6. package/dist/courier-oblique.compressed.json +1 -0
  7. package/dist/courier.compressed.json +1 -0
  8. package/dist/encoding.d.ts +31 -0
  9. package/dist/encoding.js +58 -0
  10. package/dist/encoding.js.map +1 -0
  11. package/dist/font.d.ts +99 -0
  12. package/dist/font.js +105 -0
  13. package/dist/font.js.map +1 -0
  14. package/dist/helvetica-bold-oblique.compressed.json +1 -0
  15. package/dist/helvetica-bold.compressed.json +1 -0
  16. package/dist/helvetica-oblique.compressed.json +1 -0
  17. package/dist/helvetica.compressed.json +1 -0
  18. package/dist/index.d.ts +2 -0
  19. package/dist/index.js +3 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/symbol.compressed.json +1 -0
  22. package/dist/times-bold-italic.compressed.json +1 -0
  23. package/dist/times-bold.compressed.json +1 -0
  24. package/dist/times-italic.compressed.json +1 -0
  25. package/dist/times-roman.compressed.json +1 -0
  26. package/dist/utils.d.ts +2 -0
  27. package/dist/utils.js +17 -0
  28. package/dist/utils.js.map +1 -0
  29. package/dist/zapf-dingbats.compressed.json +1 -0
  30. package/encoding-metrics/all-encodings.compressed.json +1 -0
  31. package/encoding-metrics/all-encodings.json +1 -0
  32. package/encoding-metrics/symbol-encoding.json +1 -0
  33. package/encoding-metrics/symbol.txt +261 -0
  34. package/encoding-metrics/win1252-encoding.json +1 -0
  35. package/encoding-metrics/win1252.txt +279 -0
  36. package/encoding-metrics/zapfdingbats-encoding.json +1 -0
  37. package/encoding-metrics/zapfdingbats.txt +268 -0
  38. package/font-metrics/MustRead.html +19 -0
  39. package/font-metrics/courier-bold-oblique.afm +342 -0
  40. package/font-metrics/courier-bold-oblique.compressed.json +1 -0
  41. package/font-metrics/courier-bold-oblique.json +1 -0
  42. package/font-metrics/courier-bold.afm +342 -0
  43. package/font-metrics/courier-bold.compressed.json +1 -0
  44. package/font-metrics/courier-bold.json +1 -0
  45. package/font-metrics/courier-oblique.afm +342 -0
  46. package/font-metrics/courier-oblique.compressed.json +1 -0
  47. package/font-metrics/courier-oblique.json +1 -0
  48. package/font-metrics/courier.afm +342 -0
  49. package/font-metrics/courier.compressed.json +1 -0
  50. package/font-metrics/courier.json +1 -0
  51. package/font-metrics/helvetica-bold-oblique.afm +2827 -0
  52. package/font-metrics/helvetica-bold-oblique.compressed.json +1 -0
  53. package/font-metrics/helvetica-bold-oblique.json +1 -0
  54. package/font-metrics/helvetica-bold.afm +2827 -0
  55. package/font-metrics/helvetica-bold.compressed.json +1 -0
  56. package/font-metrics/helvetica-bold.json +1 -0
  57. package/font-metrics/helvetica-oblique.afm +3051 -0
  58. package/font-metrics/helvetica-oblique.compressed.json +1 -0
  59. package/font-metrics/helvetica-oblique.json +1 -0
  60. package/font-metrics/helvetica.afm +3051 -0
  61. package/font-metrics/helvetica.compressed.json +1 -0
  62. package/font-metrics/helvetica.json +1 -0
  63. package/font-metrics/symbol.afm +213 -0
  64. package/font-metrics/symbol.compressed.json +1 -0
  65. package/font-metrics/symbol.json +1 -0
  66. package/font-metrics/times-bold-italic.afm +2384 -0
  67. package/font-metrics/times-bold-italic.compressed.json +1 -0
  68. package/font-metrics/times-bold-italic.json +1 -0
  69. package/font-metrics/times-bold.afm +2588 -0
  70. package/font-metrics/times-bold.compressed.json +1 -0
  71. package/font-metrics/times-bold.json +1 -0
  72. package/font-metrics/times-italic.afm +2667 -0
  73. package/font-metrics/times-italic.compressed.json +1 -0
  74. package/font-metrics/times-italic.json +1 -0
  75. package/font-metrics/times-roman.afm +2419 -0
  76. package/font-metrics/times-roman.compressed.json +1 -0
  77. package/font-metrics/times-roman.json +1 -0
  78. package/font-metrics/zapf-dingbats.afm +225 -0
  79. package/font-metrics/zapf-dingbats.compressed.json +1 -0
  80. package/font-metrics/zapf-dingbats.json +1 -0
  81. package/package.json +51 -0
  82. package/scripts/encodings/parse-win1252.ts +120 -0
  83. package/scripts/encodings/parse-zapf-dingbats-or-symbol.ts +22 -0
  84. package/scripts/encodings/parse.ts +72 -0
  85. package/scripts/fonts/parse-character-metrics.ts +73 -0
  86. package/scripts/fonts/parse-font-metrics.ts +95 -0
  87. package/scripts/fonts/parse-kern-pairs.ts +30 -0
  88. package/scripts/fonts/parse.ts +91 -0
  89. package/scripts/fonts/utils.ts +43 -0
  90. package/src/all-encodings.compressed.json +1 -0
  91. package/src/courier-bold-oblique.compressed.json +1 -0
  92. package/src/courier-bold.compressed.json +1 -0
  93. package/src/courier-oblique.compressed.json +1 -0
  94. package/src/courier.compressed.json +1 -0
  95. package/src/encoding.ts +85 -0
  96. package/src/font.ts +164 -0
  97. package/src/helvetica-bold-oblique.compressed.json +1 -0
  98. package/src/helvetica-bold.compressed.json +1 -0
  99. package/src/helvetica-oblique.compressed.json +1 -0
  100. package/src/helvetica.compressed.json +1 -0
  101. package/src/index.ts +2 -0
  102. package/src/symbol.compressed.json +1 -0
  103. package/src/times-bold-italic.compressed.json +1 -0
  104. package/src/times-bold.compressed.json +1 -0
  105. package/src/times-italic.compressed.json +1 -0
  106. package/src/times-roman.compressed.json +1 -0
  107. package/src/utils.ts +20 -0
  108. package/src/zapf-dingbats.compressed.json +1 -0
  109. package/test/all-fonts.test.ts +133 -0
  110. package/test/encoding.test.ts +32 -0
  111. package/test/encodings-async.test.ts +78 -0
  112. package/test/font.test.ts +35 -0
  113. package/test/utils.test.ts +34 -0
  114. package/tsconfig.json +9 -0
  115. package/tsconfig.typecheck.json +14 -0
  116. package/vitest.config.ts +8 -0
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Array of WinAnsi glyph names.
3
+ * Allows lookups of character names given a character code.
4
+ *
5
+ * From:
6
+ * https://github.com/foliojs/pdfkit/blob/83f5f7243172a017adcf6a7faa5547c55982c57b/lib/font/afm.js#L33-L105
7
+ */
8
+ const WinAnsiCharNames = `
9
+ .notdef .notdef .notdef .notdef
10
+ .notdef .notdef .notdef .notdef
11
+ .notdef .notdef .notdef .notdef
12
+ .notdef .notdef .notdef .notdef
13
+ .notdef .notdef .notdef .notdef
14
+ .notdef .notdef .notdef .notdef
15
+ .notdef .notdef .notdef .notdef
16
+ .notdef .notdef .notdef .notdef
17
+
18
+ space exclam quotedbl numbersign
19
+ dollar percent ampersand quotesingle
20
+ parenleft parenright asterisk plus
21
+ comma hyphen period slash
22
+ zero one two three
23
+ four five six seven
24
+ eight nine colon semicolon
25
+ less equal greater question
26
+
27
+ at A B C
28
+ D E F G
29
+ H I J K
30
+ L M N O
31
+ P Q R S
32
+ T U V W
33
+ X Y Z bracketleft
34
+ backslash bracketright asciicircum underscore
35
+
36
+ grave a b c
37
+ d e f g
38
+ h i j k
39
+ l m n o
40
+ p q r s
41
+ t u v w
42
+ x y z braceleft
43
+ bar braceright asciitilde .notdef
44
+
45
+ Euro .notdef quotesinglbase florin
46
+ quotedblbase ellipsis dagger daggerdbl
47
+ circumflex perthousand Scaron guilsinglleft
48
+ OE .notdef Zcaron .notdef
49
+ .notdef quoteleft quoteright quotedblleft
50
+ quotedblright bullet endash emdash
51
+ tilde trademark scaron guilsinglright
52
+ oe .notdef zcaron ydieresis
53
+
54
+ space exclamdown cent sterling
55
+ currency yen brokenbar section
56
+ dieresis copyright ordfeminine guillemotleft
57
+ logicalnot hyphen registered macron
58
+ degree plusminus twosuperior threesuperior
59
+ acute mu paragraph periodcentered
60
+ cedilla onesuperior ordmasculine guillemotright
61
+ onequarter onehalf threequarters questiondown
62
+
63
+ Agrave Aacute Acircumflex Atilde
64
+ Adieresis Aring AE Ccedilla
65
+ Egrave Eacute Ecircumflex Edieresis
66
+ Igrave Iacute Icircumflex Idieresis
67
+ Eth Ntilde Ograve Oacute
68
+ Ocircumflex Otilde Odieresis multiply
69
+ Oslash Ugrave Uacute Ucircumflex
70
+ Udieresis Yacute Thorn germandbls
71
+
72
+ agrave aacute acircumflex atilde
73
+ adieresis aring ae ccedilla
74
+ egrave eacute ecircumflex edieresis
75
+ igrave iacute icircumflex idieresis
76
+ eth ntilde ograve oacute
77
+ ocircumflex otilde odieresis divide
78
+ oslash ugrave uacute ucircumflex
79
+ udieresis yacute thorn ydieresis
80
+ `
81
+ .trim()
82
+ .split(/\s+/);
83
+
84
+ export type EncodingMap = Record<number, [number, string]>;
85
+
86
+ type EncodingTuple = [number, number, string, string];
87
+
88
+ const isValidEncodingTuple = (
89
+ entry: [number, number, string, string | undefined],
90
+ ): entry is EncodingTuple => {
91
+ const [, , , postscriptName] = entry;
92
+ return typeof postscriptName === "string" && postscriptName !== ".notdef";
93
+ };
94
+
95
+ export const parseWin1252 = (data: string): EncodingMap => {
96
+ const rows = data
97
+ .split("\n")
98
+ .filter((line) => line[0] !== "#")
99
+ .filter(Boolean)
100
+ .map((line) => line.split("\t"))
101
+ .map(([postscriptCode, unicodeCode, unicodeName]) => {
102
+ const unicode = Number(unicodeCode);
103
+ const postscript = Number(postscriptCode);
104
+ const postscriptName = WinAnsiCharNames[postscript];
105
+ return [unicode, postscript, unicodeName.substring(1), postscriptName] as [
106
+ number,
107
+ number,
108
+ string,
109
+ string | undefined,
110
+ ];
111
+ })
112
+ .filter(isValidEncodingTuple);
113
+
114
+ const encodings: EncodingMap = {};
115
+ for (const [unicodeCode, postscriptCode, , postscriptName] of rows) {
116
+ encodings[unicodeCode] = [postscriptCode, postscriptName];
117
+ }
118
+
119
+ return encodings;
120
+ };
@@ -0,0 +1,22 @@
1
+ import type { EncodingMap } from "./parse-win1252.ts";
2
+
3
+ export const parseZapfDingbatsOrSymbol = (data: string): EncodingMap => {
4
+ const rows = data
5
+ .split("\n")
6
+ .filter((line) => line[0] !== "#")
7
+ .filter(Boolean)
8
+ .map((line) => line.split("\t"))
9
+ .map(([unicodeCode, postscriptCode, unicodeName, postscriptName]) => [
10
+ Number(`0x${unicodeCode}`),
11
+ Number(`0x${postscriptCode}`),
12
+ unicodeName.substring(2),
13
+ postscriptName.substring(2).replace(" (CUS)", ""),
14
+ ]) as Array<[number, number, string, string]>;
15
+
16
+ const encodings: EncodingMap = {};
17
+ for (const [unicodeCode, postscriptCode, , postscriptName] of rows) {
18
+ encodings[unicodeCode] = [postscriptCode, postscriptName];
19
+ }
20
+
21
+ return encodings;
22
+ };
@@ -0,0 +1,72 @@
1
+ import { base64, deflate } from "@chr33s/pdf-common";
2
+ import fs from "node:fs/promises";
3
+ import { basename, dirname } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ import type { EncodingMap } from "./parse-win1252.ts";
7
+ import { parseWin1252 } from "./parse-win1252.ts";
8
+ import { parseZapfDingbatsOrSymbol } from "./parse-zapf-dingbats-or-symbol.ts";
9
+
10
+ const textEncoder = new TextEncoder();
11
+
12
+ const compressJson = async (json: string) => {
13
+ const jsonBytes = textEncoder.encode(json);
14
+ const compressed = await deflate(jsonBytes);
15
+ const arrBuf = compressed.buffer.slice(
16
+ compressed.byteOffset,
17
+ compressed.byteOffset + compressed.byteLength,
18
+ ) as ArrayBuffer;
19
+ const base64DeflatedJson = JSON.stringify(base64.encode(arrBuf));
20
+ return base64DeflatedJson;
21
+ };
22
+
23
+ const __filename = fileURLToPath(import.meta.url);
24
+ const __dirname = dirname(__filename);
25
+
26
+ const copyFileToSrc = async (src: string) => {
27
+ const fileName = basename(src);
28
+ const dest = dirname(dirname(__dirname)) + "/src/" + fileName;
29
+ await fs.copyFile(src, dest);
30
+ };
31
+
32
+ const main = async () => {
33
+ const parent = dirname(dirname(__dirname));
34
+
35
+ const fontNames = ["symbol", "zapfdingbats", "win1252"] as const;
36
+ const allEncodings: Record<(typeof fontNames)[number], EncodingMap> = {
37
+ symbol: {},
38
+ zapfdingbats: {},
39
+ win1252: {},
40
+ };
41
+
42
+ for (const fontName of fontNames) {
43
+ const file = `${parent}/encoding-metrics/${fontName}.txt`;
44
+ console.log("Parsing:", file);
45
+ const data = await fs.readFile(file);
46
+
47
+ const parser: (input: string) => EncodingMap =
48
+ fontName === "win1252" ? parseWin1252 : parseZapfDingbatsOrSymbol;
49
+ const jsonMetrics = parser(String(data));
50
+ allEncodings[fontName] = jsonMetrics;
51
+
52
+ const json = JSON.stringify(jsonMetrics);
53
+
54
+ const jsonFile = `${parent}/encoding-metrics/${fontName}-encoding.json`;
55
+ await fs.writeFile(jsonFile, json);
56
+ }
57
+
58
+ const allJson = JSON.stringify(allEncodings);
59
+ const allCompressedJson = await compressJson(allJson);
60
+
61
+ const allJsonFile = `${parent}/encoding-metrics/all-encodings.json`;
62
+ const allCompressedJsonFile = `${parent}/encoding-metrics/all-encodings.compressed.json`;
63
+
64
+ await fs.writeFile(allJsonFile, allJson);
65
+ await fs.writeFile(allCompressedJsonFile, allCompressedJson);
66
+ await copyFileToSrc(allCompressedJsonFile);
67
+ };
68
+
69
+ main().catch((err) => {
70
+ console.error(err);
71
+ process.exitCode = 1;
72
+ });
@@ -0,0 +1,73 @@
1
+ import {
2
+ error,
3
+ extractLinesFromSection,
4
+ takeAfterFirstSpace,
5
+ takeUntilFirstSpace,
6
+ } from "./utils.ts";
7
+
8
+ export interface ICharMetrics {
9
+ WX: number;
10
+ N: string;
11
+ }
12
+
13
+ /**
14
+ * From https://www.adobe.com/content/dam/acom/en/devnet/font/pdfs/5004.AFM_Spec.pdf :
15
+ *
16
+ * C integer:
17
+ * Decimal value of default character code (−1 if not encoded).
18
+ *
19
+ * WX number:
20
+ * Width of character.
21
+ *
22
+ * N name:
23
+ * (Optional.) PostScript language character name.
24
+ *
25
+ * B llx lly urx ury:
26
+ * (Optional.) Character bounding box where llx, lly, urx, and ury are all
27
+ * numbers. If a character makes no marks on the page (for example, the space
28
+ * character), this fi eld reads B 0 0 0 0 , and these values are not
29
+ * considered when computing the FontBBox.
30
+ *
31
+ * L successor ligature:
32
+ * (Optional.) Ligature sequence where successor and ligature are both names.
33
+ * The current character may join with the character named successor to form
34
+ * the character named ligature. Note that characters can have more than one
35
+ * such entry.
36
+ *
37
+ * Fallback link for AFM Spec:
38
+ * https://ia800603.us.archive.org/30/items/afm-format/afm-format.pdf
39
+ */
40
+
41
+ // prettier-ignore
42
+ const parseCharMetrics = (
43
+ // E.g. 'C 35 ; WX 600 ; N numbersign ; B 56 -45 544 651 ;'
44
+ line: string,
45
+ ): ICharMetrics => {
46
+ const SEMICOLON_WITH_SURROUDING_WHITESPACE = /\s*;\s*/;
47
+ const segments = line
48
+ .split(SEMICOLON_WITH_SURROUDING_WHITESPACE)
49
+ .map((segment) => segment.trim())
50
+ .filter((segment) => segment.length > 0);
51
+
52
+ const metrics = new Map<string, string>();
53
+ for (const metric of segments) {
54
+ const key = takeUntilFirstSpace(metric);
55
+ const value = takeAfterFirstSpace(metric);
56
+ metrics.set(key, value);
57
+ }
58
+
59
+ const rawWx = metrics.get("WX") ?? error("Missing WX metric in character data");
60
+ const rawName = metrics.get("N") ?? error("Missing N metric in character data");
61
+
62
+ return {
63
+ WX: Number(rawWx),
64
+ N: rawName,
65
+ };
66
+ };
67
+
68
+ export const parseCharMetricsSection = (data: string): ICharMetrics[] => {
69
+ return extractLinesFromSection(data, {
70
+ startAt: "StartCharMetrics",
71
+ endAt: "EndCharMetrics",
72
+ }).map(parseCharMetrics);
73
+ };
@@ -0,0 +1,95 @@
1
+ import {
2
+ error,
3
+ extractLinesFromSection,
4
+ takeAfterFirstSpace,
5
+ takeUntilFirstSpace,
6
+ } from "./utils.ts";
7
+
8
+ export interface IFontMetrics {
9
+ Comment: string;
10
+ FontName: string;
11
+ FullName: string;
12
+ FamilyName: string;
13
+ Weight: string;
14
+ CharacterSet: string;
15
+ Version: string;
16
+ Notice: string;
17
+ EncodingScheme: string;
18
+ ItalicAngle: number;
19
+ UnderlinePosition: number;
20
+ UnderlineThickness: number;
21
+ CapHeight: number | void;
22
+ XHeight: number | void;
23
+ Ascender: number | void;
24
+ Descender: number | void;
25
+ StdHW: number;
26
+ StdVW: number;
27
+ IsFixedPitch: boolean;
28
+ FontBBox: [number, number, number, number];
29
+ }
30
+
31
+ type IFontMetricKey = keyof IFontMetrics;
32
+
33
+ type FontMetricEntry = {
34
+ key: IFontMetricKey;
35
+ value: IFontMetrics[IFontMetricKey];
36
+ };
37
+
38
+ const parseString = (raw: string): string => raw;
39
+ const parseNumber = (raw: string): number => Number(raw);
40
+ const parseBoolean = (raw: string): boolean => Boolean(raw);
41
+ const parseFontBBox = (raw: string): [number, number, number, number] =>
42
+ raw.split(" ").map(Number) as [number, number, number, number];
43
+
44
+ const metricParsers: {
45
+ [K in IFontMetricKey]: (raw: string) => IFontMetrics[K];
46
+ } = {
47
+ Comment: parseString,
48
+ FontName: parseString,
49
+ FullName: parseString,
50
+ FamilyName: parseString,
51
+ Weight: parseString,
52
+ CharacterSet: parseString,
53
+ Version: parseString,
54
+ Notice: parseString,
55
+ EncodingScheme: parseString,
56
+ ItalicAngle: parseNumber,
57
+ UnderlinePosition: parseNumber,
58
+ UnderlineThickness: parseNumber,
59
+ CapHeight: parseNumber,
60
+ XHeight: parseNumber,
61
+ Ascender: parseNumber,
62
+ Descender: parseNumber,
63
+ StdHW: parseNumber,
64
+ StdVW: parseNumber,
65
+ IsFixedPitch: parseBoolean,
66
+ FontBBox: parseFontBBox,
67
+ };
68
+
69
+ const isFontMetricKey = (value: string): value is IFontMetricKey => value in metricParsers;
70
+
71
+ const parseFontMetric = (line: string): FontMetricEntry => {
72
+ const key = takeUntilFirstSpace(line);
73
+ const rawValue = takeAfterFirstSpace(line).trim();
74
+
75
+ if (!isFontMetricKey(key)) {
76
+ return error(`Unrecognized font metric key: "${key}"`);
77
+ }
78
+
79
+ const parse = metricParsers[key];
80
+ return { key, value: parse(rawValue) };
81
+ };
82
+
83
+ export const parseFontMetricsSection = (data: string): IFontMetrics => {
84
+ const metrics = extractLinesFromSection(data, {
85
+ startAt: "StartFontMetrics",
86
+ endAt: "StartCharMetrics",
87
+ }).map(parseFontMetric);
88
+
89
+ const result: Partial<Record<IFontMetricKey, IFontMetrics[IFontMetricKey]>> = {};
90
+ for (const metric of metrics) {
91
+ result[metric.key] = metric.value;
92
+ }
93
+
94
+ return result as IFontMetrics;
95
+ };
@@ -0,0 +1,30 @@
1
+ import { extractLinesFromSection } from "./utils.ts";
2
+
3
+ export type IKernPair = [string, string, number];
4
+
5
+ /**
6
+ * From https://www.adobe.com/content/dam/acom/en/devnet/font/pdfs/5004.AFM_Spec.pdf :
7
+ *
8
+ * KPX name_1 name_2 number_x:
9
+ * Name of the first character in the kerning pair followed by the name of the
10
+ * second character followed by the kerning amount in the x direction
11
+ * (y is zero). The kerning amount is specified in the units of the character
12
+ * coordinate system.
13
+ *
14
+ * Fallback link for AFM Spec:
15
+ * https://ia800603.us.archive.org/30/items/afm-format/afm-format.pdf
16
+ */
17
+ const parseKernPair = (
18
+ // E.g. 'KPX A G -50'
19
+ line: string,
20
+ ): IKernPair => {
21
+ const [, firstCharName, secondCharName, kernXAmount] = line.split(" ");
22
+ return [String(firstCharName), String(secondCharName), Number(kernXAmount)];
23
+ };
24
+
25
+ export const parseKernPairsSection = (data: string): IKernPair[] => {
26
+ return extractLinesFromSection(data, {
27
+ startAt: "StartKernPairs",
28
+ endAt: "EndKernPairs",
29
+ }).map(parseKernPair);
30
+ };
@@ -0,0 +1,91 @@
1
+ import { base64, deflate } from "@chr33s/pdf-common";
2
+ import fs from "node:fs/promises";
3
+ import { basename, dirname } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ import { type ICharMetrics, parseCharMetricsSection } from "./parse-character-metrics.ts";
7
+ import { type IFontMetrics, parseFontMetricsSection } from "./parse-font-metrics.ts";
8
+ import { type IKernPair, parseKernPairsSection } from "./parse-kern-pairs.ts";
9
+
10
+ export interface IMetrics extends IFontMetrics {
11
+ CharMetrics: ICharMetrics[];
12
+ KernPairs: IKernPair[];
13
+ }
14
+
15
+ export const parseFontMetrics = (data: string): IMetrics => ({
16
+ ...parseFontMetricsSection(data),
17
+ CharMetrics: parseCharMetricsSection(data),
18
+ KernPairs: parseKernPairsSection(data),
19
+ });
20
+
21
+ const __filename = fileURLToPath(import.meta.url);
22
+ const __dirname = dirname(__filename);
23
+
24
+ const getAfmFilePaths = async () => {
25
+ const parentDir = dirname(dirname(__dirname));
26
+ const metricsDir = `${parentDir}/font-metrics`;
27
+ const files = await fs.readdir(metricsDir);
28
+ const afmFiles = files.filter((name) => name.includes(".afm"));
29
+ return afmFiles.map((name) => `${metricsDir}/${name}`);
30
+ };
31
+
32
+ const textEncoder = new TextEncoder();
33
+
34
+ const compressJson = async (json: string) => {
35
+ const jsonBytes = textEncoder.encode(json);
36
+ const compressed = await deflate(jsonBytes);
37
+ const arrBuf = compressed.buffer.slice(
38
+ compressed.byteOffset,
39
+ compressed.byteOffset + compressed.byteLength,
40
+ ) as ArrayBuffer;
41
+ const base64DeflatedJson = JSON.stringify(base64.encode(arrBuf));
42
+ return base64DeflatedJson;
43
+ };
44
+
45
+ const fontFileNameMap: Record<string, string> = {
46
+ "courier.compressed.json": "courier.compressed.json",
47
+ "courier-bold.compressed.json": "courier-bold.compressed.json",
48
+ "courier-oblique.compressed.json": "courier-oblique.compressed.json",
49
+ "courier-bold-oblique.compressed.json": "courier-bold-oblique.compressed.json",
50
+ "helvetica.compressed.json": "helvetica.compressed.json",
51
+ "helvetica-bold.compressed.json": "helvetica-bold.compressed.json",
52
+ "helvetica-oblique.compressed.json": "helvetica-oblique.compressed.json",
53
+ "helvetica-bold-oblique.compressed.json": "helvetica-bold-oblique.compressed.json",
54
+ "times-roman.compressed.json": "times-roman.compressed.json",
55
+ "times-bold.compressed.json": "times-bold.compressed.json",
56
+ "times-italic.compressed.json": "times-italic.compressed.json",
57
+ "times-bold-italic.compressed.json": "times-bold-italic.compressed.json",
58
+ "symbol.compressed.json": "symbol.compressed.json",
59
+ "zapf-dingbats.compressed.json": "zapf-dingbats.compressed.json",
60
+ };
61
+
62
+ const copyFileToSrc = async (src: string) => {
63
+ const fileName = basename(src);
64
+ const canonicalFileName = fontFileNameMap[fileName] ?? fileName;
65
+ const dest = `${dirname(dirname(__dirname))}/src/${canonicalFileName}`;
66
+ await fs.copyFile(src, dest);
67
+ };
68
+
69
+ const main = async () => {
70
+ const afmFiles = await getAfmFilePaths();
71
+
72
+ for (const afmFile of afmFiles) {
73
+ console.log("Parsing:", afmFile);
74
+ const data = await fs.readFile(afmFile);
75
+
76
+ const metrics = parseFontMetrics(String(data));
77
+ const jsonMetrics = JSON.stringify(metrics);
78
+
79
+ const jsonFile = afmFile.replace(".afm", ".json");
80
+ const compressedJsonFile = afmFile.replace(".afm", ".compressed.json");
81
+
82
+ await fs.writeFile(jsonFile, jsonMetrics);
83
+ await fs.writeFile(compressedJsonFile, await compressJson(jsonMetrics));
84
+ await copyFileToSrc(compressedJsonFile);
85
+ }
86
+ };
87
+
88
+ main().catch((err) => {
89
+ console.error(err);
90
+ process.exitCode = 1;
91
+ });
@@ -0,0 +1,43 @@
1
+ export const takeUntilFirstSpace = (str: string): string => str.substring(0, str.indexOf(" "));
2
+
3
+ export const takeAfterFirstSpace = (str: string): string => str.substring(str.indexOf(" ") + 1);
4
+
5
+ export const error = (msg: string) => {
6
+ throw new Error(msg);
7
+ };
8
+
9
+ type ISectionName =
10
+ | "StartFontMetrics"
11
+ | "StartCharMetrics"
12
+ | "StartKernPairs"
13
+ | "EndFontMetrics"
14
+ | "EndCharMetrics"
15
+ | "EndKernPairs";
16
+
17
+ export const extractLinesFromSection = (
18
+ data: string,
19
+ { startAt, endAt }: { startAt: ISectionName; endAt: ISectionName },
20
+ ) => {
21
+ // E.g. /^StartFontMetrics\s+\d+(\.\d+)?/m
22
+ const startRegex = new RegExp(`^${startAt}\\s+\\d+(\\.\\d+)?`, "m");
23
+
24
+ // E.g. /^StartCharMetrics/m
25
+ const endRegex = new RegExp(`^${endAt}`, "m");
26
+
27
+ const startMatch = data.match(startRegex);
28
+ if (!startMatch || startMatch.index === undefined) {
29
+ return [];
30
+ }
31
+
32
+ const endMatch = data.match(endRegex);
33
+ if (!endMatch || endMatch.index === undefined) {
34
+ return [];
35
+ }
36
+
37
+ const startIdx = startMatch.index + startMatch[0].length;
38
+
39
+ const sectionData = data.slice(startIdx, endMatch.index);
40
+ const sectionLines = sectionData.trim().split(/\r\n|\r|\n|\t/);
41
+
42
+ return sectionLines;
43
+ };
@@ -0,0 +1 @@
1
+ "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"
@@ -0,0 +1 @@
1
+ "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"
@@ -0,0 +1 @@
1
+ "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"
@@ -0,0 +1 @@
1
+ "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"
@@ -0,0 +1 @@
1
+ "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"
@@ -0,0 +1,85 @@
1
+ import { decompressJson, padStart } from "./utils.js";
2
+
3
+ import AllEncodingsCompressed from "./all-encodings.compressed.json" with { type: "json" };
4
+
5
+ type EncodingCharCode = number;
6
+ type EncodingCharName = string;
7
+ interface UnicodeMappings {
8
+ [unicodeCodePoint: number]: [EncodingCharCode, EncodingCharName];
9
+ }
10
+
11
+ let allUnicodeMappings: {
12
+ symbol: UnicodeMappings;
13
+ zapfdingbats: UnicodeMappings;
14
+ win1252: UnicodeMappings;
15
+ } | null = null;
16
+
17
+ const getAllUnicodeMappings = async () => {
18
+ if (allUnicodeMappings) return allUnicodeMappings;
19
+ const decompressedEncodings = await decompressJson(AllEncodingsCompressed);
20
+ allUnicodeMappings = JSON.parse(decompressedEncodings);
21
+ return allUnicodeMappings;
22
+ };
23
+
24
+ type EncodingNames = "Symbol" | "ZapfDingbats" | "WinAnsi";
25
+
26
+ class Encoding {
27
+ name: EncodingNames;
28
+ supportedCodePoints: number[] = [];
29
+ #unicodeMappings: UnicodeMappings = {};
30
+
31
+ private constructor(name: EncodingNames) {
32
+ this.name = name;
33
+ }
34
+
35
+ static async create(name: EncodingNames, unicodeMappings: UnicodeMappings): Promise<Encoding> {
36
+ const encoding = new Encoding(name);
37
+ encoding.supportedCodePoints = Object.keys(unicodeMappings)
38
+ .map(Number)
39
+ .sort((a, b) => a - b);
40
+ encoding.#unicodeMappings = unicodeMappings;
41
+ return encoding;
42
+ }
43
+
44
+ canEncodeUnicodeCodePoint = (codePoint: number) => codePoint in this.#unicodeMappings;
45
+
46
+ encodeUnicodeCodePoint = (codePoint: number) => {
47
+ const mapped = this.#unicodeMappings[codePoint];
48
+ if (!mapped) {
49
+ const str = String.fromCharCode(codePoint);
50
+ const hexCode = `0x${padStart(codePoint.toString(16), 4, "0")}`;
51
+ const msg = `${this.name} cannot encode "${str}" (${hexCode})`;
52
+ throw new Error(msg);
53
+ }
54
+ return { code: mapped[0], name: mapped[1] };
55
+ };
56
+ }
57
+
58
+ export type EncodingType = Encoding;
59
+
60
+ let encodingsCache: {
61
+ Symbol: Encoding;
62
+ ZapfDingbats: Encoding;
63
+ WinAnsi: Encoding;
64
+ } | null = null;
65
+
66
+ export const getEncodings = async () => {
67
+ if (encodingsCache) return encodingsCache;
68
+ const mappings = await getAllUnicodeMappings();
69
+ encodingsCache = {
70
+ Symbol: await Encoding.create("Symbol", mappings!.symbol),
71
+ ZapfDingbats: await Encoding.create("ZapfDingbats", mappings!.zapfdingbats),
72
+ WinAnsi: await Encoding.create("WinAnsi", mappings!.win1252),
73
+ };
74
+ return encodingsCache;
75
+ };
76
+
77
+ /** @deprecated Use getEncodings() instead for async loading */
78
+ export const Encodings = {
79
+ Symbol: null as unknown as Encoding,
80
+ ZapfDingbats: null as unknown as Encoding,
81
+ WinAnsi: null as unknown as Encoding,
82
+ };
83
+
84
+ // Initialize encodings asynchronously
85
+ void getEncodings().then((e) => Object.assign(Encodings, e));