@mark1russell7/typeset 0.1.2 → 0.1.3
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/build/chunks/font.d.ts +25 -0
- package/build/chunks/font.d.ts.map +1 -1
- package/build/chunks/font.js +269 -40
- package/build/chunks/font.js.map +1 -1
- package/build/chunks/fonts.d.ts +25 -2
- package/build/chunks/fonts.d.ts.map +1 -1
- package/build/chunks/fonts.js +140 -3
- package/build/chunks/fonts.js.map +1 -1
- package/build/fonts-inspect.js +1 -1
- package/build/fonts.d.ts +2 -2
- package/build/fonts.js +2 -2
- package/package.json +2 -2
- package/src/fonts/bound.ts +167 -0
- package/src/fonts/font.ts +20 -4
- package/src/fonts/index.ts +1 -0
- package/src/fonts/indic.ts +54 -13
- package/src/fonts/kerning.ts +68 -7
- package/src/fonts/substitution.ts +101 -1
- package/src/fonts/tables.ts +22 -0
- package/src/fonts.ts +1 -0
package/src/fonts/kerning.ts
CHANGED
|
@@ -25,8 +25,22 @@ export interface PairLookup {
|
|
|
25
25
|
readonly skip?: (glyph: number) => boolean;
|
|
26
26
|
/** This function gives the value for a pair, or `undefined` when no subtable applies. */
|
|
27
27
|
pair(first: number, second: number): PairValue | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* This function gives the largest positive value of each glyph of a pair, over the pairs with two glyphs of `glyphs`.
|
|
30
|
+
* A value is at least 0. It uses the values of the instance, so it includes each variation delta.
|
|
31
|
+
*/
|
|
32
|
+
largest(glyphs: ReadonlySet<number>): LargestKerning;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The largest positive kerning of the first glyph and of the second glyph of a pair, in font units. */
|
|
36
|
+
export interface LargestKerning {
|
|
37
|
+
readonly first: number;
|
|
38
|
+
readonly second: number;
|
|
28
39
|
}
|
|
29
40
|
|
|
41
|
+
/** One PairPos subtable. */
|
|
42
|
+
type PairSubtable = Pick<PairLookup, "pair" | "largest">;
|
|
43
|
+
|
|
30
44
|
/** The lookup flags of OpenType. */
|
|
31
45
|
export const IGNORE_BASE = 0x2;
|
|
32
46
|
export const IGNORE_LIGATURES = 0x4;
|
|
@@ -63,8 +77,8 @@ function pairValue(first: { value: number; delta: number }, second: { value: num
|
|
|
63
77
|
return { first: first.value, second: second.value, firstDelta: first.delta, secondDelta: second.delta, skipSecond };
|
|
64
78
|
}
|
|
65
79
|
|
|
66
|
-
/** This function makes the
|
|
67
|
-
function pairSubtable(table: Reader, store: VariationStore | undefined):
|
|
80
|
+
/** This function makes the functions of one PairPos subtable. */
|
|
81
|
+
function pairSubtable(table: Reader, store: VariationStore | undefined): PairSubtable {
|
|
68
82
|
const format = table.u16(0);
|
|
69
83
|
const coverage = table.part(table.u16(2));
|
|
70
84
|
const format1 = table.u16(4);
|
|
@@ -73,11 +87,11 @@ function pairSubtable(table: Reader, store: VariationStore | undefined): (first:
|
|
|
73
87
|
const size2 = valueSize(format2);
|
|
74
88
|
const skipSecond = format2 !== 0;
|
|
75
89
|
if (format === 1) {
|
|
76
|
-
|
|
90
|
+
const recordSize = 2 + size1 + size2;
|
|
91
|
+
const pair = (first: number, second: number): PairValue | undefined => {
|
|
77
92
|
const index = coverageIndex(coverage, first);
|
|
78
93
|
if (index < 0) return undefined;
|
|
79
94
|
const set = table.part(table.u16(10 + index * 2));
|
|
80
|
-
const recordSize = 2 + size1 + size2;
|
|
81
95
|
let low = 0;
|
|
82
96
|
let high = set.u16(0) - 1;
|
|
83
97
|
while (low <= high) {
|
|
@@ -90,13 +104,30 @@ function pairSubtable(table: Reader, store: VariationStore | undefined): (first:
|
|
|
90
104
|
}
|
|
91
105
|
return undefined;
|
|
92
106
|
};
|
|
107
|
+
const largest = (glyphs: ReadonlySet<number>): LargestKerning => {
|
|
108
|
+
let first = 0;
|
|
109
|
+
let second = 0;
|
|
110
|
+
for (const glyph of glyphs) {
|
|
111
|
+
const index = coverageIndex(coverage, glyph);
|
|
112
|
+
if (index < 0) continue;
|
|
113
|
+
const set = table.part(table.u16(10 + index * 2));
|
|
114
|
+
for (let record = 0; record < set.u16(0); record++) {
|
|
115
|
+
const at = 2 + record * recordSize;
|
|
116
|
+
if (!glyphs.has(set.u16(at))) continue;
|
|
117
|
+
first = Math.max(first, xAdvance(set, at + 2, format1, set, store).value);
|
|
118
|
+
second = Math.max(second, xAdvance(set, at + 2 + size1, format2, set, store).value);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return { first, second };
|
|
122
|
+
};
|
|
123
|
+
return { pair, largest };
|
|
93
124
|
}
|
|
94
125
|
if (format === 2) {
|
|
95
126
|
const classes1 = table.part(table.u16(8));
|
|
96
127
|
const classes2 = table.part(table.u16(10));
|
|
97
128
|
const count1 = table.u16(12);
|
|
98
129
|
const count2 = table.u16(14);
|
|
99
|
-
|
|
130
|
+
const pair = (first: number, second: number): PairValue | undefined => {
|
|
100
131
|
if (coverageIndex(coverage, first) < 0) return undefined;
|
|
101
132
|
const class1 = classOf(classes1, first);
|
|
102
133
|
const class2 = classOf(classes2, second);
|
|
@@ -104,6 +135,26 @@ function pairSubtable(table: Reader, store: VariationStore | undefined): (first:
|
|
|
104
135
|
const at = 16 + (class1 * count2 + class2) * (size1 + size2);
|
|
105
136
|
return pairValue(xAdvance(table, at, format1, table, store), xAdvance(table, at + size1, format2, table, store), skipSecond);
|
|
106
137
|
};
|
|
138
|
+
const largest = (glyphs: ReadonlySet<number>): LargestKerning => {
|
|
139
|
+
const firsts = new Set<number>();
|
|
140
|
+
const seconds = new Set<number>();
|
|
141
|
+
for (const glyph of glyphs) {
|
|
142
|
+
if (coverageIndex(coverage, glyph) >= 0) firsts.add(classOf(classes1, glyph));
|
|
143
|
+
seconds.add(classOf(classes2, glyph));
|
|
144
|
+
}
|
|
145
|
+
let first = 0;
|
|
146
|
+
let second = 0;
|
|
147
|
+
for (const class1 of firsts) {
|
|
148
|
+
for (const class2 of seconds) {
|
|
149
|
+
if (class1 >= count1 || class2 >= count2) continue;
|
|
150
|
+
const at = 16 + (class1 * count2 + class2) * (size1 + size2);
|
|
151
|
+
first = Math.max(first, xAdvance(table, at, format1, table, store).value);
|
|
152
|
+
second = Math.max(second, xAdvance(table, at + size1, format2, table, store).value);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return { first, second };
|
|
156
|
+
};
|
|
157
|
+
return { pair, largest };
|
|
107
158
|
}
|
|
108
159
|
throw new Error(`the PairPos format ${format} is unknown`);
|
|
109
160
|
}
|
|
@@ -150,7 +201,7 @@ export function readGposKerning(gpos: Reader, script: string, store?: VariationS
|
|
|
150
201
|
}
|
|
151
202
|
return [...indices].sort((a, b) => a - b).flatMap((index) => {
|
|
152
203
|
const lookup = lookups.part(lookups.u16(2 + index * 2));
|
|
153
|
-
const subtables:
|
|
204
|
+
const subtables: PairSubtable[] = [];
|
|
154
205
|
const type = lookup.u16(0);
|
|
155
206
|
for (let s = 0; s < lookup.u16(4); s++) {
|
|
156
207
|
let subtable = lookup.part(lookup.u16(6 + s * 2));
|
|
@@ -176,11 +227,16 @@ export function readGposKerning(gpos: Reader, script: string, store?: VariationS
|
|
|
176
227
|
...(skip === undefined ? {} : { skip }),
|
|
177
228
|
pair(first: number, second: number): PairValue | undefined {
|
|
178
229
|
for (const subtable of subtables) {
|
|
179
|
-
const value = subtable(first, second);
|
|
230
|
+
const value = subtable.pair(first, second);
|
|
180
231
|
if (value !== undefined) return value;
|
|
181
232
|
}
|
|
182
233
|
return undefined;
|
|
183
234
|
},
|
|
235
|
+
// A pair gets the value of one subtable, so the largest value of a lookup is the largest value of its subtables
|
|
236
|
+
largest(glyphs: ReadonlySet<number>): LargestKerning {
|
|
237
|
+
const values = subtables.map((subtable) => subtable.largest(glyphs));
|
|
238
|
+
return { first: Math.max(...values.map((value) => value.first)), second: Math.max(...values.map((value) => value.second)) };
|
|
239
|
+
},
|
|
184
240
|
}];
|
|
185
241
|
});
|
|
186
242
|
}
|
|
@@ -213,5 +269,10 @@ export function readKernTable(kern: Reader): PairLookup[] {
|
|
|
213
269
|
const value = pairs.get(first * 0x10000 + second);
|
|
214
270
|
return value === undefined ? undefined : { first: value, second: 0, skipSecond: false };
|
|
215
271
|
},
|
|
272
|
+
largest(glyphs: ReadonlySet<number>): LargestKerning {
|
|
273
|
+
let first = 0;
|
|
274
|
+
for (const [key, value] of pairs) if (glyphs.has(Math.floor(key / 0x10000)) && glyphs.has(key % 0x10000)) first = Math.max(first, value);
|
|
275
|
+
return { first, second: 0 };
|
|
276
|
+
},
|
|
216
277
|
}];
|
|
217
278
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Reader } from "./binary.ts";
|
|
2
2
|
import { IGNORE_BASE, IGNORE_LIGATURES, IGNORE_MARKS, scriptTags } from "./kerning.ts";
|
|
3
|
-
import { classOf, coverageIndex } from "./tables.ts";
|
|
3
|
+
import { classOf, coverageGlyphs, coverageIndex } from "./tables.ts";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* The GSUB features that the default model applies: the features that HarfBuzz and Chromium apply to horizontal text
|
|
@@ -66,6 +66,16 @@ export interface GlyphInfo {
|
|
|
66
66
|
readonly codePoint: number;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
/** One substitution that a lookup can do, with no context. */
|
|
70
|
+
export interface SubstitutionRule {
|
|
71
|
+
/** The glyph that the rule replaces. The first glyph of a ligature is this glyph. */
|
|
72
|
+
readonly from: number;
|
|
73
|
+
/** The glyphs that replace it. A multiple substitution can give no glyph. */
|
|
74
|
+
readonly to: readonly number[];
|
|
75
|
+
/** The other glyphs of a ligature, in order. Another rule has no such glyphs. */
|
|
76
|
+
readonly components?: readonly number[];
|
|
77
|
+
}
|
|
78
|
+
|
|
69
79
|
/** A lookup of GSUB for one script and one set of features. */
|
|
70
80
|
export interface SubstitutionLookup {
|
|
71
81
|
/** The index of the lookup in the lookup list of GSUB. */
|
|
@@ -93,6 +103,12 @@ export interface SubstitutionLookup {
|
|
|
93
103
|
* `hb_ot_layout_lookup_would_substitute` with zero context tells it. A context rule with backtrack or lookahead does not apply.
|
|
94
104
|
*/
|
|
95
105
|
wouldApply(glyphs: readonly number[]): boolean;
|
|
106
|
+
/**
|
|
107
|
+
* This function gives the rules of the lookup, and the rules of each lookup that it nests in a context rule. Each lookup
|
|
108
|
+
* has one list, and the list of this lookup is the first. The lists do not tell a context, so each nested rule counts.
|
|
109
|
+
* An alternate substitution gives one rule for each alternate.
|
|
110
|
+
*/
|
|
111
|
+
rules(): readonly (readonly SubstitutionRule[])[];
|
|
96
112
|
}
|
|
97
113
|
|
|
98
114
|
/** One lookup of the lookup list, with the subtables of an extension lookup resolved. */
|
|
@@ -113,6 +129,71 @@ function records(table: Reader, at: number, count: number): SequenceRecord[] {
|
|
|
113
129
|
return Array.from({ length: count }, (_, k) => ({ sequenceIndex: table.u16(at + k * 4), lookupIndex: table.u16(at + k * 4 + 2) }));
|
|
114
130
|
}
|
|
115
131
|
|
|
132
|
+
/** This function gives each rule table of the rule sets at `at` of a context subtable of format 1 or 2. An offset 0 is an empty set. */
|
|
133
|
+
function ruleTables(t: Reader, at: number): Reader[] {
|
|
134
|
+
const tables: Reader[] = [];
|
|
135
|
+
for (let s = 0; s < t.u16(at); s++) {
|
|
136
|
+
const offset = t.u16(at + 2 + s * 2);
|
|
137
|
+
if (offset === 0) continue;
|
|
138
|
+
const set = t.part(offset);
|
|
139
|
+
for (let r = 0; r < set.u16(0); r++) tables.push(set.part(set.u16(2 + r * 2)));
|
|
140
|
+
}
|
|
141
|
+
return tables;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** This function gives the records of each rule of a context subtable (lookup type 5). */
|
|
145
|
+
function contextRecords(t: Reader): SequenceRecord[] {
|
|
146
|
+
const format = t.u16(0);
|
|
147
|
+
if (format === 3) return records(t, 6 + t.u16(2) * 2, t.u16(4));
|
|
148
|
+
return ruleTables(t, format === 2 ? 6 : 4).flatMap((rule) => records(rule, 4 + (rule.u16(0) - 1) * 2, rule.u16(2)));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** This function gives the records of each rule of a chained context subtable (lookup type 6). */
|
|
152
|
+
function chainRecords(t: Reader): SequenceRecord[] {
|
|
153
|
+
const format = t.u16(0);
|
|
154
|
+
if (format === 3) {
|
|
155
|
+
const inputAt = 4 + t.u16(2) * 2;
|
|
156
|
+
const aheadAt = inputAt + 2 + t.u16(inputAt) * 2;
|
|
157
|
+
const recordsAt = aheadAt + 2 + t.u16(aheadAt) * 2;
|
|
158
|
+
return records(t, recordsAt + 2, t.u16(recordsAt));
|
|
159
|
+
}
|
|
160
|
+
return ruleTables(t, format === 2 ? 10 : 4).flatMap((rule) => {
|
|
161
|
+
const inputAt = 2 + rule.u16(0) * 2;
|
|
162
|
+
const aheadAt = inputAt + 2 + Math.max(0, rule.u16(inputAt) - 1) * 2;
|
|
163
|
+
const recordsAt = aheadAt + 2 + rule.u16(aheadAt) * 2;
|
|
164
|
+
return records(rule, recordsAt + 2, rule.u16(recordsAt));
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** This function adds the rules of one subtable of a lookup type to `rules`, and each nested lookup to `nested`. */
|
|
169
|
+
function readRules(type: number, t: Reader, rules: SubstitutionRule[], nested: number[]): void {
|
|
170
|
+
if (type === 5 || type === 6) {
|
|
171
|
+
for (const record of type === 5 ? contextRecords(t) : chainRecords(t)) nested.push(record.lookupIndex);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (type < 1 || type > 8 || type === 7) return;
|
|
175
|
+
const format = t.u16(0);
|
|
176
|
+
for (const [from, index] of coverageGlyphs(t.part(t.u16(2)))) {
|
|
177
|
+
if (type === 1) rules.push({ from, to: [format === 1 ? (from + t.i16(4)) & 0xffff : t.u16(6 + index * 2)] });
|
|
178
|
+
else if (type === 2) {
|
|
179
|
+
const sequence = t.part(t.u16(6 + index * 2));
|
|
180
|
+
rules.push({ from, to: Array.from({ length: sequence.u16(0) }, (_, k) => sequence.u16(2 + k * 2)) });
|
|
181
|
+
} else if (type === 3) {
|
|
182
|
+
const set = t.part(t.u16(6 + index * 2));
|
|
183
|
+
for (let k = 0; k < set.u16(0); k++) rules.push({ from, to: [set.u16(2 + k * 2)] });
|
|
184
|
+
} else if (type === 4) {
|
|
185
|
+
const set = t.part(t.u16(6 + index * 2));
|
|
186
|
+
for (let l = 0; l < set.u16(0); l++) {
|
|
187
|
+
const ligature = set.part(set.u16(2 + l * 2));
|
|
188
|
+
rules.push({ from, to: [ligature.u16(0)], components: Array.from({ length: ligature.u16(2) - 1 }, (_, k) => ligature.u16(4 + k * 2)) });
|
|
189
|
+
}
|
|
190
|
+
} else {
|
|
191
|
+
const aheadAt = 6 + t.u16(4) * 2;
|
|
192
|
+
rules.push({ from, to: [t.u16(aheadAt + 2 + t.u16(aheadAt) * 2 + 2 + index * 2)] });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
116
197
|
/** The lookups of one GSUB table. It reads each lookup at its first use. */
|
|
117
198
|
class Gsub {
|
|
118
199
|
private readonly lookups: Reader;
|
|
@@ -392,6 +473,24 @@ class Gsub {
|
|
|
392
473
|
});
|
|
393
474
|
}
|
|
394
475
|
|
|
476
|
+
/** This function gives the rules of a lookup and of each lookup that it nests, as `rules` of `SubstitutionLookup` tells. */
|
|
477
|
+
rulesOf(index: number): SubstitutionRule[][] {
|
|
478
|
+
const lists: SubstitutionRule[][] = [];
|
|
479
|
+
const seen = new Set<number>();
|
|
480
|
+
const visit = (at: number): void => {
|
|
481
|
+
const lookup = this.lookup(at);
|
|
482
|
+
if (seen.has(at) || lookup === undefined) return;
|
|
483
|
+
seen.add(at);
|
|
484
|
+
const rules: SubstitutionRule[] = [];
|
|
485
|
+
const nested: number[] = [];
|
|
486
|
+
for (const subtable of lookup.subtables) readRules(lookup.type, subtable, rules, nested);
|
|
487
|
+
lists.push(rules);
|
|
488
|
+
nested.forEach(visit);
|
|
489
|
+
};
|
|
490
|
+
visit(index);
|
|
491
|
+
return lists;
|
|
492
|
+
}
|
|
493
|
+
|
|
395
494
|
/** This function applies one subtable at `i`. It gives the next position, or -1 when the subtable does not match. */
|
|
396
495
|
private subtable(lookup: Lookup, t: Reader, glyphs: number[], i: number, depth: number): number {
|
|
397
496
|
const glyph = glyphs[i]!;
|
|
@@ -580,6 +679,7 @@ export function readSubstitutions(gsub: Reader, script: string, features: readon
|
|
|
580
679
|
apply: (glyphs: number[], masks?: number[], mask?: number) => table.applyAll(index, glyphs, masks, mask),
|
|
581
680
|
applyInfos: (glyphs: number[], infos: GlyphInfo[], mask: number, perSyllable: boolean) => table.applyRun(index, glyphs, infos, mask, perSyllable),
|
|
582
681
|
wouldApply: (glyphs: readonly number[]) => table.wouldApply(index, glyphs),
|
|
682
|
+
rules: () => table.rulesOf(index),
|
|
583
683
|
});
|
|
584
684
|
}
|
|
585
685
|
return result;
|
package/src/fonts/tables.ts
CHANGED
|
@@ -130,6 +130,28 @@ export function coverageIndex(table: Reader, glyph: number): number {
|
|
|
130
130
|
throw new Error(`the Coverage format ${format} is unknown`);
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
/** This function gives each glyph of a Coverage table with its coverage index, in the order of the table. */
|
|
134
|
+
export function coverageGlyphs(table: Reader): (readonly [glyph: number, index: number])[] {
|
|
135
|
+
const format = table.u16(0);
|
|
136
|
+
const count = table.u16(2);
|
|
137
|
+
const glyphs: (readonly [number, number])[] = [];
|
|
138
|
+
if (format === 1) {
|
|
139
|
+
for (let k = 0; k < count; k++) glyphs.push([table.u16(4 + k * 2), k]);
|
|
140
|
+
return glyphs;
|
|
141
|
+
}
|
|
142
|
+
if (format === 2) {
|
|
143
|
+
for (let k = 0; k < count; k++) {
|
|
144
|
+
const at = 4 + k * 6;
|
|
145
|
+
const start = table.u16(at);
|
|
146
|
+
const end = table.u16(at + 2);
|
|
147
|
+
const index = table.u16(at + 4);
|
|
148
|
+
for (let glyph = start; glyph <= end; glyph++) glyphs.push([glyph, index + glyph - start]);
|
|
149
|
+
}
|
|
150
|
+
return glyphs;
|
|
151
|
+
}
|
|
152
|
+
throw new Error(`the Coverage format ${format} is unknown`);
|
|
153
|
+
}
|
|
154
|
+
|
|
133
155
|
/** The GDEF glyph classes: 1 base, 2 ligature, 3 mark, 4 component. */
|
|
134
156
|
export function readGlyphClasses(gdef: Reader | undefined): ((glyph: number) => number) {
|
|
135
157
|
if (gdef === undefined) return () => 0;
|
package/src/fonts.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* the width of a text as the text engine of a browser gives it. The host gives the bytes of the font files.
|
|
4
4
|
*/
|
|
5
5
|
export { measureWidth, readFont, type Font, type MeasureOptions, type ReadOptions } from "./fonts/font.ts";
|
|
6
|
+
export { widthBound, type WidthBound } from "./fonts/bound.ts";
|
|
6
7
|
export { engines, isEngineName, type EngineName, type EngineRules } from "./fonts/engines.ts";
|
|
7
8
|
export type { Decompress } from "./fonts/sfnt.ts";
|
|
8
9
|
export type { Axis, Instance } from "./fonts/variations.ts";
|