postext-pdf 1.28.6 → 1.29.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/dist/__tests__/japaneseShaping.test.js +30 -11
- package/dist/__tests__/japaneseShaping.test.js.map +1 -1
- package/dist/__tests__/otLanguages.test.d.ts +2 -0
- package/dist/__tests__/otLanguages.test.d.ts.map +1 -0
- package/dist/__tests__/otLanguages.test.js +105 -0
- package/dist/__tests__/otLanguages.test.js.map +1 -0
- package/dist/__tests__/scriptContextHan.test.js +19 -7
- package/dist/__tests__/scriptContextHan.test.js.map +1 -1
- package/dist/__tests__/shapingLanguage.test.d.ts +2 -0
- package/dist/__tests__/shapingLanguage.test.d.ts.map +1 -0
- package/dist/__tests__/shapingLanguage.test.js +329 -0
- package/dist/__tests__/shapingLanguage.test.js.map +1 -0
- package/dist/__tests__/shapingLanguageDocument.test.d.ts +2 -0
- package/dist/__tests__/shapingLanguageDocument.test.d.ts.map +1 -0
- package/dist/__tests__/shapingLanguageDocument.test.js +237 -0
- package/dist/__tests__/shapingLanguageDocument.test.js.map +1 -0
- package/dist/__tests__/taskBox.test.d.ts +2 -0
- package/dist/__tests__/taskBox.test.d.ts.map +1 -0
- package/dist/__tests__/taskBox.test.js +137 -0
- package/dist/__tests__/taskBox.test.js.map +1 -0
- package/dist/fontCache.d.ts.map +1 -1
- package/dist/fontCache.js +2 -1
- package/dist/fontCache.js.map +1 -1
- package/dist/harfbuzz.d.ts +5 -0
- package/dist/harfbuzz.d.ts.map +1 -1
- package/dist/harfbuzz.js +10 -0
- package/dist/harfbuzz.js.map +1 -1
- package/dist/otLanguageData.d.ts +12 -0
- package/dist/otLanguageData.d.ts.map +1 -0
- package/dist/otLanguageData.js +91 -0
- package/dist/otLanguageData.js.map +1 -0
- package/dist/otLanguages.d.ts +16 -0
- package/dist/otLanguages.d.ts.map +1 -0
- package/dist/otLanguages.js +111 -0
- package/dist/otLanguages.js.map +1 -0
- package/dist/pdf-backend/blockRender.d.ts.map +1 -1
- package/dist/pdf-backend/blockRender.js +22 -11
- package/dist/pdf-backend/blockRender.js.map +1 -1
- package/dist/pdf-backend/index.d.ts.map +1 -1
- package/dist/pdf-backend/index.js +9 -6
- package/dist/pdf-backend/index.js.map +1 -1
- package/dist/pdf-backend/primitives.js +1 -1
- package/dist/pdf-backend/primitives.js.map +1 -1
- package/dist/pdf-backend/renderResourceBlock.d.ts.map +1 -1
- package/dist/pdf-backend/renderResourceBlock.js +42 -37
- package/dist/pdf-backend/renderResourceBlock.js.map +1 -1
- package/dist/pdf-backend/shapedText.d.ts.map +1 -1
- package/dist/pdf-backend/shapedText.js +24 -14
- package/dist/pdf-backend/shapedText.js.map +1 -1
- package/dist/pdf-backend/structureFlow.d.ts.map +1 -1
- package/dist/pdf-backend/structureFlow.js +4 -0
- package/dist/pdf-backend/structureFlow.js.map +1 -1
- package/dist/pdf-backend/taskBox.d.ts +15 -0
- package/dist/pdf-backend/taskBox.d.ts.map +1 -0
- package/dist/pdf-backend/taskBox.js +46 -0
- package/dist/pdf-backend/taskBox.js.map +1 -0
- package/dist/shapingLanguage.d.ts +59 -19
- package/dist/shapingLanguage.d.ts.map +1 -1
- package/dist/shapingLanguage.js +286 -60
- package/dist/shapingLanguage.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll } from 'vitest';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import fontkit from '@pdf-lib/fontkit';
|
|
4
|
+
import { PDFDocument } from 'pdf-lib';
|
|
5
|
+
import { loadComplexShaper, shapeRun } from '../complexShaping';
|
|
6
|
+
import { shapeInCurrentLanguage, shapesSegmentLanguages, shapingKey, shapingLanguage, shapingScriptOf, withDocumentLanguage, withSegmentLanguage, withShapingLanguage, withShapingScript } from '../shapingLanguage';
|
|
7
|
+
import { textAdvancePx } from '../pdf-backend/primitives';
|
|
8
|
+
import { shapedSvgText } from '../pdf-backend/shapedText';
|
|
9
|
+
// #764: a text is shaped in the language the layout measured it in, by
|
|
10
|
+
// both shapers of the PDF. The widths below are the ones Chrome 154 gives
|
|
11
|
+
// these very font files on a canvas (`measureText` at 1000 px with
|
|
12
|
+
// `ctx.lang` set), each text measured alone, as the layout measures a
|
|
13
|
+
// word: thousandths of the em.
|
|
14
|
+
//
|
|
15
|
+
// Chrome shapes a string with no character above U+00FF as Latin text
|
|
16
|
+
// (`2026` and `"` take the Latin forms of the face in the language), and
|
|
17
|
+
// any other string by the scripts of its characters: `—` or `……` alone
|
|
18
|
+
// are of no script, and take the forms of no language.
|
|
19
|
+
const fixture = (name) => new Uint8Array(fs.readFileSync(new URL(`./fixtures/${name}`, import.meta.url)));
|
|
20
|
+
const FILES = {
|
|
21
|
+
serifSC: fixture('cjk/noto-serif-sc-language.ttf'),
|
|
22
|
+
serifTC: fixture('cjk/noto-serif-tc-language.ttf'),
|
|
23
|
+
serifJP: fixture('cjk/noto-serif-jp-vertical.ttf'),
|
|
24
|
+
sansSC: fixture('cjk/noto-sans-sc-language.ttf'),
|
|
25
|
+
sansJP: fixture('cjk/noto-sans-jp-language.ttf'),
|
|
26
|
+
alegreya: fixture('latin/alegreya-language.ttf'),
|
|
27
|
+
};
|
|
28
|
+
const NBSP = '\u00A0';
|
|
29
|
+
const ELLIPSIS = '\u2026';
|
|
30
|
+
const DASH = '\u2014';
|
|
31
|
+
const HAN = '\u4E00';
|
|
32
|
+
/** The texts of the CJK faces: digits, the ellipsis, the dash and the
|
|
33
|
+
* ASCII quotes alone, a DOI, and each beside a Latin letter or after a
|
|
34
|
+
* Han character. */
|
|
35
|
+
const CJK_TEXTS = ['1', '2026', '2001', ELLIPSIS, `${ELLIPSIS}${ELLIPSIS}`, DASH, `${DASH}${DASH}`, '"', "'", '10.1006/', `2001${NBSP}n`, `${ELLIPSIS}${ELLIPSIS}${NBSP}n`, `${HAN}2026`, 'n"', `n${DASH}`];
|
|
36
|
+
/** Text the Japanese serif fixture lacks a glyph for (the full stop and
|
|
37
|
+
* the slash, U+4E00, the no-break space). */
|
|
38
|
+
const NOT_IN_SERIF_JP = new Set(['10.1006/', `${HAN}2026`, `2001${NBSP}n`, `${ELLIPSIS}${ELLIPSIS}${NBSP}n`]);
|
|
39
|
+
/** Chrome's widths of {@link CJK_TEXTS}: in the language the face has
|
|
40
|
+
* forms for (`own`), and in any other (`other`). */
|
|
41
|
+
const CHROME = {
|
|
42
|
+
serifSC: {
|
|
43
|
+
own: ['zh', 'zh-Hans', 'zh-CN', 'zh-SG'],
|
|
44
|
+
other: ['zh-Hant', 'zh-TW', 'zh-HK', 'zh-MO', 'zh-Hant-HK', 'yue', 'ja', 'ko', 'en', 'en-US', 'ca', ''],
|
|
45
|
+
ownWidths: [471, 2231, 2143, 1000, 2000, 890, 1692, 392, 205, 3850, 3060, 2917, 3231, 1053, 1661],
|
|
46
|
+
otherWidths: [550, 2200, 2200, 1000, 2000, 890, 1692, 376, 196, 3979, 3117, 2717, 3231, 970, 1546],
|
|
47
|
+
},
|
|
48
|
+
serifTC: {
|
|
49
|
+
own: ['zh-Hant', 'zh-TW'],
|
|
50
|
+
other: ['zh', 'zh-Hans', 'zh-CN', 'zh-SG', 'zh-HK', 'zh-MO', 'zh-Hant-HK', 'yue', 'ja', 'ko', 'en', 'ca', ''],
|
|
51
|
+
ownWidths: [471, 2231, 2143, 1000, 2000, 890, 1692, 392, 205, 3850, 3060, 2917, 3231, 1053, 1661],
|
|
52
|
+
otherWidths: [550, 2200, 2200, 1000, 2000, 890, 1692, 376, 196, 3979, 3117, 2717, 3231, 970, 1546],
|
|
53
|
+
},
|
|
54
|
+
serifJP: {
|
|
55
|
+
own: ['ja'],
|
|
56
|
+
other: ['zh', 'zh-Hans', 'zh-Hant', 'zh-TW', 'zh-HK', 'ko', 'en', 'ca', ''],
|
|
57
|
+
ownWidths: [471, 2231, 2143, 1000, 2000, 890, 1692, 392, 205, 0, 0, 0, 0, 1053, 1661],
|
|
58
|
+
otherWidths: [550, 2200, 2200, 1000, 2000, 890, 1692, 376, 196, 0, 0, 0, 0, 970, 1546],
|
|
59
|
+
},
|
|
60
|
+
sansSC: {
|
|
61
|
+
own: ['zh', 'zh-Hans', 'zh-CN', 'zh-SG'],
|
|
62
|
+
other: ['zh-Hant', 'zh-TW', 'zh-HK', 'zh-MO', 'yue', 'ja', 'ko', 'en', 'ca', ''],
|
|
63
|
+
ownWidths: [555, 2220, 2220, 1000, 2000, 894, 1676, 475, 279, 4000, 3054, 2834, 3220, 1085, 1610],
|
|
64
|
+
otherWidths: [555, 2220, 2220, 1000, 2000, 894, 1676, 474, 278, 4000, 3054, 2952, 3220, 1084, 1504],
|
|
65
|
+
},
|
|
66
|
+
sansJP: {
|
|
67
|
+
own: ['ja'],
|
|
68
|
+
other: ['zh', 'zh-Hans', 'zh-Hant', 'zh-TW', 'zh-HK', 'ko', 'en', 'ca', ''],
|
|
69
|
+
ownWidths: [555, 2220, 2220, 1000, 2000, 894, 1676, 475, 279, 4000, 3054, 2834, 3220, 1085, 1610],
|
|
70
|
+
otherWidths: [555, 2220, 2220, 1000, 2000, 894, 1676, 474, 278, 4000, 3054, 2952, 3220, 1084, 1504],
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
/** Alegreya: `fi` and `fil`, the Catalan `l·l`, `ale`, the Romanian
|
|
74
|
+
* letters in both spellings, the Dutch `íj`, the Serbian `б`. */
|
|
75
|
+
const LATIN_TEXTS = ['fi', 'fil', 'l\u00B7l', 'ale', '\u015F', '\u0163', '\u0219', '\u021B', '\u00EDj', '\u0431', '\u0430\u0431'];
|
|
76
|
+
const ALEGREYA_PLAIN = [587, 831, 726, 1158, 402, 330, 402, 330, 532, 525, 986];
|
|
77
|
+
const ALEGREYA = {
|
|
78
|
+
en: ALEGREYA_PLAIN,
|
|
79
|
+
es: ALEGREYA_PLAIN,
|
|
80
|
+
'': ALEGREYA_PLAIN,
|
|
81
|
+
// The Romanian forms are as wide (told apart below by their glyphs), and
|
|
82
|
+
// so is the Dutch pair; Macedonian has no form in this face.
|
|
83
|
+
ro: ALEGREYA_PLAIN,
|
|
84
|
+
'ro-MD': ALEGREYA_PLAIN,
|
|
85
|
+
nl: ALEGREYA_PLAIN,
|
|
86
|
+
mk: ALEGREYA_PLAIN,
|
|
87
|
+
ca: [587, 831, 576, 1158, 402, 330, 402, 330, 532, 525, 986],
|
|
88
|
+
tr: [612, 856, 726, 1158, 402, 330, 402, 330, 532, 525, 986],
|
|
89
|
+
az: [612, 856, 726, 1158, 402, 330, 402, 330, 532, 525, 986],
|
|
90
|
+
sr: [587, 831, 726, 1158, 402, 330, 402, 330, 532, 498, 959],
|
|
91
|
+
};
|
|
92
|
+
const fonts = {};
|
|
93
|
+
beforeAll(async () => {
|
|
94
|
+
await loadComplexShaper();
|
|
95
|
+
const pdf = await PDFDocument.create();
|
|
96
|
+
pdf.registerFontkit(fontkit);
|
|
97
|
+
for (const face of Object.keys(FILES)) {
|
|
98
|
+
fonts[face] = await pdf.embedFont(FILES[face], { subset: true });
|
|
99
|
+
shapeInCurrentLanguage(fonts[face]);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
/** fontkit's advance of `text`, as pdf-lib sets plain text. */
|
|
103
|
+
const plain = (face, text) => Math.round(textAdvancePx(fonts[face], text, 1000).advance);
|
|
104
|
+
/** HarfBuzz's, through the PDF's own painter of shaped text. */
|
|
105
|
+
const shaped = (face, text) => Math.round(shapedSvgText(fonts[face], text, 1000).advance);
|
|
106
|
+
/** HarfBuzz's, asked in the language and the script the text is shaped in. */
|
|
107
|
+
function harfbuzz(face, text) {
|
|
108
|
+
const language = shapingLanguage();
|
|
109
|
+
const script = shapingScriptOf(text);
|
|
110
|
+
const run = shapeRun(FILES[face], text, { direction: 'ltr', ...(language ? { language } : {}), ...(script ? { script } : {}) });
|
|
111
|
+
return Math.round((run.glyphs.reduce((sum, g) => sum + g.xAdvance, 0) * 1000) / run.upem);
|
|
112
|
+
}
|
|
113
|
+
/** The glyphs fontkit sets `text` with. */
|
|
114
|
+
function glyphIds(face, text) {
|
|
115
|
+
const kit = fonts[face].embedder.font;
|
|
116
|
+
return kit.layout(text).glyphs.map((g) => g.id);
|
|
117
|
+
}
|
|
118
|
+
describe('the language in force (#764)', () => {
|
|
119
|
+
it('is set for one call and put back, also when the call throws', () => {
|
|
120
|
+
expect(shapingLanguage()).toBeUndefined();
|
|
121
|
+
expect(withShapingLanguage('ca', () => shapingLanguage())).toBe('ca');
|
|
122
|
+
expect(withShapingLanguage('ca', () => withShapingLanguage('zh-Hans', () => shapingLanguage()))).toBe('zh-Hans');
|
|
123
|
+
// The empty tag: a document's text in no language.
|
|
124
|
+
expect(withShapingLanguage('', () => shapingLanguage())).toBe('');
|
|
125
|
+
expect(withShapingLanguage('ca', () => withShapingLanguage(undefined, () => shapingLanguage()))).toBeUndefined();
|
|
126
|
+
expect(() => withShapingLanguage('ca', () => { throw new Error('x'); })).toThrow('x');
|
|
127
|
+
expect(shapingLanguage()).toBeUndefined();
|
|
128
|
+
});
|
|
129
|
+
it('is part of the key of every cache of shaped text, with the script', () => {
|
|
130
|
+
expect(shapingKey('2026')).toBe('2026');
|
|
131
|
+
const keys = [
|
|
132
|
+
shapingKey('2026'),
|
|
133
|
+
withShapingLanguage('', () => shapingKey('2026')),
|
|
134
|
+
withShapingLanguage('zh-Hans', () => shapingKey('2026')),
|
|
135
|
+
withShapingLanguage('zh-Hant', () => shapingKey('2026')),
|
|
136
|
+
withShapingLanguage('zh-Hans', () => withShapingScript('Latn', () => shapingKey('2026'))),
|
|
137
|
+
withShapingLanguage('zh-Hans', () => withShapingScript('Hani', () => shapingKey('2026'))),
|
|
138
|
+
withShapingScript('Latn', () => shapingKey('2026')),
|
|
139
|
+
withDocumentLanguage(undefined, 'ja', () => shapingKey('2026')),
|
|
140
|
+
withDocumentLanguage('ja', 'ja', () => shapingKey('2026')),
|
|
141
|
+
];
|
|
142
|
+
expect(new Set(keys).size).toBe(keys.length);
|
|
143
|
+
});
|
|
144
|
+
it('a page takes its document\u2019s, and a segment its own', () => {
|
|
145
|
+
withDocumentLanguage('zh-Hans', 'zh-Hans', () => {
|
|
146
|
+
expect(shapingLanguage()).toBe('zh-Hans');
|
|
147
|
+
expect(shapesSegmentLanguages()).toBe(true);
|
|
148
|
+
expect(withSegmentLanguage(undefined, () => shapingLanguage())).toBe('zh-Hans');
|
|
149
|
+
expect(withSegmentLanguage('en', () => shapingLanguage())).toBe('en');
|
|
150
|
+
// As written by the author: read in canonical form.
|
|
151
|
+
expect(withSegmentLanguage('ZH-hant', () => shapingLanguage())).toBe('zh-Hant');
|
|
152
|
+
});
|
|
153
|
+
expect(shapingLanguage()).toBeUndefined();
|
|
154
|
+
});
|
|
155
|
+
it('text with no character above U+00FF is Latin text, in a document\u2019s language only', () => {
|
|
156
|
+
expect(shapingScriptOf('2026')).toBeUndefined();
|
|
157
|
+
withShapingLanguage('zh-Hans', () => {
|
|
158
|
+
expect(shapingScriptOf('2026')).toBe('Latn');
|
|
159
|
+
expect(shapingScriptOf('"')).toBe('Latn');
|
|
160
|
+
expect(shapingScriptOf(`l\u00B7l`)).toBe('Latn');
|
|
161
|
+
expect(shapingScriptOf(DASH)).toBeUndefined();
|
|
162
|
+
expect(shapingScriptOf(`2026${DASH}`)).toBeUndefined();
|
|
163
|
+
expect(shapingScriptOf(HAN)).toBeUndefined();
|
|
164
|
+
// The script the layout gave a word comes first.
|
|
165
|
+
expect(withShapingScript('Grek', () => shapingScriptOf('2026'))).toBe('Grek');
|
|
166
|
+
// Beside an ideograph the canvas shaped the word with no script.
|
|
167
|
+
expect(withShapingScript('Hani', () => shapingScriptOf('2026'))).toBeUndefined();
|
|
168
|
+
});
|
|
169
|
+
expect(withShapingScript('Hani', () => shapingScriptOf('2026'))).toBe('Hani');
|
|
170
|
+
expect(withShapingLanguage('', () => shapingScriptOf('2026'))).toBe('Latn');
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
describe('the widths of the canvas, by both shapers (#764)', () => {
|
|
174
|
+
for (const face of Object.keys(CHROME)) {
|
|
175
|
+
it(`${face}: digits, the ellipsis, the dash and the quotes in each language`, () => {
|
|
176
|
+
const chrome = CHROME[face];
|
|
177
|
+
const rows = [[chrome.own, chrome.ownWidths], [chrome.other, chrome.otherWidths]];
|
|
178
|
+
for (const [languages, widths] of rows) {
|
|
179
|
+
for (const language of languages) {
|
|
180
|
+
withShapingLanguage(language, () => {
|
|
181
|
+
CJK_TEXTS.forEach((text, i) => {
|
|
182
|
+
if (face === 'serifJP' && NOT_IN_SERIF_JP.has(text))
|
|
183
|
+
return;
|
|
184
|
+
const label = `${face} ${language || '(none)'} ${JSON.stringify(text)}`;
|
|
185
|
+
expect(plain(face, text), `fontkit ${label}`).toBe(widths[i]);
|
|
186
|
+
expect(shaped(face, text), `HarfBuzz ${label}`).toBe(widths[i]);
|
|
187
|
+
expect(harfbuzz(face, text), `shapeRun ${label}`).toBe(widths[i]);
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// The two rows differ: the face does have forms of its own.
|
|
193
|
+
expect(chrome.ownWidths).not.toEqual(chrome.otherWidths);
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
it('Noto Serif JP: the quotes of Japanese text', () => {
|
|
197
|
+
// The ASCII quote is Latin text, and takes its Japanese form; “ and ”
|
|
198
|
+
// alone are of no script and keep the default one, on the canvas too.
|
|
199
|
+
withShapingLanguage('ja', () => {
|
|
200
|
+
for (const [text, width] of [['"', 392], ["'", 205], ['\u201C', 426], ['\u201D', 425]]) {
|
|
201
|
+
expect(plain('serifJP', text), text).toBe(width);
|
|
202
|
+
expect(shaped('serifJP', text), text).toBe(width);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
it('Alegreya: the Catalan l\u00B7l, the Turkish fi, the Serbian \u0431', () => {
|
|
207
|
+
for (const [language, widths] of Object.entries(ALEGREYA)) {
|
|
208
|
+
withShapingLanguage(language, () => {
|
|
209
|
+
LATIN_TEXTS.forEach((text, i) => {
|
|
210
|
+
const label = `${language || '(none)'} ${JSON.stringify(text)}`;
|
|
211
|
+
expect(plain('alegreya', text), `fontkit ${label}`).toBe(widths[i]);
|
|
212
|
+
expect(shaped('alegreya', text), `HarfBuzz ${label}`).toBe(widths[i]);
|
|
213
|
+
expect(harfbuzz('alegreya', text), `shapeRun ${label}`).toBe(widths[i]);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
it('Alegreya: \u015F and \u0163 are drawn as \u0219 and \u021B in Romanian, by both shapers', () => {
|
|
219
|
+
// As wide in both spellings; Chrome paints the comma forms under `ro`
|
|
220
|
+
// and `ro-MD` (the same ink as ș and ț) and the cedilla ones elsewhere.
|
|
221
|
+
const hb = (text, language) => shapeRun(FILES.alegreya, text, { direction: 'ltr', script: 'Latn', language }).glyphs.map((g) => g.gid);
|
|
222
|
+
for (const [cedilla, comma] of [['\u015F', '\u0219'], ['\u0163', '\u021B']]) {
|
|
223
|
+
for (const language of ['ro', 'ro-RO', 'ro-MD']) {
|
|
224
|
+
expect(withShapingLanguage(language, () => glyphIds('alegreya', cedilla)), language).toEqual(glyphIds('alegreya', comma));
|
|
225
|
+
expect(hb(cedilla, language), language).toEqual(hb(comma, 'en'));
|
|
226
|
+
}
|
|
227
|
+
for (const language of ['en', 'tr', 'ca', '']) {
|
|
228
|
+
expect(withShapingLanguage(language, () => glyphIds('alegreya', cedilla)), language).not.toEqual(glyphIds('alegreya', comma));
|
|
229
|
+
expect(withShapingLanguage(language, () => glyphIds('alegreya', cedilla)), language).toEqual(glyphIds('alegreya', cedilla));
|
|
230
|
+
}
|
|
231
|
+
// The glyphs HarfBuzz and fontkit set are the same ones.
|
|
232
|
+
expect(withShapingLanguage('ro', () => glyphIds('alegreya', cedilla))).toEqual(hb(cedilla, 'ro'));
|
|
233
|
+
}
|
|
234
|
+
// The Turkish i keeps f and i apart, glyph for glyph in both.
|
|
235
|
+
expect(withShapingLanguage('tr', () => glyphIds('alegreya', 'fi'))).toEqual(hb('fi', 'tr'));
|
|
236
|
+
expect(withShapingLanguage('tr', () => glyphIds('alegreya', 'fi')).length).toBe(2);
|
|
237
|
+
expect(withShapingLanguage('en', () => glyphIds('alegreya', 'fi')).length).toBe(1);
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
describe('a script the layout gave a word, in the document\u2019s language (#725, #764)', () => {
|
|
241
|
+
it('the year of a Latin word in Chinese keeps the digits of the face', () => {
|
|
242
|
+
// `2001` beside a Latin letter: 214.3 px at 100 px in Chinese, 220 in
|
|
243
|
+
// English (Chrome).
|
|
244
|
+
expect(withShapingLanguage('zh-Hans', () => withShapingScript('Latn', () => plain('serifSC', '2001')))).toBe(2143);
|
|
245
|
+
expect(withShapingLanguage('en', () => withShapingScript('Latn', () => plain('serifSC', '2001')))).toBe(2200);
|
|
246
|
+
expect(withShapingLanguage('zh-Hans', () => withShapingScript('Latn', () => shaped('serifSC', '2001')))).toBe(2143);
|
|
247
|
+
expect(withShapingLanguage('en', () => withShapingScript('Latn', () => shaped('serifSC', '2001')))).toBe(2200);
|
|
248
|
+
});
|
|
249
|
+
it('a doubled ellipsis beside a Latin letter: 2 em in Chinese, 1.8 em in English', () => {
|
|
250
|
+
const pair = `${ELLIPSIS}${ELLIPSIS}`;
|
|
251
|
+
for (const measure of [plain, shaped]) {
|
|
252
|
+
expect(withShapingLanguage('zh-Hans', () => withShapingScript('Latn', () => measure('serifSC', pair)))).toBe(2000);
|
|
253
|
+
expect(withShapingLanguage('en', () => withShapingScript('Latn', () => measure('serifSC', pair)))).toBe(1800);
|
|
254
|
+
expect(withShapingLanguage('en', () => withShapingScript('Hani', () => measure('serifSC', pair)))).toBe(2000);
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
it('a word measured beside an ideograph has no script: the canvas shaped the two apart', () => {
|
|
258
|
+
// Chrome, in Chinese: `——` + NBSP + 一 is 2948 (1692 for the dashes,
|
|
259
|
+
// their default form), `"` + NBSP + 一 1632 (376) and `2026` + NBSP +
|
|
260
|
+
// 一 3487 (2231); in a Han run the dashes would take the Chinese form,
|
|
261
|
+
// 2 em wide, and the quote the Chinese one, 392.
|
|
262
|
+
const pair = `${DASH}${DASH}`;
|
|
263
|
+
for (const measure of [plain, shaped]) {
|
|
264
|
+
withShapingLanguage('zh-Hans', () => {
|
|
265
|
+
expect(withShapingScript('Hani', () => measure('serifSC', pair))).toBe(1692);
|
|
266
|
+
expect(withShapingScript('Hani', () => measure('serifSC', '"'))).toBe(376);
|
|
267
|
+
expect(withShapingScript('Hani', () => measure('serifSC', '2026'))).toBe(2231);
|
|
268
|
+
// Beside a Latin letter they take the Chinese forms of Latin text.
|
|
269
|
+
expect(withShapingScript('Latn', () => measure('serifSC', pair))).toBe(2000);
|
|
270
|
+
expect(withShapingScript('Latn', () => measure('serifSC', '"'))).toBe(392);
|
|
271
|
+
});
|
|
272
|
+
// In English the digits beside an ideograph are the face's own too.
|
|
273
|
+
expect(withShapingLanguage('en', () => withShapingScript('Hani', () => measure('serifSC', '2026')))).toBe(2231);
|
|
274
|
+
expect(withShapingLanguage('en', () => measure('serifSC', '2026'))).toBe(2200);
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
describe('a segment in a language of its own (#764)', () => {
|
|
279
|
+
it('is shaped in it, and the text around it in the document\u2019s', () => {
|
|
280
|
+
withDocumentLanguage('en', 'en', () => {
|
|
281
|
+
expect(plain('alegreya', 'l\u00B7l')).toBe(726);
|
|
282
|
+
expect(withSegmentLanguage('ca', () => plain('alegreya', 'l\u00B7l'))).toBe(576);
|
|
283
|
+
expect(withSegmentLanguage('ca', () => shaped('alegreya', 'l\u00B7l'))).toBe(576);
|
|
284
|
+
expect(withSegmentLanguage('tr', () => plain('alegreya', 'fi'))).toBe(612);
|
|
285
|
+
expect(plain('alegreya', 'fi')).toBe(587);
|
|
286
|
+
});
|
|
287
|
+
withDocumentLanguage('zh-Hans', 'zh-Hans', () => {
|
|
288
|
+
expect(plain('serifSC', '2026')).toBe(2231);
|
|
289
|
+
expect(withSegmentLanguage('en', () => plain('serifSC', '2026'))).toBe(2200);
|
|
290
|
+
expect(withSegmentLanguage('zh-Hant', () => plain('serifSC', '2026'))).toBe(2200);
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
describe('a document that keeps the host\u2019s language (#764)', () => {
|
|
295
|
+
it('is shaped as before: JAN for Japanese through fontkit, no language otherwise', () => {
|
|
296
|
+
withDocumentLanguage(undefined, 'ja', () => {
|
|
297
|
+
// HarfBuzz was told no language.
|
|
298
|
+
expect(shapingLanguage()).toBeUndefined();
|
|
299
|
+
expect(shapesSegmentLanguages()).toBe(false);
|
|
300
|
+
expect(shapingScriptOf('2026')).toBeUndefined();
|
|
301
|
+
// Punctuation alone was shaped as Han in `JAN `: the Japanese “.
|
|
302
|
+
expect(plain('serifJP', '\u201C')).toBe(465);
|
|
303
|
+
expect(plain('serifJP', '"')).toBe(392);
|
|
304
|
+
expect(shaped('serifJP', '"')).toBe(376);
|
|
305
|
+
// A segment of the body in Japanese, or in any other language.
|
|
306
|
+
expect(withSegmentLanguage('zh', () => plain('serifJP', '\u201C'), true)).toBe(426);
|
|
307
|
+
expect(withSegmentLanguage('zh', () => plain('serifJP', '\u201C'))).toBe(465);
|
|
308
|
+
});
|
|
309
|
+
withDocumentLanguage(undefined, 'zh-Hans', () => {
|
|
310
|
+
expect(shapingLanguage()).toBeUndefined();
|
|
311
|
+
// No language, and the script fontkit reads: the default one.
|
|
312
|
+
expect(plain('serifSC', '2026')).toBe(2231);
|
|
313
|
+
expect(plain('serifSC', '"')).toBe(376);
|
|
314
|
+
expect(plain('serifSC', `n${DASH}`)).toBe(1546);
|
|
315
|
+
expect(withSegmentLanguage('ja', () => plain('serifJP', '\u201C'), true)).toBe(465);
|
|
316
|
+
});
|
|
317
|
+
withDocumentLanguage(undefined, 'ca', () => {
|
|
318
|
+
expect(plain('alegreya', 'l\u00B7l')).toBe(726);
|
|
319
|
+
expect(withSegmentLanguage('ca', () => plain('alegreya', 'l\u00B7l'), true)).toBe(726);
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
it('text with no language in force at all is shaped as it always was', () => {
|
|
323
|
+
// The digits alone in the default script of the face.
|
|
324
|
+
expect(plain('serifSC', '2026')).toBe(2231);
|
|
325
|
+
expect(plain('serifSC', '"')).toBe(376);
|
|
326
|
+
expect(plain('alegreya', 'l\u00B7l')).toBe(726);
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
//# sourceMappingURL=shapingLanguage.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shapingLanguage.test.js","sourceRoot":"","sources":["../../src/__tests__/shapingLanguage.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shapingLanguageDocument.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/shapingLanguageDocument.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll } from 'vitest';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { execFileSync } from 'node:child_process';
|
|
6
|
+
import { buildDocument } from 'postext';
|
|
7
|
+
import { renderToPdf } from '../pdf-backend';
|
|
8
|
+
import { parseFontString } from '../fontString';
|
|
9
|
+
import { loadComplexShaper, shapeRun } from '../complexShaping';
|
|
10
|
+
// #764, end to end: a document is laid out by a measurer that shapes in
|
|
11
|
+
// the language the engine sets on it (`ctx.lang`), as a browser's canvas
|
|
12
|
+
// does, and its PDF prints every word where the layout put it, as wide as
|
|
13
|
+
// it was measured. Noto Serif SC draws its digits, its dash and its ASCII
|
|
14
|
+
// quotes otherwise in Chinese; Alegreya its `l·l` in Catalan.
|
|
15
|
+
const fixture = (name) => new Uint8Array(fs.readFileSync(new URL(`./fixtures/${name}`, import.meta.url)));
|
|
16
|
+
const FACES = {
|
|
17
|
+
Song: fixture('cjk/noto-serif-sc-language.ttf'),
|
|
18
|
+
Alegreya: fixture('latin/alegreya-language.ttf'),
|
|
19
|
+
};
|
|
20
|
+
const fontProvider = async (family) => FACES[family] ?? FACES.Song;
|
|
21
|
+
/** The languages the engine set on the measuring contexts. */
|
|
22
|
+
const languagesSet = new Set();
|
|
23
|
+
/**
|
|
24
|
+
* Measures as Chrome 154's canvas does, with HarfBuzz. A string with no
|
|
25
|
+
* character above U+00FF is Latin text, whatever it holds. Any other is
|
|
26
|
+
* cut at its spaces and around each Han character, and each piece shaped
|
|
27
|
+
* in the script of its first character that has one (the default script
|
|
28
|
+
* for a piece with none). Every piece in the context's language.
|
|
29
|
+
*
|
|
30
|
+
* `font` and `lang` are accessors of the class, which is the global
|
|
31
|
+
* context class, as in a browser: the engine finds the context its text
|
|
32
|
+
* library measures plain paragraphs with by the `font` it sets, and gives
|
|
33
|
+
* it the language too.
|
|
34
|
+
*/
|
|
35
|
+
class CanvasCtx {
|
|
36
|
+
direction = 'ltr';
|
|
37
|
+
letterSpacing = '0px';
|
|
38
|
+
fontString = '';
|
|
39
|
+
language = 'inherit';
|
|
40
|
+
get font() {
|
|
41
|
+
return this.fontString;
|
|
42
|
+
}
|
|
43
|
+
set font(value) {
|
|
44
|
+
this.fontString = value;
|
|
45
|
+
}
|
|
46
|
+
get lang() {
|
|
47
|
+
return this.language;
|
|
48
|
+
}
|
|
49
|
+
set lang(value) {
|
|
50
|
+
this.language = value;
|
|
51
|
+
languagesSet.add(value);
|
|
52
|
+
}
|
|
53
|
+
measureText(s) {
|
|
54
|
+
const parsed = parseFontString(this.fontString);
|
|
55
|
+
const bytes = FACES[parsed?.family ?? ''] ?? FACES.Song;
|
|
56
|
+
const language = this.language === 'inherit' ? undefined : this.language;
|
|
57
|
+
const latin = /^[\u0000-\u00FF]*$/.test(s);
|
|
58
|
+
let advance = 0;
|
|
59
|
+
for (const piece of latin ? [s] : s.split(/( |\p{Script=Han})/u)) {
|
|
60
|
+
if (piece === '')
|
|
61
|
+
continue;
|
|
62
|
+
const run = shapeRun(bytes, piece, { direction: 'ltr', ...(language ? { language } : {}), ...(latin ? { script: 'Latn' } : {}) });
|
|
63
|
+
advance += run ? run.glyphs.reduce((sum, g) => sum + g.xAdvance, 0) / run.upem : piece.length * 0.5;
|
|
64
|
+
}
|
|
65
|
+
return { width: advance * (parsed?.sizePx ?? 16) };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const host = globalThis;
|
|
69
|
+
host.OffscreenCanvasRenderingContext2D = CanvasCtx;
|
|
70
|
+
host.OffscreenCanvas = class {
|
|
71
|
+
getContext() {
|
|
72
|
+
return new CanvasCtx();
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
const pt = (value) => ({ value, unit: 'pt' });
|
|
76
|
+
const SIZE = 20;
|
|
77
|
+
const EM = SIZE / 1000;
|
|
78
|
+
function config(locale, fontFamily, shapingLanguage) {
|
|
79
|
+
return {
|
|
80
|
+
page: { width: pt(700), height: pt(300), dpi: 72, margins: { top: pt(20), bottom: pt(20), left: pt(20), right: pt(20) } },
|
|
81
|
+
locale,
|
|
82
|
+
layout: { layoutType: 'single' },
|
|
83
|
+
header: { elements: [] },
|
|
84
|
+
footer: { elements: [] },
|
|
85
|
+
bodyText: { fontFamily, fontSize: pt(SIZE), lineHeight: pt(36), firstLineIndent: pt(0), textAlign: 'left', hyphenation: { enabled: false }, ...(shapingLanguage ? { shapingLanguage } : {}) },
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
const hasPdftotext = (() => {
|
|
89
|
+
try {
|
|
90
|
+
execFileSync('pdftotext', ['-v'], { stdio: 'ignore' });
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
})();
|
|
97
|
+
/** The words of the PDF's first page with their boxes, px at 72 dpi. */
|
|
98
|
+
function wordBoxes(bytes) {
|
|
99
|
+
const file = path.join(os.tmpdir(), `postext-document-language-${process.pid}-${Math.random().toString(36).slice(2)}.pdf`);
|
|
100
|
+
fs.writeFileSync(file, bytes);
|
|
101
|
+
const xml = execFileSync('pdftotext', ['-bbox', '-l', '1', file, '-'], { encoding: 'utf8' });
|
|
102
|
+
fs.unlinkSync(file);
|
|
103
|
+
return [...xml.matchAll(/<word xMin="([\d.]+)" yMin="[\d.]+" xMax="([\d.]+)" yMax="[\d.]+">([^<]*)<\/word>/g)]
|
|
104
|
+
.map((m) => ({ text: m[3].replace(/"/g, '"').replace(/&/g, '&'), left: Number(m[1]), right: Number(m[2]) }));
|
|
105
|
+
}
|
|
106
|
+
const firstLine = (doc) => doc.pages[0].columns[0].blocks[0].lines[0];
|
|
107
|
+
/** The words of a line as the layout placed them: what stands between
|
|
108
|
+
* two spaces is one word, as pdftotext reads it. */
|
|
109
|
+
function wordPlaces(line) {
|
|
110
|
+
const places = [];
|
|
111
|
+
let x = line.bbox.x;
|
|
112
|
+
let open;
|
|
113
|
+
for (const seg of line.segments) {
|
|
114
|
+
if (seg.kind === 'space')
|
|
115
|
+
open = undefined;
|
|
116
|
+
else if (open) {
|
|
117
|
+
open.text += seg.text;
|
|
118
|
+
open.right = x + seg.width;
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
open = { text: seg.text, left: x, right: x + seg.width };
|
|
122
|
+
places.push(open);
|
|
123
|
+
}
|
|
124
|
+
x += seg.width;
|
|
125
|
+
}
|
|
126
|
+
return places;
|
|
127
|
+
}
|
|
128
|
+
/** The width the layout gave each text of a line, thousandths of the em. */
|
|
129
|
+
const widthsOf = (line) => Object.fromEntries(line.segments.filter((s) => s.kind !== 'space').map((s) => [s.text, Math.round(s.width / EM)]));
|
|
130
|
+
/** Each word of the PDF against its place: both ends, to a hundredth of a
|
|
131
|
+
* pixel and a half (pdftotext rounds to six decimals of a point). */
|
|
132
|
+
function expectInPlace(boxes, places) {
|
|
133
|
+
expect(boxes.map((b) => b.text)).toEqual(places.map((p) => p.text));
|
|
134
|
+
boxes.forEach((box, i) => {
|
|
135
|
+
expect(box.left, `${box.text} left`).toBeCloseTo(places[i].left, 1);
|
|
136
|
+
expect(box.right, `${box.text} right`).toBeCloseTo(places[i].right, 1);
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
beforeAll(async () => {
|
|
140
|
+
await loadComplexShaper();
|
|
141
|
+
});
|
|
142
|
+
const DASH = '\u2014';
|
|
143
|
+
const ELLIPSIS = '\u2026';
|
|
144
|
+
// A reference of a Chinese book: a year, a quoted letter, the Chinese
|
|
145
|
+
// dash, a DOI, a year after a dash of its word, the Chinese ellipsis.
|
|
146
|
+
const NOTE = `GDP 2026 "n" ${DASH}${DASH} 10.1006/ 2001 ${ELLIPSIS}${ELLIPSIS} (1) a${DASH}2001 n`;
|
|
147
|
+
describe('a Chinese document, measured and printed in Chinese (#764)', () => {
|
|
148
|
+
it('the layout measures in the document\u2019s language: the Chinese digits, quotes and dash of the face', () => {
|
|
149
|
+
languagesSet.clear();
|
|
150
|
+
const widths = widthsOf(firstLine(buildDocument({ markdown: NOTE }, config('zh-Hans', 'Song'))));
|
|
151
|
+
expect([...languagesSet]).toContain('zh-Hans');
|
|
152
|
+
// Chrome's widths, in Chinese: 2026 is 223.1 px at 100 px (220 in
|
|
153
|
+
// English), "n" 39.2 + 66.1 + 39.2 (37.6 for each quote in English,
|
|
154
|
+
// kerned against the letter), 10.1006/ 385 (397.9), 2001 beside a
|
|
155
|
+
// Latin letter 214.3 (220), and the dash after a Latin letter a full
|
|
156
|
+
// em (0.89 em in English).
|
|
157
|
+
expect(widths).toEqual({ GDP: 2126, 2026: 2231, '"n"': 1445, [`${DASH}${DASH}`]: 1692, '10.1006/': 3850, 2001: 2143, [`${ELLIPSIS}${ELLIPSIS}`]: 2000, '(1)': 1199, [`a${DASH}`]: 1557, n: 661 });
|
|
158
|
+
// The same sentence in a document in a language the face has no forms
|
|
159
|
+
// for.
|
|
160
|
+
const english = widthsOf(firstLine(buildDocument({ markdown: NOTE }, config('en', 'Song'))));
|
|
161
|
+
expect(english).toMatchObject({ 2026: 2200, '"n"': 1339, '10.1006/': 3979, 2001: 2200, '(1)': 1278, [`a${DASH}`]: 1439 });
|
|
162
|
+
});
|
|
163
|
+
it.skipIf(!hasPdftotext)('the PDF prints each word in its place, as wide as it was measured', async () => {
|
|
164
|
+
for (const locale of ['zh-Hans', 'zh-CN', 'zh-Hant', 'en', 'ja']) {
|
|
165
|
+
const doc = buildDocument({ markdown: NOTE }, config(locale, 'Song'));
|
|
166
|
+
const places = wordPlaces(firstLine(doc));
|
|
167
|
+
expect(places.map((p) => p.text), locale).toEqual(['GDP', '2026', '"n"', `${DASH}${DASH}`, '10.1006/', '2001', `${ELLIPSIS}${ELLIPSIS}`, '(1)', `a${DASH}2001`, 'n']);
|
|
168
|
+
expectInPlace(wordBoxes(await renderToPdf(doc, { fontProvider, accessible: false })), places);
|
|
169
|
+
}
|
|
170
|
+
}, 60_000);
|
|
171
|
+
it.skipIf(!hasPdftotext)('a line of Han with a year, the dash and quotes ends where the layout ends it', async () => {
|
|
172
|
+
const han = '\u4E2D\u65E5\u672C2026\u5E741\u6708\u2014\u2014"\u597D"\u2026\u2026\u4E00\u5E74';
|
|
173
|
+
const doc = buildDocument({ markdown: han }, config('zh-Hans', 'Song'));
|
|
174
|
+
const line = firstLine(doc);
|
|
175
|
+
expect(line.cjkComposed).toBe(true);
|
|
176
|
+
const widths = widthsOf(line);
|
|
177
|
+
// The year and the quotes are Latin text in Chinese.
|
|
178
|
+
expect(widths).toMatchObject({ 2026: 2231, 1: 471, '"': 392 });
|
|
179
|
+
const boxes = wordBoxes(await renderToPdf(doc, { fontProvider, accessible: false }));
|
|
180
|
+
expect(boxes.map((b) => b.text)).toEqual([han]);
|
|
181
|
+
expect(boxes[0].left).toBeCloseTo(line.bbox.x, 1);
|
|
182
|
+
expect(boxes[0].right).toBeCloseTo(line.bbox.x + line.segments.reduce((sum, s) => sum + s.width, 0), 1);
|
|
183
|
+
}, 60_000);
|
|
184
|
+
it.skipIf(!hasPdftotext)('a run in a language of its own is measured and printed in it', async () => {
|
|
185
|
+
const doc = buildDocument({ markdown: 'GDP 2026 :ltr[2026 "n"]{lang=en} 2026' }, config('zh-Hans', 'Song'));
|
|
186
|
+
const line = firstLine(doc);
|
|
187
|
+
const words = line.segments.filter((s) => s.kind !== 'space');
|
|
188
|
+
expect(words.map((s) => [s.text, s.lang, Math.round(s.width / EM)])).toEqual([['GDP', undefined, 2126], ['2026', undefined, 2231], ['2026', 'en', 2200], ['"n"', 'en', 1339], ['2026', undefined, 2231]]);
|
|
189
|
+
expectInPlace(wordBoxes(await renderToPdf(doc, { fontProvider, accessible: false })), wordPlaces(line));
|
|
190
|
+
}, 60_000);
|
|
191
|
+
});
|
|
192
|
+
describe('a document that keeps the host\u2019s language (#764)', () => {
|
|
193
|
+
it.skipIf(!hasPdftotext)('is measured in no language and printed as before', async () => {
|
|
194
|
+
languagesSet.clear();
|
|
195
|
+
const doc = buildDocument({ markdown: NOTE }, config('zh-Hans', 'Song', 'host'));
|
|
196
|
+
// The engine leaves the context its own language.
|
|
197
|
+
expect([...languagesSet].filter((l) => l !== 'inherit')).toEqual([]);
|
|
198
|
+
const line = firstLine(doc);
|
|
199
|
+
// What a page in a language the face has no forms for, or a worker,
|
|
200
|
+
// measures: the widths of the English document above.
|
|
201
|
+
const widths = widthsOf(line);
|
|
202
|
+
expect(widths).toEqual({ GDP: 2126, 2026: 2200, '"n"': 1339, [`${DASH}${DASH}`]: 1692, '10.1006/': 3979, 2001: 2200, [`${ELLIPSIS}${ELLIPSIS}`]: 2000, '(1)': 1278, [`a${DASH}`]: 1439, n: 661 });
|
|
203
|
+
// The advances postext-pdf 1.28 gave this page, which it still gives:
|
|
204
|
+
// no language, each word with no letter in its script.
|
|
205
|
+
const boxes = wordBoxes(await renderToPdf(doc, { fontProvider, accessible: false }));
|
|
206
|
+
const advances = Object.fromEntries(boxes.map((b) => [b.text, Math.round((b.right - b.left) / EM)]));
|
|
207
|
+
expect(advances).toEqual({ GDP: 2126, 2026: 2200, '"n"': 1339, [`${DASH}${DASH}`]: 1692, '10.1006/': 3979, 2001: 2200, [`${ELLIPSIS}${ELLIPSIS}`]: 2000, '(1)': 1278, [`a${DASH}2001`]: 3639, n: 661 });
|
|
208
|
+
expectInPlace(boxes, wordPlaces(line));
|
|
209
|
+
}, 60_000);
|
|
210
|
+
});
|
|
211
|
+
describe('a Catalan document (#764)', () => {
|
|
212
|
+
// `l·l` is 0.576 em wide in Catalan and 0.726 em otherwise (Chrome).
|
|
213
|
+
const WORDS = 'ale l\u00B7l fil';
|
|
214
|
+
it.skipIf(!hasPdftotext)('sets l\u00B7l in the Catalan form of its face, where it was measured', async () => {
|
|
215
|
+
const doc = buildDocument({ markdown: WORDS }, config('ca', 'Alegreya'));
|
|
216
|
+
expect(widthsOf(firstLine(doc))).toEqual({ ale: 1158, 'l\u00B7l': 576, fil: 831 });
|
|
217
|
+
expectInPlace(wordBoxes(await renderToPdf(doc, { fontProvider, accessible: false })), wordPlaces(firstLine(doc)));
|
|
218
|
+
const spanish = buildDocument({ markdown: WORDS }, config('es', 'Alegreya'));
|
|
219
|
+
expect(widthsOf(firstLine(spanish))).toEqual({ ale: 1158, 'l\u00B7l': 726, fil: 831 });
|
|
220
|
+
expectInPlace(wordBoxes(await renderToPdf(spanish, { fontProvider, accessible: false })), wordPlaces(firstLine(spanish)));
|
|
221
|
+
// In Turkish f and i stay apart.
|
|
222
|
+
const turkish = buildDocument({ markdown: WORDS }, config('tr', 'Alegreya'));
|
|
223
|
+
expect(widthsOf(firstLine(turkish))).toEqual({ ale: 1158, 'l\u00B7l': 726, fil: 856 });
|
|
224
|
+
expectInPlace(wordBoxes(await renderToPdf(turkish, { fontProvider, accessible: false })), wordPlaces(firstLine(turkish)));
|
|
225
|
+
}, 60_000);
|
|
226
|
+
it.skipIf(!hasPdftotext)('a Catalan name in a Spanish document, and one in a document that keeps the host\u2019s language', async () => {
|
|
227
|
+
const doc = buildDocument({ markdown: 'ale :ltr[l\u00B7l]{lang=ca} l\u00B7l' }, config('es', 'Alegreya'));
|
|
228
|
+
const line = firstLine(doc);
|
|
229
|
+
expect(line.segments.filter((s) => s.kind !== 'space').map((s) => [s.lang, Math.round(s.width / EM)])).toEqual([[undefined, 1158], ['ca', 576], [undefined, 726]]);
|
|
230
|
+
expectInPlace(wordBoxes(await renderToPdf(doc, { fontProvider, accessible: false })), wordPlaces(line));
|
|
231
|
+
// The host's language: measured in none, printed in none.
|
|
232
|
+
const host = buildDocument({ markdown: WORDS }, config('ca', 'Alegreya', 'host'));
|
|
233
|
+
expect(widthsOf(firstLine(host))).toEqual({ ale: 1158, 'l\u00B7l': 726, fil: 831 });
|
|
234
|
+
expectInPlace(wordBoxes(await renderToPdf(host, { fontProvider, accessible: false })), wordPlaces(firstLine(host)));
|
|
235
|
+
}, 60_000);
|
|
236
|
+
});
|
|
237
|
+
//# sourceMappingURL=shapingLanguageDocument.test.js.map
|