reamkit 1.34.1 → 1.35.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.
- package/README.md +2 -1
- package/dist/esm/core/converter/ream.d.ts +15 -0
- package/dist/esm/core/converter/ream.js +35 -2
- package/dist/esm/core/font/index.d.ts +1 -1
- package/dist/esm/core/font/index.js +2 -2
- package/dist/esm/core/font/mark-shaper.d.ts +27 -0
- package/dist/esm/core/font/mark-shaper.js +116 -0
- package/dist/esm/core/font/measure.d.ts +16 -0
- package/dist/esm/core/font/measure.js +34 -2
- package/dist/esm/core/fonts/index.d.ts +2 -2
- package/dist/esm/core/fonts/remote-fonts.d.ts +37 -3
- package/dist/esm/core/fonts/remote-fonts.js +58 -1
- package/dist/esm/core/fonts/scripts.d.ts +21 -1
- package/dist/esm/core/fonts/scripts.js +46 -7
- package/dist/esm/html/html-writer.js +7 -2
- package/dist/esm/layout/line-paint.d.ts +2 -2
- package/dist/esm/layout/line-paint.js +5 -5
- package/dist/esm/layout/page-doc.d.ts +6 -0
- package/dist/esm/layout/styled-layout.js +155 -25
- package/dist/esm/pdf/cid-font.d.ts +6 -0
- package/dist/esm/pdf/cid-font.js +3 -2
- package/dist/esm/pdf/styled-page-emitter.js +4 -3
- package/dist/esm/pdf-reader/glyph-names.js +331 -0
- package/dist/esm/pdf-reader/text.js +65 -1
- package/dist/esm/word/docx-to-pdf.js +45 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -30,7 +30,8 @@ Parse once into the format-neutral interlayer, convert to any target. The
|
|
|
30
30
|
format is sniffed from the bytes; no fonts to wire up — an open
|
|
31
31
|
metric-compatible substitute font (Arimo for sans, Tinos for serif, Cousine for
|
|
32
32
|
monospace, plus Carlito/Caladea for Calibri/Cambria — the same families LibreOffice
|
|
33
|
-
substitutes
|
|
33
|
+
substitutes, with Cambria's Cyrillic and Greek from Noto Serif) is fetched
|
|
34
|
+
automatically based on the document's referenced fonts,
|
|
34
35
|
with a Noto face for the Japanese, Korean, Chinese, Arabic, Hebrew, Thai or
|
|
35
36
|
Indic (Hindi, Bengali, Tamil and six more scripts) text the document holds:
|
|
36
37
|
|
|
@@ -195,6 +195,21 @@ export declare class Ream {
|
|
|
195
195
|
* @returns The font bytes and, for docx, optional per-family registries.
|
|
196
196
|
*/
|
|
197
197
|
private resolveFonts;
|
|
198
|
+
/**
|
|
199
|
+
* Fetch the family each twin borrows the letters it lacks from: Caladea,
|
|
200
|
+
* Cambria's twin, has no Cyrillic and no Greek, and without its companion a
|
|
201
|
+
* Cambria run set every Russian or Kazakh letter in whichever face was loaded
|
|
202
|
+
* first — a grotesque beside the roman around it.
|
|
203
|
+
*
|
|
204
|
+
* A companion that fails to arrive is a recorded loss, not a throw: its
|
|
205
|
+
* letters still fall to another face on hand.
|
|
206
|
+
*
|
|
207
|
+
* @param into The registry map the run resolver looks in.
|
|
208
|
+
* @param companions The companion families the document's text needs.
|
|
209
|
+
* @param fetchOpt The injectable `fetch`, when the caller gave one.
|
|
210
|
+
* @param losses Where a companion that could not be fetched records itself.
|
|
211
|
+
*/
|
|
212
|
+
private addCompanionFonts;
|
|
198
213
|
/**
|
|
199
214
|
* Fetch one face per writing system the document holds text in — the curated
|
|
200
215
|
* families are Latin, and Han, Kana, Hangul, Arabic, the scripts of India and
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { fetchFontSet, fetchScriptFont, resolveFamilyKey } from "../fonts/remote-fonts.js";
|
|
2
|
-
import { scriptsInFlow } from "../fonts/scripts.js";
|
|
2
|
+
import { companionsInFlow, scriptsInFlow } from "../fonts/scripts.js";
|
|
3
3
|
import { FontRegistry } from "../font/font-registry.js";
|
|
4
4
|
import { createFontMeasure } from "../font/measure.js";
|
|
5
5
|
import "../font/index.js";
|
|
@@ -287,7 +287,8 @@ var Ream = class Ream {
|
|
|
287
287
|
}
|
|
288
288
|
const fetchOpt = options.fontFetch ? { fetch: options.fontFetch } : {};
|
|
289
289
|
const keys = options.fontFamily ? /* @__PURE__ */ new Set([resolveFamilyKey(options.fontFamily)]) : familiesInFlow(flow);
|
|
290
|
-
|
|
290
|
+
const companions = companionsInFlow(flow, keys);
|
|
291
|
+
if (keys.size <= 1 && companions.size === 0 && scriptsInFlow(flow).scripts.size === 0) {
|
|
291
292
|
const family = keys.values().next().value;
|
|
292
293
|
return { fonts: await fetchFontSet({
|
|
293
294
|
...family ? { family } : {},
|
|
@@ -304,6 +305,7 @@ var Ream = class Ream {
|
|
|
304
305
|
registriesByFamily.set(key, FontRegistry.fromBytes(bytes));
|
|
305
306
|
if (key === "arimo" || !baseBytes) baseBytes = bytes;
|
|
306
307
|
}
|
|
308
|
+
await this.addCompanionFonts(registriesByFamily, companions, fetchOpt, losses);
|
|
307
309
|
await this.addScriptFonts(registriesByFamily, flow, options, losses);
|
|
308
310
|
return {
|
|
309
311
|
fonts: baseBytes,
|
|
@@ -311,6 +313,37 @@ var Ream = class Ream {
|
|
|
311
313
|
};
|
|
312
314
|
}
|
|
313
315
|
/**
|
|
316
|
+
* Fetch the family each twin borrows the letters it lacks from: Caladea,
|
|
317
|
+
* Cambria's twin, has no Cyrillic and no Greek, and without its companion a
|
|
318
|
+
* Cambria run set every Russian or Kazakh letter in whichever face was loaded
|
|
319
|
+
* first — a grotesque beside the roman around it.
|
|
320
|
+
*
|
|
321
|
+
* A companion that fails to arrive is a recorded loss, not a throw: its
|
|
322
|
+
* letters still fall to another face on hand.
|
|
323
|
+
*
|
|
324
|
+
* @param into The registry map the run resolver looks in.
|
|
325
|
+
* @param companions The companion families the document's text needs.
|
|
326
|
+
* @param fetchOpt The injectable `fetch`, when the caller gave one.
|
|
327
|
+
* @param losses Where a companion that could not be fetched records itself.
|
|
328
|
+
*/
|
|
329
|
+
async addCompanionFonts(into, companions, fetchOpt, losses) {
|
|
330
|
+
for (const family of companions) {
|
|
331
|
+
if (into.has(family)) continue;
|
|
332
|
+
try {
|
|
333
|
+
into.set(family, FontRegistry.fromBytes(await fetchFontSet({
|
|
334
|
+
family,
|
|
335
|
+
...fetchOpt
|
|
336
|
+
})));
|
|
337
|
+
} catch {
|
|
338
|
+
losses.push({
|
|
339
|
+
feature: "font",
|
|
340
|
+
detail: `the ${family} face could not be fetched; the letters it draws for a substitute fall to another face`,
|
|
341
|
+
severity: "substituted"
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
314
347
|
* Fetch one face per writing system the document holds text in — the curated
|
|
315
348
|
* families are Latin, and Han, Kana, Hangul, Arabic, the scripts of India and
|
|
316
349
|
* the geometric symbols are a notdef box in every one of them.
|
|
@@ -7,7 +7,7 @@ export { pickVariant } from './font-registry.js';
|
|
|
7
7
|
export { parseGposKerning, parseGsubLigatures, shapeText } from './opentype-layout.js';
|
|
8
8
|
export type { KerningMap, LigatureMap, ShapedRun } from './opentype-layout.js';
|
|
9
9
|
export { hasSubstitutable, lettersForLigature, substituteLetters } from './ligatures.js';
|
|
10
|
-
export { createFontMeasure, shapeGlyphs } from './measure.js';
|
|
10
|
+
export { createFontMeasure, shapeGlyphs, sharedGlyphCodes } from './measure.js';
|
|
11
11
|
export type { FontMeasure } from './measure.js';
|
|
12
12
|
export { buildTrueType, editableEmbedding, MAX_BUILT_GLYPHS } from './ttf-build.js';
|
|
13
13
|
export type { BuiltFace, BuiltGlyph, BuiltKernPair, BuiltLigature, GlyphSeg, } from './ttf-build.js';
|
|
@@ -3,6 +3,6 @@ import { parseTtf } from "./ttf-parser.js";
|
|
|
3
3
|
import { glyphClosure, subsetTtf } from "./ttf-subset.js";
|
|
4
4
|
import { FontRegistry, pickVariant } from "./font-registry.js";
|
|
5
5
|
import { hasSubstitutable, lettersForLigature, substituteLetters } from "./ligatures.js";
|
|
6
|
-
import { createFontMeasure, shapeGlyphs } from "./measure.js";
|
|
6
|
+
import { createFontMeasure, shapeGlyphs, sharedGlyphCodes } from "./measure.js";
|
|
7
7
|
import { MAX_BUILT_GLYPHS, buildTrueType, editableEmbedding } from "./ttf-build.js";
|
|
8
|
-
export { FontRegistry, MAX_BUILT_GLYPHS, buildTrueType, createFontMeasure, editableEmbedding, glyphClosure, hasSubstitutable, lettersForLigature, parseTtf, pickVariant, shapeGlyphs, subsetTtf, substituteLetters };
|
|
8
|
+
export { FontRegistry, MAX_BUILT_GLYPHS, buildTrueType, createFontMeasure, editableEmbedding, glyphClosure, hasSubstitutable, lettersForLigature, parseTtf, pickVariant, shapeGlyphs, sharedGlyphCodes, subsetTtf, substituteLetters };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { IndicRun } from './indic-shaper.js';
|
|
2
|
+
import { ParsedTtf } from './ttf-parser.js';
|
|
3
|
+
/**
|
|
4
|
+
* Whether `text` holds a combining mark of the blocks Latin, Greek and
|
|
5
|
+
* Cyrillic letters take — the text {@link shapeMarks} is for.
|
|
6
|
+
*
|
|
7
|
+
* @param text The text.
|
|
8
|
+
*/
|
|
9
|
+
export declare function hasCombiningMark(text: string): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Whether a code point is one of those marks.
|
|
12
|
+
*
|
|
13
|
+
* @param cp The code point.
|
|
14
|
+
*/
|
|
15
|
+
export declare function isCombiningMark(cp: number): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Shape a run whose letters carry combining marks, in one face: the face's
|
|
18
|
+
* substitutions, then its positioning, the marks set on their letters.
|
|
19
|
+
*
|
|
20
|
+
* @param parsed The face.
|
|
21
|
+
* @param text The text.
|
|
22
|
+
* @param kern Whether to apply the face's `kern` feature (the layout profile decides).
|
|
23
|
+
* @returns The glyphs and where each stands, or `undefined` when the text holds
|
|
24
|
+
* no such mark or the face has no positioning to set one with — the
|
|
25
|
+
* caller's own shaping applies then.
|
|
26
|
+
*/
|
|
27
|
+
export declare function shapeMarks(parsed: ParsedTtf, text: string, kern?: boolean): IndicRun | undefined;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { OtlFace, planLookups, position, substitute } from "./opentype-lookups.js";
|
|
2
|
+
//#region src/core/font/mark-shaper.ts
|
|
3
|
+
/**
|
|
4
|
+
* The combining marks a Latin, Greek or Cyrillic letter takes: the Combining
|
|
5
|
+
* Diacritical Marks and their Extended and Supplement blocks, the Cyrillic
|
|
6
|
+
* combining marks, the marks for symbols and the half marks.
|
|
7
|
+
*/
|
|
8
|
+
var MARK_RANGES = [
|
|
9
|
+
[768, 879],
|
|
10
|
+
[1155, 1161],
|
|
11
|
+
[6832, 6911],
|
|
12
|
+
[7616, 7679],
|
|
13
|
+
[8400, 8447],
|
|
14
|
+
[65056, 65071]
|
|
15
|
+
];
|
|
16
|
+
/** OpenType GDEF GlyphClassDef — the class a mark glyph is given. */
|
|
17
|
+
var MARK_CLASS = 3;
|
|
18
|
+
/**
|
|
19
|
+
* Whether `text` holds a combining mark of the blocks Latin, Greek and
|
|
20
|
+
* Cyrillic letters take — the text {@link shapeMarks} is for.
|
|
21
|
+
*
|
|
22
|
+
* @param text The text.
|
|
23
|
+
*/
|
|
24
|
+
function hasCombiningMark(text) {
|
|
25
|
+
for (let i = 0; i < text.length; i++) if (isCombiningMark(text.charCodeAt(i))) return true;
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Whether a code point is one of those marks.
|
|
30
|
+
*
|
|
31
|
+
* @param cp The code point.
|
|
32
|
+
*/
|
|
33
|
+
function isCombiningMark(cp) {
|
|
34
|
+
if (cp < 768) return false;
|
|
35
|
+
for (const [lo, hi] of MARK_RANGES) if (cp >= lo && cp <= hi) return true;
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
/** The script tags a face's layout is looked up by, for the letters of `cps`. */
|
|
39
|
+
function scriptTags(cps) {
|
|
40
|
+
for (const cp of cps) {
|
|
41
|
+
if (cp >= 1024 && cp <= 1327) return ["cyrl"];
|
|
42
|
+
if (cp >= 880 && cp <= 1023 || cp >= 7936 && cp <= 8191) return ["grek"];
|
|
43
|
+
if (cp >= 65 && cp <= 591 || cp >= 7680 && cp <= 7935) return ["latn"];
|
|
44
|
+
}
|
|
45
|
+
return ["latn"];
|
|
46
|
+
}
|
|
47
|
+
var feature = (tag) => ({
|
|
48
|
+
tag,
|
|
49
|
+
mask: 1
|
|
50
|
+
});
|
|
51
|
+
/**
|
|
52
|
+
* Shape a run whose letters carry combining marks, in one face: the face's
|
|
53
|
+
* substitutions, then its positioning, the marks set on their letters.
|
|
54
|
+
*
|
|
55
|
+
* @param parsed The face.
|
|
56
|
+
* @param text The text.
|
|
57
|
+
* @param kern Whether to apply the face's `kern` feature (the layout profile decides).
|
|
58
|
+
* @returns The glyphs and where each stands, or `undefined` when the text holds
|
|
59
|
+
* no such mark or the face has no positioning to set one with — the
|
|
60
|
+
* caller's own shaping applies then.
|
|
61
|
+
*/
|
|
62
|
+
function shapeMarks(parsed, text, kern = true) {
|
|
63
|
+
if (!hasCombiningMark(text)) return void 0;
|
|
64
|
+
const face = OtlFace.of(parsed);
|
|
65
|
+
if (!face.gpos) return void 0;
|
|
66
|
+
const cps = [...text].map((ch) => ch.codePointAt(0));
|
|
67
|
+
const tags = scriptTags(cps);
|
|
68
|
+
let base = 0;
|
|
69
|
+
const buf = cps.map((cp, i) => {
|
|
70
|
+
const mark = i > 0 && isCombiningMark(cp);
|
|
71
|
+
if (!mark) base = i;
|
|
72
|
+
const gid = parsed.glyphForCodepoint(cp);
|
|
73
|
+
const cls = face.glyphClass(gid) || (mark ? MARK_CLASS : 1);
|
|
74
|
+
return {
|
|
75
|
+
gid,
|
|
76
|
+
cluster: base,
|
|
77
|
+
mask: 1,
|
|
78
|
+
cls,
|
|
79
|
+
syllable: 0,
|
|
80
|
+
ligId: 0,
|
|
81
|
+
ligComp: 0,
|
|
82
|
+
sources: [i]
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
substitute(face, planLookups(face.gsub, tags, [
|
|
86
|
+
"ccmp",
|
|
87
|
+
"liga",
|
|
88
|
+
"clig"
|
|
89
|
+
].map(feature)), buf);
|
|
90
|
+
const gpos = planLookups(face.gpos, tags, [
|
|
91
|
+
...kern ? ["kern"] : [],
|
|
92
|
+
"mark",
|
|
93
|
+
"mkmk"
|
|
94
|
+
].map(feature));
|
|
95
|
+
const pos = position(face, gpos, buf);
|
|
96
|
+
const run = {
|
|
97
|
+
gids: [],
|
|
98
|
+
clusters: [],
|
|
99
|
+
sources: [],
|
|
100
|
+
advances: [],
|
|
101
|
+
xOffsets: [],
|
|
102
|
+
yOffsets: []
|
|
103
|
+
};
|
|
104
|
+
buf.forEach((g, i) => {
|
|
105
|
+
const p = pos[i];
|
|
106
|
+
run.gids.push(g.gid);
|
|
107
|
+
run.clusters.push(g.cluster);
|
|
108
|
+
run.sources.push(g.sources ?? [g.cluster]);
|
|
109
|
+
run.advances.push(g.cls === MARK_CLASS ? 0 : p.xAdvance);
|
|
110
|
+
run.xOffsets.push(p.xOffset);
|
|
111
|
+
run.yOffsets.push(p.yOffset);
|
|
112
|
+
});
|
|
113
|
+
return run;
|
|
114
|
+
}
|
|
115
|
+
//#endregion
|
|
116
|
+
export { hasCombiningMark, isCombiningMark, shapeMarks };
|
|
@@ -80,6 +80,22 @@ export declare function shapeGlyphs(parsed: ParsedTtf, text: string): {
|
|
|
80
80
|
readonly gids: ReadonlyArray<number>;
|
|
81
81
|
readonly glyphText?: ReadonlyMap<number, string>;
|
|
82
82
|
};
|
|
83
|
+
/**
|
|
84
|
+
* The characters a face's `cmap` sends to one glyph, when it sends more than
|
|
85
|
+
* one: a /ToUnicode can name only one of them, and which one is a question of
|
|
86
|
+
* what the document drew.
|
|
87
|
+
*
|
|
88
|
+
* Arimo, Tinos and Cousine draw the Latin schwa U+0259 and the Cyrillic one
|
|
89
|
+
* U+04D9 with the same glyph (U+018F and U+04D8 likewise), and a scan that
|
|
90
|
+
* keeps the first code reaching a glyph named it Latin: every "және" of a
|
|
91
|
+
* Kazakh document set in Arial read back as "жəне".
|
|
92
|
+
*
|
|
93
|
+
* @param parsed The face.
|
|
94
|
+
* @param gid The glyph.
|
|
95
|
+
* @returns The code points mapped to it, lowest first, or `undefined` when at
|
|
96
|
+
* most one is.
|
|
97
|
+
*/
|
|
98
|
+
export declare function sharedGlyphCodes(parsed: ParsedTtf, gid: number): ReadonlyArray<number> | undefined;
|
|
83
99
|
/**
|
|
84
100
|
* Build a {@link FontMeasure} over a parsed font.
|
|
85
101
|
*
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { shapeText } from "./opentype-layout.js";
|
|
2
2
|
import { hasIndic, shapeIndic } from "./indic-shaper.js";
|
|
3
|
+
import { hasCombiningMark, shapeMarks } from "./mark-shaper.js";
|
|
3
4
|
//#region src/core/font/measure.ts
|
|
4
5
|
var EMPTY_KERNING = /* @__PURE__ */ new Map();
|
|
5
6
|
/** The most shaped words a face keeps; past it the cache starts over. */
|
|
@@ -15,7 +16,7 @@ var SHAPED_CACHE = 4096;
|
|
|
15
16
|
* @returns The glyph ids, and the text of each glyph no character reaches.
|
|
16
17
|
*/
|
|
17
18
|
function shapeGlyphs(parsed, text) {
|
|
18
|
-
const run = hasIndic(text) ? shapeIndic(parsed, text) : void 0;
|
|
19
|
+
const run = hasIndic(text) ? shapeIndic(parsed, text) : hasCombiningMark(text) ? shapeMarks(parsed, text) : void 0;
|
|
19
20
|
if (!run) return { gids: shapeText(text, parsed.glyphForCodepoint, parsed.advanceWidths, parsed.ligatures, parsed.kerning, parsed.joiningForms).gids };
|
|
20
21
|
return {
|
|
21
22
|
gids: run.gids,
|
|
@@ -41,6 +42,25 @@ function namesOf(parsed) {
|
|
|
41
42
|
}
|
|
42
43
|
return names;
|
|
43
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* The characters a face's `cmap` sends to one glyph, when it sends more than
|
|
47
|
+
* one: a /ToUnicode can name only one of them, and which one is a question of
|
|
48
|
+
* what the document drew.
|
|
49
|
+
*
|
|
50
|
+
* Arimo, Tinos and Cousine draw the Latin schwa U+0259 and the Cyrillic one
|
|
51
|
+
* U+04D9 with the same glyph (U+018F and U+04D8 likewise), and a scan that
|
|
52
|
+
* keeps the first code reaching a glyph named it Latin: every "және" of a
|
|
53
|
+
* Kazakh document set in Arial read back as "жəне".
|
|
54
|
+
*
|
|
55
|
+
* @param parsed The face.
|
|
56
|
+
* @param gid The glyph.
|
|
57
|
+
* @returns The code points mapped to it, lowest first, or `undefined` when at
|
|
58
|
+
* most one is.
|
|
59
|
+
*/
|
|
60
|
+
function sharedGlyphCodes(parsed, gid) {
|
|
61
|
+
const codes = namesOf(parsed).get(gid);
|
|
62
|
+
return codes !== void 0 && codes.length > 1 ? codes : void 0;
|
|
63
|
+
}
|
|
44
64
|
/** A character's canonical parts: क़ is क and the nukta, ো is ে and া. */
|
|
45
65
|
var partsOf = (cp) => [...String.fromCodePoint(cp).normalize("NFD")].map((ch) => ch.codePointAt(0));
|
|
46
66
|
var IGNORABLE_RE = /\p{Default_Ignorable_Code_Point}/u;
|
|
@@ -184,6 +204,18 @@ function createFontMeasure(parsed, kern = true) {
|
|
|
184
204
|
advances: run.advances,
|
|
185
205
|
indic: run
|
|
186
206
|
};
|
|
207
|
+
} else if (hasCombiningMark(text)) {
|
|
208
|
+
let run = indicRuns.get(text);
|
|
209
|
+
if (run === void 0) {
|
|
210
|
+
if (indicRuns.size >= SHAPED_CACHE) indicRuns.clear();
|
|
211
|
+
run = shapeMarks(parsed, text, kern) ?? null;
|
|
212
|
+
indicRuns.set(text, run);
|
|
213
|
+
}
|
|
214
|
+
if (run) return {
|
|
215
|
+
gids: run.gids,
|
|
216
|
+
advances: run.advances,
|
|
217
|
+
indic: run
|
|
218
|
+
};
|
|
187
219
|
}
|
|
188
220
|
return shapeText(text, parsed.glyphForCodepoint, parsed.advanceWidths, parsed.ligatures, kerning, parsed.joiningForms);
|
|
189
221
|
};
|
|
@@ -343,4 +375,4 @@ function createFontMeasure(parsed, kern = true) {
|
|
|
343
375
|
};
|
|
344
376
|
}
|
|
345
377
|
//#endregion
|
|
346
|
-
export { createFontMeasure, shapeGlyphs };
|
|
378
|
+
export { createFontMeasure, shapeGlyphs, sharedGlyphCodes };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { fetchFontSet, fetchScriptFont, isScriptKey, knowsFamily, resolveFamilyKey, resolveFamilyStyle, clearFontCache, } from './remote-fonts.js';
|
|
1
|
+
export { companionOf, companionScale, fetchFontSet, fetchScriptFont, isScriptKey, knowsFamily, resolveFamilyKey, resolveFamilyStyle, clearFontCache, } from './remote-fonts.js';
|
|
2
2
|
export { scriptForCodepoint, scriptsInFlow } from './scripts.js';
|
|
3
|
-
export type { FamilyKey, FamilyStyle, FetchFontSetOptions, FetchLike, ScriptKey, SubstituteKey, } from './remote-fonts.js';
|
|
3
|
+
export type { Companion, FamilyKey, FamilyStyle, FetchFontSetOptions, FetchLike, ScriptKey, SubstituteKey, } from './remote-fonts.js';
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import { FontBytesByVariant } from '../font/index.js';
|
|
2
|
-
/**
|
|
3
|
-
|
|
1
|
+
import { FontBytesByVariant, FontVariant } from '../font/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* A curated open substitute family (the Croscore + Carlito/Caladea set, like
|
|
4
|
+
* LibreOffice), or the family a twin borrows the letters it lacks from (see
|
|
5
|
+
* {@link companionOf}).
|
|
6
|
+
*/
|
|
7
|
+
export type FamilyKey = 'arimo' | 'tinos' | 'cousine' | 'carlito' | 'caladea' | 'notoserif';
|
|
4
8
|
/**
|
|
5
9
|
* A WRITING SYSTEM the curated five cannot draw at all. They are Latin families
|
|
6
10
|
* — Greek, Cyrillic and Hebrew ride along, everything else is a notdef box —
|
|
@@ -26,6 +30,36 @@ export declare function isScriptKey(key: SubstituteKey): key is ScriptKey;
|
|
|
26
30
|
* @returns The regular face, or `undefined` when the download fails.
|
|
27
31
|
*/
|
|
28
32
|
export declare function fetchScriptFont(script: ScriptKey, fetchImpl?: FetchLike): Promise<FontBytesByVariant | undefined>;
|
|
33
|
+
/**
|
|
34
|
+
* The letters a metric twin does not draw, and the family it borrows them
|
|
35
|
+
* from — squeezed, per writing system and face, to the width of the face the
|
|
36
|
+
* twin stands in for.
|
|
37
|
+
*/
|
|
38
|
+
export interface Companion {
|
|
39
|
+
/** The family that draws them. */
|
|
40
|
+
readonly family: FamilyKey;
|
|
41
|
+
/**
|
|
42
|
+
* The blocks it is borrowed for (inclusive ranges), each with the factor
|
|
43
|
+
* its advances are multiplied by in each face.
|
|
44
|
+
*/
|
|
45
|
+
readonly ranges: ReadonlyArray<readonly [number, number, Readonly<Record<FontVariant, number>>]>;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The family a substitute borrows the letters it lacks from, if it borrows any.
|
|
49
|
+
*
|
|
50
|
+
* @param key The substitute.
|
|
51
|
+
* @returns Its companion, or `undefined`.
|
|
52
|
+
*/
|
|
53
|
+
export declare function companionOf(key: string): Companion | undefined;
|
|
54
|
+
/**
|
|
55
|
+
* How much a companion squeezes a character it draws in a face.
|
|
56
|
+
*
|
|
57
|
+
* @param companion The companion.
|
|
58
|
+
* @param cp The code point.
|
|
59
|
+
* @param variant The face it is drawn in.
|
|
60
|
+
* @returns The factor, or `undefined` when the companion is not borrowed for it.
|
|
61
|
+
*/
|
|
62
|
+
export declare function companionScale(companion: Companion, cp: number, variant: FontVariant): number | undefined;
|
|
29
63
|
/** A family name, read: which substitute it maps to and what it says about the face. */
|
|
30
64
|
export interface FamilyStyle {
|
|
31
65
|
readonly key: FamilyKey;
|
|
@@ -109,8 +109,65 @@ var FAMILIES = {
|
|
|
109
109
|
caladea: {
|
|
110
110
|
pkg: "caladea",
|
|
111
111
|
file: "Caladea"
|
|
112
|
+
},
|
|
113
|
+
notoserif: {
|
|
114
|
+
pkg: "noto-serif",
|
|
115
|
+
file: "NotoSerif",
|
|
116
|
+
nested: true
|
|
112
117
|
}
|
|
113
118
|
};
|
|
119
|
+
var CAMBRIA_CYRILLIC = {
|
|
120
|
+
regular: .894,
|
|
121
|
+
bold: .918,
|
|
122
|
+
italic: .9,
|
|
123
|
+
boldItalic: .915
|
|
124
|
+
};
|
|
125
|
+
var CAMBRIA_GREEK = {
|
|
126
|
+
regular: .932,
|
|
127
|
+
bold: .953,
|
|
128
|
+
italic: .909,
|
|
129
|
+
boldItalic: .938
|
|
130
|
+
};
|
|
131
|
+
var COMPANIONS = { caladea: {
|
|
132
|
+
family: "notoserif",
|
|
133
|
+
ranges: [
|
|
134
|
+
[
|
|
135
|
+
880,
|
|
136
|
+
1023,
|
|
137
|
+
CAMBRIA_GREEK
|
|
138
|
+
],
|
|
139
|
+
[
|
|
140
|
+
1024,
|
|
141
|
+
1327,
|
|
142
|
+
CAMBRIA_CYRILLIC
|
|
143
|
+
],
|
|
144
|
+
[
|
|
145
|
+
7936,
|
|
146
|
+
8191,
|
|
147
|
+
CAMBRIA_GREEK
|
|
148
|
+
]
|
|
149
|
+
]
|
|
150
|
+
} };
|
|
151
|
+
/**
|
|
152
|
+
* The family a substitute borrows the letters it lacks from, if it borrows any.
|
|
153
|
+
*
|
|
154
|
+
* @param key The substitute.
|
|
155
|
+
* @returns Its companion, or `undefined`.
|
|
156
|
+
*/
|
|
157
|
+
function companionOf(key) {
|
|
158
|
+
return Object.hasOwn(COMPANIONS, key) ? COMPANIONS[key] : void 0;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* How much a companion squeezes a character it draws in a face.
|
|
162
|
+
*
|
|
163
|
+
* @param companion The companion.
|
|
164
|
+
* @param cp The code point.
|
|
165
|
+
* @param variant The face it is drawn in.
|
|
166
|
+
* @returns The factor, or `undefined` when the companion is not borrowed for it.
|
|
167
|
+
*/
|
|
168
|
+
function companionScale(companion, cp, variant) {
|
|
169
|
+
for (const [lo, hi, scale] of companion.ranges) if (cp >= lo && cp <= hi) return scale[variant];
|
|
170
|
+
}
|
|
114
171
|
var EXACT = {
|
|
115
172
|
calibri: "carlito",
|
|
116
173
|
"calibri light": "carlito",
|
|
@@ -405,4 +462,4 @@ async function fetchFontSet(options = {}) {
|
|
|
405
462
|
};
|
|
406
463
|
}
|
|
407
464
|
//#endregion
|
|
408
|
-
export { fetchFontSet, fetchScriptFont, knowsFamily, resolveFamilyKey, resolveFamilyStyle };
|
|
465
|
+
export { companionOf, companionScale, fetchFontSet, fetchScriptFont, knowsFamily, resolveFamilyKey, resolveFamilyStyle };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { FlowDoc } from '../ir/flow.js';
|
|
2
|
-
import { ScriptKey } from './remote-fonts.js';
|
|
2
|
+
import { FamilyKey, ScriptKey } from './remote-fonts.js';
|
|
3
3
|
/**
|
|
4
4
|
* The writing system a character needs a face for, or `undefined` when a Latin
|
|
5
5
|
* family covers it.
|
|
@@ -43,3 +43,23 @@ export declare function scriptsInFlow(flow: FlowDoc): {
|
|
|
43
43
|
scripts: Set<ScriptKey>;
|
|
44
44
|
hanFace: ScriptKey;
|
|
45
45
|
};
|
|
46
|
+
/**
|
|
47
|
+
* Hand every run's text in a document to `read`: the body, the bands, the
|
|
48
|
+
* notes and the comments, the cells of a table, the text of every shape and
|
|
49
|
+
* of the drawings over a spreadsheet grid.
|
|
50
|
+
*
|
|
51
|
+
* @param flow The parsed document.
|
|
52
|
+
* @param read Called with each run's text.
|
|
53
|
+
*/
|
|
54
|
+
export declare function forEachTextInFlow(flow: FlowDoc, read: (text: string) => void): void;
|
|
55
|
+
/**
|
|
56
|
+
* The companions a document's families need: those whose blocks its text
|
|
57
|
+
* holds a character of (see `companionOf`). A Cambria document in Russian
|
|
58
|
+
* needs Noto Serif for its letters; one in English does not, and is not sent
|
|
59
|
+
* for it.
|
|
60
|
+
*
|
|
61
|
+
* @param flow The parsed document.
|
|
62
|
+
* @param families The substitute families it names.
|
|
63
|
+
* @returns The companion families to fetch.
|
|
64
|
+
*/
|
|
65
|
+
export declare function companionsInFlow(flow: FlowDoc, families: Iterable<FamilyKey>): Set<FamilyKey>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { companionOf } from "./remote-fonts.js";
|
|
1
2
|
//#region src/core/fonts/scripts.ts
|
|
2
3
|
var RANGES = [
|
|
3
4
|
[
|
|
@@ -252,6 +253,23 @@ function scriptsInFlow(flow) {
|
|
|
252
253
|
scripts.add(key);
|
|
253
254
|
}
|
|
254
255
|
};
|
|
256
|
+
forEachTextInFlow(flow, read);
|
|
257
|
+
const hanFace = scripts.has("jp") ? "jp" : scripts.has("kr") ? "kr" : "sc";
|
|
258
|
+
if (marks.has("han")) scripts.add(hanFace);
|
|
259
|
+
return {
|
|
260
|
+
scripts,
|
|
261
|
+
hanFace
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Hand every run's text in a document to `read`: the body, the bands, the
|
|
266
|
+
* notes and the comments, the cells of a table, the text of every shape and
|
|
267
|
+
* of the drawings over a spreadsheet grid.
|
|
268
|
+
*
|
|
269
|
+
* @param flow The parsed document.
|
|
270
|
+
* @param read Called with each run's text.
|
|
271
|
+
*/
|
|
272
|
+
function forEachTextInFlow(flow, read) {
|
|
255
273
|
const shapeText = (shape) => {
|
|
256
274
|
if (shape.text) visit(shape.text.content);
|
|
257
275
|
for (const child of shape.children ?? []) shapeText(child.shape);
|
|
@@ -268,12 +286,33 @@ function scriptsInFlow(flow) {
|
|
|
268
286
|
for (const note of flow.footnotes?.values() ?? []) visit(note);
|
|
269
287
|
for (const note of flow.endnotes?.values() ?? []) visit(note);
|
|
270
288
|
for (const comment of flow.comments?.values() ?? []) visit(comment.content);
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* The companions a document's families need: those whose blocks its text
|
|
292
|
+
* holds a character of (see `companionOf`). A Cambria document in Russian
|
|
293
|
+
* needs Noto Serif for its letters; one in English does not, and is not sent
|
|
294
|
+
* for it.
|
|
295
|
+
*
|
|
296
|
+
* @param flow The parsed document.
|
|
297
|
+
* @param families The substitute families it names.
|
|
298
|
+
* @returns The companion families to fetch.
|
|
299
|
+
*/
|
|
300
|
+
function companionsInFlow(flow, families) {
|
|
301
|
+
const wanted = [...families].flatMap((key) => {
|
|
302
|
+
const companion = companionOf(key);
|
|
303
|
+
return companion ? [companion] : [];
|
|
304
|
+
});
|
|
305
|
+
const out = /* @__PURE__ */ new Set();
|
|
306
|
+
if (wanted.length === 0) return out;
|
|
307
|
+
forEachTextInFlow(flow, (text) => {
|
|
308
|
+
if (out.size === wanted.length) return;
|
|
309
|
+
for (const ch of text) {
|
|
310
|
+
const cp = ch.codePointAt(0) ?? 0;
|
|
311
|
+
if (cp < 880) continue;
|
|
312
|
+
for (const c of wanted) if (c.ranges.some(([lo, hi]) => cp >= lo && cp <= hi)) out.add(c.family);
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
return out;
|
|
277
316
|
}
|
|
278
317
|
//#endregion
|
|
279
|
-
export { fontSlotOf, scriptForCodepoint, scriptsInFlow };
|
|
318
|
+
export { companionsInFlow, fontSlotOf, forEachTextInFlow, scriptForCodepoint, scriptsInFlow };
|
|
@@ -528,10 +528,15 @@ function fontStack(family) {
|
|
|
528
528
|
/** What follows a family in its stack, by the open substitute it maps to. */
|
|
529
529
|
var FALLBACKS = {
|
|
530
530
|
carlito: ["Carlito", "sans-serif"],
|
|
531
|
-
caladea: [
|
|
531
|
+
caladea: [
|
|
532
|
+
"Caladea",
|
|
533
|
+
"Noto Serif",
|
|
534
|
+
"serif"
|
|
535
|
+
],
|
|
532
536
|
arimo: ["sans-serif"],
|
|
533
537
|
tinos: ["serif"],
|
|
534
|
-
cousine: ["monospace"]
|
|
538
|
+
cousine: ["monospace"],
|
|
539
|
+
notoserif: ["Noto Serif", "serif"]
|
|
535
540
|
};
|
|
536
541
|
function decorationStyle(u) {
|
|
537
542
|
switch (u) {
|
|
@@ -19,8 +19,8 @@ export declare const BORDER_DASHES: ReadonlyMap<BorderStyle, ReadonlyArray<numbe
|
|
|
19
19
|
export declare function lineVisualOrder(line: Line): Array<number>;
|
|
20
20
|
/**
|
|
21
21
|
* The extra width each space of a justified line takes — 0 for a line that is
|
|
22
|
-
* not justified
|
|
23
|
-
*
|
|
22
|
+
* not justified. The last line of a paragraph stays flush by convention: it is
|
|
23
|
+
* never stretched, only pulled in where the breaker packed it past the measure.
|
|
24
24
|
*
|
|
25
25
|
* @param line The laid-out line.
|
|
26
26
|
* @returns Points added to (or, squeezed, taken from) each space token.
|
|
@@ -41,8 +41,8 @@ function lineVisualOrder(line) {
|
|
|
41
41
|
}
|
|
42
42
|
/**
|
|
43
43
|
* The extra width each space of a justified line takes — 0 for a line that is
|
|
44
|
-
* not justified
|
|
45
|
-
*
|
|
44
|
+
* not justified. The last line of a paragraph stays flush by convention: it is
|
|
45
|
+
* never stretched, only pulled in where the breaker packed it past the measure.
|
|
46
46
|
*
|
|
47
47
|
* @param line The laid-out line.
|
|
48
48
|
* @returns Points added to (or, squeezed, taken from) each space token.
|
|
@@ -50,8 +50,7 @@ function lineVisualOrder(line) {
|
|
|
50
50
|
function computeJustifyExtra(line) {
|
|
51
51
|
const alignment = line.resolved.alignment;
|
|
52
52
|
if (alignment !== "both" && alignment !== "distribute") return 0;
|
|
53
|
-
|
|
54
|
-
if (line.isLastInParagraph && alignment !== "distribute") return 0;
|
|
53
|
+
const flush = line.noJustify === true || line.isLastInParagraph && alignment !== "distribute";
|
|
55
54
|
let last = line.tokens.length - 1;
|
|
56
55
|
while (last >= 0 && line.tokens[last].kind === "text" && line.tokens[last].isSpace) last--;
|
|
57
56
|
let spaces = 0;
|
|
@@ -66,7 +65,8 @@ function computeJustifyExtra(line) {
|
|
|
66
65
|
}
|
|
67
66
|
if (spaces === 0) return 0;
|
|
68
67
|
const extra = (line.availableWidthPt - contentWidthPt) / spaces;
|
|
69
|
-
|
|
68
|
+
if (extra < 0) return Math.max(extra, -narrowest * GLUE_SHRINK_RATIO);
|
|
69
|
+
return flush ? 0 : extra;
|
|
70
70
|
}
|
|
71
71
|
/**
|
|
72
72
|
* Whether a line is ONE text matrix and a run of glyphs: nothing in it placed
|
|
@@ -21,6 +21,12 @@ export interface FontResource {
|
|
|
21
21
|
* a reph, a vowel sign's contextual form — for the PDF's /ToUnicode.
|
|
22
22
|
*/
|
|
23
23
|
readonly glyphText?: ReadonlyMap<number, string>;
|
|
24
|
+
/**
|
|
25
|
+
* For each collected glyph several characters share in the face's `cmap`,
|
|
26
|
+
* the one the document drew it for most often — what the PDF's /ToUnicode
|
|
27
|
+
* names it (see `sharedGlyphCodes`).
|
|
28
|
+
*/
|
|
29
|
+
readonly glyphChars?: ReadonlyMap<number, number>;
|
|
24
30
|
}
|
|
25
31
|
/**
|
|
26
32
|
* The face a run asked for and the registry could not give — what the page has
|