reamkit 1.6.0 → 1.7.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/esm/core/drawingml/chart-serializer.d.ts +2 -0
- package/dist/esm/core/drawingml/chart-serializer.js +53 -0
- package/dist/esm/excel/print-model.js +6 -3
- package/dist/esm/excel/xlsx-writer.js +81 -17
- package/dist/esm/pdf-reader/content.d.ts +39 -1
- package/dist/esm/pdf-reader/content.js +132 -4
- package/dist/esm/pdf-reader/crypto.d.ts +7 -0
- package/dist/esm/pdf-reader/crypto.js +609 -0
- package/dist/esm/pdf-reader/decrypt.d.ts +5 -0
- package/dist/esm/pdf-reader/decrypt.js +199 -0
- package/dist/esm/pdf-reader/document.d.ts +7 -0
- package/dist/esm/pdf-reader/document.js +209 -26
- package/dist/esm/pdf-reader/flow-build.d.ts +16 -1
- package/dist/esm/pdf-reader/flow-build.js +102 -3
- package/dist/esm/pdf-reader/image-decode.d.ts +15 -0
- package/dist/esm/pdf-reader/image-decode.js +442 -0
- package/dist/esm/pdf-reader/images.d.ts +16 -0
- package/dist/esm/pdf-reader/images.js +90 -0
- package/dist/esm/pdf-reader/layout.d.ts +2 -2
- package/dist/esm/pdf-reader/layout.js +48 -14
- package/dist/esm/pdf-reader/png-encode.d.ts +2 -0
- package/dist/esm/pdf-reader/png-encode.js +98 -0
- package/dist/esm/pdf-reader/predictor.d.ts +7 -0
- package/dist/esm/pdf-reader/predictor.js +61 -0
- package/dist/esm/pdf-reader/reader.js +11 -4
- package/dist/esm/pdf-reader/tagged.d.ts +2 -2
- package/dist/esm/pdf-reader/tagged.js +60 -7
- package/dist/esm/pdf-reader/text.js +48 -1
- package/dist/esm/pdf-reader/vector.d.ts +12 -0
- package/dist/esm/pdf-reader/vector.js +54 -0
- package/dist/esm/word/docx-writer.js +96 -7
- package/dist/esm/word/omml-serializer.d.ts +2 -0
- package/dist/esm/word/omml-serializer.js +48 -0
- package/package.json +1 -1
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { zlibSync } from "fflate";
|
|
2
|
+
//#region src/pdf-reader/png-encode.ts
|
|
3
|
+
var COLOR_TYPE = {
|
|
4
|
+
gray: 0,
|
|
5
|
+
rgb: 2,
|
|
6
|
+
"gray-alpha": 4,
|
|
7
|
+
rgba: 6
|
|
8
|
+
};
|
|
9
|
+
var CHANNELS = {
|
|
10
|
+
gray: 1,
|
|
11
|
+
rgb: 3,
|
|
12
|
+
"gray-alpha": 2,
|
|
13
|
+
rgba: 4
|
|
14
|
+
};
|
|
15
|
+
var SIGNATURE = Uint8Array.from([
|
|
16
|
+
137,
|
|
17
|
+
80,
|
|
18
|
+
78,
|
|
19
|
+
71,
|
|
20
|
+
13,
|
|
21
|
+
10,
|
|
22
|
+
26,
|
|
23
|
+
10
|
|
24
|
+
]);
|
|
25
|
+
function encodePng(width, height, color, samples) {
|
|
26
|
+
const stride = width * CHANNELS[color];
|
|
27
|
+
const raw = new Uint8Array(height * (stride + 1));
|
|
28
|
+
for (let y = 0; y < height; y++) {
|
|
29
|
+
const dst = y * (stride + 1);
|
|
30
|
+
raw[dst] = 0;
|
|
31
|
+
raw.set(samples.subarray(y * stride, y * stride + stride), dst + 1);
|
|
32
|
+
}
|
|
33
|
+
const idat = zlibSync(raw);
|
|
34
|
+
const ihdr = new Uint8Array(13);
|
|
35
|
+
writeU32(ihdr, 0, width);
|
|
36
|
+
writeU32(ihdr, 4, height);
|
|
37
|
+
ihdr[8] = 8;
|
|
38
|
+
ihdr[9] = COLOR_TYPE[color];
|
|
39
|
+
ihdr[10] = 0;
|
|
40
|
+
ihdr[11] = 0;
|
|
41
|
+
ihdr[12] = 0;
|
|
42
|
+
return concat([
|
|
43
|
+
SIGNATURE,
|
|
44
|
+
chunk("IHDR", ihdr),
|
|
45
|
+
chunk("IDAT", idat),
|
|
46
|
+
chunk("IEND", new Uint8Array(0))
|
|
47
|
+
]);
|
|
48
|
+
}
|
|
49
|
+
function chunk(type, data) {
|
|
50
|
+
const typeBytes = Uint8Array.from([
|
|
51
|
+
type.charCodeAt(0),
|
|
52
|
+
type.charCodeAt(1),
|
|
53
|
+
type.charCodeAt(2),
|
|
54
|
+
type.charCodeAt(3)
|
|
55
|
+
]);
|
|
56
|
+
const out = new Uint8Array(12 + data.length);
|
|
57
|
+
writeU32(out, 0, data.length);
|
|
58
|
+
out.set(typeBytes, 4);
|
|
59
|
+
out.set(data, 8);
|
|
60
|
+
const crcInput = new Uint8Array(4 + data.length);
|
|
61
|
+
crcInput.set(typeBytes, 0);
|
|
62
|
+
crcInput.set(data, 4);
|
|
63
|
+
writeU32(out, 8 + data.length, crc32(crcInput));
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
function writeU32(buf, offset, value) {
|
|
67
|
+
buf[offset] = value >>> 24 & 255;
|
|
68
|
+
buf[offset + 1] = value >>> 16 & 255;
|
|
69
|
+
buf[offset + 2] = value >>> 8 & 255;
|
|
70
|
+
buf[offset + 3] = value & 255;
|
|
71
|
+
}
|
|
72
|
+
function concat(parts) {
|
|
73
|
+
let total = 0;
|
|
74
|
+
for (const p of parts) total += p.length;
|
|
75
|
+
const out = new Uint8Array(total);
|
|
76
|
+
let off = 0;
|
|
77
|
+
for (const p of parts) {
|
|
78
|
+
out.set(p, off);
|
|
79
|
+
off += p.length;
|
|
80
|
+
}
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
var crcTable;
|
|
84
|
+
function crc32(bytes) {
|
|
85
|
+
if (!crcTable) {
|
|
86
|
+
crcTable = new Uint32Array(256);
|
|
87
|
+
for (let n = 0; n < 256; n++) {
|
|
88
|
+
let c = n;
|
|
89
|
+
for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
90
|
+
crcTable[n] = c >>> 0;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
let crc = 4294967295;
|
|
94
|
+
for (const b of bytes) crc = crcTable[(crc ^ b) & 255] ^ crc >>> 8;
|
|
95
|
+
return (crc ^ 4294967295) >>> 0;
|
|
96
|
+
}
|
|
97
|
+
//#endregion
|
|
98
|
+
export { encodePng };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
//#region src/pdf-reader/predictor.ts
|
|
2
|
+
function reversePredictor(data, p) {
|
|
3
|
+
if (p.predictor < 2) return data;
|
|
4
|
+
const bpp = Math.max(1, Math.ceil(p.colors * p.bitsPerComponent / 8));
|
|
5
|
+
const rowBytes = Math.ceil(p.colors * p.bitsPerComponent * p.columns / 8);
|
|
6
|
+
if (rowBytes <= 0) return data;
|
|
7
|
+
if (p.predictor === 2) {
|
|
8
|
+
if (p.bitsPerComponent !== 8) return data;
|
|
9
|
+
const rows = Math.floor(data.length / rowBytes);
|
|
10
|
+
const out = data.slice(0, rows * rowBytes);
|
|
11
|
+
for (let r = 0; r < rows; r++) {
|
|
12
|
+
const off = r * rowBytes;
|
|
13
|
+
for (let i = bpp; i < rowBytes; i++) out[off + i] = out[off + i] + out[off + i - bpp] & 255;
|
|
14
|
+
}
|
|
15
|
+
return out;
|
|
16
|
+
}
|
|
17
|
+
const stride = rowBytes + 1;
|
|
18
|
+
const rows = Math.floor(data.length / stride);
|
|
19
|
+
const out = new Uint8Array(rows * rowBytes);
|
|
20
|
+
let prev = new Uint8Array(rowBytes);
|
|
21
|
+
for (let r = 0; r < rows; r++) {
|
|
22
|
+
const ft = data[r * stride];
|
|
23
|
+
const src = r * stride + 1;
|
|
24
|
+
const dst = r * rowBytes;
|
|
25
|
+
for (let i = 0; i < rowBytes; i++) {
|
|
26
|
+
const x = data[src + i];
|
|
27
|
+
const a = i >= bpp ? out[dst + i - bpp] : 0;
|
|
28
|
+
const b = prev[i];
|
|
29
|
+
const c = i >= bpp ? prev[i - bpp] : 0;
|
|
30
|
+
let v;
|
|
31
|
+
switch (ft) {
|
|
32
|
+
case 1:
|
|
33
|
+
v = x + a;
|
|
34
|
+
break;
|
|
35
|
+
case 2:
|
|
36
|
+
v = x + b;
|
|
37
|
+
break;
|
|
38
|
+
case 3:
|
|
39
|
+
v = x + (a + b >> 1);
|
|
40
|
+
break;
|
|
41
|
+
case 4:
|
|
42
|
+
v = x + paeth(a, b, c);
|
|
43
|
+
break;
|
|
44
|
+
default: v = x;
|
|
45
|
+
}
|
|
46
|
+
out[dst + i] = v & 255;
|
|
47
|
+
}
|
|
48
|
+
prev = out.subarray(dst, dst + rowBytes);
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
function paeth(a, b, c) {
|
|
53
|
+
const p = a + b - c;
|
|
54
|
+
const pa = Math.abs(p - a);
|
|
55
|
+
const pb = Math.abs(p - b);
|
|
56
|
+
const pc = Math.abs(p - c);
|
|
57
|
+
if (pa <= pb && pa <= pc) return a;
|
|
58
|
+
return pb <= pc ? b : c;
|
|
59
|
+
}
|
|
60
|
+
//#endregion
|
|
61
|
+
export { reversePredictor };
|
|
@@ -11,20 +11,26 @@ function sniffPdf(bytes) {
|
|
|
11
11
|
function readPdf(bytes) {
|
|
12
12
|
const file = PdfFile.parse(bytes);
|
|
13
13
|
const losses = [];
|
|
14
|
+
if (file.encryptionUnsupported) losses.push({
|
|
15
|
+
severity: "dropped",
|
|
16
|
+
feature: FEATURES.text,
|
|
17
|
+
detail: "encrypted PDF — a user password is required and was not supplied"
|
|
18
|
+
});
|
|
14
19
|
const tagged = reconstructTaggedPdf(file);
|
|
15
|
-
const
|
|
20
|
+
const reconstruction = tagged ?? reconstructByLayout(file);
|
|
16
21
|
if (!tagged) losses.push({
|
|
17
22
|
severity: "degraded",
|
|
18
23
|
feature: FEATURES.text,
|
|
19
24
|
detail: "untagged PDF — text and headings reconstructed heuristically from glyph positions; structure is approximate"
|
|
20
25
|
});
|
|
26
|
+
losses.push(...reconstruction.losses);
|
|
21
27
|
losses.push({
|
|
22
28
|
severity: "dropped",
|
|
23
29
|
feature: FEATURES.images,
|
|
24
|
-
detail: "PDF
|
|
30
|
+
detail: "PDF stroked / shaded vector graphics (lines, gradients, clips) are not reconstructed"
|
|
25
31
|
});
|
|
26
32
|
return {
|
|
27
|
-
doc,
|
|
33
|
+
doc: reconstruction.doc,
|
|
28
34
|
losses
|
|
29
35
|
};
|
|
30
36
|
}
|
|
@@ -34,7 +40,8 @@ var pdfReader = {
|
|
|
34
40
|
supports: new Set([
|
|
35
41
|
FEATURES.text,
|
|
36
42
|
FEATURES.tables,
|
|
37
|
-
FEATURES.lists
|
|
43
|
+
FEATURES.lists,
|
|
44
|
+
FEATURES.images
|
|
38
45
|
]),
|
|
39
46
|
sniff: sniffPdf,
|
|
40
47
|
read: (bytes) => readPdf(bytes)
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { FlowDoc } from '../core/ir/flow.js';
|
|
2
1
|
import { PdfFile } from './document.js';
|
|
3
|
-
|
|
2
|
+
import { Reconstruction } from './flow-build.js';
|
|
3
|
+
export declare function reconstructTaggedPdf(file: PdfFile): Reconstruction | undefined;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { pt } from "../core/ir/units.js";
|
|
2
|
-
import {
|
|
2
|
+
import { ResourceStore } from "../core/ir/resources.js";
|
|
3
|
+
import { buildFlowDoc, dedupeLosses, imageBlock, paragraphBlock, paragraphFromRuns } from "./flow-build.js";
|
|
4
|
+
import { collectPageImages } from "./images.js";
|
|
3
5
|
import { extractPageText } from "./text.js";
|
|
4
6
|
import { readStructTree } from "./struct-tree.js";
|
|
5
7
|
//#region src/pdf-reader/tagged.ts
|
|
@@ -7,12 +9,45 @@ var ASSUMED_CONTENT_WIDTH_PT = 468;
|
|
|
7
9
|
function reconstructTaggedPdf(file) {
|
|
8
10
|
const root = readStructTree(file);
|
|
9
11
|
if (!root) return void 0;
|
|
10
|
-
const
|
|
12
|
+
const pages = file.pages();
|
|
13
|
+
const pageRuns = pages.map((page) => {
|
|
11
14
|
const byMcid = /* @__PURE__ */ new Map();
|
|
12
|
-
for (const run of extractPageText(file, page))
|
|
15
|
+
for (const run of extractPageText(file, page)) {
|
|
16
|
+
if (run.mcid === void 0) continue;
|
|
17
|
+
const list = byMcid.get(run.mcid);
|
|
18
|
+
if (list) list.push(run);
|
|
19
|
+
else byMcid.set(run.mcid, [run]);
|
|
20
|
+
}
|
|
21
|
+
return byMcid;
|
|
22
|
+
});
|
|
23
|
+
const runsOfMcid = (page, mcid) => pageRuns[page]?.get(mcid) ?? [];
|
|
24
|
+
const resources = new ResourceStore();
|
|
25
|
+
const pageImages = pages.map((page) => collectPageImages(file, page));
|
|
26
|
+
const imageLosses = dedupeLosses(pageImages.flatMap((p) => p.losses));
|
|
27
|
+
const imagesByMcid = pageImages.map((p) => {
|
|
28
|
+
const byMcid = /* @__PURE__ */ new Map();
|
|
29
|
+
for (const img of p.images) {
|
|
30
|
+
if (img.mcid === void 0) continue;
|
|
31
|
+
const list = byMcid.get(img.mcid);
|
|
32
|
+
if (list) list.push(img);
|
|
33
|
+
else byMcid.set(img.mcid, [img]);
|
|
34
|
+
}
|
|
13
35
|
return byMcid;
|
|
14
36
|
});
|
|
15
|
-
const
|
|
37
|
+
const emitted = /* @__PURE__ */ new Set();
|
|
38
|
+
const imagesForNode = (node) => node.mcids.flatMap(({ page, mcid }) => imagesByMcid[page]?.get(mcid) ?? []);
|
|
39
|
+
const textOf = (node) => squash(node.mcids.map(({ page, mcid }) => runsOfMcid(page, mcid).map((r) => r.text).join("")).join(" "));
|
|
40
|
+
const spansOf = (node) => {
|
|
41
|
+
const spans = [];
|
|
42
|
+
node.mcids.forEach(({ page, mcid }, i) => {
|
|
43
|
+
if (i > 0) spans.push({ text: " " });
|
|
44
|
+
for (const run of runsOfMcid(page, mcid)) spans.push(run.href !== void 0 ? {
|
|
45
|
+
text: run.text,
|
|
46
|
+
href: run.href
|
|
47
|
+
} : { text: run.text });
|
|
48
|
+
});
|
|
49
|
+
return spans;
|
|
50
|
+
};
|
|
16
51
|
const collectText = (node) => squash([textOf(node), ...node.children.map(collectText)].join(" "));
|
|
17
52
|
function emit(node, out) {
|
|
18
53
|
if (node.type === "Table") {
|
|
@@ -20,14 +55,20 @@ function reconstructTaggedPdf(file) {
|
|
|
20
55
|
if (table) out.push(table);
|
|
21
56
|
return;
|
|
22
57
|
}
|
|
58
|
+
if (node.type === "Figure") {
|
|
59
|
+
for (const img of imagesForNode(node)) {
|
|
60
|
+
emitted.add(img);
|
|
61
|
+
out.push(imageBlock(img, resources, node.alt));
|
|
62
|
+
}
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
23
65
|
if (node.type === "LI") {
|
|
24
66
|
const text = collectText(node);
|
|
25
67
|
if (text.length > 0) out.push(paragraphBlock(text, void 0));
|
|
26
68
|
return;
|
|
27
69
|
}
|
|
28
70
|
if (node.children.length === 0) {
|
|
29
|
-
|
|
30
|
-
if (text.length > 0) out.push(paragraphBlock(text, headingLevel(node.type)));
|
|
71
|
+
if (textOf(node).length > 0) out.push(paragraphFromRuns(spansOf(node), headingLevel(node.type)));
|
|
31
72
|
return;
|
|
32
73
|
}
|
|
33
74
|
for (const child of node.children) emit(child, out);
|
|
@@ -74,8 +115,20 @@ function reconstructTaggedPdf(file) {
|
|
|
74
115
|
}
|
|
75
116
|
const body = [];
|
|
76
117
|
emit(root, body);
|
|
118
|
+
const orphans = [];
|
|
119
|
+
pageImages.forEach((p, page) => {
|
|
120
|
+
for (const img of p.images) if (!emitted.has(img)) orphans.push({
|
|
121
|
+
page,
|
|
122
|
+
img
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
orphans.sort((a, b) => a.page - b.page || b.img.y - a.img.y);
|
|
126
|
+
for (const { img } of orphans) body.push(imageBlock(img, resources));
|
|
77
127
|
if (body.length === 0) return void 0;
|
|
78
|
-
return
|
|
128
|
+
return {
|
|
129
|
+
doc: buildFlowDoc(body, resources),
|
|
130
|
+
losses: imageLosses
|
|
131
|
+
};
|
|
79
132
|
}
|
|
80
133
|
function squash(text) {
|
|
81
134
|
return text.replace(/\s+/g, " ").trim();
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PdfName } from "../pdf/objects.js";
|
|
1
2
|
import { interpretContent } from "./content.js";
|
|
2
3
|
import { buildContentFont } from "./font.js";
|
|
3
4
|
//#region src/pdf-reader/text.ts
|
|
@@ -12,7 +13,53 @@ function extractPageText(file, page) {
|
|
|
12
13
|
} catch {}
|
|
13
14
|
}
|
|
14
15
|
}
|
|
15
|
-
|
|
16
|
+
const runs = interpretContent(file.pageContent(page), fonts).texts;
|
|
17
|
+
const links = collectLinks(file, page);
|
|
18
|
+
if (links.length === 0) return runs;
|
|
19
|
+
return runs.map((run) => {
|
|
20
|
+
const link = links.find((l) => inRect(run.x, run.y, l.rect));
|
|
21
|
+
return link ? {
|
|
22
|
+
...run,
|
|
23
|
+
href: link.href
|
|
24
|
+
} : run;
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
function collectLinks(file, page) {
|
|
28
|
+
const annots = file.get(page.dict, "Annots");
|
|
29
|
+
if (!Array.isArray(annots)) return [];
|
|
30
|
+
const out = [];
|
|
31
|
+
for (const a of annots) {
|
|
32
|
+
const annot = file.resolve(a);
|
|
33
|
+
if (!(annot instanceof Map)) continue;
|
|
34
|
+
const sub = file.get(annot, "Subtype");
|
|
35
|
+
if (!(sub instanceof PdfName) || sub.value !== "Link") continue;
|
|
36
|
+
const rect = normRect(file.get(annot, "Rect"));
|
|
37
|
+
if (!rect) continue;
|
|
38
|
+
const action = file.get(annot, "A");
|
|
39
|
+
if (!(action instanceof Map)) continue;
|
|
40
|
+
const s = file.get(action, "S");
|
|
41
|
+
if (!(s instanceof PdfName) || s.value !== "URI") continue;
|
|
42
|
+
const uri = file.get(action, "URI");
|
|
43
|
+
if (typeof uri === "string" && uri.length > 0) out.push({
|
|
44
|
+
rect,
|
|
45
|
+
href: uri
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
function normRect(v) {
|
|
51
|
+
if (!Array.isArray(v) || v.length < 4) return void 0;
|
|
52
|
+
const n = v.slice(0, 4).map((x) => typeof x === "number" ? x : NaN);
|
|
53
|
+
if (n.some((x) => !Number.isFinite(x))) return void 0;
|
|
54
|
+
return [
|
|
55
|
+
Math.min(n[0], n[2]),
|
|
56
|
+
Math.min(n[1], n[3]),
|
|
57
|
+
Math.max(n[0], n[2]),
|
|
58
|
+
Math.max(n[1], n[3])
|
|
59
|
+
];
|
|
60
|
+
}
|
|
61
|
+
function inRect(x, y, r) {
|
|
62
|
+
return x >= r[0] - 1 && x <= r[2] + 1 && y >= r[1] - 2 && y <= r[3] + 2;
|
|
16
63
|
}
|
|
17
64
|
//#endregion
|
|
18
65
|
export { extractPageText };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { PathSeg } from './content.js';
|
|
2
|
+
import { PdfFile, PdfPage } from './document.js';
|
|
3
|
+
export interface PdfVector {
|
|
4
|
+
readonly segs: ReadonlyArray<PathSeg>;
|
|
5
|
+
readonly fillHex: string;
|
|
6
|
+
readonly minX: number;
|
|
7
|
+
readonly minY: number;
|
|
8
|
+
readonly maxX: number;
|
|
9
|
+
readonly maxY: number;
|
|
10
|
+
readonly mcid?: number;
|
|
11
|
+
}
|
|
12
|
+
export declare function collectPageVectors(file: PdfFile, page: PdfPage): Array<PdfVector>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { interpretContent } from "./content.js";
|
|
2
|
+
//#region src/pdf-reader/vector.ts
|
|
3
|
+
var NO_FONTS = /* @__PURE__ */ new Map();
|
|
4
|
+
var MIN_SIDE = 2;
|
|
5
|
+
var MIN_AREA = 16;
|
|
6
|
+
var MAX_VECTORS = 2e3;
|
|
7
|
+
function collectPageVectors(file, page) {
|
|
8
|
+
const [px0, py0, px1, py1] = page.mediaBox;
|
|
9
|
+
const pageArea = Math.max(1, Math.abs((px1 - px0) * (py1 - py0)));
|
|
10
|
+
const out = [];
|
|
11
|
+
for (const v of interpretContent(file.pageContent(page), NO_FONTS).vectors) {
|
|
12
|
+
if (out.length >= MAX_VECTORS) break;
|
|
13
|
+
if (v.fillHex === "FFFFFF") continue;
|
|
14
|
+
const b = bbox(v.segs);
|
|
15
|
+
if (!b) continue;
|
|
16
|
+
const w = b.maxX - b.minX;
|
|
17
|
+
const h = b.maxY - b.minY;
|
|
18
|
+
if (w < MIN_SIDE || h < MIN_SIDE || w * h < MIN_AREA) continue;
|
|
19
|
+
if (w * h > .85 * pageArea) continue;
|
|
20
|
+
out.push({
|
|
21
|
+
segs: v.segs,
|
|
22
|
+
fillHex: v.fillHex,
|
|
23
|
+
...b,
|
|
24
|
+
...v.mcid !== void 0 ? { mcid: v.mcid } : {}
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
function bbox(segs) {
|
|
30
|
+
let minX = Infinity;
|
|
31
|
+
let minY = Infinity;
|
|
32
|
+
let maxX = -Infinity;
|
|
33
|
+
let maxY = -Infinity;
|
|
34
|
+
const add = (x, y) => {
|
|
35
|
+
minX = Math.min(minX, x);
|
|
36
|
+
minY = Math.min(minY, y);
|
|
37
|
+
maxX = Math.max(maxX, x);
|
|
38
|
+
maxY = Math.max(maxY, y);
|
|
39
|
+
};
|
|
40
|
+
for (const s of segs) if (s.op === "move" || s.op === "line") add(s.x, s.y);
|
|
41
|
+
else if (s.op === "cubic") {
|
|
42
|
+
add(s.x1, s.y1);
|
|
43
|
+
add(s.x2, s.y2);
|
|
44
|
+
add(s.x, s.y);
|
|
45
|
+
}
|
|
46
|
+
return Number.isFinite(minX) ? {
|
|
47
|
+
minX,
|
|
48
|
+
minY,
|
|
49
|
+
maxX,
|
|
50
|
+
maxY
|
|
51
|
+
} : void 0;
|
|
52
|
+
}
|
|
53
|
+
//#endregion
|
|
54
|
+
export { collectPageVectors };
|
|
@@ -4,6 +4,8 @@ import { EMPTY_STYLE_SHEET, resolveParagraphProperties, resolveRunProperties } f
|
|
|
4
4
|
import "../core/style-cascade/index.js";
|
|
5
5
|
import { buildOpcPackage } from "../core/opc/opc-writer.js";
|
|
6
6
|
import "../core/opc/index.js";
|
|
7
|
+
import { omathXml } from "./omml-serializer.js";
|
|
8
|
+
import { chartSpaceXml } from "../core/drawingml/chart-serializer.js";
|
|
7
9
|
//#region src/word/docx-writer.ts
|
|
8
10
|
var encoder = new TextEncoder();
|
|
9
11
|
var DEFAULT_RUN = resolveRunProperties({}, {}, EMPTY_STYLE_SHEET);
|
|
@@ -15,6 +17,14 @@ var REL_NUMBERING = "http://schemas.openxmlformats.org/officeDocument/2006/relat
|
|
|
15
17
|
var REL_HYPERLINK = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink";
|
|
16
18
|
var REL_IMAGE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image";
|
|
17
19
|
var NUMBERING_PART = "word/numbering.xml";
|
|
20
|
+
var REL_FOOTNOTES = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes";
|
|
21
|
+
var REL_ENDNOTES = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/endnotes";
|
|
22
|
+
var FOOTNOTES_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml";
|
|
23
|
+
var ENDNOTES_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml";
|
|
24
|
+
var FOOTNOTES_PART = "word/footnotes.xml";
|
|
25
|
+
var ENDNOTES_PART = "word/endnotes.xml";
|
|
26
|
+
var REL_CHART = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart";
|
|
27
|
+
var CHART_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.drawingml.chart+xml";
|
|
18
28
|
var EMU_PER_PT = 12700;
|
|
19
29
|
var RASTER_MEDIA = {
|
|
20
30
|
png: {
|
|
@@ -78,7 +88,10 @@ function writeDocx(flow) {
|
|
|
78
88
|
mediaParts: [],
|
|
79
89
|
mediaFileByResource: /* @__PURE__ */ new Map(),
|
|
80
90
|
bookmarkSeq: 0,
|
|
81
|
-
drawingSeq: 0
|
|
91
|
+
drawingSeq: 0,
|
|
92
|
+
chartParts: [],
|
|
93
|
+
chartSeq: 0,
|
|
94
|
+
...flow.charts ? { charts: flow.charts } : {}
|
|
82
95
|
};
|
|
83
96
|
const docScope = newScope();
|
|
84
97
|
const extraParts = [];
|
|
@@ -112,6 +125,24 @@ function writeDocx(flow) {
|
|
|
112
125
|
target: "numbering.xml",
|
|
113
126
|
targetMode: "Internal"
|
|
114
127
|
});
|
|
128
|
+
emitNotes(flow.footnotes, {
|
|
129
|
+
noteKind: "footnote",
|
|
130
|
+
partPath: FOOTNOTES_PART,
|
|
131
|
+
rootTag: "w:footnotes",
|
|
132
|
+
noteTag: "w:footnote",
|
|
133
|
+
contentType: FOOTNOTES_CONTENT_TYPE,
|
|
134
|
+
relType: REL_FOOTNOTES,
|
|
135
|
+
target: "footnotes.xml"
|
|
136
|
+
}, state, losses, docScope, extraParts, extraPartRels);
|
|
137
|
+
emitNotes(flow.endnotes, {
|
|
138
|
+
noteKind: "endnote",
|
|
139
|
+
partPath: ENDNOTES_PART,
|
|
140
|
+
rootTag: "w:endnotes",
|
|
141
|
+
noteTag: "w:endnote",
|
|
142
|
+
contentType: ENDNOTES_CONTENT_TYPE,
|
|
143
|
+
relType: REL_ENDNOTES,
|
|
144
|
+
target: "endnotes.xml"
|
|
145
|
+
}, state, losses, docScope, extraParts, extraPartRels);
|
|
115
146
|
const partRelationships = [...docScope.rels.length > 0 ? [{
|
|
116
147
|
sourcePart: "word/document.xml",
|
|
117
148
|
relationships: docScope.rels
|
|
@@ -126,6 +157,7 @@ function writeDocx(flow) {
|
|
|
126
157
|
},
|
|
127
158
|
...numberingPart ? [numberingPart] : [],
|
|
128
159
|
...extraParts,
|
|
160
|
+
...state.chartParts,
|
|
129
161
|
...state.mediaParts
|
|
130
162
|
],
|
|
131
163
|
rootRelationships: [{
|
|
@@ -139,6 +171,34 @@ function writeDocx(flow) {
|
|
|
139
171
|
losses
|
|
140
172
|
};
|
|
141
173
|
}
|
|
174
|
+
function emitNotes(notes, cfg, state, losses, docScope, extraParts, extraPartRels) {
|
|
175
|
+
if (!notes || notes.size === 0) return;
|
|
176
|
+
const scope = newScope();
|
|
177
|
+
scope.noteKind = cfg.noteKind;
|
|
178
|
+
const stub = (type, id, mark) => `<${cfg.noteTag} w:type="${type}" w:id="${id}"><w:p><w:r>${mark}</w:r></w:p></${cfg.noteTag}>`;
|
|
179
|
+
const noteXmls = [];
|
|
180
|
+
for (const [id, content] of notes) {
|
|
181
|
+
const inner = [];
|
|
182
|
+
for (const el of content) emitBlock(inner, el, losses, state, scope);
|
|
183
|
+
noteXmls.push(`<${cfg.noteTag} w:id="${escapeAttr(id)}">${inner.join("") || "<w:p/>"}</${cfg.noteTag}>`);
|
|
184
|
+
}
|
|
185
|
+
const xml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><${cfg.rootTag} xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">` + stub("separator", -1, "<w:separator/>") + stub("continuationSeparator", 0, "<w:continuationSeparator/>") + noteXmls.join("") + `</${cfg.rootTag}>`;
|
|
186
|
+
extraParts.push({
|
|
187
|
+
path: cfg.partPath,
|
|
188
|
+
data: encoder.encode(xml),
|
|
189
|
+
contentType: cfg.contentType
|
|
190
|
+
});
|
|
191
|
+
if (scope.rels.length > 0) extraPartRels.push({
|
|
192
|
+
sourcePart: cfg.partPath,
|
|
193
|
+
relationships: scope.rels
|
|
194
|
+
});
|
|
195
|
+
docScope.rels.push({
|
|
196
|
+
id: `rId${++docScope.relSeq}`,
|
|
197
|
+
type: cfg.relType,
|
|
198
|
+
target: cfg.target,
|
|
199
|
+
targetMode: "Internal"
|
|
200
|
+
});
|
|
201
|
+
}
|
|
142
202
|
function emitHeadersFooters(flow, section, state, docScope, extraParts, extraPartRels, hfCache, losses) {
|
|
143
203
|
const refs = {
|
|
144
204
|
headers: [],
|
|
@@ -226,12 +286,37 @@ function emitBlock(out, el, losses, state, scope, closingSectPr) {
|
|
|
226
286
|
out.push(`<w:p>${pPrWithSect(el.shape.paragraphProperties, closingSectPr)}<w:r>${drawing}</w:r></w:p>`);
|
|
227
287
|
return;
|
|
228
288
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
289
|
+
const chartDrawing = chartBlockXml(el.chart, state, scope, losses);
|
|
290
|
+
if (chartDrawing) out.push(`<w:p>${pPrWithSect(el.chart.paragraphProperties, closingSectPr)}<w:r>${chartDrawing}</w:r></w:p>`);
|
|
291
|
+
else if (closingSectPr) out.push(`<w:p><w:pPr>${closingSectPr}</w:pPr></w:p>`);
|
|
292
|
+
}
|
|
293
|
+
function chartBlockXml(chart, state, scope, losses) {
|
|
294
|
+
const data = state.charts?.get(chart.chartRelId);
|
|
295
|
+
if (!data) {
|
|
296
|
+
losses.push({
|
|
297
|
+
severity: "dropped",
|
|
298
|
+
feature: FEATURES.charts,
|
|
299
|
+
detail: "chart data missing"
|
|
300
|
+
});
|
|
301
|
+
return "";
|
|
302
|
+
}
|
|
303
|
+
const cid = ++state.chartSeq;
|
|
304
|
+
state.chartParts.push({
|
|
305
|
+
path: `word/charts/chart${cid}.xml`,
|
|
306
|
+
data: encoder.encode(chartSpaceXml(data)),
|
|
307
|
+
contentType: CHART_CONTENT_TYPE
|
|
308
|
+
});
|
|
309
|
+
const relId = `rId${++scope.relSeq}`;
|
|
310
|
+
scope.rels.push({
|
|
311
|
+
id: relId,
|
|
312
|
+
type: REL_CHART,
|
|
313
|
+
target: `charts/chart${cid}.xml`,
|
|
314
|
+
targetMode: "Internal"
|
|
234
315
|
});
|
|
316
|
+
const cx = Math.round(chart.width * EMU_PER_PT);
|
|
317
|
+
const cy = Math.round(chart.height * EMU_PER_PT);
|
|
318
|
+
const id = ++state.drawingSeq;
|
|
319
|
+
return `<w:drawing><wp:inline xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"><wp:extent cx="${cx}" cy="${cy}"/><wp:docPr id="${id}" name="Chart ${id}"${chart.altText ? ` descr="${escapeAttr(chart.altText)}"` : ""}/><a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"><a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/chart"><c:chart xmlns:c="http://schemas.openxmlformats.org/drawingml/2006/chart" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" r:id="${relId}"/></a:graphicData></a:graphic></wp:inline></w:drawing>`;
|
|
235
320
|
}
|
|
236
321
|
function drawingXml(resource, widthPt, heightPt, altText, state, scope) {
|
|
237
322
|
if (resource === void 0) return "";
|
|
@@ -402,7 +487,7 @@ function cellMarginsXml(tag, margins) {
|
|
|
402
487
|
return inner ? `<${tag}>${inner}</${tag}>` : "";
|
|
403
488
|
}
|
|
404
489
|
function paragraphXml(p, state, scope, closingSectPr) {
|
|
405
|
-
const visible = p.runs.filter((run) => !run.listMarker && run.math
|
|
490
|
+
const visible = p.runs.filter((run) => !run.listMarker && (run.math !== void 0 || run.text !== "" || run.inlineImage !== void 0 || run.pageBreak || run.footnoteRef !== void 0 || run.endnoteRef !== void 0 || run.noteNumber === true || run.href !== void 0 || run.anchor !== void 0));
|
|
406
491
|
const inner = [];
|
|
407
492
|
let i = 0;
|
|
408
493
|
while (i < visible.length) {
|
|
@@ -440,6 +525,7 @@ function hyperlinkXml(run, inner, _state, scope) {
|
|
|
440
525
|
return `<w:hyperlink w:anchor="${escapeAttr(run.anchor)}">${inner}</w:hyperlink>`;
|
|
441
526
|
}
|
|
442
527
|
function runXml(run, state, scope) {
|
|
528
|
+
if (run.math !== void 0) return `<m:oMath xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math">${omathXml(run.math)}</m:oMath>`;
|
|
443
529
|
const rPr = rPrXml(run.properties);
|
|
444
530
|
if (run.inlineImage !== void 0) {
|
|
445
531
|
const img = run.inlineImage;
|
|
@@ -447,6 +533,9 @@ function runXml(run, state, scope) {
|
|
|
447
533
|
if (drawing) return `<w:r>${rPr}${drawing}</w:r>`;
|
|
448
534
|
if (run.text === "" && !run.pageBreak) return "";
|
|
449
535
|
}
|
|
536
|
+
if (run.footnoteRef !== void 0) return `<w:r>${rPr}<w:footnoteReference w:id="${escapeAttr(run.footnoteRef)}"/></w:r>`;
|
|
537
|
+
if (run.endnoteRef !== void 0) return `<w:r>${rPr}<w:endnoteReference w:id="${escapeAttr(run.endnoteRef)}"/></w:r>`;
|
|
538
|
+
if (run.noteNumber) return `<w:r>${rPr}<${scope.noteKind === "endnote" ? "w:endnoteRef" : "w:footnoteRef"}/></w:r>`;
|
|
450
539
|
const brk = run.pageBreak ? "<w:br w:type=\"page\"/>" : "";
|
|
451
540
|
if (run.text === "") return brk ? `<w:r>${rPr}${brk}</w:r>` : "";
|
|
452
541
|
return `<w:r>${rPr}<w:t xml:space="preserve">${escapeXml(run.text)}</w:t>${brk}</w:r>`;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
//#region src/word/omml-serializer.ts
|
|
2
|
+
function omathXml(node) {
|
|
3
|
+
return ser(node);
|
|
4
|
+
}
|
|
5
|
+
function ser(node) {
|
|
6
|
+
switch (node.type) {
|
|
7
|
+
case "row": return node.children.map(ser).join("");
|
|
8
|
+
case "run": return `<m:r>${runPr(node)}<m:t xml:space="preserve">${esc(node.text)}</m:t></m:r>`;
|
|
9
|
+
case "fraction": return "<m:f>" + (node.barless ? "<m:fPr><m:type m:val=\"noBar\"/></m:fPr>" : "") + `<m:num>${ser(node.num)}</m:num><m:den>${ser(node.den)}</m:den></m:f>`;
|
|
10
|
+
case "script": return scriptXml(node);
|
|
11
|
+
case "radical": return "<m:rad>" + (node.degree ? "" : "<m:radPr><m:degHide m:val=\"1\"/></m:radPr>") + (node.degree ? `<m:deg>${ser(node.degree)}</m:deg>` : "<m:deg/>") + `<m:e>${ser(node.radicand)}</m:e></m:rad>`;
|
|
12
|
+
case "nary": return `<m:nary><m:naryPr><m:chr m:val="${attr(node.op)}"/>` + (node.limLoc ? `<m:limLoc m:val="${node.limLoc}"/>` : "") + (node.sub ? "" : "<m:subHide m:val=\"1\"/>") + (node.sup ? "" : "<m:supHide m:val=\"1\"/>") + `</m:naryPr><m:sub>${node.sub ? ser(node.sub) : ""}</m:sub><m:sup>${node.sup ? ser(node.sup) : ""}</m:sup><m:e>${ser(node.body)}</m:e></m:nary>`;
|
|
13
|
+
case "func": return `<m:func><m:fName>${ser(node.name)}</m:fName><m:e>${ser(node.body)}</m:e></m:func>`;
|
|
14
|
+
case "limit": return `<m:lim${node.pos === "upp" ? "Upp" : "Low"}><m:e>${ser(node.base)}</m:e><m:lim>${ser(node.lim)}</m:lim></m:lim${node.pos === "upp" ? "Upp" : "Low"}>`;
|
|
15
|
+
case "delimiter": return `<m:d><m:dPr><m:begChr m:val="${attr(node.begChr)}"/>` + (node.sepChr !== void 0 ? `<m:sepChr m:val="${attr(node.sepChr)}"/>` : "") + `<m:endChr m:val="${attr(node.endChr)}"/></m:dPr>` + node.children.map((c) => `<m:e>${ser(c)}</m:e>`).join("") + "</m:d>";
|
|
16
|
+
case "matrix": return "<m:m>" + node.rows.map((row) => `<m:mr>${row.map((c) => `<m:e>${ser(c)}</m:e>`).join("")}</m:mr>`).join("") + "</m:m>";
|
|
17
|
+
case "accent": return `<m:acc><m:accPr><m:chr m:val="${attr(node.char)}"/></m:accPr><m:e>${ser(node.base)}</m:e></m:acc>`;
|
|
18
|
+
case "bar": return `<m:bar><m:barPr><m:pos m:val="${node.pos}"/></m:barPr><m:e>${ser(node.base)}</m:e></m:bar>`;
|
|
19
|
+
case "groupChr": return `<m:groupChr><m:groupChrPr><m:chr m:val="${attr(node.char)}"/><m:pos m:val="${node.pos}"/></m:groupChrPr><m:e>${ser(node.base)}</m:e></m:groupChr>`;
|
|
20
|
+
case "eqArr": return `<m:eqArr>${node.rows.map((r) => `<m:e>${ser(r)}</m:e>`).join("")}</m:eqArr>`;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function scriptXml(node) {
|
|
24
|
+
const base = `<m:e>${ser(node.base)}</m:e>`;
|
|
25
|
+
const sub = `<m:sub>${node.sub ? ser(node.sub) : ""}</m:sub>`;
|
|
26
|
+
const sup = `<m:sup>${node.sup ? ser(node.sup) : ""}</m:sup>`;
|
|
27
|
+
if (node.pre) return `<m:sPre>${base}${sub}${sup}</m:sPre>`;
|
|
28
|
+
if (node.sub && node.sup) return `<m:sSubSup>${base}${sub}${sup}</m:sSubSup>`;
|
|
29
|
+
if (node.sub) return `<m:sSub>${base}${sub}</m:sSub>`;
|
|
30
|
+
if (node.sup) return `<m:sSup>${base}${sup}</m:sSup>`;
|
|
31
|
+
return ser(node.base);
|
|
32
|
+
}
|
|
33
|
+
function runPr(run) {
|
|
34
|
+
let sty;
|
|
35
|
+
if (run.bold && run.italic) sty = "bi";
|
|
36
|
+
else if (run.bold) sty = "b";
|
|
37
|
+
else if (run.italic) sty = "i";
|
|
38
|
+
else if (run.nor) sty = "p";
|
|
39
|
+
return sty ? `<m:rPr><m:sty m:val="${sty}"/></m:rPr>` : "";
|
|
40
|
+
}
|
|
41
|
+
function esc(s) {
|
|
42
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
43
|
+
}
|
|
44
|
+
function attr(s) {
|
|
45
|
+
return esc(s).replace(/"/g, """);
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
export { omathXml };
|