reamkit 1.34.0 → 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 +6 -1
|
@@ -34,7 +34,7 @@ function extractPageText(file, page, painted) {
|
|
|
34
34
|
};
|
|
35
35
|
const links = collectLinks(file, page);
|
|
36
36
|
const marks = collectTextMarkup(file, page);
|
|
37
|
-
const shown = withAccentsComposed(withoutRestrikes(runs));
|
|
37
|
+
const shown = withCyrillicSchwa(withAccentsComposed(withoutRestrikes(runs)));
|
|
38
38
|
if (links.length === 0 && marks.length === 0) return shown;
|
|
39
39
|
return shown.flatMap((run) => {
|
|
40
40
|
const link = links.find((l) => inRect(run.x, run.y, l.rect));
|
|
@@ -217,6 +217,70 @@ function withAccentsComposed(runs) {
|
|
|
217
217
|
return out;
|
|
218
218
|
}
|
|
219
219
|
/**
|
|
220
|
+
* The Latin schwa a page wrote for the Cyrillic one, read as the Cyrillic.
|
|
221
|
+
*
|
|
222
|
+
* Arial, Times New Roman and Courier New draw ә (U+04D9) with the glyph of the
|
|
223
|
+
* Latin schwa ə (U+0259), Ә with that of Ə, and a writer that names a glyph
|
|
224
|
+
* after the first character reaching it says Latin: Word, Excel and PowerPoint
|
|
225
|
+
* for Mac export every ә of a Kazakh, Tatar, Bashkir or Azerbaijani document
|
|
226
|
+
* set in those faces as ə (probes of 2026-11-06), and Ream itself did until
|
|
227
|
+
* it named each glyph after the letter it drew. Read as written, "және" is a
|
|
228
|
+
* Cyrillic word with a Latin letter in it, which no language spells.
|
|
229
|
+
*
|
|
230
|
+
* A schwa is read as Cyrillic where its word's other letters are Cyrillic and
|
|
231
|
+
* none Latin, and a word that is nothing but schwas (an alphabet's "Әә") where
|
|
232
|
+
* its line's letters are: Cyrillic, at least three of them, and no Latin besides.
|
|
233
|
+
* An Azerbaijani word in the Latin alphabet — "Azərbaycan" — is left as it is.
|
|
234
|
+
*
|
|
235
|
+
* @param runs The page's runs.
|
|
236
|
+
* @returns The runs, each schwa among Cyrillic letters made Cyrillic.
|
|
237
|
+
*/
|
|
238
|
+
function withCyrillicSchwa(runs) {
|
|
239
|
+
if (!runs.some((r) => /[\u0259\u018f]/u.test(r.text))) return [...runs];
|
|
240
|
+
const out = [...runs];
|
|
241
|
+
let from = 0;
|
|
242
|
+
for (let i = 1; i <= runs.length; i++) {
|
|
243
|
+
const prev = runs[i - 1];
|
|
244
|
+
const run = runs[i];
|
|
245
|
+
if (run && continuesLine(prev, run)) continue;
|
|
246
|
+
if (runs.slice(from, i).some((r) => /[\u0259\u018f]/u.test(r.text))) {
|
|
247
|
+
const texts = runs.slice(from, i).map((r) => r.text);
|
|
248
|
+
const line = cyrillicSchwas(texts.join(""));
|
|
249
|
+
let at = 0;
|
|
250
|
+
texts.forEach((t, k) => {
|
|
251
|
+
const mine = line.slice(at, at + t.length);
|
|
252
|
+
at += t.length;
|
|
253
|
+
if (mine !== t) out[from + k] = {
|
|
254
|
+
...runs[from + k],
|
|
255
|
+
text: mine
|
|
256
|
+
};
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
from = i;
|
|
260
|
+
}
|
|
261
|
+
return out;
|
|
262
|
+
}
|
|
263
|
+
/** Whether one run takes up where another left off, on its baseline. */
|
|
264
|
+
function continuesLine(a, b) {
|
|
265
|
+
if (a.angleDeg !== void 0 || b.angleDeg !== void 0) return false;
|
|
266
|
+
const size = b.fontSizePt || a.fontSizePt || 10;
|
|
267
|
+
return Math.abs(a.y - b.y) < size * .3 && Math.abs(b.x - a.endX) < size * .3;
|
|
268
|
+
}
|
|
269
|
+
/** A line's schwas among Cyrillic letters, made Cyrillic (see {@link withCyrillicSchwa}). */
|
|
270
|
+
function cyrillicSchwas(line) {
|
|
271
|
+
const letters = [...line].filter((ch) => /\p{L}/u.test(ch));
|
|
272
|
+
const cyrillic = letters.filter((ch) => /\p{Script=Cyrillic}/u.test(ch)).length;
|
|
273
|
+
const otherLatin = letters.some((ch) => /\p{Script=Latin}/u.test(ch) && !SCHWA.has(ch));
|
|
274
|
+
const lineIsCyrillic = cyrillic >= 3 && !otherLatin;
|
|
275
|
+
return line.replace(/[\p{L}\p{M}]+/gu, (word) => {
|
|
276
|
+
if (!/[\u0259\u018f]/u.test(word)) return word;
|
|
277
|
+
const rest = [...word].filter((ch) => !SCHWA.has(ch) && /\p{L}/u.test(ch));
|
|
278
|
+
return (rest.length > 0 ? rest.some((ch) => /\p{Script=Cyrillic}/u.test(ch)) && !rest.some((ch) => /\p{Script=Latin}/u.test(ch)) : lineIsCyrillic) ? word.replace(/[\u0259\u018f]/gu, (ch) => SCHWA.get(ch)) : word;
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
/** The Latin schwas, and the Cyrillic letters a page drawn in Cyrillic meant by them. */
|
|
282
|
+
var SCHWA = /* @__PURE__ */ new Map([["ə", "ә"], ["Ə", "Ә"]]);
|
|
283
|
+
/**
|
|
220
284
|
* Whether an accent stands over the first letter of a run (the pen taken back
|
|
221
285
|
* under it) or over the last (the pen taken back to strike it): on one
|
|
222
286
|
* baseline, give or take the lift an accent over a capital takes, and
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { fetchFontSet, resolveFamilyKey } from "../core/fonts/remote-fonts.js";
|
|
1
|
+
import { companionOf, fetchFontSet, resolveFamilyKey } from "../core/fonts/remote-fonts.js";
|
|
2
2
|
import { FontRegistry } from "../core/font/font-registry.js";
|
|
3
3
|
import "../core/font/index.js";
|
|
4
4
|
import { signPdf } from "../pdf/signature.js";
|
|
@@ -137,7 +137,8 @@ async function buildUnsignedDocxPdf(docx, options) {
|
|
|
137
137
|
async function resolveDocxAutoFonts(docx, options = {}) {
|
|
138
138
|
const fetchOpt = options.fontFetch ? { fetch: options.fontFetch } : {};
|
|
139
139
|
const keys = options.fontFamily ? /* @__PURE__ */ new Set([resolveFamilyKey(options.fontFamily)]) : detectDocxFamilyKeys(docx);
|
|
140
|
-
|
|
140
|
+
const companions = detectDocxCompanions(docx, keys);
|
|
141
|
+
if (keys.size <= 1 && companions.size === 0) {
|
|
141
142
|
const family = keys.values().next().value;
|
|
142
143
|
return { fonts: await fetchFontSet({
|
|
143
144
|
...family ? { family } : {},
|
|
@@ -154,10 +155,52 @@ async function resolveDocxAutoFonts(docx, options = {}) {
|
|
|
154
155
|
registriesByFamily.set(key, FontRegistry.fromBytes(bytes));
|
|
155
156
|
if (key === "arimo" || !baseBytes) baseBytes = bytes;
|
|
156
157
|
}
|
|
158
|
+
for (const family of companions) {
|
|
159
|
+
if (registriesByFamily.has(family)) continue;
|
|
160
|
+
try {
|
|
161
|
+
registriesByFamily.set(family, FontRegistry.fromBytes(await fetchFontSet({
|
|
162
|
+
family,
|
|
163
|
+
...fetchOpt
|
|
164
|
+
})));
|
|
165
|
+
} catch {}
|
|
166
|
+
}
|
|
157
167
|
return {
|
|
158
168
|
fonts: baseBytes,
|
|
159
169
|
registriesByFamily
|
|
160
170
|
};
|
|
161
171
|
}
|
|
172
|
+
/**
|
|
173
|
+
* The companions a `.docx`'s substitutes need (see `companionOf`): those whose
|
|
174
|
+
* blocks the document's text holds a character of — a Cambria document in
|
|
175
|
+
* Russian needs Noto Serif for its letters, one in English does not.
|
|
176
|
+
*
|
|
177
|
+
* @param docx The `.docx` bytes.
|
|
178
|
+
* @param families The substitute families it names.
|
|
179
|
+
* @returns The companion families to fetch.
|
|
180
|
+
*/
|
|
181
|
+
function detectDocxCompanions(docx, families) {
|
|
182
|
+
const wanted = [...families].flatMap((key) => {
|
|
183
|
+
const companion = companionOf(key);
|
|
184
|
+
return companion ? [companion] : [];
|
|
185
|
+
});
|
|
186
|
+
const out = /* @__PURE__ */ new Set();
|
|
187
|
+
if (wanted.length === 0) return out;
|
|
188
|
+
let xml;
|
|
189
|
+
try {
|
|
190
|
+
const main = OpcPackage.open(docx).getPart(MAIN_DOCUMENT_PART);
|
|
191
|
+
if (!main) return out;
|
|
192
|
+
xml = new TextDecoder("utf-8").decode(main);
|
|
193
|
+
} catch {
|
|
194
|
+
return out;
|
|
195
|
+
}
|
|
196
|
+
for (const c of wanted) for (const ch of xml) {
|
|
197
|
+
const cp = ch.codePointAt(0);
|
|
198
|
+
if (c.ranges.some(([lo, hi]) => cp >= lo && cp <= hi)) {
|
|
199
|
+
out.add(c.family);
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return out;
|
|
204
|
+
}
|
|
162
205
|
//#endregion
|
|
163
206
|
export { convertDocxToPdf, convertDocxToPdfSync, detectDocxFamilyKeys, resolveDocxAutoFonts };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reamkit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.35.0",
|
|
4
4
|
"description": "Ream — convert DOCX, XLSX, PPTX, PDF and legacy DOC, XLS and PPT to PDF, SVG, HTML, Markdown, DOCX and XLSX, built from scratch on the ECMA-376 and ISO 32000 specifications. Parse once, convert anywhere.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Alexandr Krassavin <info@reamkit.dev>",
|
|
@@ -113,5 +113,10 @@
|
|
|
113
113
|
},
|
|
114
114
|
"engines": {
|
|
115
115
|
"node": ">=20"
|
|
116
|
+
},
|
|
117
|
+
"allowScripts": {
|
|
118
|
+
"esbuild": false,
|
|
119
|
+
"unrs-resolver": false,
|
|
120
|
+
"fsevents": false
|
|
116
121
|
}
|
|
117
122
|
}
|