@ai-matrx/print 0.8.1 → 0.8.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/CHANGELOG.md +10 -0
- package/dist/document.cjs +56 -1
- package/dist/document.cjs.map +1 -1
- package/dist/document.d.cts +16 -2
- package/dist/document.d.ts +16 -2
- package/dist/document.js +56 -1
- package/dist/document.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.2
|
|
4
|
+
|
|
5
|
+
- **PDF font fallback covers Japanese / Chinese / Korean.** Characters the
|
|
6
|
+
body, emoji and symbol faces cannot draw now fall back to Noto Sans JP
|
|
7
|
+
(SIL OFL, fontsource) — loading only the unicode-range subsets the document
|
|
8
|
+
uses (`fonts.cjk` loader; default jsDelivr). Found on a real conversation
|
|
9
|
+
whose citations carried Japanese source titles. A glyph is announced only
|
|
10
|
+
when no face in the chain can draw it.
|
|
11
|
+
- Consumer action: none — refresh to latest.
|
|
12
|
+
|
|
3
13
|
## 0.8.1
|
|
4
14
|
|
|
5
15
|
- **Fixed (Word):** exporting DOCX laid out a whole PDF to number the table of
|
package/dist/document.cjs
CHANGED
|
@@ -35,12 +35,14 @@ __export(document_exports, {
|
|
|
35
35
|
CitationEngine: () => CitationEngine,
|
|
36
36
|
DEFAULT_FOOTER: () => DEFAULT_FOOTER,
|
|
37
37
|
DOCUMENT_FORMATS: () => DOCUMENT_FORMATS,
|
|
38
|
+
NOTO_CJK_BASE: () => NOTO_CJK_BASE,
|
|
38
39
|
NOTO_EMOJI_URL: () => NOTO_EMOJI_URL,
|
|
39
40
|
NOTO_SYMBOLS_URL: () => NOTO_SYMBOLS_URL,
|
|
40
41
|
PAGE_SIZES: () => PAGE_SIZES,
|
|
41
42
|
buildPdfDefinition: () => buildPdfDefinition,
|
|
42
43
|
countUnplacedWords: () => countUnplacedWords,
|
|
43
44
|
cslStyleId: () => cslStyleId,
|
|
45
|
+
defaultCjkFontLoader: () => defaultCjkFontLoader,
|
|
44
46
|
defaultCslFetcher: () => defaultCslFetcher,
|
|
45
47
|
defaultEmojiFontLoader: () => defaultEmojiFontLoader,
|
|
46
48
|
defaultImageLoader: () => defaultImageLoader,
|
|
@@ -63,6 +65,7 @@ __export(document_exports, {
|
|
|
63
65
|
parseLength: () => parseLength,
|
|
64
66
|
parseLocator: () => parseLocator,
|
|
65
67
|
parseMathML: () => parseMathML,
|
|
68
|
+
parseUnicodeRangeCss: () => parseUnicodeRangeCss,
|
|
66
69
|
plainText: () => plainText,
|
|
67
70
|
prepareDocument: () => prepareDocument,
|
|
68
71
|
renderBlocksHtml: () => renderBlocksHtml,
|
|
@@ -4533,12 +4536,13 @@ async function loadTypography(tree, loaders = {}) {
|
|
|
4533
4536
|
const base = await loadFaces(vfs, s.fontSize);
|
|
4534
4537
|
let needEmoji = false;
|
|
4535
4538
|
let needSymbols = false;
|
|
4539
|
+
const cjk = /* @__PURE__ */ new Set();
|
|
4536
4540
|
for (const ch of documentText(tree)) {
|
|
4537
4541
|
const cp = ch.codePointAt(0);
|
|
4538
4542
|
if (cp < 32 || cp === 8205 || cp === 65039 || base.body(cp)) continue;
|
|
4539
4543
|
if (EMOJI_CP.test(ch)) needEmoji = true;
|
|
4544
|
+
else if (isCjk(cp)) cjk.add(cp);
|
|
4540
4545
|
else needSymbols = true;
|
|
4541
|
-
if (needEmoji && needSymbols) break;
|
|
4542
4546
|
}
|
|
4543
4547
|
const fallbacks = [];
|
|
4544
4548
|
if (needEmoji) {
|
|
@@ -4549,6 +4553,26 @@ async function loadTypography(tree, loaders = {}) {
|
|
|
4549
4553
|
const b = await registerFace("NotoSymbols", "NotoSansMath.woff", loaders.symbols ?? defaultSymbolFontLoader, tree, "symbols");
|
|
4550
4554
|
if (b) fallbacks.push({ font: "NotoSymbols", bytes: b });
|
|
4551
4555
|
}
|
|
4556
|
+
if (cjk.size) {
|
|
4557
|
+
let subsets = [];
|
|
4558
|
+
try {
|
|
4559
|
+
subsets = await (loaders.cjk ?? defaultCjkFontLoader)([...cjk]);
|
|
4560
|
+
} catch {
|
|
4561
|
+
subsets = [];
|
|
4562
|
+
}
|
|
4563
|
+
for (const sub of subsets) {
|
|
4564
|
+
const font = `NotoCJK_${sub.name.replace(/[^\w]/g, "_")}`;
|
|
4565
|
+
const b = await registerFace(font, `${sub.name}.woff`, async () => sub.bytes, tree, "Japanese / Chinese");
|
|
4566
|
+
if (b) fallbacks.push({ font, bytes: b });
|
|
4567
|
+
}
|
|
4568
|
+
if (!subsets.length) {
|
|
4569
|
+
tree.notices.push({
|
|
4570
|
+
code: "glyph-substituted",
|
|
4571
|
+
message: "The Japanese / Chinese font could not be loaded, so those characters were left out of the PDF (Word, EPUB and HTML keep them).",
|
|
4572
|
+
remedy: "Check the internet connection (the font comes from cdn.jsdelivr.net) and export again."
|
|
4573
|
+
});
|
|
4574
|
+
}
|
|
4575
|
+
}
|
|
4552
4576
|
const faces = fallbacks.length ? await loadFaces(vfs, s.fontSize, fallbacks) : base;
|
|
4553
4577
|
if (!s.hyphenate) return { faces };
|
|
4554
4578
|
const split = await loadHyphenator(s.lang);
|
|
@@ -4571,6 +4595,37 @@ var urlLoader = (url) => async () => {
|
|
|
4571
4595
|
};
|
|
4572
4596
|
var defaultEmojiFontLoader = urlLoader(NOTO_EMOJI_URL);
|
|
4573
4597
|
var defaultSymbolFontLoader = urlLoader(NOTO_SYMBOLS_URL);
|
|
4598
|
+
var NOTO_CJK_BASE = "https://cdn.jsdelivr.net/npm/@fontsource/noto-sans-jp@5.3.0";
|
|
4599
|
+
function parseUnicodeRangeCss(css) {
|
|
4600
|
+
const out = [];
|
|
4601
|
+
for (const block3 of css.split("@font-face").slice(1)) {
|
|
4602
|
+
const file = /url\(\.\/files\/([\w-]+)\.woff\)/.exec(block3)?.[1];
|
|
4603
|
+
const range = /unicode-range:\s*([^;]+);/.exec(block3)?.[1];
|
|
4604
|
+
if (!file || !range) continue;
|
|
4605
|
+
const ranges = range.split(",").map((r) => {
|
|
4606
|
+
const [a, b] = r.trim().replace(/^U\+/i, "").split("-");
|
|
4607
|
+
const lo = parseInt(a, 16);
|
|
4608
|
+
return [lo, b ? parseInt(b, 16) : lo];
|
|
4609
|
+
});
|
|
4610
|
+
out.push({ file, ranges });
|
|
4611
|
+
}
|
|
4612
|
+
return out;
|
|
4613
|
+
}
|
|
4614
|
+
var defaultCjkFontLoader = async (cps) => {
|
|
4615
|
+
if (typeof fetch !== "function" || cps.length === 0) return [];
|
|
4616
|
+
const res = await fetch(`${NOTO_CJK_BASE}/400.css`);
|
|
4617
|
+
if (!res.ok) return [];
|
|
4618
|
+
const faces = parseUnicodeRangeCss(await res.text()).filter((f) => cps.some((cp) => f.ranges.some(([a, b]) => cp >= a && cp <= b)));
|
|
4619
|
+
const out = [];
|
|
4620
|
+
await Promise.all(
|
|
4621
|
+
faces.map(async (f) => {
|
|
4622
|
+
const r = await fetch(`${NOTO_CJK_BASE}/files/${f.file}.woff`);
|
|
4623
|
+
if (r.ok) out.push({ name: f.file, bytes: new Uint8Array(await r.arrayBuffer()) });
|
|
4624
|
+
})
|
|
4625
|
+
);
|
|
4626
|
+
return out;
|
|
4627
|
+
};
|
|
4628
|
+
var isCjk = (cp) => cp >= 11904 && cp <= 40959 || cp >= 44032 && cp <= 55215 || cp >= 63744 && cp <= 64255 || cp >= 65072 && cp <= 65103 || cp >= 65280 && cp <= 65519 || cp >= 131072 && cp <= 201551;
|
|
4574
4629
|
var EMOJI_CP = /\p{Extended_Pictographic}|\p{Regional_Indicator}|\u20E3/u;
|
|
4575
4630
|
function documentText(tree) {
|
|
4576
4631
|
const s = tree.settings;
|