@sametalmdr/word-editor 0.2.0-alpha → 0.3.0-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +108 -70
- package/LICENSE +21 -21
- package/README.md +286 -247
- package/dist/canvas/box-clipboard.js +238 -238
- package/dist/canvas/box-selection.js +80 -80
- package/dist/canvas/canvas-editor.js +502 -502
- package/dist/canvas/controller.js +717 -717
- package/dist/canvas/geometry.js +131 -131
- package/dist/canvas/gesture.d.ts +0 -1
- package/dist/canvas/gesture.js +289 -289
- package/dist/canvas/handles.js +212 -212
- package/dist/canvas/menu.js +82 -82
- package/dist/canvas/mode-controller.js +81 -81
- package/dist/canvas/nudge.js +108 -108
- package/dist/canvas/overlay.js +149 -149
- package/dist/canvas/snap.js +132 -132
- package/dist/canvas/viewport.js +112 -112
- package/dist/canvas/z-order.js +171 -171
- package/dist/commands/alignment.js +150 -150
- package/dist/commands/box.js +272 -272
- package/dist/commands/clipboard.js +93 -93
- package/dist/commands/graphemes.js +112 -112
- package/dist/commands/marks.js +153 -153
- package/dist/commands/paragraph-props.js +176 -176
- package/dist/commands/size.js +368 -368
- package/dist/commands/text.js +291 -291
- package/dist/docx.d.ts +1 -0
- package/dist/docx.js +15 -13
- package/dist/editor/editing-host.js +434 -434
- package/dist/element.js +41 -41
- package/dist/fonts/OFL-Carlito.txt +94 -0
- package/dist/fonts/calibri-metrics.d.ts +27 -0
- package/dist/fonts/calibri-metrics.js +88 -0
- package/dist/history/history.js +433 -433
- package/dist/index.js +63 -63
- package/dist/input/beforeinput.js +224 -224
- package/dist/input/clipboard.js +107 -107
- package/dist/input/composition.js +341 -341
- package/dist/input/keymap.js +54 -54
- package/dist/io/ooxml/custgeom.js +72 -72
- package/dist/io/ooxml/drawingml.js +232 -232
- package/dist/io/ooxml/edits.js +67 -67
- package/dist/io/ooxml/export-splice.js +880 -880
- package/dist/io/ooxml/export.js +365 -365
- package/dist/io/ooxml/fill.d.ts +93 -8
- package/dist/io/ooxml/fill.js +618 -562
- package/dist/io/ooxml/fit.d.ts +252 -0
- package/dist/io/ooxml/fit.js +598 -0
- package/dist/io/ooxml/import-gate.js +200 -200
- package/dist/io/ooxml/import.js +691 -691
- package/dist/io/ooxml/inflate.js +126 -126
- package/dist/io/ooxml/media.js +87 -87
- package/dist/io/ooxml/ns.d.ts +2 -0
- package/dist/io/ooxml/ns.js +26 -21
- package/dist/io/ooxml/numbering.js +290 -290
- package/dist/io/ooxml/opc-write.js +171 -171
- package/dist/io/ooxml/provenance.js +93 -93
- package/dist/io/ooxml/recipe.js +708 -708
- package/dist/io/ooxml/relationships.js +59 -59
- package/dist/io/ooxml/run-fonts.d.ts +89 -0
- package/dist/io/ooxml/run-fonts.js +378 -0
- package/dist/io/ooxml/slots.d.ts +4 -0
- package/dist/io/ooxml/slots.js +582 -581
- package/dist/io/ooxml/styles-resolve.js +257 -257
- package/dist/io/ooxml/units.js +43 -43
- package/dist/io/ooxml/value.js +120 -120
- package/dist/io/ooxml/xml-scan.js +616 -616
- package/dist/io/ooxml/zip-container.js +228 -228
- package/dist/io/ooxml/zip-reject.js +106 -106
- package/dist/measure/placement.js +83 -83
- package/dist/measure/report.js +75 -75
- package/dist/model/ids.js +70 -70
- package/dist/model/normalize.js +146 -146
- package/dist/model/position.js +283 -283
- package/dist/model/transaction.js +209 -209
- package/dist/platform/capabilities.js +25 -25
- package/dist/react/index.js +21 -21
- package/dist/selection/controller.js +315 -315
- package/dist/selection/dom-map.js +334 -334
- package/dist/state/editor-state.js +109 -109
- package/dist/styles.css +66 -66
- package/package.json +94 -93
package/dist/io/ooxml/inflate.js
CHANGED
|
@@ -1,126 +1,126 @@
|
|
|
1
|
-
// The ONE file in src/io/ooxml/ that touches a platform global (ADR-0020
|
|
2
|
-
// Karar 8, §1/§7's own precedent in src/measure/probe.js). Decompression is
|
|
3
|
-
// lazy and per-entry: zip-container.js never decompresses anything, so a
|
|
4
|
-
// caller only pays this async cost for the members it actually needs —
|
|
5
|
-
// e.g. it can list every part in a package and read sizes/names without
|
|
6
|
-
// ever calling this function.
|
|
7
|
-
//
|
|
8
|
-
// `typeof DecompressionStream !== "undefined"` is checked INSIDE the
|
|
9
|
-
// function, never at module scope (invariant #5, ssr-safety) — importing
|
|
10
|
-
// this file must never touch a global, only calling inflateEntry() may.
|
|
11
|
-
// Measured 2026-08-27: DecompressionStream is a Node v24.13.1 global,
|
|
12
|
-
// matching ADR-0020's own claim, but WHICH Node 18.x point release first
|
|
13
|
-
// made it global (vs. requiring an import) was not checked — the lazy
|
|
14
|
-
// `typeof` guard means that question never has to be answered, the code is
|
|
15
|
-
// correct either way, and a missing API fails at the first call, not at
|
|
16
|
-
// import time.
|
|
17
|
-
//
|
|
18
|
-
// CRC-32 verification happens HERE, not in zip-container.js, because it
|
|
19
|
-
// needs decompressed bytes — the pure/sync layer never sees them. Ported
|
|
20
|
-
// check from scripts/opc-bytes.py's read_package(): "CRC-32 mismatch in the
|
|
21
|
-
// SOURCE file — refusing to touch it" (never trust the central directory's
|
|
22
|
-
// claim alone).
|
|
23
|
-
|
|
24
|
-
const METHOD_STORE = 0;
|
|
25
|
-
|
|
26
|
-
/** @type {Uint32Array | null} */
|
|
27
|
-
let crcTable = null;
|
|
28
|
-
|
|
29
|
-
function getCrcTable() {
|
|
30
|
-
if (crcTable !== null) return crcTable;
|
|
31
|
-
const table = new Uint32Array(256);
|
|
32
|
-
for (let n = 0; n < 256; n++) {
|
|
33
|
-
let c = n;
|
|
34
|
-
for (let k = 0; k < 8; k++) {
|
|
35
|
-
c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
|
|
36
|
-
}
|
|
37
|
-
table[n] = c >>> 0;
|
|
38
|
-
}
|
|
39
|
-
crcTable = table;
|
|
40
|
-
return table;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Standard CRC-32 (ISO 3309 / PKZIP / zlib) — the same algorithm
|
|
45
|
-
* `zlib.crc32` implements in Python, bit for bit. A fixed, well-known
|
|
46
|
-
* specification, not a guess.
|
|
47
|
-
* @param {Uint8Array} bytes
|
|
48
|
-
* @returns {number}
|
|
49
|
-
*/
|
|
50
|
-
export function crc32(bytes) {
|
|
51
|
-
const table = getCrcTable();
|
|
52
|
-
let crc = 0xffffffff;
|
|
53
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
54
|
-
crc = table[(crc ^ bytes[i]) & 0xff] ^ (crc >>> 8);
|
|
55
|
-
}
|
|
56
|
-
return (crc ^ 0xffffffff) >>> 0;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* @param {ReadableStream<Uint8Array>} stream
|
|
61
|
-
* @returns {Promise<Uint8Array>}
|
|
62
|
-
*/
|
|
63
|
-
async function collect(stream) {
|
|
64
|
-
const reader = stream.getReader();
|
|
65
|
-
/** @type {Uint8Array[]} */
|
|
66
|
-
const chunks = [];
|
|
67
|
-
let total = 0;
|
|
68
|
-
for (;;) {
|
|
69
|
-
const { done, value } = await reader.read();
|
|
70
|
-
if (done) break;
|
|
71
|
-
chunks.push(value);
|
|
72
|
-
total += value.length;
|
|
73
|
-
}
|
|
74
|
-
const out = new Uint8Array(total);
|
|
75
|
-
let offset = 0;
|
|
76
|
-
for (const chunk of chunks) {
|
|
77
|
-
out.set(chunk, offset);
|
|
78
|
-
offset += chunk.length;
|
|
79
|
-
}
|
|
80
|
-
return out;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Decompresses one zip entry's compressed bytes and verifies the result
|
|
85
|
-
* against its RECORDED crc32 — never trusts the central directory's claim
|
|
86
|
-
* alone (the exact discipline scripts/opc-bytes.py's read_package() already
|
|
87
|
-
* applies). Throws if the platform has no DecompressionStream, or if the
|
|
88
|
-
* decompressed content's own CRC-32 does not match the entry's recorded one.
|
|
89
|
-
*
|
|
90
|
-
* @param {Uint8Array} bytes the full original buffer parseZipContainer() was given
|
|
91
|
-
* @param {import("./zip-container.js").ZipEntry} entry
|
|
92
|
-
* @returns {Promise<Uint8Array>}
|
|
93
|
-
*/
|
|
94
|
-
export async function inflateEntry(bytes, entry) {
|
|
95
|
-
const compressed = bytes.subarray(entry.compressedRange.start, entry.compressedRange.end);
|
|
96
|
-
|
|
97
|
-
let content;
|
|
98
|
-
if (entry.method === METHOD_STORE) {
|
|
99
|
-
content = compressed;
|
|
100
|
-
} else {
|
|
101
|
-
if (typeof DecompressionStream === "undefined") {
|
|
102
|
-
throw new Error(
|
|
103
|
-
`inflate: ${JSON.stringify(entry.name)} needs DecompressionStream ("deflate-raw"), which this ` +
|
|
104
|
-
"runtime does not provide"
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
const source = new ReadableStream({
|
|
108
|
-
start(controller) {
|
|
109
|
-
controller.enqueue(compressed);
|
|
110
|
-
controller.close();
|
|
111
|
-
},
|
|
112
|
-
});
|
|
113
|
-
const decompressedStream = source.pipeThrough(new DecompressionStream("deflate-raw"));
|
|
114
|
-
content = await collect(decompressedStream);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const actual = crc32(content);
|
|
118
|
-
if (actual !== entry.crc32) {
|
|
119
|
-
throw new Error(
|
|
120
|
-
`inflate: ${JSON.stringify(entry.name)}: CRC-32 mismatch after decompression ` +
|
|
121
|
-
`(expected ${entry.crc32.toString(16)}, got ${actual.toString(16)}) — refusing to use this content`
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return content;
|
|
126
|
-
}
|
|
1
|
+
// The ONE file in src/io/ooxml/ that touches a platform global (ADR-0020
|
|
2
|
+
// Karar 8, §1/§7's own precedent in src/measure/probe.js). Decompression is
|
|
3
|
+
// lazy and per-entry: zip-container.js never decompresses anything, so a
|
|
4
|
+
// caller only pays this async cost for the members it actually needs —
|
|
5
|
+
// e.g. it can list every part in a package and read sizes/names without
|
|
6
|
+
// ever calling this function.
|
|
7
|
+
//
|
|
8
|
+
// `typeof DecompressionStream !== "undefined"` is checked INSIDE the
|
|
9
|
+
// function, never at module scope (invariant #5, ssr-safety) — importing
|
|
10
|
+
// this file must never touch a global, only calling inflateEntry() may.
|
|
11
|
+
// Measured 2026-08-27: DecompressionStream is a Node v24.13.1 global,
|
|
12
|
+
// matching ADR-0020's own claim, but WHICH Node 18.x point release first
|
|
13
|
+
// made it global (vs. requiring an import) was not checked — the lazy
|
|
14
|
+
// `typeof` guard means that question never has to be answered, the code is
|
|
15
|
+
// correct either way, and a missing API fails at the first call, not at
|
|
16
|
+
// import time.
|
|
17
|
+
//
|
|
18
|
+
// CRC-32 verification happens HERE, not in zip-container.js, because it
|
|
19
|
+
// needs decompressed bytes — the pure/sync layer never sees them. Ported
|
|
20
|
+
// check from scripts/opc-bytes.py's read_package(): "CRC-32 mismatch in the
|
|
21
|
+
// SOURCE file — refusing to touch it" (never trust the central directory's
|
|
22
|
+
// claim alone).
|
|
23
|
+
|
|
24
|
+
const METHOD_STORE = 0;
|
|
25
|
+
|
|
26
|
+
/** @type {Uint32Array | null} */
|
|
27
|
+
let crcTable = null;
|
|
28
|
+
|
|
29
|
+
function getCrcTable() {
|
|
30
|
+
if (crcTable !== null) return crcTable;
|
|
31
|
+
const table = new Uint32Array(256);
|
|
32
|
+
for (let n = 0; n < 256; n++) {
|
|
33
|
+
let c = n;
|
|
34
|
+
for (let k = 0; k < 8; k++) {
|
|
35
|
+
c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
|
|
36
|
+
}
|
|
37
|
+
table[n] = c >>> 0;
|
|
38
|
+
}
|
|
39
|
+
crcTable = table;
|
|
40
|
+
return table;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Standard CRC-32 (ISO 3309 / PKZIP / zlib) — the same algorithm
|
|
45
|
+
* `zlib.crc32` implements in Python, bit for bit. A fixed, well-known
|
|
46
|
+
* specification, not a guess.
|
|
47
|
+
* @param {Uint8Array} bytes
|
|
48
|
+
* @returns {number}
|
|
49
|
+
*/
|
|
50
|
+
export function crc32(bytes) {
|
|
51
|
+
const table = getCrcTable();
|
|
52
|
+
let crc = 0xffffffff;
|
|
53
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
54
|
+
crc = table[(crc ^ bytes[i]) & 0xff] ^ (crc >>> 8);
|
|
55
|
+
}
|
|
56
|
+
return (crc ^ 0xffffffff) >>> 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @param {ReadableStream<Uint8Array>} stream
|
|
61
|
+
* @returns {Promise<Uint8Array>}
|
|
62
|
+
*/
|
|
63
|
+
async function collect(stream) {
|
|
64
|
+
const reader = stream.getReader();
|
|
65
|
+
/** @type {Uint8Array[]} */
|
|
66
|
+
const chunks = [];
|
|
67
|
+
let total = 0;
|
|
68
|
+
for (;;) {
|
|
69
|
+
const { done, value } = await reader.read();
|
|
70
|
+
if (done) break;
|
|
71
|
+
chunks.push(value);
|
|
72
|
+
total += value.length;
|
|
73
|
+
}
|
|
74
|
+
const out = new Uint8Array(total);
|
|
75
|
+
let offset = 0;
|
|
76
|
+
for (const chunk of chunks) {
|
|
77
|
+
out.set(chunk, offset);
|
|
78
|
+
offset += chunk.length;
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Decompresses one zip entry's compressed bytes and verifies the result
|
|
85
|
+
* against its RECORDED crc32 — never trusts the central directory's claim
|
|
86
|
+
* alone (the exact discipline scripts/opc-bytes.py's read_package() already
|
|
87
|
+
* applies). Throws if the platform has no DecompressionStream, or if the
|
|
88
|
+
* decompressed content's own CRC-32 does not match the entry's recorded one.
|
|
89
|
+
*
|
|
90
|
+
* @param {Uint8Array} bytes the full original buffer parseZipContainer() was given
|
|
91
|
+
* @param {import("./zip-container.js").ZipEntry} entry
|
|
92
|
+
* @returns {Promise<Uint8Array>}
|
|
93
|
+
*/
|
|
94
|
+
export async function inflateEntry(bytes, entry) {
|
|
95
|
+
const compressed = bytes.subarray(entry.compressedRange.start, entry.compressedRange.end);
|
|
96
|
+
|
|
97
|
+
let content;
|
|
98
|
+
if (entry.method === METHOD_STORE) {
|
|
99
|
+
content = compressed;
|
|
100
|
+
} else {
|
|
101
|
+
if (typeof DecompressionStream === "undefined") {
|
|
102
|
+
throw new Error(
|
|
103
|
+
`inflate: ${JSON.stringify(entry.name)} needs DecompressionStream ("deflate-raw"), which this ` +
|
|
104
|
+
"runtime does not provide"
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
const source = new ReadableStream({
|
|
108
|
+
start(controller) {
|
|
109
|
+
controller.enqueue(compressed);
|
|
110
|
+
controller.close();
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
const decompressedStream = source.pipeThrough(new DecompressionStream("deflate-raw"));
|
|
114
|
+
content = await collect(decompressedStream);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const actual = crc32(content);
|
|
118
|
+
if (actual !== entry.crc32) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
`inflate: ${JSON.stringify(entry.name)}: CRC-32 mismatch after decompression ` +
|
|
121
|
+
`(expected ${entry.crc32.toString(16)}, got ${actual.toString(16)}) — refusing to use this content`
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return content;
|
|
126
|
+
}
|
package/dist/io/ooxml/media.js
CHANGED
|
@@ -1,87 +1,87 @@
|
|
|
1
|
-
// Turns the retained package's media parts into `data:` URIs the renderer can
|
|
2
|
-
// put on a Doc.image's `<img src>`. Read-only: it inflates `word/media/*` from
|
|
3
|
-
// the ALREADY-retained package bytes (provenance.packageBytes/entries) with no
|
|
4
|
-
// network access at all (invariant #3 — no request is ever issued), and the
|
|
5
|
-
// export path is untouched (the media part stays a verbatim OPC part). base64
|
|
6
|
-
// is a pure encoder rather than btoa/Buffer so nothing depends on a platform
|
|
7
|
-
// global.
|
|
8
|
-
|
|
9
|
-
import { inflateEntry } from "./inflate.js";
|
|
10
|
-
|
|
11
|
-
/** @type {Record<string, string>} */
|
|
12
|
-
const IMAGE_MIME = {
|
|
13
|
-
png: "image/png",
|
|
14
|
-
jpg: "image/jpeg",
|
|
15
|
-
jpeg: "image/jpeg",
|
|
16
|
-
gif: "image/gif",
|
|
17
|
-
bmp: "image/bmp",
|
|
18
|
-
tif: "image/tiff",
|
|
19
|
-
tiff: "image/tiff",
|
|
20
|
-
svg: "image/svg+xml",
|
|
21
|
-
webp: "image/webp",
|
|
22
|
-
emf: "image/emf",
|
|
23
|
-
wmf: "image/wmf",
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* The image MIME type for a media part, from its extension. A wrong MIME breaks
|
|
28
|
-
* the `<img>`, so this maps the extensions the corpus actually uses; an unknown
|
|
29
|
-
* one falls back to the generic binary type (the browser sniffs common formats).
|
|
30
|
-
* @param {string} partName
|
|
31
|
-
* @returns {string}
|
|
32
|
-
*/
|
|
33
|
-
function mimeForPart(partName) {
|
|
34
|
-
const dot = partName.lastIndexOf(".");
|
|
35
|
-
const ext = dot === -1 ? "" : partName.slice(dot + 1).toLowerCase();
|
|
36
|
-
return IMAGE_MIME[ext] ?? "application/octet-stream";
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Standard base64 of a byte array — pure, no `btoa`/`Buffer` (invariant #5:
|
|
43
|
-
* nothing platform-specific at import or here).
|
|
44
|
-
* @param {Uint8Array} bytes
|
|
45
|
-
* @returns {string}
|
|
46
|
-
*/
|
|
47
|
-
function toBase64(bytes) {
|
|
48
|
-
let out = "";
|
|
49
|
-
for (let i = 0; i < bytes.length; i += 3) {
|
|
50
|
-
const b0 = bytes[i];
|
|
51
|
-
const b1 = i + 1 < bytes.length ? bytes[i + 1] : -1;
|
|
52
|
-
const b2 = i + 2 < bytes.length ? bytes[i + 2] : -1;
|
|
53
|
-
out += B64[b0 >> 2];
|
|
54
|
-
out += B64[((b0 & 3) << 4) | (b1 < 0 ? 0 : b1 >> 4)];
|
|
55
|
-
out += b1 < 0 ? "=" : B64[((b1 & 15) << 2) | (b2 < 0 ? 0 : b2 >> 6)];
|
|
56
|
-
out += b2 < 0 ? "=" : B64[b2 & 63];
|
|
57
|
-
}
|
|
58
|
-
return out;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* `mediaPartName` -> `data:` URI for every image `doc` references, read from the
|
|
63
|
-
* retained package. Only the parts actually referenced by `Doc.images` are
|
|
64
|
-
* decoded. An empty/absent package (convertDocument's provenance, no images) or
|
|
65
|
-
* a missing part yields no entry for that name — the renderer then draws the
|
|
66
|
-
* `<img>` without a `src`.
|
|
67
|
-
*
|
|
68
|
-
* @param {import("../../model/node.js").Doc} doc
|
|
69
|
-
* @param {import("./provenance.js").Provenance} provenance
|
|
70
|
-
* @returns {Promise<Map<string, string>>}
|
|
71
|
-
*/
|
|
72
|
-
export async function buildMediaMap(doc, provenance) {
|
|
73
|
-
/** @type {Map<string, string>} */
|
|
74
|
-
const map = new Map();
|
|
75
|
-
const { packageBytes, entries } = provenance;
|
|
76
|
-
if (packageBytes === undefined || entries === undefined) {
|
|
77
|
-
return map;
|
|
78
|
-
}
|
|
79
|
-
const wanted = new Set(doc.images.map((im) => im.mediaPartName));
|
|
80
|
-
for (const partName of wanted) {
|
|
81
|
-
const entry = entries.find((e) => e.name === partName);
|
|
82
|
-
if (entry === undefined) continue;
|
|
83
|
-
const bytes = await inflateEntry(packageBytes, entry);
|
|
84
|
-
map.set(partName, `data:${mimeForPart(partName)};base64,${toBase64(bytes)}`);
|
|
85
|
-
}
|
|
86
|
-
return map;
|
|
87
|
-
}
|
|
1
|
+
// Turns the retained package's media parts into `data:` URIs the renderer can
|
|
2
|
+
// put on a Doc.image's `<img src>`. Read-only: it inflates `word/media/*` from
|
|
3
|
+
// the ALREADY-retained package bytes (provenance.packageBytes/entries) with no
|
|
4
|
+
// network access at all (invariant #3 — no request is ever issued), and the
|
|
5
|
+
// export path is untouched (the media part stays a verbatim OPC part). base64
|
|
6
|
+
// is a pure encoder rather than btoa/Buffer so nothing depends on a platform
|
|
7
|
+
// global.
|
|
8
|
+
|
|
9
|
+
import { inflateEntry } from "./inflate.js";
|
|
10
|
+
|
|
11
|
+
/** @type {Record<string, string>} */
|
|
12
|
+
const IMAGE_MIME = {
|
|
13
|
+
png: "image/png",
|
|
14
|
+
jpg: "image/jpeg",
|
|
15
|
+
jpeg: "image/jpeg",
|
|
16
|
+
gif: "image/gif",
|
|
17
|
+
bmp: "image/bmp",
|
|
18
|
+
tif: "image/tiff",
|
|
19
|
+
tiff: "image/tiff",
|
|
20
|
+
svg: "image/svg+xml",
|
|
21
|
+
webp: "image/webp",
|
|
22
|
+
emf: "image/emf",
|
|
23
|
+
wmf: "image/wmf",
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The image MIME type for a media part, from its extension. A wrong MIME breaks
|
|
28
|
+
* the `<img>`, so this maps the extensions the corpus actually uses; an unknown
|
|
29
|
+
* one falls back to the generic binary type (the browser sniffs common formats).
|
|
30
|
+
* @param {string} partName
|
|
31
|
+
* @returns {string}
|
|
32
|
+
*/
|
|
33
|
+
function mimeForPart(partName) {
|
|
34
|
+
const dot = partName.lastIndexOf(".");
|
|
35
|
+
const ext = dot === -1 ? "" : partName.slice(dot + 1).toLowerCase();
|
|
36
|
+
return IMAGE_MIME[ext] ?? "application/octet-stream";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Standard base64 of a byte array — pure, no `btoa`/`Buffer` (invariant #5:
|
|
43
|
+
* nothing platform-specific at import or here).
|
|
44
|
+
* @param {Uint8Array} bytes
|
|
45
|
+
* @returns {string}
|
|
46
|
+
*/
|
|
47
|
+
function toBase64(bytes) {
|
|
48
|
+
let out = "";
|
|
49
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
50
|
+
const b0 = bytes[i];
|
|
51
|
+
const b1 = i + 1 < bytes.length ? bytes[i + 1] : -1;
|
|
52
|
+
const b2 = i + 2 < bytes.length ? bytes[i + 2] : -1;
|
|
53
|
+
out += B64[b0 >> 2];
|
|
54
|
+
out += B64[((b0 & 3) << 4) | (b1 < 0 ? 0 : b1 >> 4)];
|
|
55
|
+
out += b1 < 0 ? "=" : B64[((b1 & 15) << 2) | (b2 < 0 ? 0 : b2 >> 6)];
|
|
56
|
+
out += b2 < 0 ? "=" : B64[b2 & 63];
|
|
57
|
+
}
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* `mediaPartName` -> `data:` URI for every image `doc` references, read from the
|
|
63
|
+
* retained package. Only the parts actually referenced by `Doc.images` are
|
|
64
|
+
* decoded. An empty/absent package (convertDocument's provenance, no images) or
|
|
65
|
+
* a missing part yields no entry for that name — the renderer then draws the
|
|
66
|
+
* `<img>` without a `src`.
|
|
67
|
+
*
|
|
68
|
+
* @param {import("../../model/node.js").Doc} doc
|
|
69
|
+
* @param {import("./provenance.js").Provenance} provenance
|
|
70
|
+
* @returns {Promise<Map<string, string>>}
|
|
71
|
+
*/
|
|
72
|
+
export async function buildMediaMap(doc, provenance) {
|
|
73
|
+
/** @type {Map<string, string>} */
|
|
74
|
+
const map = new Map();
|
|
75
|
+
const { packageBytes, entries } = provenance;
|
|
76
|
+
if (packageBytes === undefined || entries === undefined) {
|
|
77
|
+
return map;
|
|
78
|
+
}
|
|
79
|
+
const wanted = new Set(doc.images.map((im) => im.mediaPartName));
|
|
80
|
+
for (const partName of wanted) {
|
|
81
|
+
const entry = entries.find((e) => e.name === partName);
|
|
82
|
+
if (entry === undefined) continue;
|
|
83
|
+
const bytes = await inflateEntry(packageBytes, entry);
|
|
84
|
+
map.set(partName, `data:${mimeForPart(partName)};base64,${toBase64(bytes)}`);
|
|
85
|
+
}
|
|
86
|
+
return map;
|
|
87
|
+
}
|
package/dist/io/ooxml/ns.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ export const WP_NS: "http://schemas.openxmlformats.org/drawingml/2006/wordproces
|
|
|
3
3
|
export const A_NS: "http://schemas.openxmlformats.org/drawingml/2006/main";
|
|
4
4
|
export const WPS_NS: "http://schemas.microsoft.com/office/word/2010/wordprocessingShape";
|
|
5
5
|
export const WPG_NS: "http://schemas.microsoft.com/office/word/2010/wordprocessingGroup";
|
|
6
|
+
export const WP14_NS: "http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing";
|
|
7
|
+
export const WORD_COMPAT_URI: "http://schemas.microsoft.com/office/word";
|
|
6
8
|
export const PIC_NS: "http://schemas.openxmlformats.org/drawingml/2006/picture";
|
|
7
9
|
export const MC_NS: "http://schemas.openxmlformats.org/markup-compatibility/2006";
|
|
8
10
|
export const R_NS: "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
package/dist/io/ooxml/ns.js
CHANGED
|
@@ -1,21 +1,26 @@
|
|
|
1
|
-
// Shared OOXML namespace URIs. One definition, several consumers
|
|
2
|
-
// (drawingml.js, sections.js, import-gate.js, custgeom.js) — a namespace URI
|
|
3
|
-
// written twice risks the exact drift this project's own
|
|
4
|
-
// scripts/network-patterns.js already had once (a copy in verify.js, a copy
|
|
5
|
-
// in the test, one updated when P2 added the SVG namespace, the other not).
|
|
6
|
-
|
|
7
|
-
export const W_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
8
|
-
export const WP_NS = "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing";
|
|
9
|
-
export const A_NS = "http://schemas.openxmlformats.org/drawingml/2006/main";
|
|
10
|
-
export const WPS_NS = "http://schemas.microsoft.com/office/word/2010/wordprocessingShape";
|
|
11
|
-
export const WPG_NS = "http://schemas.microsoft.com/office/word/2010/wordprocessingGroup";
|
|
12
|
-
|
|
13
|
-
export const
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
export const
|
|
19
|
-
// The `
|
|
20
|
-
|
|
21
|
-
|
|
1
|
+
// Shared OOXML namespace URIs. One definition, several consumers
|
|
2
|
+
// (drawingml.js, sections.js, import-gate.js, custgeom.js) — a namespace URI
|
|
3
|
+
// written twice risks the exact drift this project's own
|
|
4
|
+
// scripts/network-patterns.js already had once (a copy in verify.js, a copy
|
|
5
|
+
// in the test, one updated when P2 added the SVG namespace, the other not).
|
|
6
|
+
|
|
7
|
+
export const W_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
8
|
+
export const WP_NS = "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing";
|
|
9
|
+
export const A_NS = "http://schemas.openxmlformats.org/drawingml/2006/main";
|
|
10
|
+
export const WPS_NS = "http://schemas.microsoft.com/office/word/2010/wordprocessingShape";
|
|
11
|
+
export const WPG_NS = "http://schemas.microsoft.com/office/word/2010/wordprocessingGroup";
|
|
12
|
+
// Word 2010's drawing extensions — checkFit reads wp14:sizeRelH (a box sized as a share of the page).
|
|
13
|
+
export const WP14_NS = "http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing";
|
|
14
|
+
// Not an element namespace: the value of w:compatSetting@w:uri that names Word's own compatibility
|
|
15
|
+
// settings (compatibilityMode). An identifier, never fetched.
|
|
16
|
+
export const WORD_COMPAT_URI = "http://schemas.microsoft.com/office/word";
|
|
17
|
+
export const PIC_NS = "http://schemas.openxmlformats.org/drawingml/2006/picture";
|
|
18
|
+
export const MC_NS = "http://schemas.openxmlformats.org/markup-compatibility/2006";
|
|
19
|
+
// The `r:` prefix on `<a:blip r:embed="rIdN">` — an officeDocument relationship id.
|
|
20
|
+
export const R_NS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
21
|
+
// The namespace of `<Relationship>` inside a *_rels/*.rels part (a different URI
|
|
22
|
+
// from R_NS — the package relationships schema, not the officeDocument one).
|
|
23
|
+
export const PKG_RELS_NS = "http://schemas.openxmlformats.org/package/2006/relationships";
|
|
24
|
+
// The `xml:` prefix's own namespace — predeclared, never written in a document
|
|
25
|
+
// (`xml:space` on `w:t`). An identifier, already on verify's whitelist.
|
|
26
|
+
export const XML_NS = "http://www.w3.org/XML/1998/namespace";
|