wpd-codec 3.3.0 → 3.4.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 +6 -1
- package/dist/bytes/base64.cjs +21 -0
- package/dist/bytes/base64.d.cts +4 -0
- package/dist/bytes/base64.d.ts +4 -0
- package/dist/bytes/base64.js +20 -0
- package/dist/{diagnostics-Dtb2uPw4.d.cts → diagnostics-DhO7mgqJ.d.cts} +1 -0
- package/dist/{diagnostics-Dtb2uPw4.d.ts → diagnostics-DhO7mgqJ.d.ts} +1 -0
- package/dist/diagnostics.cjs +1 -0
- package/dist/diagnostics.d.cts +1 -1
- package/dist/diagnostics.d.ts +1 -1
- package/dist/diagnostics.js +1 -0
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/read.cjs +176 -6
- package/dist/read.d.cts +8 -3
- package/dist/read.d.ts +8 -3
- package/dist/read.js +176 -6
- package/dist/stream/furniture.cjs +33 -0
- package/dist/stream/furniture.d.cts +15 -0
- package/dist/stream/furniture.d.ts +15 -0
- package/dist/stream/furniture.js +25 -0
- package/dist/stream/image.cjs +72 -0
- package/dist/stream/image.d.cts +8 -0
- package/dist/stream/image.d.ts +8 -0
- package/dist/stream/image.js +71 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -174,7 +174,12 @@ This is a bet against libwpd's _code_, not against citing its _data_: the charac
|
|
|
174
174
|
|
|
175
175
|
Everything below is recognised by the tokeniser and skipped by the fold, so a document containing it still reads — losing that construct's own structure, never the surrounding text. Each is reported through the diagnostic sink rather than passed over in silence.
|
|
176
176
|
|
|
177
|
-
- **
|
|
177
|
+
- **A box whose content is a presentation, video, macro, sound, or external payload**, and a box relying on its own template's inherited geometry rather than an explicit function-level position/size override. Reported through `wpd/box-content-unresolved` and `wpd/box-frame-unresolved` respectively. An image box IS lifted when its content packet carries a whole PNG or JPEG payload: the packet's raw bytes are scanned by signature and structural walk (`src/stream/image.ts` — the chunk chain to IEND for PNG, the marker segments to EOI for JPEG), never by guessing at a container header, and the span lifts as a `ContentImageBlock` sized by the box's own frame, with an absolute-from-page-edge position carried as the image's `floatPosition`. What stays unresolved: a WPG vector graphic (a distinct binary graphics format `ContentImageBlock`'s `png`/`jpeg`/`svg`/`gif` set cannot hold as recovered, and which this package does not decode — a project on the scale of this package's own WordPerfect reader, not a wiring job) and a native OLE object (see the next bullet). A box with no function-level content override at all — relying entirely on its template's own rendering defaults — is reported through `wpd/box-dropped`, unchanged from before.
|
|
178
|
+
- **Watermarks** (the 0xD6 group's two watermark subfunctions). Reported through `wpd/header-footer-dropped`: a watermark is neither a header nor a footer and owns no parity, so the shared page-furniture vocabulary has no slot for one.
|
|
179
|
+
- **A second header or footer claiming a slot a first already filled.** WordPerfect's own A/B two-slot-per-kind mechanism is a shape the shared one-flow-per-slot vocabulary does not carry; the first function to claim a slot is the one lifted, and the collision is reported through `wpd/header-footer-dropped`.
|
|
180
|
+
|
|
181
|
+
Headers and footers themselves are LIFTED (ExaDev/documents.js#1128): a D6 function's occurrence bits narrow onto the shared furniture vocabulary's slots (`ContentSection.headers`/`footers`, `default`/`even` — odd-only and both-parities are the default slot, even-only the even slot), its body folded from the General WP Text packet its first prefix ID names. Footnotes and endnotes are anchored in the flat form (a footnote/endnote anchor construct around the reference site, `definition` naming `note-1`, `note-2`, ... in document order) and their bodies are carried by `readWpd` as definitions-table entries in the tree form — the flat `readWpdContent` reports each still-borne body through `wpd/note-dropped`, since the flat `ContentDocument` genuinely has no home for one (`rtf-codec` documents the same split for its own equivalent constructs).
|
|
182
|
+
|
|
178
183
|
- **A box whose content is an image, presentation, video, macro, sound, or external payload**, and a box relying on its own template's inherited geometry rather than an explicit function-level position/size override. Reported through `wpd/box-content-unresolved` and `wpd/box-frame-unresolved` respectively. An image box's own content resolves to a Graphics Filename prefix packet (type 0x40) whose children carry either WPG vector graphics (a distinct binary graphics format `ContentImageBlock`'s `png`/`jpeg`/`svg`/`gif` set cannot hold as recovered, and which this package does not decode — a project on the scale of this package's own WordPerfect reader, not a wiring job) or a native OLE object (see the next bullet). A box with no function-level content override at all — relying entirely on its template's own rendering defaults — is reported through `wpd/box-dropped`, unchanged from before.
|
|
179
184
|
- **Embedded OLE objects**, stored under the compound file's `PerfectOffice_OBJECTS` storage and named by an image box's Graphics Filename packet's own `0x70`/`0x71` (OLE Object Descriptor / OLE Object Data) children. `archive-codec`'s compound-file reader already reaches that storage, which is how `ooxml.js` recovers a ZIP-payload embedded object — but a WordPerfect OLE object's payload is a native OLE server's own stream rather than a nested document package (`ooxml.js`'s own equivalent case, a classic OLE1 `.bin` payload with no `Package` stream, stays opaque by the identical scope boundary), so recovering one generically is a project in its own right, not a wiring job.
|
|
180
185
|
- **The counter groups** (0xD8, 0xD9, 0xDB, 0xDC): setting, numbering-method, increment and decrement carry no text and change no structure this reader models, so only the Display Number group's own paragraph-number pair is read.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_bytes_view = require("./view.cjs");
|
|
3
|
+
//#region src/bytes/base64.ts
|
|
4
|
+
const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
5
|
+
function bytesToBase64(bytes) {
|
|
6
|
+
let out = "";
|
|
7
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
8
|
+
const b0 = require_bytes_view.byteAt(bytes, i);
|
|
9
|
+
const b1 = bytes[i + 1];
|
|
10
|
+
const b2 = bytes[i + 2];
|
|
11
|
+
out += ALPHABET.charAt(b0 >>> 2);
|
|
12
|
+
out += ALPHABET.charAt((b0 & 3) << 4 | (b1 ?? 0) >>> 4);
|
|
13
|
+
if (b1 === void 0) return out + "==";
|
|
14
|
+
out += ALPHABET.charAt((b1 & 15) << 2 | (b2 ?? 0) >>> 6);
|
|
15
|
+
if (b2 === void 0) return out + "=";
|
|
16
|
+
out += ALPHABET.charAt(b2 & 63);
|
|
17
|
+
}
|
|
18
|
+
return out;
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
exports.bytesToBase64 = bytesToBase64;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { byteAt } from "./view.js";
|
|
2
|
+
//#region src/bytes/base64.ts
|
|
3
|
+
const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
4
|
+
function bytesToBase64(bytes) {
|
|
5
|
+
let out = "";
|
|
6
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
7
|
+
const b0 = byteAt(bytes, i);
|
|
8
|
+
const b1 = bytes[i + 1];
|
|
9
|
+
const b2 = bytes[i + 2];
|
|
10
|
+
out += ALPHABET.charAt(b0 >>> 2);
|
|
11
|
+
out += ALPHABET.charAt((b0 & 3) << 4 | (b1 ?? 0) >>> 4);
|
|
12
|
+
if (b1 === void 0) return out + "==";
|
|
13
|
+
out += ALPHABET.charAt((b1 & 15) << 2 | (b2 ?? 0) >>> 6);
|
|
14
|
+
if (b2 === void 0) return out + "=";
|
|
15
|
+
out += ALPHABET.charAt(b2 & 63);
|
|
16
|
+
}
|
|
17
|
+
return out;
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
export { bytesToBase64 };
|
|
@@ -16,6 +16,7 @@ declare const WpdDiagnosticCodes: {
|
|
|
16
16
|
readonly OutlineNumberRegenerated: "wpd/outline-number-regenerated";
|
|
17
17
|
readonly BoxDropped: "wpd/box-dropped";
|
|
18
18
|
readonly NoteDropped: "wpd/note-dropped";
|
|
19
|
+
readonly NoteSpansParagraphs: "wpd/note-spans-paragraphs";
|
|
19
20
|
readonly HeaderFooterDropped: "wpd/header-footer-dropped";
|
|
20
21
|
readonly CrossReferenceFlattened: "wpd/cross-reference-flattened";
|
|
21
22
|
readonly MergeCodeDropped: "wpd/merge-code-dropped";
|
|
@@ -16,6 +16,7 @@ declare const WpdDiagnosticCodes: {
|
|
|
16
16
|
readonly OutlineNumberRegenerated: "wpd/outline-number-regenerated";
|
|
17
17
|
readonly BoxDropped: "wpd/box-dropped";
|
|
18
18
|
readonly NoteDropped: "wpd/note-dropped";
|
|
19
|
+
readonly NoteSpansParagraphs: "wpd/note-spans-paragraphs";
|
|
19
20
|
readonly HeaderFooterDropped: "wpd/header-footer-dropped";
|
|
20
21
|
readonly CrossReferenceFlattened: "wpd/cross-reference-flattened";
|
|
21
22
|
readonly MergeCodeDropped: "wpd/merge-code-dropped";
|
package/dist/diagnostics.cjs
CHANGED
|
@@ -12,6 +12,7 @@ const WpdDiagnosticCodes = {
|
|
|
12
12
|
OutlineNumberRegenerated: "wpd/outline-number-regenerated",
|
|
13
13
|
BoxDropped: "wpd/box-dropped",
|
|
14
14
|
NoteDropped: "wpd/note-dropped",
|
|
15
|
+
NoteSpansParagraphs: "wpd/note-spans-paragraphs",
|
|
15
16
|
HeaderFooterDropped: "wpd/header-footer-dropped",
|
|
16
17
|
CrossReferenceFlattened: "wpd/cross-reference-flattened",
|
|
17
18
|
MergeCodeDropped: "wpd/merge-code-dropped",
|
package/dist/diagnostics.d.cts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { i as WpdDiagnosticSink, n as WpdDiagnostic, r as WpdDiagnosticCodes, t as NOOP_WPD_DIAGNOSTIC_SINK } from "./diagnostics-
|
|
1
|
+
import { i as WpdDiagnosticSink, n as WpdDiagnostic, r as WpdDiagnosticCodes, t as NOOP_WPD_DIAGNOSTIC_SINK } from "./diagnostics-DhO7mgqJ.cjs";
|
|
2
2
|
export { NOOP_WPD_DIAGNOSTIC_SINK, WpdDiagnostic, WpdDiagnosticCodes, WpdDiagnosticSink };
|
package/dist/diagnostics.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { i as WpdDiagnosticSink, n as WpdDiagnostic, r as WpdDiagnosticCodes, t as NOOP_WPD_DIAGNOSTIC_SINK } from "./diagnostics-
|
|
1
|
+
import { i as WpdDiagnosticSink, n as WpdDiagnostic, r as WpdDiagnosticCodes, t as NOOP_WPD_DIAGNOSTIC_SINK } from "./diagnostics-DhO7mgqJ.js";
|
|
2
2
|
export { NOOP_WPD_DIAGNOSTIC_SINK, WpdDiagnostic, WpdDiagnosticCodes, WpdDiagnosticSink };
|
package/dist/diagnostics.js
CHANGED
|
@@ -11,6 +11,7 @@ const WpdDiagnosticCodes = {
|
|
|
11
11
|
OutlineNumberRegenerated: "wpd/outline-number-regenerated",
|
|
12
12
|
BoxDropped: "wpd/box-dropped",
|
|
13
13
|
NoteDropped: "wpd/note-dropped",
|
|
14
|
+
NoteSpansParagraphs: "wpd/note-spans-paragraphs",
|
|
14
15
|
HeaderFooterDropped: "wpd/header-footer-dropped",
|
|
15
16
|
CrossReferenceFlattened: "wpd/cross-reference-flattened",
|
|
16
17
|
MergeCodeDropped: "wpd/merge-code-dropped",
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as WpdDiagnosticSink, n as WpdDiagnostic, r as WpdDiagnosticCodes, t as NOOP_WPD_DIAGNOSTIC_SINK } from "./diagnostics-
|
|
1
|
+
import { i as WpdDiagnosticSink, n as WpdDiagnostic, r as WpdDiagnosticCodes, t as NOOP_WPD_DIAGNOSTIC_SINK } from "./diagnostics-DhO7mgqJ.cjs";
|
|
2
2
|
import { ReadWpdOptions, readWpd, readWpdContent } from "./read.cjs";
|
|
3
3
|
import { WpdBytesSchema, wpdContentCodec } from "./codec.cjs";
|
|
4
4
|
import { WPD_FILE_ID, WPD_PREFIX_HEADER_SIZE, WpdFileHeader, hasWordPerfectFileId, readFileHeader } from "./container/header.cjs";
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as WpdDiagnosticSink, n as WpdDiagnostic, r as WpdDiagnosticCodes, t as NOOP_WPD_DIAGNOSTIC_SINK } from "./diagnostics-
|
|
1
|
+
import { i as WpdDiagnosticSink, n as WpdDiagnostic, r as WpdDiagnosticCodes, t as NOOP_WPD_DIAGNOSTIC_SINK } from "./diagnostics-DhO7mgqJ.js";
|
|
2
2
|
import { ReadWpdOptions, readWpd, readWpdContent } from "./read.js";
|
|
3
3
|
import { WpdBytesSchema, wpdContentCodec } from "./codec.js";
|
|
4
4
|
import { WPD_FILE_ID, WPD_PREFIX_HEADER_SIZE, WpdFileHeader, hasWordPerfectFileId, readFileHeader } from "./container/header.js";
|
package/dist/read.cjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_bytes_view = require("./bytes/view.cjs");
|
|
3
|
+
const require_bytes_base64 = require("./bytes/base64.cjs");
|
|
4
|
+
const require_stream_furniture = require("./stream/furniture.cjs");
|
|
3
5
|
const require_stream_characters = require("./stream/characters.cjs");
|
|
4
6
|
const require_container_prefix = require("./container/prefix.cjs");
|
|
5
7
|
const require_container_container = require("./container/container.cjs");
|
|
@@ -12,6 +14,7 @@ const require_stream_style = require("./stream/style.cjs");
|
|
|
12
14
|
const require_stream_table = require("./stream/table.cjs");
|
|
13
15
|
const require_stream_box = require("./stream/box.cjs");
|
|
14
16
|
const require_stream_formula = require("./stream/formula.cjs");
|
|
17
|
+
const require_stream_image = require("./stream/image.cjs");
|
|
15
18
|
const require_stream_tab = require("./stream/tab.cjs");
|
|
16
19
|
const require_stream_tokenise = require("./stream/tokenise.cjs");
|
|
17
20
|
let document_schema_js = require("document-schema.js");
|
|
@@ -87,6 +90,10 @@ function targetBlocks(state) {
|
|
|
87
90
|
}
|
|
88
91
|
function flushParagraph(state, sink) {
|
|
89
92
|
flushRun(state);
|
|
93
|
+
if (state.openNote !== void 0) {
|
|
94
|
+
state.openNote = void 0;
|
|
95
|
+
reportOnce(state, sink, require_diagnostics.WpdDiagnosticCodes.NoteSpansParagraphs, "A footnote or endnote's own On/Off pair straddled a paragraph boundary, which the run-scoped note anchor cannot express; its reference text became ordinary paragraph text with no note anchor.");
|
|
96
|
+
}
|
|
90
97
|
if (state.openMergeFieldStartRun !== void 0) {
|
|
91
98
|
state.openMergeFieldStartRun = void 0;
|
|
92
99
|
reportOnce(state, sink, require_diagnostics.WpdDiagnosticCodes.MergeFieldSpansParagraphs, "A merge field's own On/Off pair straddled a paragraph boundary, which the run-scoped field construct cannot express; its text became ordinary paragraph text with no field tag.");
|
|
@@ -427,6 +434,90 @@ function applyCharacterGroup(state, token, container, sink) {
|
|
|
427
434
|
default: return;
|
|
428
435
|
}
|
|
429
436
|
}
|
|
437
|
+
function applyHeaderFooterGroup(state, token, container, sink) {
|
|
438
|
+
const claim = require_stream_furniture.readFurnitureClaim(token.subgroup, token.nonDeletable);
|
|
439
|
+
if (claim === "none") return;
|
|
440
|
+
if (claim === "watermark") {
|
|
441
|
+
reportOnce(state, sink, require_diagnostics.WpdDiagnosticCodes.HeaderFooterDropped, "This document declares a watermark, which is neither a header nor a footer and owns no parity -- the shared page-furniture vocabulary has no slot for one.");
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
const slotKey = `${claim.kind}:${claim.slot}`;
|
|
445
|
+
if (state.furnitureFilled.has(slotKey)) {
|
|
446
|
+
reportOnce(state, sink, require_diagnostics.WpdDiagnosticCodes.HeaderFooterDropped, `This document declares a second ${claim.kind} for the ${claim.slot} slot -- WordPerfect's own A/B two-slot-per-kind mechanism, which the shared one-flow-per-slot page-furniture vocabulary does not carry; the first ${claim.kind} to claim the slot is the one lifted.`);
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
const blocks = furnitureBodyBlocks(state, token, container, sink);
|
|
450
|
+
if (blocks === void 0) return;
|
|
451
|
+
state.furnitureFilled.add(slotKey);
|
|
452
|
+
const furniture = claim.kind === "header" ? state.headers : state.footers;
|
|
453
|
+
furniture[claim.slot] = blocks;
|
|
454
|
+
}
|
|
455
|
+
function furnitureBodyBlocks(state, token, container, sink) {
|
|
456
|
+
const prefixId = token.prefixIds[0];
|
|
457
|
+
const packet = prefixId === void 0 ? void 0 : require_container_prefix.packetByPrefixId(container.packets, prefixId);
|
|
458
|
+
if (packet?.packetType !== 8) {
|
|
459
|
+
reportOnce(state, sink, require_diagnostics.WpdDiagnosticCodes.HeaderFooterDropped, "This document declares a header or footer whose body packet this reader could not resolve; it was not lifted.");
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
const textBlocks = require_container_prefix.readGeneralWpTextBlocks(packet.bytes);
|
|
463
|
+
if (textBlocks === void 0) {
|
|
464
|
+
reportOnce(state, sink, require_diagnostics.WpdDiagnosticCodes.HeaderFooterDropped, "This document declares a header or footer whose body packet this reader could not read; it was not lifted.");
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
return foldTokens(require_stream_tokenise.tokeniseDocumentArea(textBlocks, 0, textBlocks.length), container, sink).blocks;
|
|
468
|
+
}
|
|
469
|
+
const FOOTNOTE_ON = 0;
|
|
470
|
+
const FOOTNOTE_OFF = 1;
|
|
471
|
+
const ENDNOTE_ON = 2;
|
|
472
|
+
const ENDNOTE_OFF = 3;
|
|
473
|
+
function applyNoteGroup(state, token, container, sink) {
|
|
474
|
+
if (token.subgroup === FOOTNOTE_ON || token.subgroup === ENDNOTE_ON) {
|
|
475
|
+
flushRun(state);
|
|
476
|
+
const prefixId = token.prefixIds[0];
|
|
477
|
+
if (prefixId === void 0) {
|
|
478
|
+
reportOnce(state, sink, require_diagnostics.WpdDiagnosticCodes.NoteDropped, "This document contains a footnote or endnote whose body packet this reader could not resolve; only its reference text survived.");
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
state.openNote = {
|
|
482
|
+
anchorType: token.subgroup === FOOTNOTE_ON ? "footnote" : "endnote",
|
|
483
|
+
startRun: state.runs.length,
|
|
484
|
+
prefixId
|
|
485
|
+
};
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
const closing = token.subgroup === FOOTNOTE_OFF ? "footnote" : token.subgroup === ENDNOTE_OFF ? "endnote" : void 0;
|
|
489
|
+
if (closing === void 0) return;
|
|
490
|
+
const open = state.openNote;
|
|
491
|
+
state.openNote = void 0;
|
|
492
|
+
if (open?.anchorType !== closing) return;
|
|
493
|
+
flushRun(state);
|
|
494
|
+
const endRun = state.runs.length;
|
|
495
|
+
const marker = state.runs.slice(open.startRun, endRun).map((run) => run.text).join("") || String(state.notes.length + 1);
|
|
496
|
+
const definition = `note-${state.notes.length + 1}`;
|
|
497
|
+
state.pendingConstructs.push({
|
|
498
|
+
descriptor: {
|
|
499
|
+
kind: "anchor",
|
|
500
|
+
anchorType: open.anchorType,
|
|
501
|
+
name: marker,
|
|
502
|
+
definition
|
|
503
|
+
},
|
|
504
|
+
startRun: open.startRun,
|
|
505
|
+
endRun
|
|
506
|
+
});
|
|
507
|
+
const packet = require_container_prefix.packetByPrefixId(container.packets, open.prefixId);
|
|
508
|
+
const textBlocks = packet?.packetType === 8 ? require_container_prefix.readGeneralWpTextBlocks(packet.bytes) : void 0;
|
|
509
|
+
const nested = textBlocks === void 0 ? void 0 : require_stream_tokenise.tokeniseDocumentArea(textBlocks, 0, textBlocks.length);
|
|
510
|
+
const blocks = nested === void 0 ? void 0 : foldTokens(nested, container, sink).blocks;
|
|
511
|
+
if (blocks === void 0) {
|
|
512
|
+
reportOnce(state, sink, require_diagnostics.WpdDiagnosticCodes.NoteDropped, "This document contains a footnote or endnote whose body packet this reader could not read; its reference anchor survives and its body does not.");
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
state.notes.push({
|
|
516
|
+
anchorType: open.anchorType,
|
|
517
|
+
marker,
|
|
518
|
+
blocks
|
|
519
|
+
});
|
|
520
|
+
}
|
|
430
521
|
function applyMergeGroup(state, token, sink) {
|
|
431
522
|
if (token.subgroup === MERGE_FIELD_ON) {
|
|
432
523
|
flushRun(state);
|
|
@@ -461,6 +552,37 @@ function applyBoxGroup(state, token, container, sink) {
|
|
|
461
552
|
reportOnce(state, sink, require_diagnostics.WpdDiagnosticCodes.BoxDropped, "This document contains a box -- a figure, text box, equation, or graphic -- whose function-level override names no content this reader can resolve.");
|
|
462
553
|
return;
|
|
463
554
|
}
|
|
555
|
+
if (boxContent.contentType === 3) {
|
|
556
|
+
const imagePacket = require_container_prefix.packetByPrefixId(container.packets, boxContent.contentPrefixId);
|
|
557
|
+
const payload = imagePacket === void 0 ? void 0 : require_stream_image.scanImagePayload(imagePacket.bytes);
|
|
558
|
+
if (payload === void 0) {
|
|
559
|
+
reportOnce(state, sink, require_diagnostics.WpdDiagnosticCodes.BoxContentUnresolved, "This document contains an image box whose content packet carries no decodable PNG or JPEG payload -- a WPG graphic or other image spelling this reader does not decode.");
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
if (boxContent.frame === void 0) {
|
|
563
|
+
reportOnce(state, sink, require_diagnostics.WpdDiagnosticCodes.BoxFrameUnresolved, "This document contains a box whose content this reader could read, but whose function-level override states no width and height this reader can trust, so its content was not lifted.");
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
flushParagraphIfContent(state, sink);
|
|
567
|
+
targetBlocks(state).push({
|
|
568
|
+
kind: "image",
|
|
569
|
+
format: payload.format,
|
|
570
|
+
base64: require_bytes_base64.bytesToBase64(payload.bytes),
|
|
571
|
+
widthPt: boxContent.frame.widthPt,
|
|
572
|
+
heightPt: boxContent.frame.heightPt,
|
|
573
|
+
...boxContent.frame.positionResolved ? { floatPosition: {
|
|
574
|
+
horizontal: {
|
|
575
|
+
relativeTo: "page",
|
|
576
|
+
offsetPt: boxContent.frame.xPt
|
|
577
|
+
},
|
|
578
|
+
vertical: {
|
|
579
|
+
relativeTo: "page",
|
|
580
|
+
offsetPt: boxContent.frame.yPt
|
|
581
|
+
}
|
|
582
|
+
} } : {}
|
|
583
|
+
});
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
464
586
|
const packet = boxContent.contentType === 1 || boxContent.contentType === 2 || boxContent.contentType === 4 ? require_container_prefix.packetByPrefixId(container.packets, boxContent.contentPrefixId) : void 0;
|
|
465
587
|
const textBlocks = packet?.packetType !== 8 ? void 0 : require_container_prefix.readGeneralWpTextBlocks(packet.bytes);
|
|
466
588
|
if (textBlocks === void 0) {
|
|
@@ -564,10 +686,10 @@ function applyVariableFunction(state, token, container, sink) {
|
|
|
564
686
|
reportOnce(state, sink, require_diagnostics.WpdDiagnosticCodes.CrossReferenceFlattened, "This document contains a cross-reference; its displayed text survives as ordinary text, and the reference's own target binding does not.");
|
|
565
687
|
return;
|
|
566
688
|
case HEADER_FOOTER_GROUP:
|
|
567
|
-
|
|
689
|
+
applyHeaderFooterGroup(state, token, container, sink);
|
|
568
690
|
return;
|
|
569
691
|
case FOOTNOTE_ENDNOTE_GROUP:
|
|
570
|
-
|
|
692
|
+
applyNoteGroup(state, token, container, sink);
|
|
571
693
|
return;
|
|
572
694
|
case MERGE_GROUP:
|
|
573
695
|
applyMergeGroup(state, token, sink);
|
|
@@ -663,7 +785,12 @@ function foldTokens(tokens, container, sink) {
|
|
|
663
785
|
table: void 0,
|
|
664
786
|
reported: /* @__PURE__ */ new Set(),
|
|
665
787
|
pendingConstructs: [],
|
|
666
|
-
openMergeFieldStartRun: void 0
|
|
788
|
+
openMergeFieldStartRun: void 0,
|
|
789
|
+
headers: {},
|
|
790
|
+
footers: {},
|
|
791
|
+
furnitureFilled: /* @__PURE__ */ new Set(),
|
|
792
|
+
openNote: void 0,
|
|
793
|
+
notes: []
|
|
667
794
|
};
|
|
668
795
|
for (const token of tokens) applyToken(state, token, container, sink);
|
|
669
796
|
flushRun(state);
|
|
@@ -674,7 +801,10 @@ function foldTokens(tokens, container, sink) {
|
|
|
674
801
|
}
|
|
675
802
|
return {
|
|
676
803
|
blocks: state.blocks,
|
|
677
|
-
page: state.page
|
|
804
|
+
page: state.page,
|
|
805
|
+
headers: state.headers,
|
|
806
|
+
footers: state.footers,
|
|
807
|
+
notes: state.notes
|
|
678
808
|
};
|
|
679
809
|
}
|
|
680
810
|
function readMetadata(container) {
|
|
@@ -684,7 +814,11 @@ function readMetadata(container) {
|
|
|
684
814
|
function readWpdContent(bytes, options = {}) {
|
|
685
815
|
const sink = options.sink ?? require_diagnostics.NOOP_WPD_DIAGNOSTIC_SINK;
|
|
686
816
|
const container = require_container_container.openWpdDocument(bytes, { password: options.password });
|
|
687
|
-
const { blocks, page } = foldTokens(require_stream_tokenise.tokeniseDocumentArea(container.bytes, container.documentAreaOffset, container.documentAreaEnd), container, sink);
|
|
817
|
+
const { blocks, page, headers, footers, notes } = foldTokens(require_stream_tokenise.tokeniseDocumentArea(container.bytes, container.documentAreaOffset, container.documentAreaEnd), container, sink);
|
|
818
|
+
for (const note of notes) sink({
|
|
819
|
+
code: require_diagnostics.WpdDiagnosticCodes.NoteDropped,
|
|
820
|
+
message: `This document contains a ${note.anchorType} whose body the flat ContentDocument has no home for; its reference anchor survives and readWpd lifts the body into the tree form's definitions table.`
|
|
821
|
+
});
|
|
688
822
|
return {
|
|
689
823
|
kind: "wordprocessing",
|
|
690
824
|
metadata: readMetadata(container),
|
|
@@ -699,12 +833,48 @@ function readWpdContent(bytes, options = {}) {
|
|
|
699
833
|
bottomPt: page.bottomPt ?? 72,
|
|
700
834
|
leftPt: page.leftPt ?? 72
|
|
701
835
|
},
|
|
836
|
+
...Object.keys(headers).length > 0 ? { headers } : {},
|
|
837
|
+
...Object.keys(footers).length > 0 ? { footers } : {},
|
|
702
838
|
blocks
|
|
703
839
|
}]
|
|
704
840
|
};
|
|
705
841
|
}
|
|
706
842
|
function readWpd(bytes, options = {}) {
|
|
707
|
-
|
|
843
|
+
const container = require_container_container.openWpdDocument(bytes, { password: options.password });
|
|
844
|
+
const { notes, ...flatRest } = foldTokens(require_stream_tokenise.tokeniseDocumentArea(container.bytes, container.documentAreaOffset, container.documentAreaEnd), container, options.sink ?? require_diagnostics.NOOP_WPD_DIAGNOSTIC_SINK);
|
|
845
|
+
const document = {
|
|
846
|
+
kind: "wordprocessing",
|
|
847
|
+
metadata: readMetadata(container),
|
|
848
|
+
sections: [{
|
|
849
|
+
pageSize: {
|
|
850
|
+
widthPt: flatRest.page.widthPt ?? 612,
|
|
851
|
+
heightPt: flatRest.page.heightPt ?? 792
|
|
852
|
+
},
|
|
853
|
+
margins: {
|
|
854
|
+
topPt: flatRest.page.topPt ?? 72,
|
|
855
|
+
rightPt: flatRest.page.rightPt ?? 72,
|
|
856
|
+
bottomPt: flatRest.page.bottomPt ?? 72,
|
|
857
|
+
leftPt: flatRest.page.leftPt ?? 72
|
|
858
|
+
},
|
|
859
|
+
...Object.keys(flatRest.headers).length > 0 ? { headers: flatRest.headers } : {},
|
|
860
|
+
...Object.keys(flatRest.footers).length > 0 ? { footers: flatRest.footers } : {},
|
|
861
|
+
blocks: flatRest.blocks
|
|
862
|
+
}]
|
|
863
|
+
};
|
|
864
|
+
const assembled = (0, document_schema_js.assembleTree)(document);
|
|
865
|
+
if (notes.length === 0) return assembled;
|
|
866
|
+
const definitions = Object.fromEntries(notes.map((note, index) => [`note-${index + 1}`, {
|
|
867
|
+
kind: note.anchorType,
|
|
868
|
+
marker: note.marker,
|
|
869
|
+
blocks: note.blocks
|
|
870
|
+
}]));
|
|
871
|
+
return {
|
|
872
|
+
...assembled,
|
|
873
|
+
definitions: {
|
|
874
|
+
...assembled.definitions,
|
|
875
|
+
...definitions
|
|
876
|
+
}
|
|
877
|
+
};
|
|
708
878
|
}
|
|
709
879
|
//#endregion
|
|
710
880
|
exports.readWpd = readWpd;
|
package/dist/read.d.cts
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
import { i as WpdDiagnosticSink } from "./diagnostics-
|
|
2
|
-
import { ContentDocument, DocumentTree } from "document-schema.js";
|
|
1
|
+
import { i as WpdDiagnosticSink } from "./diagnostics-DhO7mgqJ.cjs";
|
|
2
|
+
import { ContentBlock, ContentDocument, DocumentTree } from "document-schema.js";
|
|
3
3
|
//#region src/read.d.ts
|
|
4
4
|
interface ReadWpdOptions {
|
|
5
5
|
readonly sink?: WpdDiagnosticSink;
|
|
6
6
|
readonly password?: string;
|
|
7
7
|
}
|
|
8
|
+
interface WpdNoteDefinition {
|
|
9
|
+
readonly anchorType: "footnote" | "endnote";
|
|
10
|
+
readonly marker: string;
|
|
11
|
+
readonly blocks: readonly ContentBlock[];
|
|
12
|
+
}
|
|
8
13
|
declare function readWpdContent(bytes: Uint8Array, options?: ReadWpdOptions): ContentDocument;
|
|
9
14
|
declare function readWpd(bytes: Uint8Array, options?: ReadWpdOptions): DocumentTree;
|
|
10
15
|
//#endregion
|
|
11
|
-
export { ReadWpdOptions, readWpd, readWpdContent };
|
|
16
|
+
export { ReadWpdOptions, WpdNoteDefinition, readWpd, readWpdContent };
|
package/dist/read.d.ts
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
import { i as WpdDiagnosticSink } from "./diagnostics-
|
|
2
|
-
import { ContentDocument, DocumentTree } from "document-schema.js";
|
|
1
|
+
import { i as WpdDiagnosticSink } from "./diagnostics-DhO7mgqJ.js";
|
|
2
|
+
import { ContentBlock, ContentDocument, DocumentTree } from "document-schema.js";
|
|
3
3
|
//#region src/read.d.ts
|
|
4
4
|
interface ReadWpdOptions {
|
|
5
5
|
readonly sink?: WpdDiagnosticSink;
|
|
6
6
|
readonly password?: string;
|
|
7
7
|
}
|
|
8
|
+
interface WpdNoteDefinition {
|
|
9
|
+
readonly anchorType: "footnote" | "endnote";
|
|
10
|
+
readonly marker: string;
|
|
11
|
+
readonly blocks: readonly ContentBlock[];
|
|
12
|
+
}
|
|
8
13
|
declare function readWpdContent(bytes: Uint8Array, options?: ReadWpdOptions): ContentDocument;
|
|
9
14
|
declare function readWpd(bytes: Uint8Array, options?: ReadWpdOptions): DocumentTree;
|
|
10
15
|
//#endregion
|
|
11
|
-
export { ReadWpdOptions, readWpd, readWpdContent };
|
|
16
|
+
export { ReadWpdOptions, WpdNoteDefinition, readWpd, readWpdContent };
|
package/dist/read.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { uint16At } from "./bytes/view.js";
|
|
2
|
+
import { bytesToBase64 } from "./bytes/base64.js";
|
|
3
|
+
import { readFurnitureClaim } from "./stream/furniture.js";
|
|
2
4
|
import { decodeSingleByteCharacter, decodeWpCharacter } from "./stream/characters.js";
|
|
3
5
|
import { packetByPrefixId, readGeneralWpTextBlocks, readTypefaceName } from "./container/prefix.js";
|
|
4
6
|
import { openWpdDocument } from "./container/container.js";
|
|
@@ -11,6 +13,7 @@ import { isParagraphNumberDisplayOff, isParagraphNumberDisplayOn, isStyleScopeCl
|
|
|
11
13
|
import { findEmbeddedSubfunction, readCellFill, readCellInformation, readCellSpanning, readEmbeddedSubfunctions, readRowInformation, readTableColumnWidthPt } from "./stream/table.js";
|
|
12
14
|
import { readBoxContent } from "./stream/box.js";
|
|
13
15
|
import { readTableFormula } from "./stream/formula.js";
|
|
16
|
+
import { scanImagePayload } from "./stream/image.js";
|
|
14
17
|
import { tabEffectFor } from "./stream/tab.js";
|
|
15
18
|
import { tokeniseDocumentArea } from "./stream/tokenise.js";
|
|
16
19
|
import { assembleTree } from "document-schema.js";
|
|
@@ -86,6 +89,10 @@ function targetBlocks(state) {
|
|
|
86
89
|
}
|
|
87
90
|
function flushParagraph(state, sink) {
|
|
88
91
|
flushRun(state);
|
|
92
|
+
if (state.openNote !== void 0) {
|
|
93
|
+
state.openNote = void 0;
|
|
94
|
+
reportOnce(state, sink, WpdDiagnosticCodes.NoteSpansParagraphs, "A footnote or endnote's own On/Off pair straddled a paragraph boundary, which the run-scoped note anchor cannot express; its reference text became ordinary paragraph text with no note anchor.");
|
|
95
|
+
}
|
|
89
96
|
if (state.openMergeFieldStartRun !== void 0) {
|
|
90
97
|
state.openMergeFieldStartRun = void 0;
|
|
91
98
|
reportOnce(state, sink, WpdDiagnosticCodes.MergeFieldSpansParagraphs, "A merge field's own On/Off pair straddled a paragraph boundary, which the run-scoped field construct cannot express; its text became ordinary paragraph text with no field tag.");
|
|
@@ -426,6 +433,90 @@ function applyCharacterGroup(state, token, container, sink) {
|
|
|
426
433
|
default: return;
|
|
427
434
|
}
|
|
428
435
|
}
|
|
436
|
+
function applyHeaderFooterGroup(state, token, container, sink) {
|
|
437
|
+
const claim = readFurnitureClaim(token.subgroup, token.nonDeletable);
|
|
438
|
+
if (claim === "none") return;
|
|
439
|
+
if (claim === "watermark") {
|
|
440
|
+
reportOnce(state, sink, WpdDiagnosticCodes.HeaderFooterDropped, "This document declares a watermark, which is neither a header nor a footer and owns no parity -- the shared page-furniture vocabulary has no slot for one.");
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
const slotKey = `${claim.kind}:${claim.slot}`;
|
|
444
|
+
if (state.furnitureFilled.has(slotKey)) {
|
|
445
|
+
reportOnce(state, sink, WpdDiagnosticCodes.HeaderFooterDropped, `This document declares a second ${claim.kind} for the ${claim.slot} slot -- WordPerfect's own A/B two-slot-per-kind mechanism, which the shared one-flow-per-slot page-furniture vocabulary does not carry; the first ${claim.kind} to claim the slot is the one lifted.`);
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
const blocks = furnitureBodyBlocks(state, token, container, sink);
|
|
449
|
+
if (blocks === void 0) return;
|
|
450
|
+
state.furnitureFilled.add(slotKey);
|
|
451
|
+
const furniture = claim.kind === "header" ? state.headers : state.footers;
|
|
452
|
+
furniture[claim.slot] = blocks;
|
|
453
|
+
}
|
|
454
|
+
function furnitureBodyBlocks(state, token, container, sink) {
|
|
455
|
+
const prefixId = token.prefixIds[0];
|
|
456
|
+
const packet = prefixId === void 0 ? void 0 : packetByPrefixId(container.packets, prefixId);
|
|
457
|
+
if (packet?.packetType !== 8) {
|
|
458
|
+
reportOnce(state, sink, WpdDiagnosticCodes.HeaderFooterDropped, "This document declares a header or footer whose body packet this reader could not resolve; it was not lifted.");
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
const textBlocks = readGeneralWpTextBlocks(packet.bytes);
|
|
462
|
+
if (textBlocks === void 0) {
|
|
463
|
+
reportOnce(state, sink, WpdDiagnosticCodes.HeaderFooterDropped, "This document declares a header or footer whose body packet this reader could not read; it was not lifted.");
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
return foldTokens(tokeniseDocumentArea(textBlocks, 0, textBlocks.length), container, sink).blocks;
|
|
467
|
+
}
|
|
468
|
+
const FOOTNOTE_ON = 0;
|
|
469
|
+
const FOOTNOTE_OFF = 1;
|
|
470
|
+
const ENDNOTE_ON = 2;
|
|
471
|
+
const ENDNOTE_OFF = 3;
|
|
472
|
+
function applyNoteGroup(state, token, container, sink) {
|
|
473
|
+
if (token.subgroup === FOOTNOTE_ON || token.subgroup === ENDNOTE_ON) {
|
|
474
|
+
flushRun(state);
|
|
475
|
+
const prefixId = token.prefixIds[0];
|
|
476
|
+
if (prefixId === void 0) {
|
|
477
|
+
reportOnce(state, sink, WpdDiagnosticCodes.NoteDropped, "This document contains a footnote or endnote whose body packet this reader could not resolve; only its reference text survived.");
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
state.openNote = {
|
|
481
|
+
anchorType: token.subgroup === FOOTNOTE_ON ? "footnote" : "endnote",
|
|
482
|
+
startRun: state.runs.length,
|
|
483
|
+
prefixId
|
|
484
|
+
};
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
const closing = token.subgroup === FOOTNOTE_OFF ? "footnote" : token.subgroup === ENDNOTE_OFF ? "endnote" : void 0;
|
|
488
|
+
if (closing === void 0) return;
|
|
489
|
+
const open = state.openNote;
|
|
490
|
+
state.openNote = void 0;
|
|
491
|
+
if (open?.anchorType !== closing) return;
|
|
492
|
+
flushRun(state);
|
|
493
|
+
const endRun = state.runs.length;
|
|
494
|
+
const marker = state.runs.slice(open.startRun, endRun).map((run) => run.text).join("") || String(state.notes.length + 1);
|
|
495
|
+
const definition = `note-${state.notes.length + 1}`;
|
|
496
|
+
state.pendingConstructs.push({
|
|
497
|
+
descriptor: {
|
|
498
|
+
kind: "anchor",
|
|
499
|
+
anchorType: open.anchorType,
|
|
500
|
+
name: marker,
|
|
501
|
+
definition
|
|
502
|
+
},
|
|
503
|
+
startRun: open.startRun,
|
|
504
|
+
endRun
|
|
505
|
+
});
|
|
506
|
+
const packet = packetByPrefixId(container.packets, open.prefixId);
|
|
507
|
+
const textBlocks = packet?.packetType === 8 ? readGeneralWpTextBlocks(packet.bytes) : void 0;
|
|
508
|
+
const nested = textBlocks === void 0 ? void 0 : tokeniseDocumentArea(textBlocks, 0, textBlocks.length);
|
|
509
|
+
const blocks = nested === void 0 ? void 0 : foldTokens(nested, container, sink).blocks;
|
|
510
|
+
if (blocks === void 0) {
|
|
511
|
+
reportOnce(state, sink, WpdDiagnosticCodes.NoteDropped, "This document contains a footnote or endnote whose body packet this reader could not read; its reference anchor survives and its body does not.");
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
state.notes.push({
|
|
515
|
+
anchorType: open.anchorType,
|
|
516
|
+
marker,
|
|
517
|
+
blocks
|
|
518
|
+
});
|
|
519
|
+
}
|
|
429
520
|
function applyMergeGroup(state, token, sink) {
|
|
430
521
|
if (token.subgroup === MERGE_FIELD_ON) {
|
|
431
522
|
flushRun(state);
|
|
@@ -460,6 +551,37 @@ function applyBoxGroup(state, token, container, sink) {
|
|
|
460
551
|
reportOnce(state, sink, WpdDiagnosticCodes.BoxDropped, "This document contains a box -- a figure, text box, equation, or graphic -- whose function-level override names no content this reader can resolve.");
|
|
461
552
|
return;
|
|
462
553
|
}
|
|
554
|
+
if (boxContent.contentType === 3) {
|
|
555
|
+
const imagePacket = packetByPrefixId(container.packets, boxContent.contentPrefixId);
|
|
556
|
+
const payload = imagePacket === void 0 ? void 0 : scanImagePayload(imagePacket.bytes);
|
|
557
|
+
if (payload === void 0) {
|
|
558
|
+
reportOnce(state, sink, WpdDiagnosticCodes.BoxContentUnresolved, "This document contains an image box whose content packet carries no decodable PNG or JPEG payload -- a WPG graphic or other image spelling this reader does not decode.");
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
if (boxContent.frame === void 0) {
|
|
562
|
+
reportOnce(state, sink, WpdDiagnosticCodes.BoxFrameUnresolved, "This document contains a box whose content this reader could read, but whose function-level override states no width and height this reader can trust, so its content was not lifted.");
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
flushParagraphIfContent(state, sink);
|
|
566
|
+
targetBlocks(state).push({
|
|
567
|
+
kind: "image",
|
|
568
|
+
format: payload.format,
|
|
569
|
+
base64: bytesToBase64(payload.bytes),
|
|
570
|
+
widthPt: boxContent.frame.widthPt,
|
|
571
|
+
heightPt: boxContent.frame.heightPt,
|
|
572
|
+
...boxContent.frame.positionResolved ? { floatPosition: {
|
|
573
|
+
horizontal: {
|
|
574
|
+
relativeTo: "page",
|
|
575
|
+
offsetPt: boxContent.frame.xPt
|
|
576
|
+
},
|
|
577
|
+
vertical: {
|
|
578
|
+
relativeTo: "page",
|
|
579
|
+
offsetPt: boxContent.frame.yPt
|
|
580
|
+
}
|
|
581
|
+
} } : {}
|
|
582
|
+
});
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
463
585
|
const packet = boxContent.contentType === 1 || boxContent.contentType === 2 || boxContent.contentType === 4 ? packetByPrefixId(container.packets, boxContent.contentPrefixId) : void 0;
|
|
464
586
|
const textBlocks = packet?.packetType !== 8 ? void 0 : readGeneralWpTextBlocks(packet.bytes);
|
|
465
587
|
if (textBlocks === void 0) {
|
|
@@ -563,10 +685,10 @@ function applyVariableFunction(state, token, container, sink) {
|
|
|
563
685
|
reportOnce(state, sink, WpdDiagnosticCodes.CrossReferenceFlattened, "This document contains a cross-reference; its displayed text survives as ordinary text, and the reference's own target binding does not.");
|
|
564
686
|
return;
|
|
565
687
|
case HEADER_FOOTER_GROUP:
|
|
566
|
-
|
|
688
|
+
applyHeaderFooterGroup(state, token, container, sink);
|
|
567
689
|
return;
|
|
568
690
|
case FOOTNOTE_ENDNOTE_GROUP:
|
|
569
|
-
|
|
691
|
+
applyNoteGroup(state, token, container, sink);
|
|
570
692
|
return;
|
|
571
693
|
case MERGE_GROUP:
|
|
572
694
|
applyMergeGroup(state, token, sink);
|
|
@@ -662,7 +784,12 @@ function foldTokens(tokens, container, sink) {
|
|
|
662
784
|
table: void 0,
|
|
663
785
|
reported: /* @__PURE__ */ new Set(),
|
|
664
786
|
pendingConstructs: [],
|
|
665
|
-
openMergeFieldStartRun: void 0
|
|
787
|
+
openMergeFieldStartRun: void 0,
|
|
788
|
+
headers: {},
|
|
789
|
+
footers: {},
|
|
790
|
+
furnitureFilled: /* @__PURE__ */ new Set(),
|
|
791
|
+
openNote: void 0,
|
|
792
|
+
notes: []
|
|
666
793
|
};
|
|
667
794
|
for (const token of tokens) applyToken(state, token, container, sink);
|
|
668
795
|
flushRun(state);
|
|
@@ -673,7 +800,10 @@ function foldTokens(tokens, container, sink) {
|
|
|
673
800
|
}
|
|
674
801
|
return {
|
|
675
802
|
blocks: state.blocks,
|
|
676
|
-
page: state.page
|
|
803
|
+
page: state.page,
|
|
804
|
+
headers: state.headers,
|
|
805
|
+
footers: state.footers,
|
|
806
|
+
notes: state.notes
|
|
677
807
|
};
|
|
678
808
|
}
|
|
679
809
|
function readMetadata(container) {
|
|
@@ -683,7 +813,11 @@ function readMetadata(container) {
|
|
|
683
813
|
function readWpdContent(bytes, options = {}) {
|
|
684
814
|
const sink = options.sink ?? NOOP_WPD_DIAGNOSTIC_SINK;
|
|
685
815
|
const container = openWpdDocument(bytes, { password: options.password });
|
|
686
|
-
const { blocks, page } = foldTokens(tokeniseDocumentArea(container.bytes, container.documentAreaOffset, container.documentAreaEnd), container, sink);
|
|
816
|
+
const { blocks, page, headers, footers, notes } = foldTokens(tokeniseDocumentArea(container.bytes, container.documentAreaOffset, container.documentAreaEnd), container, sink);
|
|
817
|
+
for (const note of notes) sink({
|
|
818
|
+
code: WpdDiagnosticCodes.NoteDropped,
|
|
819
|
+
message: `This document contains a ${note.anchorType} whose body the flat ContentDocument has no home for; its reference anchor survives and readWpd lifts the body into the tree form's definitions table.`
|
|
820
|
+
});
|
|
687
821
|
return {
|
|
688
822
|
kind: "wordprocessing",
|
|
689
823
|
metadata: readMetadata(container),
|
|
@@ -698,12 +832,48 @@ function readWpdContent(bytes, options = {}) {
|
|
|
698
832
|
bottomPt: page.bottomPt ?? 72,
|
|
699
833
|
leftPt: page.leftPt ?? 72
|
|
700
834
|
},
|
|
835
|
+
...Object.keys(headers).length > 0 ? { headers } : {},
|
|
836
|
+
...Object.keys(footers).length > 0 ? { footers } : {},
|
|
701
837
|
blocks
|
|
702
838
|
}]
|
|
703
839
|
};
|
|
704
840
|
}
|
|
705
841
|
function readWpd(bytes, options = {}) {
|
|
706
|
-
|
|
842
|
+
const container = openWpdDocument(bytes, { password: options.password });
|
|
843
|
+
const { notes, ...flatRest } = foldTokens(tokeniseDocumentArea(container.bytes, container.documentAreaOffset, container.documentAreaEnd), container, options.sink ?? NOOP_WPD_DIAGNOSTIC_SINK);
|
|
844
|
+
const document = {
|
|
845
|
+
kind: "wordprocessing",
|
|
846
|
+
metadata: readMetadata(container),
|
|
847
|
+
sections: [{
|
|
848
|
+
pageSize: {
|
|
849
|
+
widthPt: flatRest.page.widthPt ?? 612,
|
|
850
|
+
heightPt: flatRest.page.heightPt ?? 792
|
|
851
|
+
},
|
|
852
|
+
margins: {
|
|
853
|
+
topPt: flatRest.page.topPt ?? 72,
|
|
854
|
+
rightPt: flatRest.page.rightPt ?? 72,
|
|
855
|
+
bottomPt: flatRest.page.bottomPt ?? 72,
|
|
856
|
+
leftPt: flatRest.page.leftPt ?? 72
|
|
857
|
+
},
|
|
858
|
+
...Object.keys(flatRest.headers).length > 0 ? { headers: flatRest.headers } : {},
|
|
859
|
+
...Object.keys(flatRest.footers).length > 0 ? { footers: flatRest.footers } : {},
|
|
860
|
+
blocks: flatRest.blocks
|
|
861
|
+
}]
|
|
862
|
+
};
|
|
863
|
+
const assembled = assembleTree(document);
|
|
864
|
+
if (notes.length === 0) return assembled;
|
|
865
|
+
const definitions = Object.fromEntries(notes.map((note, index) => [`note-${index + 1}`, {
|
|
866
|
+
kind: note.anchorType,
|
|
867
|
+
marker: note.marker,
|
|
868
|
+
blocks: note.blocks
|
|
869
|
+
}]));
|
|
870
|
+
return {
|
|
871
|
+
...assembled,
|
|
872
|
+
definitions: {
|
|
873
|
+
...assembled.definitions,
|
|
874
|
+
...definitions
|
|
875
|
+
}
|
|
876
|
+
};
|
|
707
877
|
}
|
|
708
878
|
//#endregion
|
|
709
879
|
export { readWpd, readWpdContent };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/stream/furniture.ts
|
|
3
|
+
const HEADER_FOOTER_GROUP = 214;
|
|
4
|
+
const HEADER_A = 0;
|
|
5
|
+
const HEADER_B = 1;
|
|
6
|
+
const FOOTER_A = 2;
|
|
7
|
+
const FOOTER_B = 3;
|
|
8
|
+
const WATERMARK_A = 4;
|
|
9
|
+
const WATERMARK_B = 5;
|
|
10
|
+
const OCCURS_ON_ODD = 1;
|
|
11
|
+
const OCCURS_ON_EVEN = 2;
|
|
12
|
+
function readFurnitureClaim(subgroup, nonDeletable) {
|
|
13
|
+
if (subgroup === 4 || subgroup === 5) return "watermark";
|
|
14
|
+
const kind = subgroup === 0 || subgroup === 1 ? "header" : subgroup === 2 || subgroup === 3 ? "footer" : void 0;
|
|
15
|
+
if (kind === void 0) return "none";
|
|
16
|
+
const occurrence = nonDeletable[0] ?? 0;
|
|
17
|
+
const odd = (occurrence & OCCURS_ON_ODD) !== 0;
|
|
18
|
+
const even = (occurrence & OCCURS_ON_EVEN) !== 0;
|
|
19
|
+
if (!odd && !even) return "none";
|
|
20
|
+
return {
|
|
21
|
+
kind,
|
|
22
|
+
slot: even && !odd ? "even" : "default"
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
exports.FOOTER_A = FOOTER_A;
|
|
27
|
+
exports.FOOTER_B = FOOTER_B;
|
|
28
|
+
exports.HEADER_A = HEADER_A;
|
|
29
|
+
exports.HEADER_B = HEADER_B;
|
|
30
|
+
exports.HEADER_FOOTER_GROUP = HEADER_FOOTER_GROUP;
|
|
31
|
+
exports.WATERMARK_A = WATERMARK_A;
|
|
32
|
+
exports.WATERMARK_B = WATERMARK_B;
|
|
33
|
+
exports.readFurnitureClaim = readFurnitureClaim;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//#region src/stream/furniture.d.ts
|
|
2
|
+
declare const HEADER_FOOTER_GROUP = 214;
|
|
3
|
+
declare const HEADER_A = 0;
|
|
4
|
+
declare const HEADER_B = 1;
|
|
5
|
+
declare const FOOTER_A = 2;
|
|
6
|
+
declare const FOOTER_B = 3;
|
|
7
|
+
declare const WATERMARK_A = 4;
|
|
8
|
+
declare const WATERMARK_B = 5;
|
|
9
|
+
interface WpdFurnitureClaim {
|
|
10
|
+
readonly kind: "header" | "footer";
|
|
11
|
+
readonly slot: "default" | "even";
|
|
12
|
+
}
|
|
13
|
+
declare function readFurnitureClaim(subgroup: number, nonDeletable: Uint8Array): WpdFurnitureClaim | "watermark" | "none";
|
|
14
|
+
//#endregion
|
|
15
|
+
export { FOOTER_A, FOOTER_B, HEADER_A, HEADER_B, HEADER_FOOTER_GROUP, WATERMARK_A, WATERMARK_B, WpdFurnitureClaim, readFurnitureClaim };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//#region src/stream/furniture.d.ts
|
|
2
|
+
declare const HEADER_FOOTER_GROUP = 214;
|
|
3
|
+
declare const HEADER_A = 0;
|
|
4
|
+
declare const HEADER_B = 1;
|
|
5
|
+
declare const FOOTER_A = 2;
|
|
6
|
+
declare const FOOTER_B = 3;
|
|
7
|
+
declare const WATERMARK_A = 4;
|
|
8
|
+
declare const WATERMARK_B = 5;
|
|
9
|
+
interface WpdFurnitureClaim {
|
|
10
|
+
readonly kind: "header" | "footer";
|
|
11
|
+
readonly slot: "default" | "even";
|
|
12
|
+
}
|
|
13
|
+
declare function readFurnitureClaim(subgroup: number, nonDeletable: Uint8Array): WpdFurnitureClaim | "watermark" | "none";
|
|
14
|
+
//#endregion
|
|
15
|
+
export { FOOTER_A, FOOTER_B, HEADER_A, HEADER_B, HEADER_FOOTER_GROUP, WATERMARK_A, WATERMARK_B, WpdFurnitureClaim, readFurnitureClaim };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
//#region src/stream/furniture.ts
|
|
2
|
+
const HEADER_FOOTER_GROUP = 214;
|
|
3
|
+
const HEADER_A = 0;
|
|
4
|
+
const HEADER_B = 1;
|
|
5
|
+
const FOOTER_A = 2;
|
|
6
|
+
const FOOTER_B = 3;
|
|
7
|
+
const WATERMARK_A = 4;
|
|
8
|
+
const WATERMARK_B = 5;
|
|
9
|
+
const OCCURS_ON_ODD = 1;
|
|
10
|
+
const OCCURS_ON_EVEN = 2;
|
|
11
|
+
function readFurnitureClaim(subgroup, nonDeletable) {
|
|
12
|
+
if (subgroup === 4 || subgroup === 5) return "watermark";
|
|
13
|
+
const kind = subgroup === 0 || subgroup === 1 ? "header" : subgroup === 2 || subgroup === 3 ? "footer" : void 0;
|
|
14
|
+
if (kind === void 0) return "none";
|
|
15
|
+
const occurrence = nonDeletable[0] ?? 0;
|
|
16
|
+
const odd = (occurrence & OCCURS_ON_ODD) !== 0;
|
|
17
|
+
const even = (occurrence & OCCURS_ON_EVEN) !== 0;
|
|
18
|
+
if (!odd && !even) return "none";
|
|
19
|
+
return {
|
|
20
|
+
kind,
|
|
21
|
+
slot: even && !odd ? "even" : "default"
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
export { FOOTER_A, FOOTER_B, HEADER_A, HEADER_B, HEADER_FOOTER_GROUP, WATERMARK_A, WATERMARK_B, readFurnitureClaim };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_bytes_view = require("../bytes/view.cjs");
|
|
3
|
+
//#region src/stream/image.ts
|
|
4
|
+
const PNG_SIGNATURE = [
|
|
5
|
+
137,
|
|
6
|
+
80,
|
|
7
|
+
78,
|
|
8
|
+
71,
|
|
9
|
+
13,
|
|
10
|
+
10,
|
|
11
|
+
26,
|
|
12
|
+
10
|
|
13
|
+
];
|
|
14
|
+
const JPEG_SOI = [255, 216];
|
|
15
|
+
function indexOf(bytes, needle, from) {
|
|
16
|
+
outer: for (let i = from; i + needle.length <= bytes.length; i += 1) {
|
|
17
|
+
for (let j = 0; j < needle.length; j += 1) if (bytes[i + j] !== needle[j]) continue outer;
|
|
18
|
+
return i;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function scanPng(bytes, signatureAt) {
|
|
22
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
23
|
+
let cursor = signatureAt + PNG_SIGNATURE.length;
|
|
24
|
+
for (;;) {
|
|
25
|
+
if (cursor + 8 > bytes.length) return;
|
|
26
|
+
const length = view.getUint32(cursor);
|
|
27
|
+
const type = String.fromCharCode(require_bytes_view.byteAt(bytes, cursor + 4), require_bytes_view.byteAt(bytes, cursor + 5), require_bytes_view.byteAt(bytes, cursor + 6), require_bytes_view.byteAt(bytes, cursor + 7));
|
|
28
|
+
cursor += 8 + length + 4;
|
|
29
|
+
if (cursor > bytes.length) return;
|
|
30
|
+
if (type === "IEND") return {
|
|
31
|
+
format: "png",
|
|
32
|
+
bytes: bytes.subarray(signatureAt, cursor)
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function scanJpeg(bytes, soiAt) {
|
|
37
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
38
|
+
let cursor = soiAt + JPEG_SOI.length;
|
|
39
|
+
for (;;) {
|
|
40
|
+
if (cursor >= bytes.length) return;
|
|
41
|
+
if (require_bytes_view.byteAt(bytes, cursor) !== 255) return;
|
|
42
|
+
while (cursor < bytes.length && require_bytes_view.byteAt(bytes, cursor) === 255) cursor += 1;
|
|
43
|
+
if (cursor >= bytes.length) return;
|
|
44
|
+
const marker = require_bytes_view.byteAt(bytes, cursor);
|
|
45
|
+
cursor += 1;
|
|
46
|
+
if (marker === 216 || marker === 1 || marker >= 208 && marker <= 215) continue;
|
|
47
|
+
if (marker === 217) return {
|
|
48
|
+
format: "jpeg",
|
|
49
|
+
bytes: bytes.subarray(soiAt, cursor)
|
|
50
|
+
};
|
|
51
|
+
if (cursor + 2 > bytes.length) return;
|
|
52
|
+
const length = view.getUint16(cursor);
|
|
53
|
+
if (length < 2 || cursor + length > bytes.length) return;
|
|
54
|
+
cursor += length;
|
|
55
|
+
if (marker === 218) {
|
|
56
|
+
const eoi = indexOf(bytes, [255, 217], cursor);
|
|
57
|
+
if (eoi === void 0) return;
|
|
58
|
+
return {
|
|
59
|
+
format: "jpeg",
|
|
60
|
+
bytes: bytes.subarray(soiAt, eoi + 2)
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function scanImagePayload(bytes) {
|
|
66
|
+
const pngAt = indexOf(bytes, PNG_SIGNATURE, 0);
|
|
67
|
+
const jpegAt = indexOf(bytes, JPEG_SOI, 0);
|
|
68
|
+
if (pngAt !== void 0 && (jpegAt === void 0 || pngAt < jpegAt)) return scanPng(bytes, pngAt);
|
|
69
|
+
if (jpegAt !== void 0) return scanJpeg(bytes, jpegAt);
|
|
70
|
+
}
|
|
71
|
+
//#endregion
|
|
72
|
+
exports.scanImagePayload = scanImagePayload;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//#region src/stream/image.d.ts
|
|
2
|
+
interface WpdImagePayload {
|
|
3
|
+
readonly format: "png" | "jpeg";
|
|
4
|
+
readonly bytes: Uint8Array;
|
|
5
|
+
}
|
|
6
|
+
declare function scanImagePayload(bytes: Uint8Array): WpdImagePayload | undefined;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { WpdImagePayload, scanImagePayload };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//#region src/stream/image.d.ts
|
|
2
|
+
interface WpdImagePayload {
|
|
3
|
+
readonly format: "png" | "jpeg";
|
|
4
|
+
readonly bytes: Uint8Array;
|
|
5
|
+
}
|
|
6
|
+
declare function scanImagePayload(bytes: Uint8Array): WpdImagePayload | undefined;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { WpdImagePayload, scanImagePayload };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { byteAt } from "../bytes/view.js";
|
|
2
|
+
//#region src/stream/image.ts
|
|
3
|
+
const PNG_SIGNATURE = [
|
|
4
|
+
137,
|
|
5
|
+
80,
|
|
6
|
+
78,
|
|
7
|
+
71,
|
|
8
|
+
13,
|
|
9
|
+
10,
|
|
10
|
+
26,
|
|
11
|
+
10
|
|
12
|
+
];
|
|
13
|
+
const JPEG_SOI = [255, 216];
|
|
14
|
+
function indexOf(bytes, needle, from) {
|
|
15
|
+
outer: for (let i = from; i + needle.length <= bytes.length; i += 1) {
|
|
16
|
+
for (let j = 0; j < needle.length; j += 1) if (bytes[i + j] !== needle[j]) continue outer;
|
|
17
|
+
return i;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function scanPng(bytes, signatureAt) {
|
|
21
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
22
|
+
let cursor = signatureAt + PNG_SIGNATURE.length;
|
|
23
|
+
for (;;) {
|
|
24
|
+
if (cursor + 8 > bytes.length) return;
|
|
25
|
+
const length = view.getUint32(cursor);
|
|
26
|
+
const type = String.fromCharCode(byteAt(bytes, cursor + 4), byteAt(bytes, cursor + 5), byteAt(bytes, cursor + 6), byteAt(bytes, cursor + 7));
|
|
27
|
+
cursor += 8 + length + 4;
|
|
28
|
+
if (cursor > bytes.length) return;
|
|
29
|
+
if (type === "IEND") return {
|
|
30
|
+
format: "png",
|
|
31
|
+
bytes: bytes.subarray(signatureAt, cursor)
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function scanJpeg(bytes, soiAt) {
|
|
36
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
37
|
+
let cursor = soiAt + JPEG_SOI.length;
|
|
38
|
+
for (;;) {
|
|
39
|
+
if (cursor >= bytes.length) return;
|
|
40
|
+
if (byteAt(bytes, cursor) !== 255) return;
|
|
41
|
+
while (cursor < bytes.length && byteAt(bytes, cursor) === 255) cursor += 1;
|
|
42
|
+
if (cursor >= bytes.length) return;
|
|
43
|
+
const marker = byteAt(bytes, cursor);
|
|
44
|
+
cursor += 1;
|
|
45
|
+
if (marker === 216 || marker === 1 || marker >= 208 && marker <= 215) continue;
|
|
46
|
+
if (marker === 217) return {
|
|
47
|
+
format: "jpeg",
|
|
48
|
+
bytes: bytes.subarray(soiAt, cursor)
|
|
49
|
+
};
|
|
50
|
+
if (cursor + 2 > bytes.length) return;
|
|
51
|
+
const length = view.getUint16(cursor);
|
|
52
|
+
if (length < 2 || cursor + length > bytes.length) return;
|
|
53
|
+
cursor += length;
|
|
54
|
+
if (marker === 218) {
|
|
55
|
+
const eoi = indexOf(bytes, [255, 217], cursor);
|
|
56
|
+
if (eoi === void 0) return;
|
|
57
|
+
return {
|
|
58
|
+
format: "jpeg",
|
|
59
|
+
bytes: bytes.subarray(soiAt, eoi + 2)
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function scanImagePayload(bytes) {
|
|
65
|
+
const pngAt = indexOf(bytes, PNG_SIGNATURE, 0);
|
|
66
|
+
const jpegAt = indexOf(bytes, JPEG_SOI, 0);
|
|
67
|
+
if (pngAt !== void 0 && (jpegAt === void 0 || pngAt < jpegAt)) return scanPng(bytes, pngAt);
|
|
68
|
+
if (jpegAt !== void 0) return scanJpeg(bytes, jpegAt);
|
|
69
|
+
}
|
|
70
|
+
//#endregion
|
|
71
|
+
export { scanImagePayload };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wpd-codec",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.0",
|
|
4
4
|
"description": "Hand-written read-only WordPerfect 6.x-X6 (.wpd) reader over document-schema.js's ContentDocument",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -80,8 +80,8 @@
|
|
|
80
80
|
"license": "MIT",
|
|
81
81
|
"packageManager": "pnpm@11.6.0",
|
|
82
82
|
"dependencies": {
|
|
83
|
-
"archive-codec": "1.10.
|
|
84
|
-
"document-schema.js": "7.
|
|
83
|
+
"archive-codec": "1.10.2",
|
|
84
|
+
"document-schema.js": "7.6.0",
|
|
85
85
|
"zod": "4.4.3"
|
|
86
86
|
},
|
|
87
87
|
"devDependencies": {
|