@mark1russell7/typeset 0.1.1 → 0.1.2
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 +21 -1
- package/build/chunks/engines.d.ts +32 -0
- package/build/chunks/engines.d.ts.map +1 -0
- package/build/chunks/engines.js +35 -0
- package/build/chunks/engines.js.map +1 -0
- package/build/chunks/font.d.ts +203 -0
- package/build/chunks/font.d.ts.map +1 -0
- package/build/chunks/font.js +3550 -0
- package/build/chunks/font.js.map +1 -0
- package/build/chunks/fonts.d.ts +8 -0
- package/build/chunks/fonts.d.ts.map +1 -0
- package/build/chunks/fonts.js +13 -0
- package/build/chunks/fonts.js.map +1 -0
- package/build/chunks/measurer.d.ts +67 -0
- package/build/chunks/measurer.d.ts.map +1 -0
- package/build/chunks/measurer.js +277 -0
- package/build/chunks/measurer.js.map +1 -0
- package/build/chunks/sfnt.d.ts +22 -0
- package/build/chunks/sfnt.d.ts.map +1 -0
- package/build/chunks/text.js.map +1 -1
- package/build/fonts-inspect.d.ts +10 -0
- package/build/fonts-inspect.d.ts.map +1 -0
- package/build/fonts-inspect.js +1 -0
- package/build/fonts.d.ts +5 -0
- package/build/fonts.js +4 -0
- package/build/index.d.ts +3 -1
- package/build/index.js +3 -1
- package/build/measurer.d.ts +2 -0
- package/build/measurer.js +1 -0
- package/package.json +17 -3
- package/src/fonts/bidi.ts +125 -0
- package/src/fonts/binary.ts +41 -0
- package/src/fonts/engines.ts +38 -0
- package/src/fonts/font.ts +442 -0
- package/src/fonts/gvar.ts +220 -0
- package/src/fonts/index.ts +10 -0
- package/src/fonts/indic.ts +585 -0
- package/src/fonts/joining.ts +141 -0
- package/src/fonts/kerning.ts +217 -0
- package/src/fonts/sfnt.ts +102 -0
- package/src/fonts/substitution.ts +586 -0
- package/src/fonts/tables.ts +140 -0
- package/src/fonts/variations.ts +175 -0
- package/src/fonts/words.ts +119 -0
- package/src/fonts-inspect.ts +8 -0
- package/src/fonts.ts +8 -0
- package/src/index.ts +2 -0
- package/src/measurer/advance.ts +307 -0
- package/src/measurer.ts +5 -0
- package/src/text/rope.ts +1 -1
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { n as EngineRules, t as EngineName } from "./engines.js";
|
|
2
|
+
import { t as Decompress } from "./sfnt.js";
|
|
3
|
+
/** One font file of the host, with the family, the weight and the style that CSS matches. */
|
|
4
|
+
interface FontFace {
|
|
5
|
+
/** The family name, as the CSS font names it. The match ignores the case. */
|
|
6
|
+
readonly family: string;
|
|
7
|
+
/** The bytes of a TrueType, OpenType or WOFF2 file. */
|
|
8
|
+
readonly data: Uint8Array;
|
|
9
|
+
/**
|
|
10
|
+
* The weight of the face: one value, or the range of a variable font. The default is the range of the `wght` axis of the
|
|
11
|
+
* font, or 400 for a font with no `wght` axis.
|
|
12
|
+
*/
|
|
13
|
+
readonly weight?: number | readonly [number, number];
|
|
14
|
+
/** The style of the face. The default is `normal`. */
|
|
15
|
+
readonly style?: "normal" | "italic" | "oblique";
|
|
16
|
+
/** The Brotli decompressor of a WOFF2 file. */
|
|
17
|
+
readonly decompress?: Decompress;
|
|
18
|
+
}
|
|
19
|
+
/** The options of `fontAdvance`. */
|
|
20
|
+
interface FontAdvanceOptions {
|
|
21
|
+
/** The font files of the host. The order sets the choice between two faces with the same match. */
|
|
22
|
+
readonly faces: readonly FontFace[];
|
|
23
|
+
/** The text engine: a row name of `engines`, or the rules of a row. The default is `freetype`, as Chromium on Linux. */
|
|
24
|
+
readonly engine?: EngineName | EngineRules;
|
|
25
|
+
/**
|
|
26
|
+
* The shaping of `measureWidth`. The default is `canvas`, as `canvasAdvance` of layout-contract measures each segment
|
|
27
|
+
* with `measureText`. Use `dom` for the widths of a text in a page.
|
|
28
|
+
*/
|
|
29
|
+
readonly shaping?: "dom" | "canvas";
|
|
30
|
+
}
|
|
31
|
+
/** The parts of a CSS font shorthand that change a width. */
|
|
32
|
+
interface FontSpec {
|
|
33
|
+
readonly style: "normal" | "italic" | "oblique";
|
|
34
|
+
/** The angle of an oblique style in degrees. CSS gives 14 when the font names no angle. */
|
|
35
|
+
readonly angle: number;
|
|
36
|
+
/** The weight from 1 to 1000. */
|
|
37
|
+
readonly weight: number;
|
|
38
|
+
/** The width of the font as a percent of the normal width, from the keyword of `font-stretch`. */
|
|
39
|
+
readonly stretch: number;
|
|
40
|
+
/** The size in px. */
|
|
41
|
+
readonly size: number;
|
|
42
|
+
/** The family names in the order of the font, with no quotes. */
|
|
43
|
+
readonly families: readonly string[];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* This function reads a CSS font shorthand, for example `italic 700 16px/1.2 "Noto Sans", sans-serif`. The size must be in
|
|
47
|
+
* `px` or `pt`. The function refuses `small-caps`, the system font keywords and the relative sizes with a `RangeError`.
|
|
48
|
+
*/
|
|
49
|
+
declare function parseFont(font: string): FontSpec;
|
|
50
|
+
/**
|
|
51
|
+
* This function gives the OpenType script tag of a text, as HarfBuzz guesses it. The tag is the script of the first
|
|
52
|
+
* character that is not of the scripts Common, Inherited or Unknown. A text of only those characters, for example emoji, gets `DFLT`.
|
|
53
|
+
*/
|
|
54
|
+
declare function scriptTag(text: string): string;
|
|
55
|
+
/**
|
|
56
|
+
* This function gives the advance function of the faces of the host: the width in px of a text in a CSS font. Each run
|
|
57
|
+
* of the text gets the first family of the font with a glyph for its first character. When no family has a glyph, the
|
|
58
|
+
* run gets the first family that has a face. The width of a run is `measureWidth` with the size of the font, the script
|
|
59
|
+
* of `scriptTag` and the rules of the engine. The instance of a variable face takes the weight and the stretch as `wdth`.
|
|
60
|
+
* It takes the style as `ital` or `slnt`, and the size as `opsz`, as `font-optical-sizing: auto` does.
|
|
61
|
+
*/
|
|
62
|
+
declare function fontAdvance(options: FontAdvanceOptions): (text: string, font: string) => number;
|
|
63
|
+
declare namespace measurer_d_exports {
|
|
64
|
+
export { FontAdvanceOptions, FontFace, FontSpec, fontAdvance, parseFont, scriptTag };
|
|
65
|
+
}
|
|
66
|
+
export { fontAdvance as a, FontSpec as i, FontAdvanceOptions as n, parseFont as o, FontFace as r, scriptTag as s, measurer_d_exports as t };
|
|
67
|
+
//# sourceMappingURL=measurer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"measurer.d.ts","names":[],"sources":["../../src/measurer/advance.ts","../../src/measurer.ts"],"mappings":";;;UAWiB;;WAEN;;WAEA,MAAM;;;;;WAKN;;WAEA;;WAEA,aAAa;;;UAIP;;WAEN,gBAAgB;;WAEhB,SAAS,aAAa;;;;;WAKtB;;;UAIM;WACN;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;;;;;iBAuBK,UAAU,eAAe;;;;;iBAiFzB,UAAU;;;;;;;;iBAmDV,YAAY,SAAS,sBAAsB,cAAc"}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime.js";
|
|
2
|
+
import { n as isEngineName, t as engines } from "./engines.js";
|
|
3
|
+
import { n as measureWidth, r as readFont } from "./font.js";
|
|
4
|
+
// src/measurer/advance.ts
|
|
5
|
+
/**
|
|
6
|
+
* The advance function of the font model: the width of a text in a CSS font, as the measurer of
|
|
7
|
+
* `@mark1russell7/layout-contract/text` asks for it. The host gives the font files as faces. The function finds the face
|
|
8
|
+
* of each run of the text with the matching rules of CSS. It finds the script of the run, and measures the run with the
|
|
9
|
+
* rules of one text engine.
|
|
10
|
+
*/
|
|
11
|
+
const stretches = {
|
|
12
|
+
"ultra-condensed": 50,
|
|
13
|
+
"extra-condensed": 62.5,
|
|
14
|
+
condensed: 75,
|
|
15
|
+
"semi-condensed": 87.5,
|
|
16
|
+
"semi-expanded": 112.5,
|
|
17
|
+
expanded: 125,
|
|
18
|
+
"extra-expanded": 150,
|
|
19
|
+
"ultra-expanded": 200
|
|
20
|
+
};
|
|
21
|
+
const sizePattern = /^((?:\d+(?:\.\d+)?|\.\d+)(?:e[+-]?\d+)?)(px|pt)(\/.*)?$/i;
|
|
22
|
+
const anglePattern = /^(-?(?:\d+(?:\.\d+)?|\.\d+))deg$/i;
|
|
23
|
+
const weightPattern = /^(?:\d+(?:\.\d+)?|\.\d+)$/;
|
|
24
|
+
/**
|
|
25
|
+
* This function reads a CSS font shorthand, for example `italic 700 16px/1.2 "Noto Sans", sans-serif`. The size must be in
|
|
26
|
+
* `px` or `pt`. The function refuses `small-caps`, the system font keywords and the relative sizes with a `RangeError`.
|
|
27
|
+
*/
|
|
28
|
+
function parseFont(font) {
|
|
29
|
+
const tokens = font.trim().split(/\s+/);
|
|
30
|
+
let style = "normal";
|
|
31
|
+
let angle = 14;
|
|
32
|
+
let weight = 400;
|
|
33
|
+
let stretch = 100;
|
|
34
|
+
let index = 0;
|
|
35
|
+
for (; index < tokens.length; index++) {
|
|
36
|
+
const token = tokens[index];
|
|
37
|
+
const lower = token.toLowerCase();
|
|
38
|
+
if (sizePattern.exec(token) !== null) break;
|
|
39
|
+
if (lower === "normal") continue;
|
|
40
|
+
if (lower === "italic") style = "italic";
|
|
41
|
+
else if (lower === "oblique") {
|
|
42
|
+
style = "oblique";
|
|
43
|
+
const next = anglePattern.exec(tokens[index + 1] ?? "");
|
|
44
|
+
if (next !== null) {
|
|
45
|
+
angle = Number(next[1]);
|
|
46
|
+
index++;
|
|
47
|
+
}
|
|
48
|
+
} else if (lower === "bold" || lower === "bolder") weight = 700;
|
|
49
|
+
else if (lower === "lighter") weight = 100;
|
|
50
|
+
else if (weightPattern.test(token) && Number(token) >= 1 && Number(token) <= 1e3) weight = Number(token);
|
|
51
|
+
else if (Object.hasOwn(stretches, lower)) stretch = stretches[lower];
|
|
52
|
+
else throw new RangeError(`the font "${font}" has the part "${token}", which the measurer does not know`);
|
|
53
|
+
}
|
|
54
|
+
const size = sizePattern.exec(tokens[index] ?? "");
|
|
55
|
+
if (size === null) throw new RangeError(`the font "${font}" has no size in px or pt`);
|
|
56
|
+
let rest = tokens.slice(index + 1).join(" ");
|
|
57
|
+
if (size[3] === void 0 && rest.startsWith("/")) rest = rest.slice(1).trim().replace(/^\S+\s*/, "");
|
|
58
|
+
const families = rest.split(",").map((family) => family.trim().replace(/^(["'])(.*)\1$/, "$2").trim()).filter((family) => family.length > 0);
|
|
59
|
+
if (families.length === 0) throw new RangeError(`the font "${font}" has no family`);
|
|
60
|
+
const value = Number(size[1]);
|
|
61
|
+
return {
|
|
62
|
+
style,
|
|
63
|
+
angle,
|
|
64
|
+
weight,
|
|
65
|
+
stretch,
|
|
66
|
+
size: size[2].toLowerCase() === "pt" ? value * 4 / 3 : value,
|
|
67
|
+
families
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/** The Unicode scripts and their OpenType tags. A script that is not in the list gets `DFLT`. */
|
|
71
|
+
const scriptPatterns = [
|
|
72
|
+
[/\p{sc=Latin}/u, "latn"],
|
|
73
|
+
[/\p{sc=Greek}/u, "grek"],
|
|
74
|
+
[/\p{sc=Cyrillic}/u, "cyrl"],
|
|
75
|
+
[/\p{sc=Armenian}/u, "armn"],
|
|
76
|
+
[/\p{sc=Hebrew}/u, "hebr"],
|
|
77
|
+
[/\p{sc=Arabic}/u, "arab"],
|
|
78
|
+
[/\p{sc=Syriac}/u, "syrc"],
|
|
79
|
+
[/\p{sc=Thaana}/u, "thaa"],
|
|
80
|
+
[/\p{sc=Devanagari}/u, "deva"],
|
|
81
|
+
[/\p{sc=Bengali}/u, "beng"],
|
|
82
|
+
[/\p{sc=Gurmukhi}/u, "guru"],
|
|
83
|
+
[/\p{sc=Gujarati}/u, "gujr"],
|
|
84
|
+
[/\p{sc=Oriya}/u, "orya"],
|
|
85
|
+
[/\p{sc=Tamil}/u, "taml"],
|
|
86
|
+
[/\p{sc=Telugu}/u, "telu"],
|
|
87
|
+
[/\p{sc=Kannada}/u, "knda"],
|
|
88
|
+
[/\p{sc=Malayalam}/u, "mlym"],
|
|
89
|
+
[/\p{sc=Sinhala}/u, "sinh"],
|
|
90
|
+
[/\p{sc=Thai}/u, "thai"],
|
|
91
|
+
[/\p{sc=Lao}/u, "lao "],
|
|
92
|
+
[/\p{sc=Tibetan}/u, "tibt"],
|
|
93
|
+
[/\p{sc=Myanmar}/u, "mymr"],
|
|
94
|
+
[/\p{sc=Georgian}/u, "geor"],
|
|
95
|
+
[/\p{sc=Hangul}/u, "hang"],
|
|
96
|
+
[/\p{sc=Ethiopic}/u, "ethi"],
|
|
97
|
+
[/\p{sc=Khmer}/u, "khmr"],
|
|
98
|
+
[/\p{sc=Mongolian}/u, "mong"],
|
|
99
|
+
[/[\p{sc=Hiragana}\p{sc=Katakana}]/u, "kana"],
|
|
100
|
+
[/\p{sc=Bopomofo}/u, "bopo"],
|
|
101
|
+
[/\p{sc=Han}/u, "hani"]
|
|
102
|
+
];
|
|
103
|
+
const neutral = /[\p{sc=Common}\p{sc=Inherited}\p{sc=Unknown}]/u;
|
|
104
|
+
/**
|
|
105
|
+
* This function gives the OpenType script tag of a text, as HarfBuzz guesses it. The tag is the script of the first
|
|
106
|
+
* character that is not of the scripts Common, Inherited or Unknown. A text of only those characters, for example emoji, gets `DFLT`.
|
|
107
|
+
*/
|
|
108
|
+
function scriptTag(text) {
|
|
109
|
+
for (const character of text) {
|
|
110
|
+
if (neutral.test(character)) continue;
|
|
111
|
+
for (const [pattern, tag] of scriptPatterns) if (pattern.test(character)) return tag;
|
|
112
|
+
return "DFLT";
|
|
113
|
+
}
|
|
114
|
+
return "DFLT";
|
|
115
|
+
}
|
|
116
|
+
/** A character that stays in the run of the character before it: a mark, a joiner, a variation selector, a tag or a skin tone. */
|
|
117
|
+
const attached = /^[\p{M}︀-️\u{e0020}-\u{e007f}\u{e0100}-\u{e01ef}\u{1f3fb}-\u{1f3ff}]$/u;
|
|
118
|
+
const styleOrder = {
|
|
119
|
+
normal: [
|
|
120
|
+
"normal",
|
|
121
|
+
"oblique",
|
|
122
|
+
"italic"
|
|
123
|
+
],
|
|
124
|
+
italic: [
|
|
125
|
+
"italic",
|
|
126
|
+
"oblique",
|
|
127
|
+
"normal"
|
|
128
|
+
],
|
|
129
|
+
oblique: [
|
|
130
|
+
"oblique",
|
|
131
|
+
"italic",
|
|
132
|
+
"normal"
|
|
133
|
+
]
|
|
134
|
+
};
|
|
135
|
+
/**
|
|
136
|
+
* This function gives the rank of a face for the weight `weight`, with the rules of CSS Fonts 4. A lower rank is a
|
|
137
|
+
* better match. A face whose range holds the weight has the rank 0.
|
|
138
|
+
*/
|
|
139
|
+
function weightRank(range, weight) {
|
|
140
|
+
const [min, max] = range;
|
|
141
|
+
if (min <= weight && weight <= max) return 0;
|
|
142
|
+
const above = min > weight;
|
|
143
|
+
if (weight >= 400 && weight <= 500) {
|
|
144
|
+
if (above && min <= 500) return 1e3 + min;
|
|
145
|
+
if (!above) return 3e3 - max;
|
|
146
|
+
return 5e3 + min;
|
|
147
|
+
}
|
|
148
|
+
if (weight < 400) return above ? 3e3 + min : 1e3 - max;
|
|
149
|
+
return above ? 1e3 + min : 3e3 - max;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* This function gives the advance function of the faces of the host: the width in px of a text in a CSS font. Each run
|
|
153
|
+
* of the text gets the first family of the font with a glyph for its first character. When no family has a glyph, the
|
|
154
|
+
* run gets the first family that has a face. The width of a run is `measureWidth` with the size of the font, the script
|
|
155
|
+
* of `scriptTag` and the rules of the engine. The instance of a variable face takes the weight and the stretch as `wdth`.
|
|
156
|
+
* It takes the style as `ital` or `slnt`, and the size as `opsz`, as `font-optical-sizing: auto` does.
|
|
157
|
+
*/
|
|
158
|
+
function fontAdvance(options) {
|
|
159
|
+
const engine = options.engine ?? "freetype";
|
|
160
|
+
if (typeof engine === "string" && !isEngineName(engine)) throw new RangeError(`the engine "${engine}" is not a row of engines`);
|
|
161
|
+
const rules = typeof engine === "string" ? engines[engine] : engine;
|
|
162
|
+
const shaping = options.shaping ?? "canvas";
|
|
163
|
+
const faces = options.faces;
|
|
164
|
+
const loaded = /* @__PURE__ */ new Map();
|
|
165
|
+
const specs = /* @__PURE__ */ new Map();
|
|
166
|
+
const instances = /* @__PURE__ */ new Map();
|
|
167
|
+
const families = /* @__PURE__ */ new Map();
|
|
168
|
+
const load = (index) => {
|
|
169
|
+
let entry = loaded.get(index);
|
|
170
|
+
if (entry === void 0) {
|
|
171
|
+
const face = faces[index];
|
|
172
|
+
const font = readFont(face.data, {
|
|
173
|
+
deltas: rules.deltas,
|
|
174
|
+
metrics: rules.metrics,
|
|
175
|
+
...face.decompress === void 0 ? {} : { decompress: face.decompress }
|
|
176
|
+
});
|
|
177
|
+
const axis = font.axes.find((value) => value.tag === "wght");
|
|
178
|
+
const declared = face.weight;
|
|
179
|
+
entry = {
|
|
180
|
+
face,
|
|
181
|
+
index,
|
|
182
|
+
font,
|
|
183
|
+
range: typeof declared === "number" ? [declared, declared] : declared ?? (axis === void 0 ? [400, 400] : [axis.min, axis.max])
|
|
184
|
+
};
|
|
185
|
+
loaded.set(index, entry);
|
|
186
|
+
}
|
|
187
|
+
return entry;
|
|
188
|
+
};
|
|
189
|
+
/** This function gives the font of the best face of a family for the spec, or `undefined` when the family has no face. */
|
|
190
|
+
const fontOf = (family, spec) => {
|
|
191
|
+
const name = family.toLowerCase();
|
|
192
|
+
const candidates = [];
|
|
193
|
+
for (let i = 0; i < faces.length; i++) if (faces[i].family.toLowerCase() === name) candidates.push(load(i));
|
|
194
|
+
if (candidates.length === 0) return void 0;
|
|
195
|
+
const style = styleOrder[spec.style].find((value) => candidates.some((entry) => (entry.face.style ?? "normal") === value));
|
|
196
|
+
let best;
|
|
197
|
+
let bestRank = Infinity;
|
|
198
|
+
for (const entry of candidates) {
|
|
199
|
+
if ((entry.face.style ?? "normal") !== style) continue;
|
|
200
|
+
const rank = weightRank(entry.range, spec.weight);
|
|
201
|
+
if (rank < bestRank) {
|
|
202
|
+
best = entry;
|
|
203
|
+
bestRank = rank;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const chosen = best;
|
|
207
|
+
const tags = new Set(chosen.font.axes.map((axis) => axis.tag));
|
|
208
|
+
const instance = {};
|
|
209
|
+
if (tags.has("wght")) instance["wght"] = Math.min(chosen.range[1], Math.max(chosen.range[0], spec.weight));
|
|
210
|
+
if (tags.has("wdth")) instance["wdth"] = spec.stretch;
|
|
211
|
+
if (tags.has("opsz")) instance["opsz"] = spec.size;
|
|
212
|
+
if (tags.has("ital")) instance["ital"] = spec.style === "italic" ? 1 : 0;
|
|
213
|
+
if (tags.has("slnt")) instance["slnt"] = spec.style === "oblique" ? -spec.angle : 0;
|
|
214
|
+
const key = `${chosen.index}|${JSON.stringify(instance)}`;
|
|
215
|
+
let font = instances.get(key);
|
|
216
|
+
if (font === void 0) {
|
|
217
|
+
font = Object.keys(instance).length === 0 ? chosen.font : chosen.font.at(instance);
|
|
218
|
+
instances.set(key, font);
|
|
219
|
+
}
|
|
220
|
+
return font;
|
|
221
|
+
};
|
|
222
|
+
const fontsOf = (font) => {
|
|
223
|
+
let spec = specs.get(font);
|
|
224
|
+
if (spec === void 0) {
|
|
225
|
+
spec = parseFont(font);
|
|
226
|
+
if (specs.size >= 256) specs.clear();
|
|
227
|
+
specs.set(font, spec);
|
|
228
|
+
}
|
|
229
|
+
let fonts = families.get(font);
|
|
230
|
+
if (fonts === void 0) {
|
|
231
|
+
fonts = spec.families.map((family) => fontOf(family, spec)).filter((value) => value !== void 0);
|
|
232
|
+
if (fonts.length === 0) throw new RangeError(`no face of the host has a family of the font "${font}"`);
|
|
233
|
+
if (families.size >= 256) families.clear();
|
|
234
|
+
families.set(font, fonts);
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
spec,
|
|
238
|
+
fonts
|
|
239
|
+
};
|
|
240
|
+
};
|
|
241
|
+
return (text, font) => {
|
|
242
|
+
const { spec, fonts } = fontsOf(font);
|
|
243
|
+
const measure = (run, face) => measureWidth(face, run, spec.size, {
|
|
244
|
+
script: scriptTag(run),
|
|
245
|
+
shaping,
|
|
246
|
+
rounding: rules.rounding,
|
|
247
|
+
sum: rules.sum
|
|
248
|
+
});
|
|
249
|
+
if (fonts.length === 1) return measure(text, fonts[0]);
|
|
250
|
+
let width = 0;
|
|
251
|
+
let run = "";
|
|
252
|
+
let current;
|
|
253
|
+
let joined = false;
|
|
254
|
+
for (const character of text) {
|
|
255
|
+
const code = character.codePointAt(0);
|
|
256
|
+
const face = current !== void 0 && (joined || attached.test(character)) ? current : fonts.find((value) => value.glyph(code) !== 0) ?? fonts[0];
|
|
257
|
+
if (face !== current && run.length > 0) {
|
|
258
|
+
width += measure(run, current);
|
|
259
|
+
run = "";
|
|
260
|
+
}
|
|
261
|
+
current = face;
|
|
262
|
+
run += character;
|
|
263
|
+
joined = code === 8205;
|
|
264
|
+
}
|
|
265
|
+
if (run.length > 0) width += measure(run, current);
|
|
266
|
+
return width;
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
// src/measurer.ts
|
|
270
|
+
var measurer_exports = /* @__PURE__ */ __exportAll({
|
|
271
|
+
fontAdvance: () => fontAdvance,
|
|
272
|
+
parseFont: () => parseFont,
|
|
273
|
+
scriptTag: () => scriptTag
|
|
274
|
+
});
|
|
275
|
+
export { scriptTag as i, fontAdvance as n, parseFont as r, measurer_exports as t };
|
|
276
|
+
|
|
277
|
+
//# sourceMappingURL=measurer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"measurer.js","names":[],"sources":["../../src/measurer/advance.ts","../../src/measurer.ts"],"sourcesContent":["/**\n * The advance function of the font model: the width of a text in a CSS font, as the measurer of\n * `@mark1russell7/layout-contract/text` asks for it. The host gives the font files as faces. The function finds the face\n * of each run of the text with the matching rules of CSS. It finds the script of the run, and measures the run with the\n * rules of one text engine.\n */\nimport { engines, isEngineName, type EngineName, type EngineRules } from \"../fonts/engines.ts\";\nimport { measureWidth, readFont, type Font } from \"../fonts/font.ts\";\nimport type { Decompress } from \"../fonts/sfnt.ts\";\n\n/** One font file of the host, with the family, the weight and the style that CSS matches. */\nexport interface FontFace {\n /** The family name, as the CSS font names it. The match ignores the case. */\n readonly family: string;\n /** The bytes of a TrueType, OpenType or WOFF2 file. */\n readonly data: Uint8Array;\n /**\n * The weight of the face: one value, or the range of a variable font. The default is the range of the `wght` axis of the\n * font, or 400 for a font with no `wght` axis.\n */\n readonly weight?: number | readonly [number, number];\n /** The style of the face. The default is `normal`. */\n readonly style?: \"normal\" | \"italic\" | \"oblique\";\n /** The Brotli decompressor of a WOFF2 file. */\n readonly decompress?: Decompress;\n}\n\n/** The options of `fontAdvance`. */\nexport interface FontAdvanceOptions {\n /** The font files of the host. The order sets the choice between two faces with the same match. */\n readonly faces: readonly FontFace[];\n /** The text engine: a row name of `engines`, or the rules of a row. The default is `freetype`, as Chromium on Linux. */\n readonly engine?: EngineName | EngineRules;\n /**\n * The shaping of `measureWidth`. The default is `canvas`, as `canvasAdvance` of layout-contract measures each segment\n * with `measureText`. Use `dom` for the widths of a text in a page.\n */\n readonly shaping?: \"dom\" | \"canvas\";\n}\n\n/** The parts of a CSS font shorthand that change a width. */\nexport interface FontSpec {\n readonly style: \"normal\" | \"italic\" | \"oblique\";\n /** The angle of an oblique style in degrees. CSS gives 14 when the font names no angle. */\n readonly angle: number;\n /** The weight from 1 to 1000. */\n readonly weight: number;\n /** The width of the font as a percent of the normal width, from the keyword of `font-stretch`. */\n readonly stretch: number;\n /** The size in px. */\n readonly size: number;\n /** The family names in the order of the font, with no quotes. */\n readonly families: readonly string[];\n}\n\nconst stretches: Readonly<Record<string, number>> = {\n \"ultra-condensed\": 50,\n \"extra-condensed\": 62.5,\n condensed: 75,\n \"semi-condensed\": 87.5,\n \"semi-expanded\": 112.5,\n expanded: 125,\n \"extra-expanded\": 150,\n \"ultra-expanded\": 200,\n};\n\n// Each number pattern matches a text in one way only. Thus a long run of digits does not make the match slow.\nconst sizePattern = /^((?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:e[+-]?\\d+)?)(px|pt)(\\/.*)?$/i;\nconst anglePattern = /^(-?(?:\\d+(?:\\.\\d+)?|\\.\\d+))deg$/i;\nconst weightPattern = /^(?:\\d+(?:\\.\\d+)?|\\.\\d+)$/;\n\n/**\n * This function reads a CSS font shorthand, for example `italic 700 16px/1.2 \"Noto Sans\", sans-serif`. The size must be in\n * `px` or `pt`. The function refuses `small-caps`, the system font keywords and the relative sizes with a `RangeError`.\n */\nexport function parseFont(font: string): FontSpec {\n const tokens = font.trim().split(/\\s+/);\n let style: FontSpec[\"style\"] = \"normal\";\n let angle = 14;\n let weight = 400;\n let stretch = 100;\n let index = 0;\n for (; index < tokens.length; index++) {\n const token = tokens[index]!;\n const lower = token.toLowerCase();\n const size = sizePattern.exec(token);\n if (size !== null) break;\n if (lower === \"normal\") continue;\n if (lower === \"italic\") style = \"italic\";\n else if (lower === \"oblique\") {\n style = \"oblique\";\n const next = anglePattern.exec(tokens[index + 1] ?? \"\");\n if (next !== null) {\n angle = Number(next[1]);\n index++;\n }\n } else if (lower === \"bold\" || lower === \"bolder\") weight = 700;\n else if (lower === \"lighter\") weight = 100;\n else if (weightPattern.test(token) && Number(token) >= 1 && Number(token) <= 1000) weight = Number(token);\n else if (Object.hasOwn(stretches, lower)) stretch = stretches[lower]!;\n else throw new RangeError(`the font \"${font}\" has the part \"${token}\", which the measurer does not know`);\n }\n const size = sizePattern.exec(tokens[index] ?? \"\");\n if (size === null) throw new RangeError(`the font \"${font}\" has no size in px or pt`);\n let rest = tokens.slice(index + 1).join(\" \");\n // A line height after a space: \"16px / 1.2\" or \"16px /1.2\"\n if (size[3] === undefined && rest.startsWith(\"/\")) rest = rest.slice(1).trim().replace(/^\\S+\\s*/, \"\");\n const families = rest\n .split(\",\")\n .map((family) => family.trim().replace(/^([\"'])(.*)\\1$/, \"$2\").trim())\n .filter((family) => family.length > 0);\n if (families.length === 0) throw new RangeError(`the font \"${font}\" has no family`);\n const value = Number(size[1]);\n return { style, angle, weight, stretch, size: size[2]!.toLowerCase() === \"pt\" ? (value * 4) / 3 : value, families };\n}\n\n/** The Unicode scripts and their OpenType tags. A script that is not in the list gets `DFLT`. */\nconst scriptPatterns: readonly (readonly [RegExp, string])[] = [\n [/\\p{sc=Latin}/u, \"latn\"],\n [/\\p{sc=Greek}/u, \"grek\"],\n [/\\p{sc=Cyrillic}/u, \"cyrl\"],\n [/\\p{sc=Armenian}/u, \"armn\"],\n [/\\p{sc=Hebrew}/u, \"hebr\"],\n [/\\p{sc=Arabic}/u, \"arab\"],\n [/\\p{sc=Syriac}/u, \"syrc\"],\n [/\\p{sc=Thaana}/u, \"thaa\"],\n [/\\p{sc=Devanagari}/u, \"deva\"],\n [/\\p{sc=Bengali}/u, \"beng\"],\n [/\\p{sc=Gurmukhi}/u, \"guru\"],\n [/\\p{sc=Gujarati}/u, \"gujr\"],\n [/\\p{sc=Oriya}/u, \"orya\"],\n [/\\p{sc=Tamil}/u, \"taml\"],\n [/\\p{sc=Telugu}/u, \"telu\"],\n [/\\p{sc=Kannada}/u, \"knda\"],\n [/\\p{sc=Malayalam}/u, \"mlym\"],\n [/\\p{sc=Sinhala}/u, \"sinh\"],\n [/\\p{sc=Thai}/u, \"thai\"],\n [/\\p{sc=Lao}/u, \"lao \"],\n [/\\p{sc=Tibetan}/u, \"tibt\"],\n [/\\p{sc=Myanmar}/u, \"mymr\"],\n [/\\p{sc=Georgian}/u, \"geor\"],\n [/\\p{sc=Hangul}/u, \"hang\"],\n [/\\p{sc=Ethiopic}/u, \"ethi\"],\n [/\\p{sc=Khmer}/u, \"khmr\"],\n [/\\p{sc=Mongolian}/u, \"mong\"],\n [/[\\p{sc=Hiragana}\\p{sc=Katakana}]/u, \"kana\"],\n [/\\p{sc=Bopomofo}/u, \"bopo\"],\n [/\\p{sc=Han}/u, \"hani\"],\n];\n\nconst neutral = /[\\p{sc=Common}\\p{sc=Inherited}\\p{sc=Unknown}]/u;\n\n/**\n * This function gives the OpenType script tag of a text, as HarfBuzz guesses it. The tag is the script of the first\n * character that is not of the scripts Common, Inherited or Unknown. A text of only those characters, for example emoji, gets `DFLT`.\n */\nexport function scriptTag(text: string): string {\n for (const character of text) {\n if (neutral.test(character)) continue;\n for (const [pattern, tag] of scriptPatterns) if (pattern.test(character)) return tag;\n return \"DFLT\";\n }\n return \"DFLT\";\n}\n\n/** A character that stays in the run of the character before it: a mark, a joiner, a variation selector, a tag or a skin tone. */\nconst attached = /^[\\p{M}︀-️\\u{e0020}-\\u{e007f}\\u{e0100}-\\u{e01ef}\\u{1f3fb}-\\u{1f3ff}]$/u;\n\nconst styleOrder: Readonly<Record<FontSpec[\"style\"], readonly FontSpec[\"style\"][]>> = {\n normal: [\"normal\", \"oblique\", \"italic\"],\n italic: [\"italic\", \"oblique\", \"normal\"],\n oblique: [\"oblique\", \"italic\", \"normal\"],\n};\n\n/** A face with its font, read at the first use. */\ninterface Loaded {\n readonly face: FontFace;\n readonly index: number;\n readonly font: Font;\n readonly range: readonly [number, number];\n}\n\n/**\n * This function gives the rank of a face for the weight `weight`, with the rules of CSS Fonts 4. A lower rank is a\n * better match. A face whose range holds the weight has the rank 0.\n */\nfunction weightRank(range: readonly [number, number], weight: number): number {\n const [min, max] = range;\n if (min <= weight && weight <= max) return 0;\n const above = min > weight;\n // The groups of CSS: 1 is the first choice after an exact match, 3 is the last\n if (weight >= 400 && weight <= 500) {\n if (above && min <= 500) return 1000 + min;\n if (!above) return 3000 - max;\n return 5000 + min;\n }\n if (weight < 400) return above ? 3000 + min : 1000 - max;\n return above ? 1000 + min : 3000 - max;\n}\n\n/**\n * This function gives the advance function of the faces of the host: the width in px of a text in a CSS font. Each run\n * of the text gets the first family of the font with a glyph for its first character. When no family has a glyph, the\n * run gets the first family that has a face. The width of a run is `measureWidth` with the size of the font, the script\n * of `scriptTag` and the rules of the engine. The instance of a variable face takes the weight and the stretch as `wdth`.\n * It takes the style as `ital` or `slnt`, and the size as `opsz`, as `font-optical-sizing: auto` does.\n */\nexport function fontAdvance(options: FontAdvanceOptions): (text: string, font: string) => number {\n const engine = options.engine ?? \"freetype\";\n if (typeof engine === \"string\" && !isEngineName(engine)) throw new RangeError(`the engine \"${engine}\" is not a row of engines`);\n const rules: EngineRules = typeof engine === \"string\" ? engines[engine] : engine;\n const shaping = options.shaping ?? \"canvas\";\n const faces = options.faces;\n const loaded = new Map<number, Loaded>();\n const specs = new Map<string, FontSpec>();\n const instances = new Map<string, Font>();\n const families = new Map<string, readonly Font[] | undefined>();\n\n const load = (index: number): Loaded => {\n let entry = loaded.get(index);\n if (entry === undefined) {\n const face = faces[index]!;\n const font = readFont(face.data, { deltas: rules.deltas, metrics: rules.metrics, ...(face.decompress === undefined ? {} : { decompress: face.decompress }) });\n const axis = font.axes.find((value) => value.tag === \"wght\");\n const declared = face.weight;\n const range: readonly [number, number] = typeof declared === \"number\" ? [declared, declared] : (declared ?? (axis === undefined ? [400, 400] : [axis.min, axis.max]));\n entry = { face, index, font, range };\n loaded.set(index, entry);\n }\n return entry;\n };\n\n /** This function gives the font of the best face of a family for the spec, or `undefined` when the family has no face. */\n const fontOf = (family: string, spec: FontSpec): Font | undefined => {\n const name = family.toLowerCase();\n const candidates: Loaded[] = [];\n for (let i = 0; i < faces.length; i++) if (faces[i]!.family.toLowerCase() === name) candidates.push(load(i));\n if (candidates.length === 0) return undefined;\n const style = styleOrder[spec.style].find((value) => candidates.some((entry) => (entry.face.style ?? \"normal\") === value))!;\n let best: Loaded | undefined;\n let bestRank = Infinity;\n for (const entry of candidates) {\n if ((entry.face.style ?? \"normal\") !== style) continue;\n const rank = weightRank(entry.range, spec.weight);\n if (rank < bestRank) {\n best = entry;\n bestRank = rank;\n }\n }\n const chosen = best!;\n const tags = new Set(chosen.font.axes.map((axis) => axis.tag));\n const instance: Record<string, number> = {};\n if (tags.has(\"wght\")) instance[\"wght\"] = Math.min(chosen.range[1], Math.max(chosen.range[0], spec.weight));\n if (tags.has(\"wdth\")) instance[\"wdth\"] = spec.stretch;\n if (tags.has(\"opsz\")) instance[\"opsz\"] = spec.size;\n if (tags.has(\"ital\")) instance[\"ital\"] = spec.style === \"italic\" ? 1 : 0;\n if (tags.has(\"slnt\")) instance[\"slnt\"] = spec.style === \"oblique\" ? -spec.angle : 0;\n const key = `${chosen.index}|${JSON.stringify(instance)}`;\n let font = instances.get(key);\n if (font === undefined) {\n font = Object.keys(instance).length === 0 ? chosen.font : chosen.font.at(instance);\n instances.set(key, font);\n }\n return font;\n };\n\n const fontsOf = (font: string): { readonly spec: FontSpec; readonly fonts: readonly Font[] } => {\n let spec = specs.get(font);\n if (spec === undefined) {\n spec = parseFont(font);\n if (specs.size >= 256) specs.clear();\n specs.set(font, spec);\n }\n let fonts = families.get(font);\n if (fonts === undefined) {\n fonts = spec.families.map((family) => fontOf(family, spec)).filter((value) => value !== undefined);\n if (fonts.length === 0) throw new RangeError(`no face of the host has a family of the font \"${font}\"`);\n if (families.size >= 256) families.clear();\n families.set(font, fonts);\n }\n return { spec, fonts };\n };\n\n return (text: string, font: string): number => {\n const { spec, fonts } = fontsOf(font);\n const measure = (run: string, face: Font): number => measureWidth(face, run, spec.size, { script: scriptTag(run), shaping, rounding: rules.rounding, sum: rules.sum });\n if (fonts.length === 1) return measure(text, fonts[0]!);\n let width = 0;\n let run = \"\";\n let current: Font | undefined;\n let joined = false;\n for (const character of text) {\n const code = character.codePointAt(0)!;\n const stays = current !== undefined && (joined || attached.test(character));\n const face = stays ? current! : (fonts.find((value) => value.glyph(code) !== 0) ?? fonts[0]!);\n if (face !== current && run.length > 0) {\n width += measure(run, current!);\n run = \"\";\n }\n current = face;\n run += character;\n joined = code === 0x200d;\n }\n if (run.length > 0) width += measure(run, current!);\n return width;\n };\n}\n","/**\n * The subpath `./measurer`: the advance function of a layout engine, from the font model of typeset. The measurer of\n * `@mark1russell7/layout-contract/text` takes it as its `AdvanceFn`.\n */\nexport { fontAdvance, parseFont, scriptTag, type FontAdvanceOptions, type FontFace, type FontSpec } from \"./measurer/advance.ts\";\n"],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** A big-endian reader over a part of a font file. The offsets are relative to the start of the part. */
|
|
2
|
+
declare class Reader {
|
|
3
|
+
readonly bytes: Uint8Array;
|
|
4
|
+
private readonly view;
|
|
5
|
+
constructor(bytes: Uint8Array);
|
|
6
|
+
get length(): number;
|
|
7
|
+
u8(offset: number): number;
|
|
8
|
+
u16(offset: number): number;
|
|
9
|
+
i16(offset: number): number;
|
|
10
|
+
u32(offset: number): number;
|
|
11
|
+
tag(offset: number): string;
|
|
12
|
+
/** This method gives a reader over the bytes from `offset`, with the length `length` or to the end. */
|
|
13
|
+
part(offset: number, length?: number): Reader;
|
|
14
|
+
}
|
|
15
|
+
/** A function that decompresses Brotli data. Node gives one as `brotliDecompressSync` of `node:zlib`. */
|
|
16
|
+
type Decompress = (data: Uint8Array) => Uint8Array;
|
|
17
|
+
/** The tables of a font, by tag. A table that the decoder does not need can be absent. */
|
|
18
|
+
type TableMap = ReadonlyMap<string, Reader>;
|
|
19
|
+
/** This function gives the tables of a TrueType, OpenType or WOFF2 file. */
|
|
20
|
+
declare function readTables(bytes: Uint8Array, decompress?: Decompress): TableMap;
|
|
21
|
+
export { readTables as n, Decompress as t };
|
|
22
|
+
//# sourceMappingURL=sfnt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sfnt.d.ts","names":[],"sources":["../../src/fonts/binary.ts","../../src/fonts/sfnt.ts"],"mappings":";cACa;WACF,OAAO;mBACC;EAEjB,YAAY,OAAO;MAKf;EAIJ,GAAG;EAIH,IAAI;EAIJ,IAAI;EAIJ,IAAI;EAIJ,IAAI;;EAKJ,KAAK,gBAAgB,kBAAkB;;;KChC7B,cAAc,MAAM,eAAe;;KAGnC,WAAW,oBAAoB;;iBAW3B,WAAW,OAAO,YAAY,aAAa,aAAa"}
|
package/build/chunks/text.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"text.js","names":[],"sources":["../../src/text/rope.ts","../../src/text.ts"],"sourcesContent":["/**\n * The `Text` rope: a persistent balanced tree of strings with the width and the length of each subtree. An edit makes a\n * new rope. The new rope shares the subtrees that do not change. A width function gives the width of a run of text. The\n * rope uses it only for the leaves and for the windows at the seams. Thus an edit uses it of the order of `log n` times.\n *\n * The seams are exact when the width function obeys the separator law. For each text `p + s + q` with a separator `s`:\n * `width(p + s + q) = width(p + s) + width(s + q) - width(s)`. The model of `@typeset/fonts` obeys it for a space and a\n * tab. It shapes each word alone, and its kerning goes only between two adjacent glyphs.\n */\nimport type { WidthOf } from \"../breaking/paragraph.ts\";\n\n/** The options of a rope. */\nexport interface TextOptions {\n /**\n * The separators of the separator law: each character is one UTF-16 code unit. The default is a space and a tab. An\n * empty string makes each seam measure the full text of the two sides.\n */\n readonly separators?: string;\n /** The largest count of UTF-16 code units in a leaf. The default is 256. */\n readonly chunk?: number;\n}\n\n/** The summary of a subtree: its length, its width, and the two ends that a seam measures again. */\ninterface Summary {\n /** The count of UTF-16 code units. */\n readonly length: number;\n readonly width: number;\n /** This value is `true` when the text has a separator. */\n readonly separated: boolean;\n /** The text through the first separator, or all the text when the text has no separator. */\n readonly head: string;\n readonly headWidth: number;\n /** The text from the last separator, or all the text when the text has no separator. */\n readonly tail: string;\n readonly tailWidth: number;\n}\n\ninterface Leaf extends Summary {\n readonly kind: \"leaf\";\n readonly text: string;\n readonly height: 0;\n}\n\ninterface Branch extends Summary {\n readonly kind: \"branch\";\n readonly left: Node;\n readonly right: Node;\n readonly height: number;\n}\n\ntype Node = Leaf | Branch;\n\n/** The shared state of the ropes of one width function and one set of options. */\ninterface Context {\n readonly width: WidthOf;\n readonly separators: string;\n readonly chunk: number;\n}\n\nfunction isSeparator(context: Context, code: number): boolean {\n for (let i = 0; i < context.separators.length; i++) if (context.separators.charCodeAt(i) === code) return true;\n return false;\n}\n\nfunction leafOf(context: Context, text: string): Leaf {\n const width = context.width(text);\n let first = -1;\n let last = -1;\n for (let i = 0; i < text.length; i++) {\n if (!isSeparator(context, text.charCodeAt(i))) continue;\n if (first < 0) first = i;\n last = i;\n }\n if (first < 0) return { kind: \"leaf\", text, height: 0, length: text.length, width, separated: false, head: text, headWidth: width, tail: text, tailWidth: width };\n const head = text.slice(0, first + 1);\n const tail = text.slice(last);\n return { kind: \"leaf\", text, height: 0, length: text.length, width, separated: true, head, headWidth: context.width(head), tail, tailWidth: context.width(tail) };\n}\n\n/**\n * This function makes the branch of two subtrees. It measures one window: the tail of the left side and the head of the right\n * side. The separator law then gives the width of all the text from the widths of the two sides.\n */\nfunction branchOf(context: Context, left: Node, right: Node): Branch {\n const seam = left.tail + right.head;\n const seamWidth = context.width(seam);\n let width: number;\n if (!left.separated && !right.separated) width = seamWidth;\n else if (!left.separated) width = right.width - right.headWidth + seamWidth;\n else if (!right.separated) width = left.width - left.tailWidth + seamWidth;\n else width = left.width + right.width - left.tailWidth - right.headWidth + seamWidth;\n return {\n kind: \"branch\",\n left,\n right,\n height: Math.max(left.height, right.height) + 1,\n length: left.length + right.length,\n width,\n separated: left.separated || right.separated,\n head: left.separated ? left.head : seam,\n headWidth: left.separated ? left.headWidth : seamWidth,\n tail: right.separated ? right.tail : seam,\n tailWidth: right.separated ? right.tailWidth : seamWidth,\n };\n}\n\n/** This function joins two trees in the order of the text and keeps the heights of the two children within 1. */\nfunction join(context: Context, left: Node | undefined, right: Node | undefined): Node | undefined {\n if (left === undefined) return right;\n if (right === undefined) return left;\n if (left.kind === \"leaf\" && right.kind === \"leaf\" && left.length + right.length <= context.chunk) return leafOf(context, left.text + right.text);\n if (left.height > right.height + 1) {\n const branch = left as Branch;\n const joined = join(context, branch.right, right)!;\n if (joined.height <= branch.left.height + 1) return branchOf(context, branch.left, joined);\n return rotateLeft(context, branch.left, joined as Branch);\n }\n if (right.height > left.height + 1) {\n const branch = right as Branch;\n const joined = join(context, left, branch.left)!;\n if (joined.height <= branch.right.height + 1) return branchOf(context, joined, branch.right);\n return rotateRight(context, joined as Branch, branch.right);\n }\n return branchOf(context, left, right);\n}\n\n/** This function balances the branch of `left` and `heavy`, when `heavy` is 2 levels higher than `left`. */\nfunction rotateLeft(context: Context, left: Node, heavy: Branch): Branch {\n if (heavy.left.height <= heavy.right.height) return branchOf(context, branchOf(context, left, heavy.left), heavy.right);\n const middle = heavy.left as Branch;\n return branchOf(context, branchOf(context, left, middle.left), branchOf(context, middle.right, heavy.right));\n}\n\n/** This function balances the branch of `heavy` and `right`, when `heavy` is 2 levels higher than `right`. */\nfunction rotateRight(context: Context, heavy: Branch, right: Node): Branch {\n if (heavy.right.height <= heavy.left.height) return branchOf(context, heavy.left, branchOf(context, heavy.right, right));\n const middle = heavy.right as Branch;\n return branchOf(context, branchOf(context, heavy.left, middle.left), branchOf(context, middle.right, right));\n}\n\n/** This function divides a tree at the UTF-16 offset `at` into the text before and the text after. */\nfunction split(context: Context, node: Node | undefined, at: number): [Node | undefined, Node | undefined] {\n if (node === undefined) return [undefined, undefined];\n if (at <= 0) return [undefined, node];\n if (at >= node.length) return [node, undefined];\n if (node.kind === \"leaf\") return [leafOf(context, node.text.slice(0, at)), leafOf(context, node.text.slice(at))];\n if (at === node.left.length) return [node.left, node.right];\n if (at < node.left.length) {\n const [before, after] = split(context, node.left, at);\n return [before, join(context, after, node.right)];\n }\n const [before, after] = split(context, node.right, at - node.left.length);\n return [join(context, node.left, before), after];\n}\n\n/** This function gives the end of the next leaf of `text` from `start`: after a separator when it can, and not in a surrogate pair. */\nfunction chunkEnd(context: Context, text: string, start: number): number {\n const limit = start + context.chunk;\n if (limit >= text.length) return text.length;\n for (let i = limit - 1; i >= start + context.chunk / 2; i--) if (isSeparator(context, text.charCodeAt(i))) return i + 1;\n const code = text.charCodeAt(limit - 1);\n return code >= 0xd800 && code <= 0xdbff ? limit - 1 : limit;\n}\n\n/** This function makes a balanced tree of a text: leaves of at most `chunk` code units, and then a level of branches at a time. */\nfunction build(context: Context, text: string): Node | undefined {\n let level: Node[] = [];\n for (let start = 0; start < text.length; ) {\n const end = chunkEnd(context, text, start);\n level.push(leafOf(context, text.slice(start, end)));\n start = end;\n }\n if (level.length === 0) return undefined;\n while (level.length > 1) {\n const next: Node[] = [];\n for (let i = 0; i + 1 < level.length; i += 2) next.push(branchOf(context, level[i]!, level[i + 1]!));\n if (level.length % 2 === 1) next.push(level[level.length - 1]!);\n level = next;\n }\n return level[0];\n}\n\nfunction leaves(node: Node | undefined, out: string[]): string[] {\n if (node === undefined) return out;\n const stack: Node[] = [node];\n while (stack.length > 0) {\n const top = stack.pop()!;\n if (top.kind === \"leaf\") out.push(top.text);\n else stack.push(top.right, top.left);\n }\n return out;\n}\n\n/**\n * A text with its width: a persistent balanced rope. Each edit gives a new `Text` and does not change this one. The offsets\n * are in UTF-16 code units, as the offsets of a JavaScript string.\n */\nexport class Text {\n readonly #context: Context;\n readonly #root: Node | undefined;\n\n private constructor(context: Context, root: Node | undefined) {\n this.#context = context;\n this.#root = root;\n }\n\n /** This function makes the rope of `text` with the width function `width`. */\n static from(text: string, width: WidthOf, options: TextOptions = {}): Text {\n const separators = options.separators ?? \" \\t\";\n const chunk = options.chunk ?? 256;\n if (!Number.isInteger(chunk) || chunk < 2) throw new RangeError(`the chunk is ${chunk}, not an integer of 2 or more`);\n for (let i = 0; i < separators.length; i++) {\n const code = separators.charCodeAt(i);\n if (code >= 0xd800 && code <= 0xdfff) throw new RangeError(\"a separator is one UTF-16 code unit, not a half of a surrogate pair\");\n }\n const context: Context = { width, separators, chunk };\n return new Text(context, build(context, text));\n }\n\n /** The count of UTF-16 code units of the text. */\n get length(): number {\n return this.#root?.length ?? 0;\n }\n\n /** The width of the text. It is equal to the width of the flat string when the width function obeys the separator law. */\n get width(): number {\n return this.#root?.width ?? 0;\n }\n\n /** The height of the tree: 0 for a text of one leaf. The tests use it to examine the balance. */\n get height(): number {\n return this.#root?.height ?? 0;\n }\n\n /** This function gives the flat string. */\n toString(): string {\n return leaves(this.#root, []).join(\"\");\n }\n\n /** This function gives the strings of the leaves in the order of the text. */\n chunks(): string[] {\n return leaves(this.#root, []);\n }\n\n /** This function gives the text from `start` to `end`, as a new rope. */\n slice(start: number, end: number = this.length): Text {\n this.#check(start, end);\n const [, after] = split(this.#context, this.#root, start);\n const [middle] = split(this.#context, after, end - start);\n return new Text(this.#context, middle);\n }\n\n /** This function gives the width of the text from `start` to `end`. */\n widthOf(start: number, end: number = this.length): number {\n return this.slice(start, end).width;\n }\n\n /** This function gives this text and then `other`. The two ropes must have the same width function and options. */\n concat(other: Text): Text {\n const context = other.#context;\n if (context.width !== this.#context.width || context.separators !== this.#context.separators || context.chunk !== this.#context.chunk) {\n throw new TypeError(\"the two ropes have different width functions or options\");\n }\n return new Text(this.#context, join(this.#context, this.#root, other.#root));\n }\n\n /** This function puts `text` at the offset `at`. */\n insert(at: number, text: string): Text {\n return this.replace(at, at, text);\n }\n\n /** This function removes the text from `start` to `end`. */\n remove(start: number, end: number): Text {\n return this.replace(start, end, \"\");\n }\n\n /** This function puts `text` in place of the text from `start` to `end`. */\n replace(start: number, end: number, text: string): Text {\n this.#check(start, end);\n const [before, rest] = split(this.#context, this.#root, start);\n const [, after] = split(this.#context, rest, end - start);\n const middle = build(this.#context, text);\n return new Text(this.#context, join(this.#context, join(this.#context, before, middle), after));\n }\n\n #check(start: number, end: number): void {\n if (!Number.isInteger(start) || !Number.isInteger(end) || start < 0 || end < start || end > this.length) {\n throw new RangeError(`the range ${start} to ${end} is not in the text of length ${this.length}`);\n }\n }\n}\n","export { Text, type TextOptions } from \"./text/rope.ts\";\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"text.js","names":[],"sources":["../../src/text/rope.ts","../../src/text.ts"],"sourcesContent":["/**\n * The `Text` rope: a persistent balanced tree of strings with the width and the length of each subtree. An edit makes a\n * new rope. The new rope shares the subtrees that do not change. A width function gives the width of a run of text. The\n * rope uses it only for the leaves and for the windows at the seams. Thus an edit uses it of the order of `log n` times.\n *\n * The seams are exact when the width function obeys the separator law. For each text `p + s + q` with a separator `s`:\n * `width(p + s + q) = width(p + s) + width(s + q) - width(s)`. The model of `@mark1russell7/typeset/fonts` obeys it for a space and a\n * tab. It shapes each word alone, and its kerning goes only between two adjacent glyphs.\n */\nimport type { WidthOf } from \"../breaking/paragraph.ts\";\n\n/** The options of a rope. */\nexport interface TextOptions {\n /**\n * The separators of the separator law: each character is one UTF-16 code unit. The default is a space and a tab. An\n * empty string makes each seam measure the full text of the two sides.\n */\n readonly separators?: string;\n /** The largest count of UTF-16 code units in a leaf. The default is 256. */\n readonly chunk?: number;\n}\n\n/** The summary of a subtree: its length, its width, and the two ends that a seam measures again. */\ninterface Summary {\n /** The count of UTF-16 code units. */\n readonly length: number;\n readonly width: number;\n /** This value is `true` when the text has a separator. */\n readonly separated: boolean;\n /** The text through the first separator, or all the text when the text has no separator. */\n readonly head: string;\n readonly headWidth: number;\n /** The text from the last separator, or all the text when the text has no separator. */\n readonly tail: string;\n readonly tailWidth: number;\n}\n\ninterface Leaf extends Summary {\n readonly kind: \"leaf\";\n readonly text: string;\n readonly height: 0;\n}\n\ninterface Branch extends Summary {\n readonly kind: \"branch\";\n readonly left: Node;\n readonly right: Node;\n readonly height: number;\n}\n\ntype Node = Leaf | Branch;\n\n/** The shared state of the ropes of one width function and one set of options. */\ninterface Context {\n readonly width: WidthOf;\n readonly separators: string;\n readonly chunk: number;\n}\n\nfunction isSeparator(context: Context, code: number): boolean {\n for (let i = 0; i < context.separators.length; i++) if (context.separators.charCodeAt(i) === code) return true;\n return false;\n}\n\nfunction leafOf(context: Context, text: string): Leaf {\n const width = context.width(text);\n let first = -1;\n let last = -1;\n for (let i = 0; i < text.length; i++) {\n if (!isSeparator(context, text.charCodeAt(i))) continue;\n if (first < 0) first = i;\n last = i;\n }\n if (first < 0) return { kind: \"leaf\", text, height: 0, length: text.length, width, separated: false, head: text, headWidth: width, tail: text, tailWidth: width };\n const head = text.slice(0, first + 1);\n const tail = text.slice(last);\n return { kind: \"leaf\", text, height: 0, length: text.length, width, separated: true, head, headWidth: context.width(head), tail, tailWidth: context.width(tail) };\n}\n\n/**\n * This function makes the branch of two subtrees. It measures one window: the tail of the left side and the head of the right\n * side. The separator law then gives the width of all the text from the widths of the two sides.\n */\nfunction branchOf(context: Context, left: Node, right: Node): Branch {\n const seam = left.tail + right.head;\n const seamWidth = context.width(seam);\n let width: number;\n if (!left.separated && !right.separated) width = seamWidth;\n else if (!left.separated) width = right.width - right.headWidth + seamWidth;\n else if (!right.separated) width = left.width - left.tailWidth + seamWidth;\n else width = left.width + right.width - left.tailWidth - right.headWidth + seamWidth;\n return {\n kind: \"branch\",\n left,\n right,\n height: Math.max(left.height, right.height) + 1,\n length: left.length + right.length,\n width,\n separated: left.separated || right.separated,\n head: left.separated ? left.head : seam,\n headWidth: left.separated ? left.headWidth : seamWidth,\n tail: right.separated ? right.tail : seam,\n tailWidth: right.separated ? right.tailWidth : seamWidth,\n };\n}\n\n/** This function joins two trees in the order of the text and keeps the heights of the two children within 1. */\nfunction join(context: Context, left: Node | undefined, right: Node | undefined): Node | undefined {\n if (left === undefined) return right;\n if (right === undefined) return left;\n if (left.kind === \"leaf\" && right.kind === \"leaf\" && left.length + right.length <= context.chunk) return leafOf(context, left.text + right.text);\n if (left.height > right.height + 1) {\n const branch = left as Branch;\n const joined = join(context, branch.right, right)!;\n if (joined.height <= branch.left.height + 1) return branchOf(context, branch.left, joined);\n return rotateLeft(context, branch.left, joined as Branch);\n }\n if (right.height > left.height + 1) {\n const branch = right as Branch;\n const joined = join(context, left, branch.left)!;\n if (joined.height <= branch.right.height + 1) return branchOf(context, joined, branch.right);\n return rotateRight(context, joined as Branch, branch.right);\n }\n return branchOf(context, left, right);\n}\n\n/** This function balances the branch of `left` and `heavy`, when `heavy` is 2 levels higher than `left`. */\nfunction rotateLeft(context: Context, left: Node, heavy: Branch): Branch {\n if (heavy.left.height <= heavy.right.height) return branchOf(context, branchOf(context, left, heavy.left), heavy.right);\n const middle = heavy.left as Branch;\n return branchOf(context, branchOf(context, left, middle.left), branchOf(context, middle.right, heavy.right));\n}\n\n/** This function balances the branch of `heavy` and `right`, when `heavy` is 2 levels higher than `right`. */\nfunction rotateRight(context: Context, heavy: Branch, right: Node): Branch {\n if (heavy.right.height <= heavy.left.height) return branchOf(context, heavy.left, branchOf(context, heavy.right, right));\n const middle = heavy.right as Branch;\n return branchOf(context, branchOf(context, heavy.left, middle.left), branchOf(context, middle.right, right));\n}\n\n/** This function divides a tree at the UTF-16 offset `at` into the text before and the text after. */\nfunction split(context: Context, node: Node | undefined, at: number): [Node | undefined, Node | undefined] {\n if (node === undefined) return [undefined, undefined];\n if (at <= 0) return [undefined, node];\n if (at >= node.length) return [node, undefined];\n if (node.kind === \"leaf\") return [leafOf(context, node.text.slice(0, at)), leafOf(context, node.text.slice(at))];\n if (at === node.left.length) return [node.left, node.right];\n if (at < node.left.length) {\n const [before, after] = split(context, node.left, at);\n return [before, join(context, after, node.right)];\n }\n const [before, after] = split(context, node.right, at - node.left.length);\n return [join(context, node.left, before), after];\n}\n\n/** This function gives the end of the next leaf of `text` from `start`: after a separator when it can, and not in a surrogate pair. */\nfunction chunkEnd(context: Context, text: string, start: number): number {\n const limit = start + context.chunk;\n if (limit >= text.length) return text.length;\n for (let i = limit - 1; i >= start + context.chunk / 2; i--) if (isSeparator(context, text.charCodeAt(i))) return i + 1;\n const code = text.charCodeAt(limit - 1);\n return code >= 0xd800 && code <= 0xdbff ? limit - 1 : limit;\n}\n\n/** This function makes a balanced tree of a text: leaves of at most `chunk` code units, and then a level of branches at a time. */\nfunction build(context: Context, text: string): Node | undefined {\n let level: Node[] = [];\n for (let start = 0; start < text.length; ) {\n const end = chunkEnd(context, text, start);\n level.push(leafOf(context, text.slice(start, end)));\n start = end;\n }\n if (level.length === 0) return undefined;\n while (level.length > 1) {\n const next: Node[] = [];\n for (let i = 0; i + 1 < level.length; i += 2) next.push(branchOf(context, level[i]!, level[i + 1]!));\n if (level.length % 2 === 1) next.push(level[level.length - 1]!);\n level = next;\n }\n return level[0];\n}\n\nfunction leaves(node: Node | undefined, out: string[]): string[] {\n if (node === undefined) return out;\n const stack: Node[] = [node];\n while (stack.length > 0) {\n const top = stack.pop()!;\n if (top.kind === \"leaf\") out.push(top.text);\n else stack.push(top.right, top.left);\n }\n return out;\n}\n\n/**\n * A text with its width: a persistent balanced rope. Each edit gives a new `Text` and does not change this one. The offsets\n * are in UTF-16 code units, as the offsets of a JavaScript string.\n */\nexport class Text {\n readonly #context: Context;\n readonly #root: Node | undefined;\n\n private constructor(context: Context, root: Node | undefined) {\n this.#context = context;\n this.#root = root;\n }\n\n /** This function makes the rope of `text` with the width function `width`. */\n static from(text: string, width: WidthOf, options: TextOptions = {}): Text {\n const separators = options.separators ?? \" \\t\";\n const chunk = options.chunk ?? 256;\n if (!Number.isInteger(chunk) || chunk < 2) throw new RangeError(`the chunk is ${chunk}, not an integer of 2 or more`);\n for (let i = 0; i < separators.length; i++) {\n const code = separators.charCodeAt(i);\n if (code >= 0xd800 && code <= 0xdfff) throw new RangeError(\"a separator is one UTF-16 code unit, not a half of a surrogate pair\");\n }\n const context: Context = { width, separators, chunk };\n return new Text(context, build(context, text));\n }\n\n /** The count of UTF-16 code units of the text. */\n get length(): number {\n return this.#root?.length ?? 0;\n }\n\n /** The width of the text. It is equal to the width of the flat string when the width function obeys the separator law. */\n get width(): number {\n return this.#root?.width ?? 0;\n }\n\n /** The height of the tree: 0 for a text of one leaf. The tests use it to examine the balance. */\n get height(): number {\n return this.#root?.height ?? 0;\n }\n\n /** This function gives the flat string. */\n toString(): string {\n return leaves(this.#root, []).join(\"\");\n }\n\n /** This function gives the strings of the leaves in the order of the text. */\n chunks(): string[] {\n return leaves(this.#root, []);\n }\n\n /** This function gives the text from `start` to `end`, as a new rope. */\n slice(start: number, end: number = this.length): Text {\n this.#check(start, end);\n const [, after] = split(this.#context, this.#root, start);\n const [middle] = split(this.#context, after, end - start);\n return new Text(this.#context, middle);\n }\n\n /** This function gives the width of the text from `start` to `end`. */\n widthOf(start: number, end: number = this.length): number {\n return this.slice(start, end).width;\n }\n\n /** This function gives this text and then `other`. The two ropes must have the same width function and options. */\n concat(other: Text): Text {\n const context = other.#context;\n if (context.width !== this.#context.width || context.separators !== this.#context.separators || context.chunk !== this.#context.chunk) {\n throw new TypeError(\"the two ropes have different width functions or options\");\n }\n return new Text(this.#context, join(this.#context, this.#root, other.#root));\n }\n\n /** This function puts `text` at the offset `at`. */\n insert(at: number, text: string): Text {\n return this.replace(at, at, text);\n }\n\n /** This function removes the text from `start` to `end`. */\n remove(start: number, end: number): Text {\n return this.replace(start, end, \"\");\n }\n\n /** This function puts `text` in place of the text from `start` to `end`. */\n replace(start: number, end: number, text: string): Text {\n this.#check(start, end);\n const [before, rest] = split(this.#context, this.#root, start);\n const [, after] = split(this.#context, rest, end - start);\n const middle = build(this.#context, text);\n return new Text(this.#context, join(this.#context, join(this.#context, before, middle), after));\n }\n\n #check(start: number, end: number): void {\n if (!Number.isInteger(start) || !Number.isInteger(end) || start < 0 || end < start || end > this.length) {\n throw new RangeError(`the range ${start} to ${end} is not in the text of length ${this.length}`);\n }\n }\n}\n","export { Text, type TextOptions } from \"./text/rope.ts\";\n"],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { i as glyphAdvances, s as shapingWords, u as defaultFeatures } from "./chunks/font.js";
|
|
2
|
+
import { n as readTables } from "./chunks/sfnt.js";
|
|
3
|
+
/**
|
|
4
|
+
* This function divides the text of a canvas into the words that Blink shapes one at a time. Each space, tab and zero
|
|
5
|
+
* width space is a word alone. Each CJK character starts a word, with its marks and the CJK characters of the script
|
|
6
|
+
* Common after it.
|
|
7
|
+
*/
|
|
8
|
+
export declare function canvasWords(text: string): string[];
|
|
9
|
+
export { defaultFeatures, glyphAdvances, readTables, shapingWords };
|
|
10
|
+
//# sourceMappingURL=fonts-inspect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fonts-inspect.d.ts","names":[],"sources":["../src/fonts/words.ts"],"mappings":";;;;;;;wBAwDgB,YAAY"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { a as canvasWords, o as defaultFeatures, t as glyphAdvances, s as readTables, i as shapingWords } from "./chunks/font.js";
|
package/build/fonts.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { t as Decompress } from "./chunks/sfnt.js";
|
|
2
|
+
import { a as measureWidth, c as Axis, l as Instance, n as MeasureOptions, o as readFont, r as ReadOptions, t as Font } from "./chunks/font.js";
|
|
3
|
+
import { i as isEngineName, n as EngineRules, r as engines, t as EngineName } from "./chunks/engines.js";
|
|
4
|
+
import "./chunks/fonts.js";
|
|
5
|
+
export { type Axis, type Decompress, type EngineName, type EngineRules, type Font, type Instance, type MeasureOptions, type ReadOptions, engines, isEngineName, measureWidth, readFont };
|
package/build/fonts.js
ADDED
package/build/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import { t as breaking_d_exports } from "./chunks/breaking.js";
|
|
2
|
+
import { t as fonts_d_exports } from "./chunks/fonts.js";
|
|
3
|
+
import { t as measurer_d_exports } from "./chunks/measurer.js";
|
|
2
4
|
import { t as text_d_exports } from "./chunks/text.js";
|
|
3
|
-
export { breaking_d_exports as breaking, text_d_exports as text };
|
|
5
|
+
export { breaking_d_exports as breaking, fonts_d_exports as fonts, measurer_d_exports as measurer, text_d_exports as text };
|
package/build/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import { t as breaking_exports } from "./chunks/breaking.js";
|
|
2
|
+
import { t as fonts_exports } from "./chunks/fonts.js";
|
|
3
|
+
import { t as measurer_exports } from "./chunks/measurer.js";
|
|
2
4
|
import { t as text_exports } from "./chunks/text.js";
|
|
3
|
-
export { breaking_exports as breaking, text_exports as text };
|
|
5
|
+
export { breaking_exports as breaking, fonts_exports as fonts, measurer_exports as measurer, text_exports as text };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { n as fontAdvance, r as parseFont, i as scriptTag } from "./chunks/measurer.js";
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package",
|
|
3
3
|
"name": "@mark1russell7/typeset",
|
|
4
|
-
"version": "0.1.
|
|
5
|
-
"description": "Text measurement and line breaking. The subpath ./breaking has the line breakers, and ./text has a rope
|
|
4
|
+
"version": "0.1.2",
|
|
5
|
+
"description": "Text measurement and line breaking. The subpath ./fonts reads font tables and gives widths, ./measurer gives the advance function of a layout, ./breaking has the line breakers, and ./text has a rope.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"typeset",
|
|
8
8
|
"line-breaking",
|
|
@@ -34,6 +34,18 @@
|
|
|
34
34
|
"types": "./build/breaking.d.ts",
|
|
35
35
|
"default": "./build/breaking.js"
|
|
36
36
|
},
|
|
37
|
+
"./fonts": {
|
|
38
|
+
"types": "./build/fonts.d.ts",
|
|
39
|
+
"default": "./build/fonts.js"
|
|
40
|
+
},
|
|
41
|
+
"./fonts/inspect": {
|
|
42
|
+
"types": "./build/fonts-inspect.d.ts",
|
|
43
|
+
"default": "./build/fonts-inspect.js"
|
|
44
|
+
},
|
|
45
|
+
"./measurer": {
|
|
46
|
+
"types": "./build/measurer.d.ts",
|
|
47
|
+
"default": "./build/measurer.js"
|
|
48
|
+
},
|
|
37
49
|
"./text": {
|
|
38
50
|
"types": "./build/text.d.ts",
|
|
39
51
|
"default": "./build/text.js"
|
|
@@ -52,10 +64,12 @@
|
|
|
52
64
|
"publishConfig": {
|
|
53
65
|
"access": "public"
|
|
54
66
|
},
|
|
67
|
+
"dependencies": {
|
|
68
|
+
"@mark1russell7/rigor-generators": "^0.1.0"
|
|
69
|
+
},
|
|
55
70
|
"devDependencies": {
|
|
56
71
|
"@mark1russell7/cue": "^1.0.2",
|
|
57
72
|
"@mark1russell7/layout-contract": "^0.3.2",
|
|
58
|
-
"@mark1russell7/rigor-generators": "^0.1.0",
|
|
59
73
|
"@types/node": "^26.6.5",
|
|
60
74
|
"@typeset/corpus": "0.1.0",
|
|
61
75
|
"tsdown": "^0.23.1",
|