reamkit 1.30.0 → 1.31.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 +60 -24
- package/dist/esm/core/converter/project.js +3 -1
- package/dist/esm/core/crypto/offcrypto.js +1 -1
- package/dist/esm/core/document-model/index.d.ts +1 -1
- package/dist/esm/core/document-model/types.d.ts +63 -0
- package/dist/esm/core/font/index.d.ts +2 -0
- package/dist/esm/core/font/ttf-build.d.ts +113 -0
- package/dist/esm/core/font/ttf-build.js +1224 -0
- package/dist/esm/core/font/ttf-subset.d.ts +19 -0
- package/dist/esm/core/font/ttf-subset.js +14 -1
- package/dist/esm/core/fonts/provider.d.ts +17 -0
- package/dist/esm/core/fonts/provider.js +27 -2
- package/dist/esm/core/fonts/remote-fonts.js +8 -1
- package/dist/esm/core/ir/flow.d.ts +71 -1
- package/dist/esm/core/numbering/index.d.ts +1 -1
- package/dist/esm/core/numbering/state.d.ts +11 -1
- package/dist/esm/core/numbering/state.js +10 -1
- package/dist/esm/core/style-cascade/resolver.js +35 -4
- package/dist/esm/core/style-cascade/types.d.ts +19 -1
- package/dist/esm/core/style-cascade/types.js +3 -0
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/layout/page-doc.d.ts +10 -3
- package/dist/esm/layout/styled-layout.d.ts +48 -7
- package/dist/esm/layout/styled-layout.js +881 -119
- package/dist/esm/layout/turned-section.d.ts +38 -0
- package/dist/esm/layout/turned-section.js +193 -0
- package/dist/esm/pdf/styled-page-emitter.js +78 -2
- package/dist/esm/pdf-reader/cff-outline.d.ts +27 -0
- package/dist/esm/pdf-reader/cff-outline.js +169 -21
- package/dist/esm/pdf-reader/content.d.ts +53 -0
- package/dist/esm/pdf-reader/content.js +9 -1
- package/dist/esm/pdf-reader/display.d.ts +36 -0
- package/dist/esm/pdf-reader/display.js +66 -1
- package/dist/esm/pdf-reader/document.js +5 -1
- package/dist/esm/pdf-reader/embedded-fonts.d.ts +2 -1
- package/dist/esm/pdf-reader/embedded-fonts.js +32 -2
- package/dist/esm/pdf-reader/encodings.d.ts +8 -0
- package/dist/esm/pdf-reader/encodings.js +25 -3
- package/dist/esm/pdf-reader/face-outlines.d.ts +78 -0
- package/dist/esm/pdf-reader/face-outlines.js +362 -0
- package/dist/esm/pdf-reader/figures.d.ts +52 -0
- package/dist/esm/pdf-reader/figures.js +433 -0
- package/dist/esm/pdf-reader/flow-build.d.ts +131 -5
- package/dist/esm/pdf-reader/flow-build.js +345 -26
- package/dist/esm/pdf-reader/font.js +321 -36
- package/dist/esm/pdf-reader/glyf-outline.d.ts +33 -0
- package/dist/esm/pdf-reader/glyf-outline.js +135 -1
- package/dist/esm/pdf-reader/glyph-names.js +154 -1
- package/dist/esm/pdf-reader/layout.d.ts +93 -0
- package/dist/esm/pdf-reader/layout.js +1703 -214
- package/dist/esm/pdf-reader/page-numbers.d.ts +53 -0
- package/dist/esm/pdf-reader/page-numbers.js +167 -0
- package/dist/esm/pdf-reader/tagged.js +182 -23
- package/dist/esm/pdf-reader/text.d.ts +6 -3
- package/dist/esm/pdf-reader/text.js +98 -9
- package/dist/esm/pdf-reader/type1-outline.d.ts +11 -0
- package/dist/esm/pdf-reader/type1-outline.js +63 -8
- package/dist/esm/pdf-reader/vector.js +71 -1
- package/dist/esm/word/doc/doc-reader.js +6 -2
- package/dist/esm/word/doc/doc-text.d.ts +6 -0
- package/dist/esm/word/doc/doc-text.js +19 -1
- package/dist/esm/word/document-parser.d.ts +2 -2
- package/dist/esm/word/document-parser.js +11 -1
- package/dist/esm/word/docx-reader.js +5 -3
- package/dist/esm/word/docx-writer.js +207 -21
- package/dist/esm/word/drawing-parser.d.ts +5 -3
- package/dist/esm/word/drawing-parser.js +49 -8
- package/dist/esm/word/font-embed.d.ts +30 -0
- package/dist/esm/word/font-embed.js +173 -0
- package/dist/esm/word/font-table.d.ts +10 -0
- package/dist/esm/word/font-table.js +13 -1
- package/dist/esm/word/index.js +1 -1
- package/dist/esm/word/numbering-parser.d.ts +3 -1
- package/dist/esm/word/numbering-parser.js +2 -1
- package/dist/esm/word/paragraph-properties.d.ts +7 -6
- package/dist/esm/word/paragraph-properties.js +14 -2
- package/dist/esm/word/run-properties.js +26 -0
- package/package.json +8 -3
|
@@ -5,6 +5,7 @@ import { textMarkupOf } from "./annot-draw.js";
|
|
|
5
5
|
import { appearanceContent, collectPageAppearances } from "./annots.js";
|
|
6
6
|
import { buildContentFont } from "./font.js";
|
|
7
7
|
import { hiddenProperties, hiddenXObject } from "./optional-content.js";
|
|
8
|
+
import { addShown } from "./face-outlines.js";
|
|
8
9
|
import { patternTint, tintedHex } from "./pattern-tint.js";
|
|
9
10
|
//#region src/pdf-reader/text.ts
|
|
10
11
|
var MAX_FORM_DEPTH = 8;
|
|
@@ -15,22 +16,24 @@ var MAX_FORM_DEPTH = 8;
|
|
|
15
16
|
* origin falls inside a `/Link` annotation's `/Rect` with that link's URI (EP8)
|
|
16
17
|
* so hyperlinks survive.
|
|
17
18
|
*
|
|
18
|
-
* @param file
|
|
19
|
-
* @param page
|
|
19
|
+
* @param file The owning {@link PdfFile}.
|
|
20
|
+
* @param page The page to extract.
|
|
21
|
+
* @param painted Where to gather the codes each font painted, for a caller
|
|
22
|
+
* that embeds the faces (see `./face-outlines`).
|
|
20
23
|
* @returns The page's runs, each carrying an `href` when it sits under a link.
|
|
21
24
|
*/
|
|
22
|
-
function extractPageText(file, page) {
|
|
25
|
+
function extractPageText(file, page, painted) {
|
|
23
26
|
const runs = [];
|
|
24
|
-
collectRuns(file, page.resources, file.pageContent(page), IDENTITY, 0, /* @__PURE__ */ new Set(), runs);
|
|
27
|
+
collectRuns(file, page.resources, file.pageContent(page), IDENTITY, 0, /* @__PURE__ */ new Set(), runs, painted);
|
|
25
28
|
const own = runs.length;
|
|
26
|
-
for (const appearance of collectPageAppearances(file, page)) collectRuns(file, appearance.resources ?? page.resources, appearanceContent(file, appearance), appearance.ctm, 1, new Set([appearance.stream]), runs);
|
|
29
|
+
for (const appearance of collectPageAppearances(file, page)) collectRuns(file, appearance.resources ?? page.resources, appearanceContent(file, appearance), appearance.ctm, 1, new Set([appearance.stream]), runs, painted);
|
|
27
30
|
for (let i = own; i < runs.length; i++) runs[i] = {
|
|
28
31
|
...runs[i],
|
|
29
32
|
annotation: true
|
|
30
33
|
};
|
|
31
34
|
const links = collectLinks(file, page);
|
|
32
35
|
const marks = collectTextMarkup(file, page);
|
|
33
|
-
const shown = withoutRestrikes(runs);
|
|
36
|
+
const shown = withAccentsComposed(withoutRestrikes(runs));
|
|
34
37
|
if (links.length === 0 && marks.length === 0) return shown;
|
|
35
38
|
return shown.flatMap((run) => {
|
|
36
39
|
const link = links.find((l) => inRect(run.x, run.y, l.rect));
|
|
@@ -168,6 +171,91 @@ function withoutRestrikes(runs) {
|
|
|
168
171
|
}
|
|
169
172
|
/** How near, in ems, a re-strike of the same text lands to the one it thickens. */
|
|
170
173
|
var RESTRIKE_EM = .08;
|
|
174
|
+
/**
|
|
175
|
+
* §9.4.3 — an accent struck over a letter, composed with it.
|
|
176
|
+
*
|
|
177
|
+
* TeX sets an accented letter its font has no glyph for as two: the accent,
|
|
178
|
+
* and the letter drawn back under it — "ï" is a dieresis and a dotless i.
|
|
179
|
+
* Read as they come, comments.pdf's "naïve" came back "na¨ıve", the accent a
|
|
180
|
+
* character of the word. A run that is one spacing accent, with the pen taken
|
|
181
|
+
* back under it for the run after it, is the accent of that run's first letter
|
|
182
|
+
* — or, where the accent was struck after its letter, of the last letter of the
|
|
183
|
+
* run before it. The two are written as the one character Unicode composes them
|
|
184
|
+
* into, a dotless i or j taking back its dot's place under the accent.
|
|
185
|
+
*
|
|
186
|
+
* @param runs The page's runs, in painting order.
|
|
187
|
+
* @returns The runs with each such accent composed into its letter.
|
|
188
|
+
*/
|
|
189
|
+
function withAccentsComposed(runs) {
|
|
190
|
+
const out = [];
|
|
191
|
+
for (let i = 0; i < runs.length; i++) {
|
|
192
|
+
const run = runs[i];
|
|
193
|
+
const mark = ACCENTS.get(run.text);
|
|
194
|
+
const next = runs[i + 1];
|
|
195
|
+
const prev = out[out.length - 1];
|
|
196
|
+
if (mark !== void 0 && next !== void 0 && struckOver(run, next, "first")) {
|
|
197
|
+
const [first = "", ...rest] = [...next.text];
|
|
198
|
+
out.push({
|
|
199
|
+
...next,
|
|
200
|
+
text: accented(first, mark) + rest.join("")
|
|
201
|
+
});
|
|
202
|
+
i++;
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (mark !== void 0 && prev !== void 0 && struckOver(run, prev, "last")) {
|
|
206
|
+
const chars = [...prev.text];
|
|
207
|
+
const last = chars.pop() ?? "";
|
|
208
|
+
out[out.length - 1] = {
|
|
209
|
+
...prev,
|
|
210
|
+
text: chars.join("") + accented(last, mark)
|
|
211
|
+
};
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
out.push(run);
|
|
215
|
+
}
|
|
216
|
+
return out;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Whether an accent stands over the first letter of a run (the pen taken back
|
|
220
|
+
* under it) or over the last (the pen taken back to strike it): on one
|
|
221
|
+
* baseline, give or take the lift an accent over a capital takes, and
|
|
222
|
+
* overlapping it by more than a hair.
|
|
223
|
+
*/
|
|
224
|
+
function struckOver(accent, base, which) {
|
|
225
|
+
if (accent.angleDeg !== void 0 || base.angleDeg !== void 0) return false;
|
|
226
|
+
const size = base.fontSizePt || accent.fontSizePt || 10;
|
|
227
|
+
if (Math.abs(accent.y - base.y) > size * ACCENT_LIFT_EM) return false;
|
|
228
|
+
if (!/^\p{L}/u.test(which === "first" ? base.text : [...base.text].at(-1) ?? "")) return false;
|
|
229
|
+
const hair = size * ACCENT_OVERLAP_EM;
|
|
230
|
+
return which === "first" ? base.x < accent.endX - hair && base.x > accent.x - size : accent.x < base.endX - hair && accent.x > base.x;
|
|
231
|
+
}
|
|
232
|
+
/** A letter with an accent composed onto it: a dotless i or j takes back its dot's place. */
|
|
233
|
+
function accented(letter, mark) {
|
|
234
|
+
return ((letter === "ı" ? "i" : letter === "ȷ" ? "j" : letter) + mark).normalize("NFC");
|
|
235
|
+
}
|
|
236
|
+
/** How far over the line, in ems, TeX lifts an accent to stand over a capital. */
|
|
237
|
+
var ACCENT_LIFT_EM = .6;
|
|
238
|
+
/** How far, in ems, an accent must overlap its letter to stand over it. */
|
|
239
|
+
var ACCENT_OVERLAP_EM = .05;
|
|
240
|
+
/** The spacing accents TeX strikes over a letter, and the combining marks they are. */
|
|
241
|
+
var ACCENTS = new Map([
|
|
242
|
+
["`", "̀"],
|
|
243
|
+
["´", "́"],
|
|
244
|
+
["ˆ", "̂"],
|
|
245
|
+
["^", "̂"],
|
|
246
|
+
["˜", "̃"],
|
|
247
|
+
["~", "̃"],
|
|
248
|
+
["¯", "̄"],
|
|
249
|
+
["ˉ", "̄"],
|
|
250
|
+
["˘", "̆"],
|
|
251
|
+
["˙", "̇"],
|
|
252
|
+
["¨", "̈"],
|
|
253
|
+
["˚", "̊"],
|
|
254
|
+
["˝", "̋"],
|
|
255
|
+
["ˇ", "̌"],
|
|
256
|
+
["¸", "̧"],
|
|
257
|
+
["˛", "̨"]
|
|
258
|
+
]);
|
|
171
259
|
var spaceCache = /* @__PURE__ */ new WeakMap();
|
|
172
260
|
/** The page's shading patterns, read once per resource dictionary. */
|
|
173
261
|
var shadingCache = /* @__PURE__ */ new WeakMap();
|
|
@@ -187,14 +275,15 @@ function spacesOf(file, resources) {
|
|
|
187
275
|
spaceCache.set(resources, made);
|
|
188
276
|
return made;
|
|
189
277
|
}
|
|
190
|
-
function collectRuns(file, resources, content, baseCtm, depth, visiting, out) {
|
|
278
|
+
function collectRuns(file, resources, content, baseCtm, depth, visiting, out, shown) {
|
|
191
279
|
const result = interpretContent(content, buildFonts(file, resources), baseCtm, shadingsOf(file, resources), void 0, spacesOf(file, resources), hiddenProperties(file, resources));
|
|
192
280
|
out.push(...result.texts.map((r) => withPatternColour(file, resources, r, visiting)));
|
|
281
|
+
if (shown) addShown(shown, result.shown);
|
|
193
282
|
if (depth >= MAX_FORM_DEPTH) return;
|
|
194
283
|
for (const glyph of result.glyphs) {
|
|
195
284
|
if (visiting.has(glyph.stream)) continue;
|
|
196
285
|
visiting.add(glyph.stream);
|
|
197
|
-
collectRuns(file, glyph.resources ?? resources, file.streamData(glyph.stream), glyph.ctm, depth + 1, visiting, out);
|
|
286
|
+
collectRuns(file, glyph.resources ?? resources, file.streamData(glyph.stream), glyph.ctm, depth + 1, visiting, out, shown);
|
|
198
287
|
visiting.delete(glyph.stream);
|
|
199
288
|
}
|
|
200
289
|
if (!resources) return;
|
|
@@ -208,7 +297,7 @@ function collectRuns(file, resources, content, baseCtm, depth, visiting, out) {
|
|
|
208
297
|
if (!(sub instanceof PdfName) || sub.value !== "Form") continue;
|
|
209
298
|
visiting.add(stream);
|
|
210
299
|
const formRes = file.get(stream.dict, "Resources");
|
|
211
|
-
collectRuns(file, formRes instanceof Map ? formRes : resources, file.streamData(stream), multiply(matrixOf(file, stream.dict), placement.ctm), depth + 1, visiting, out);
|
|
300
|
+
collectRuns(file, formRes instanceof Map ? formRes : resources, file.streamData(stream), multiply(matrixOf(file, stream.dict), placement.ctm), depth + 1, visiting, out, shown);
|
|
212
301
|
visiting.delete(stream);
|
|
213
302
|
}
|
|
214
303
|
}
|
|
@@ -10,6 +10,17 @@ export interface Type1Font {
|
|
|
10
10
|
readonly has: (name: string) => boolean;
|
|
11
11
|
/** The program's own `/Encoding`: code → glyph name, where it states one. */
|
|
12
12
|
readonly encoding?: ReadonlyMap<number, string>;
|
|
13
|
+
/**
|
|
14
|
+
* The embedding the program's licence allows — the OS/2 `fsType` Adobe
|
|
15
|
+
* writes into `FontInfo` as `/FSType`, where the program states one.
|
|
16
|
+
*/
|
|
17
|
+
readonly fsType?: number;
|
|
18
|
+
/**
|
|
19
|
+
* §6.4 — how far the pen moves after a glyph, in thousandths of an em: the
|
|
20
|
+
* width its `hsbw` (or `sbw`) states. `undefined` where the program holds no
|
|
21
|
+
* such glyph or its charstring states no width first.
|
|
22
|
+
*/
|
|
23
|
+
readonly advance: (name: string) => number | undefined;
|
|
13
24
|
}
|
|
14
25
|
/**
|
|
15
26
|
* Read an embedded Type 1 program.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { postScriptFsType } from "./cff-outline.js";
|
|
1
2
|
//#region src/pdf-reader/type1-outline.ts
|
|
2
3
|
/**
|
|
3
4
|
* Read an embedded Type 1 program.
|
|
@@ -31,7 +32,13 @@ function type1Font(program) {
|
|
|
31
32
|
return out;
|
|
32
33
|
},
|
|
33
34
|
has: (name) => parsed.charstrings.has(name),
|
|
34
|
-
|
|
35
|
+
advance: (name) => {
|
|
36
|
+
const charstring = parsed.charstrings.get(name);
|
|
37
|
+
const width = charstring ? statedWidth(charstring) : void 0;
|
|
38
|
+
return width === void 0 ? void 0 : width * parsed.scale * 1e3;
|
|
39
|
+
},
|
|
40
|
+
...parsed.encoding ? { encoding: parsed.encoding } : {},
|
|
41
|
+
...parsed.fsType !== void 0 ? { fsType: parsed.fsType } : {}
|
|
35
42
|
};
|
|
36
43
|
}
|
|
37
44
|
function parseType1(program) {
|
|
@@ -47,11 +54,13 @@ function parseType1(program) {
|
|
|
47
54
|
const charstrings = readCharstrings(plain, text, lenIV);
|
|
48
55
|
if (charstrings.size === 0) return void 0;
|
|
49
56
|
const encoding = readEncoding(clear);
|
|
57
|
+
const fsType = postScriptFsType(clear);
|
|
50
58
|
return {
|
|
51
59
|
charstrings,
|
|
52
60
|
subrs: readSubrs(plain, text, lenIV),
|
|
53
61
|
...encoding ? { encoding } : {},
|
|
54
|
-
scale: readMatrixScale(clear) ?? .001
|
|
62
|
+
scale: readMatrixScale(clear) ?? .001,
|
|
63
|
+
...fsType !== void 0 ? { fsType } : {}
|
|
55
64
|
};
|
|
56
65
|
}
|
|
57
66
|
/** The `eexec` key (§7), and the one every charstring is encrypted with (§6.2). */
|
|
@@ -211,6 +220,7 @@ function runGlyph(font, program, depth) {
|
|
|
211
220
|
let inFlex = false;
|
|
212
221
|
let x = 0;
|
|
213
222
|
let y = 0;
|
|
223
|
+
let sideBearing = 0;
|
|
214
224
|
let open = false;
|
|
215
225
|
let steps = 0;
|
|
216
226
|
const moveTo = () => {
|
|
@@ -255,6 +265,7 @@ function runGlyph(font, program, depth) {
|
|
|
255
265
|
case 13:
|
|
256
266
|
x = stack[0] ?? 0;
|
|
257
267
|
y = 0;
|
|
268
|
+
sideBearing = x;
|
|
258
269
|
stack.length = 0;
|
|
259
270
|
break;
|
|
260
271
|
case 9:
|
|
@@ -404,18 +415,25 @@ function runGlyph(font, program, depth) {
|
|
|
404
415
|
break;
|
|
405
416
|
}
|
|
406
417
|
if (op2 === 6) {
|
|
407
|
-
const
|
|
408
|
-
|
|
409
|
-
|
|
418
|
+
const [asb = 0, adx = 0, ady = 0, bchar = 0, achar = 0] = stack;
|
|
419
|
+
if (open) out.push({ op: "close" });
|
|
420
|
+
open = false;
|
|
421
|
+
const part = (charCode, dx, dy) => {
|
|
422
|
+
const name = STANDARD_ENCODING[charCode];
|
|
423
|
+
const charstring = name !== void 0 ? font.charstrings.get(name) : void 0;
|
|
424
|
+
if (!charstring || depth >= 2) return;
|
|
410
425
|
const drawn = runGlyph(font, charstring, depth + 1);
|
|
411
|
-
if (drawn) out.push(...drawn.map((seg) => unscale(seg, font.scale)));
|
|
412
|
-
}
|
|
426
|
+
if (drawn) out.push(...drawn.map((seg) => shift(unscale(seg, font.scale), dx, dy)));
|
|
427
|
+
};
|
|
428
|
+
part(bchar, 0, 0);
|
|
429
|
+
part(achar, adx + sideBearing - asb, ady);
|
|
413
430
|
stack.length = 0;
|
|
414
431
|
return false;
|
|
415
432
|
}
|
|
416
433
|
if (op2 === 7) {
|
|
417
434
|
x = stack[0] ?? 0;
|
|
418
435
|
y = stack[1] ?? 0;
|
|
436
|
+
sideBearing = x;
|
|
419
437
|
stack.length = 0;
|
|
420
438
|
break;
|
|
421
439
|
}
|
|
@@ -457,10 +475,47 @@ function scaleSeg(seg, k) {
|
|
|
457
475
|
y: seg.y * k
|
|
458
476
|
};
|
|
459
477
|
}
|
|
460
|
-
/**
|
|
478
|
+
/**
|
|
479
|
+
* §6.4 — the width a charstring states before it draws: the second operand of
|
|
480
|
+
* `hsbw` (`sbx wx hsbw`), or the third of `sbw` (`sbx sby wx wy sbw`).
|
|
481
|
+
*/
|
|
482
|
+
function statedWidth(code) {
|
|
483
|
+
const stack = [];
|
|
484
|
+
for (let i = 0; i < code.length && stack.length <= 4;) {
|
|
485
|
+
const b = code[i++];
|
|
486
|
+
if (b >= 32 && b <= 246) stack.push(b - 139);
|
|
487
|
+
else if (b >= 247 && b <= 250) stack.push((b - 247) * 256 + (code[i++] ?? 0) + 108);
|
|
488
|
+
else if (b >= 251 && b <= 254) stack.push(-(b - 251) * 256 - (code[i++] ?? 0) - 108);
|
|
489
|
+
else if (b === 255) {
|
|
490
|
+
stack.push(code[i] << 24 | code[i + 1] << 16 | code[i + 2] << 8 | code[i + 3] | 0);
|
|
491
|
+
i += 4;
|
|
492
|
+
} else if (b === 13) return stack[1];
|
|
493
|
+
else if (b === 12 && code[i] === 7) return stack[2];
|
|
494
|
+
else return void 0;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
/** The parts of a `seac` come back already scaled; this puts them back in glyph space. */
|
|
461
498
|
function unscale(seg, k) {
|
|
462
499
|
return scaleSeg(seg, k === 0 ? 1 : 1 / k);
|
|
463
500
|
}
|
|
501
|
+
/** A segment moved by `(dx, dy)`: where a `seac` puts its accent. */
|
|
502
|
+
function shift(seg, dx, dy) {
|
|
503
|
+
if (seg.op === "close" || dx === 0 && dy === 0) return seg;
|
|
504
|
+
if (seg.op === "cubic") return {
|
|
505
|
+
op: "cubic",
|
|
506
|
+
x1: seg.x1 + dx,
|
|
507
|
+
y1: seg.y1 + dy,
|
|
508
|
+
x2: seg.x2 + dx,
|
|
509
|
+
y2: seg.y2 + dy,
|
|
510
|
+
x: seg.x + dx,
|
|
511
|
+
y: seg.y + dy
|
|
512
|
+
};
|
|
513
|
+
return {
|
|
514
|
+
op: seg.op,
|
|
515
|
+
x: seg.x + dx,
|
|
516
|
+
y: seg.y + dy
|
|
517
|
+
};
|
|
518
|
+
}
|
|
464
519
|
/** §C.1 — the names `seac` selects its two parts by, at the codes it states. */
|
|
465
520
|
var STANDARD_ENCODING = {
|
|
466
521
|
32: "space",
|
|
@@ -266,15 +266,23 @@ function overlaps(a, b) {
|
|
|
266
266
|
* path intersection, and where the two merely overlap the answer is the smaller
|
|
267
267
|
* of them — bounded, never larger than the truth.
|
|
268
268
|
*
|
|
269
|
+
* A path is measured by where its curves reach, not where their handles stand,
|
|
270
|
+
* and one that reaches past its clip by no more than a sliver is inside it. An
|
|
271
|
+
* appearance's `/BBox` is drawn round the path it holds: comments.pdf signs its
|
|
272
|
+
* sixth page with a stamp of sixty-seven strokes that its box meets exactly,
|
|
273
|
+
* and a handle standing 0.03pt past the box made the signature a path round
|
|
274
|
+
* its clip — it came back a black bar the width of the text.
|
|
275
|
+
*
|
|
269
276
|
* @param v The painted path as the interpreter saw it.
|
|
270
277
|
* @returns The path to draw, or `undefined` when the clip leaves nothing.
|
|
271
278
|
*/
|
|
272
279
|
function clipped(v) {
|
|
273
280
|
const clip = v.clip;
|
|
274
281
|
if (!clip) return v;
|
|
275
|
-
const b =
|
|
282
|
+
const b = reachOf(v.segs);
|
|
276
283
|
if (!b) return v;
|
|
277
284
|
if (clip.minX >= b.maxX || clip.maxX <= b.minX || clip.minY >= b.maxY || clip.maxY <= b.minY) return;
|
|
285
|
+
if (b.minX >= clip.minX - SLIVER_PT && b.maxX <= clip.maxX + SLIVER_PT && b.minY >= clip.minY - SLIVER_PT && b.maxY <= clip.maxY + SLIVER_PT) return v;
|
|
278
286
|
const pathArea = Math.max(0, b.maxX - b.minX) * Math.max(0, b.maxY - b.minY);
|
|
279
287
|
if (Math.max(0, clip.maxX - clip.minX) * Math.max(0, clip.maxY - clip.minY) >= pathArea) return v;
|
|
280
288
|
return {
|
|
@@ -283,6 +291,68 @@ function clipped(v) {
|
|
|
283
291
|
segs: clip.segs
|
|
284
292
|
};
|
|
285
293
|
}
|
|
294
|
+
/** How far past its clip a path may reach and still be inside it: a box's rounding. */
|
|
295
|
+
var SLIVER_PT = .5;
|
|
296
|
+
/**
|
|
297
|
+
* The box a path's curves reach: its points, and the turns of its curves —
|
|
298
|
+
* a Bézier stays inside its handles, and rarely reaches them.
|
|
299
|
+
*
|
|
300
|
+
* @param segs The path.
|
|
301
|
+
*/
|
|
302
|
+
function reachOf(segs) {
|
|
303
|
+
let minX = Infinity;
|
|
304
|
+
let minY = Infinity;
|
|
305
|
+
let maxX = -Infinity;
|
|
306
|
+
let maxY = -Infinity;
|
|
307
|
+
const add = (x, y) => {
|
|
308
|
+
minX = Math.min(minX, x);
|
|
309
|
+
minY = Math.min(minY, y);
|
|
310
|
+
maxX = Math.max(maxX, x);
|
|
311
|
+
maxY = Math.max(maxY, y);
|
|
312
|
+
};
|
|
313
|
+
let [x, y] = [0, 0];
|
|
314
|
+
let [startX, startY] = [0, 0];
|
|
315
|
+
for (const s of segs) {
|
|
316
|
+
if (s.op === "close") {
|
|
317
|
+
[x, y] = [startX, startY];
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
if (s.op === "cubic") {
|
|
321
|
+
const [x0, y0] = [x, y];
|
|
322
|
+
for (const t of [...turnsOf(x0, s.x1, s.x2, s.x), ...turnsOf(y0, s.y1, s.y2, s.y)]) add(bezierAt(x0, s.x1, s.x2, s.x, t), bezierAt(y0, s.y1, s.y2, s.y, t));
|
|
323
|
+
}
|
|
324
|
+
add(s.x, s.y);
|
|
325
|
+
[x, y] = [s.x, s.y];
|
|
326
|
+
if (s.op === "move") [startX, startY] = [s.x, s.y];
|
|
327
|
+
}
|
|
328
|
+
return Number.isFinite(minX) ? {
|
|
329
|
+
minX,
|
|
330
|
+
minY,
|
|
331
|
+
maxX,
|
|
332
|
+
maxY
|
|
333
|
+
} : void 0;
|
|
334
|
+
}
|
|
335
|
+
/** Where, between its ends, one coordinate of a cubic Bézier turns back. */
|
|
336
|
+
function turnsOf(p0, p1, p2, p3) {
|
|
337
|
+
const a = p3 - 3 * p2 + 3 * p1 - p0;
|
|
338
|
+
const b = 2 * (p2 - 2 * p1 + p0);
|
|
339
|
+
const c = p1 - p0;
|
|
340
|
+
let roots = [];
|
|
341
|
+
if (Math.abs(a) < FLAT) {
|
|
342
|
+
if (Math.abs(b) >= FLAT) roots = [-c / b];
|
|
343
|
+
} else {
|
|
344
|
+
const d = b * b - 4 * a * c;
|
|
345
|
+
if (d >= 0) roots = [(-b + Math.sqrt(d)) / (2 * a), (-b - Math.sqrt(d)) / (2 * a)];
|
|
346
|
+
}
|
|
347
|
+
return roots.filter((t) => t > 0 && t < 1);
|
|
348
|
+
}
|
|
349
|
+
/** A coefficient this small is none: the curve's derivative is of lower degree. */
|
|
350
|
+
var FLAT = 1e-12;
|
|
351
|
+
/** One coordinate of a cubic Bézier at `t`. */
|
|
352
|
+
function bezierAt(p0, p1, p2, p3, t) {
|
|
353
|
+
const u = 1 - t;
|
|
354
|
+
return u * u * u * p0 + 3 * u * u * t * p1 + 3 * u * t * t * p2 + t * t * t * p3;
|
|
355
|
+
}
|
|
286
356
|
function bbox(segs) {
|
|
287
357
|
let minX = Infinity;
|
|
288
358
|
let minY = Infinity;
|
|
@@ -178,7 +178,8 @@ function readDoc(bytes) {
|
|
|
178
178
|
},
|
|
179
179
|
styles: EMPTY_STYLE_SHEET,
|
|
180
180
|
resources,
|
|
181
|
-
...headersFooters.size > 0 ? { headersFooters } : {}
|
|
181
|
+
...headersFooters.size > 0 ? { headersFooters } : {},
|
|
182
|
+
typesetBy: "word"
|
|
182
183
|
},
|
|
183
184
|
losses: [content.encrypted ? DOC_ENCRYPTED_LOSS : DOC_TEXT_LOSS]
|
|
184
185
|
};
|
|
@@ -406,7 +407,10 @@ function toParaProperties(p) {
|
|
|
406
407
|
...p.indentRightTwips !== void 0 ? { indentRight: pt(p.indentRightTwips / 20) } : {},
|
|
407
408
|
...p.indentFirstTwips !== void 0 ? { indentFirstLine: pt(p.indentFirstTwips / 20) } : {},
|
|
408
409
|
...p.spaceBeforeTwips !== void 0 ? { spacingBefore: pt(p.spaceBeforeTwips / 20) } : {},
|
|
409
|
-
...p.spaceAfterTwips !== void 0 ? { spacingAfter: pt(p.spaceAfterTwips / 20) } : {}
|
|
410
|
+
...p.spaceAfterTwips !== void 0 ? { spacingAfter: pt(p.spaceAfterTwips / 20) } : {},
|
|
411
|
+
...p.widowControl !== void 0 ? { widowControl: p.widowControl } : {},
|
|
412
|
+
...p.keepNext !== void 0 ? { keepNext: p.keepNext } : {},
|
|
413
|
+
...p.keepLines !== void 0 ? { keepLines: p.keepLines } : {}
|
|
410
414
|
};
|
|
411
415
|
}
|
|
412
416
|
function underlineStyle(kul) {
|
|
@@ -74,6 +74,12 @@ export interface DocParaProps {
|
|
|
74
74
|
readonly listIlfo?: number;
|
|
75
75
|
/** `sprmPIlvl` — list level (0–8). */
|
|
76
76
|
readonly listIlvl?: number;
|
|
77
|
+
/** `sprmPFWidowControl` — no first or last line left alone on a page. */
|
|
78
|
+
readonly widowControl?: boolean;
|
|
79
|
+
/** `sprmPFKeepFollow` — on the same page as the start of the next paragraph. */
|
|
80
|
+
readonly keepNext?: boolean;
|
|
81
|
+
/** `sprmPFKeep` — every line on one page. */
|
|
82
|
+
readonly keepLines?: boolean;
|
|
77
83
|
}
|
|
78
84
|
/** One maximal same-formatting run of text, or an embedded picture. */
|
|
79
85
|
export interface DocRun {
|
|
@@ -40,6 +40,9 @@ var SPRM_P_DYA_BEFORE = 42003;
|
|
|
40
40
|
var SPRM_P_DYA_AFTER = 42004;
|
|
41
41
|
var SPRM_P_F_IN_TABLE = 9238;
|
|
42
42
|
var SPRM_P_F_TTP = 9239;
|
|
43
|
+
var SPRM_P_F_WIDOW_CONTROL = 9265;
|
|
44
|
+
var SPRM_P_F_KEEP = 9221;
|
|
45
|
+
var SPRM_P_F_KEEP_FOLLOW = 9222;
|
|
43
46
|
var SPRM_T_DEF_TABLE = 54792;
|
|
44
47
|
var SPRM_T_DEF_TABLE_SHD = 54802;
|
|
45
48
|
var MAX_ROW_CELLS = 256;
|
|
@@ -433,6 +436,9 @@ function decodePapxGrpprl(d) {
|
|
|
433
436
|
let cellShadings;
|
|
434
437
|
let listIlfo;
|
|
435
438
|
let listIlvl;
|
|
439
|
+
let widowControl;
|
|
440
|
+
let keepNext;
|
|
441
|
+
let keepLines;
|
|
436
442
|
for (const s of sprms(d)) switch (s.sprm) {
|
|
437
443
|
case SPRM_P_JC:
|
|
438
444
|
jc = d[s.op];
|
|
@@ -458,6 +464,15 @@ function decodePapxGrpprl(d) {
|
|
|
458
464
|
case SPRM_P_F_TTP:
|
|
459
465
|
rowEnd = d[s.op] !== 0;
|
|
460
466
|
break;
|
|
467
|
+
case SPRM_P_F_WIDOW_CONTROL:
|
|
468
|
+
widowControl = d[s.op] !== 0;
|
|
469
|
+
break;
|
|
470
|
+
case SPRM_P_F_KEEP_FOLLOW:
|
|
471
|
+
keepNext = d[s.op] !== 0;
|
|
472
|
+
break;
|
|
473
|
+
case SPRM_P_F_KEEP:
|
|
474
|
+
keepLines = d[s.op] !== 0;
|
|
475
|
+
break;
|
|
461
476
|
case SPRM_T_DEF_TABLE: {
|
|
462
477
|
const cols = d[s.op] ?? 0;
|
|
463
478
|
const edges = [];
|
|
@@ -498,7 +513,10 @@ function decodePapxGrpprl(d) {
|
|
|
498
513
|
...cellDescriptors ? { cellDescriptors } : {},
|
|
499
514
|
...cellShadings ? { cellShadings } : {},
|
|
500
515
|
...listIlfo !== void 0 ? { listIlfo } : {},
|
|
501
|
-
...listIlvl !== void 0 ? { listIlvl } : {}
|
|
516
|
+
...listIlvl !== void 0 ? { listIlvl } : {},
|
|
517
|
+
...widowControl !== void 0 ? { widowControl } : {},
|
|
518
|
+
...keepNext !== void 0 ? { keepNext } : {},
|
|
519
|
+
...keepLines !== void 0 ? { keepLines } : {}
|
|
502
520
|
};
|
|
503
521
|
}
|
|
504
522
|
function* sprms(d) {
|
|
@@ -80,8 +80,8 @@ export interface ParseContext {
|
|
|
80
80
|
readonly openCommentRanges?: Set<string>;
|
|
81
81
|
/**
|
|
82
82
|
* §17.3.1.1/§17.3.1.3 — what `w:beforeAutospacing`/`w:afterAutospacing`
|
|
83
|
-
* resolve to
|
|
84
|
-
*
|
|
83
|
+
* resolve to: HTML's 14pt for a Word document, nothing (the default) where
|
|
84
|
+
* the caller gives none.
|
|
85
85
|
*/
|
|
86
86
|
readonly autoSpacingPt?: number;
|
|
87
87
|
}
|
|
@@ -9,6 +9,7 @@ import { parseRunProperties } from "./run-properties.js";
|
|
|
9
9
|
import { parseParagraphProperties } from "./paragraph-properties.js";
|
|
10
10
|
import { poElementToFlat } from "./po-to-flat.js";
|
|
11
11
|
import { parseBorders, parseTable } from "./table-parser.js";
|
|
12
|
+
import { FORMATS } from "./numbering-parser.js";
|
|
12
13
|
import { XMLParser } from "fast-xml-parser";
|
|
13
14
|
//#region src/word/document-parser.ts
|
|
14
15
|
var decoder = new TextDecoder("utf-8");
|
|
@@ -199,11 +200,13 @@ function parseSectPrNode(sectPr) {
|
|
|
199
200
|
let margins;
|
|
200
201
|
let titlePg = false;
|
|
201
202
|
let pageNumberStart;
|
|
203
|
+
let pageNumberFormat;
|
|
202
204
|
let lineNumbering;
|
|
203
205
|
let columns;
|
|
204
206
|
let sectionStart;
|
|
205
207
|
let pageBorders;
|
|
206
208
|
let gridLinePitchPt;
|
|
209
|
+
let textDirection;
|
|
207
210
|
const headers = [];
|
|
208
211
|
const footers = [];
|
|
209
212
|
for (const child of poChildren(sectPr)) if (poIs(child, "w:pgSz")) {
|
|
@@ -251,6 +254,8 @@ function parseSectPrNode(sectPr) {
|
|
|
251
254
|
} else if (poIs(child, "w:pgNumType")) {
|
|
252
255
|
const start = poIntAttr(child, "start");
|
|
253
256
|
if (start !== void 0) pageNumberStart = start;
|
|
257
|
+
const fmt = poAttr(child, "fmt");
|
|
258
|
+
if (fmt !== void 0 && FORMATS.has(fmt) && fmt !== "decimal") pageNumberFormat = fmt;
|
|
254
259
|
} else if (poIs(child, "w:cols")) columns = parseColumns(child);
|
|
255
260
|
else if (poIs(child, "w:pgBorders")) {
|
|
256
261
|
const borders = parseBorders(child);
|
|
@@ -265,6 +270,9 @@ function parseSectPrNode(sectPr) {
|
|
|
265
270
|
} else if (poIs(child, "w:type")) {
|
|
266
271
|
const val = poAttr(child, "val");
|
|
267
272
|
sectionStart = val === "continuous" || val === "oddPage" || val === "evenPage" ? val : "nextPage";
|
|
273
|
+
} else if (poIs(child, "w:textDirection")) {
|
|
274
|
+
const val = poAttr(child, "val");
|
|
275
|
+
if (val === "tbRl" || val === "btLr") textDirection = val;
|
|
268
276
|
}
|
|
269
277
|
return {
|
|
270
278
|
pageSize,
|
|
@@ -273,11 +281,13 @@ function parseSectPrNode(sectPr) {
|
|
|
273
281
|
footers,
|
|
274
282
|
...titlePg ? { titlePg: true } : {},
|
|
275
283
|
...pageNumberStart !== void 0 ? { pageNumberStart } : {},
|
|
284
|
+
...pageNumberFormat !== void 0 ? { pageNumberFormat } : {},
|
|
276
285
|
...lineNumbering ? { lineNumbering } : {},
|
|
277
286
|
...columns ? { columns } : {},
|
|
278
287
|
...sectionStart ? { sectionStart } : {},
|
|
279
288
|
...pageBorders ? { pageBorders } : {},
|
|
280
|
-
...gridLinePitchPt !== void 0 ? { gridLinePitchPt } : {}
|
|
289
|
+
...gridLinePitchPt !== void 0 ? { gridLinePitchPt } : {},
|
|
290
|
+
...textDirection ? { textDirection } : {}
|
|
281
291
|
};
|
|
282
292
|
}
|
|
283
293
|
function parseColumns(cols) {
|
|
@@ -15,10 +15,10 @@ import { OpcPackage } from "../core/opc/package.js";
|
|
|
15
15
|
import { parseCoreProperties } from "../core/opc/core-properties.js";
|
|
16
16
|
import "../core/opc/index.js";
|
|
17
17
|
import { parseXml } from "../pptx/pptx-reader.js";
|
|
18
|
+
import { EMPTY_NUMBERING, parseNumbering } from "./numbering-parser.js";
|
|
18
19
|
import { EMPTY_SECTION, applyAuthorIds, bodyIndexForBlock, newBlockCounter, parseBackgroundColor, parseBackgroundFill, parseCommentThreads, parseDocument, parseHeaderFooter, parseNotes, parsePeople, parseSections } from "./document-parser.js";
|
|
19
20
|
import { parseStyles } from "./styles-parser.js";
|
|
20
21
|
import { loadEmbeddedFonts } from "./font-table.js";
|
|
21
|
-
import { EMPTY_NUMBERING, parseNumbering } from "./numbering-parser.js";
|
|
22
22
|
import { EMPTY_SETTINGS, parseSettings } from "./settings-parser.js";
|
|
23
23
|
import "./index.js";
|
|
24
24
|
//#region src/word/docx-reader.ts
|
|
@@ -66,7 +66,7 @@ function readDocx(docx) {
|
|
|
66
66
|
const settings = settingsData ? parseSettings(settingsData) : EMPTY_SETTINGS;
|
|
67
67
|
const ctx = {
|
|
68
68
|
resolveColor,
|
|
69
|
-
|
|
69
|
+
autoSpacingPt: 14,
|
|
70
70
|
...themeLineWidths && themeLineWidths.length > 0 ? { themeLineWidths } : {},
|
|
71
71
|
...themeStyles ? { themeStyles } : {},
|
|
72
72
|
...themeFonts ? { themeFonts } : {},
|
|
@@ -142,7 +142,9 @@ function readDocx(docx) {
|
|
|
142
142
|
...settings.doNotExpandShiftReturn ? { doNotExpandShiftReturn: true } : {},
|
|
143
143
|
...settings.displayBackgroundShape && backgroundFill ? { pageBackgroundFill: backgroundFill } : {},
|
|
144
144
|
...settings.displayBackgroundShape && backgroundColorHex ? { pageBackgroundColorHex: backgroundColorHex } : {},
|
|
145
|
-
...settings.gutterAtTop ? { gutterAtTop: true } : {}
|
|
145
|
+
...settings.gutterAtTop ? { gutterAtTop: true } : {},
|
|
146
|
+
...settings.compatibilityMode !== void 0 ? { compatibilityMode: settings.compatibilityMode } : {},
|
|
147
|
+
typesetBy: "word"
|
|
146
148
|
},
|
|
147
149
|
losses
|
|
148
150
|
};
|