wpd-codec 1.0.2 → 1.1.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.
Files changed (42) hide show
  1. package/README.md +71 -28
  2. package/dist/container/summary.cjs +78 -0
  3. package/dist/container/summary.d.cts +6 -0
  4. package/dist/container/summary.d.ts +6 -0
  5. package/dist/container/summary.js +76 -0
  6. package/dist/diagnostics-CR0pXOyb.d.cts +25 -0
  7. package/dist/diagnostics-CR0pXOyb.d.ts +25 -0
  8. package/dist/diagnostics.cjs +11 -1
  9. package/dist/diagnostics.d.cts +1 -1
  10. package/dist/diagnostics.d.ts +1 -1
  11. package/dist/diagnostics.js +11 -1
  12. package/dist/index.cjs +48 -0
  13. package/dist/index.d.cts +8 -2
  14. package/dist/index.d.ts +8 -2
  15. package/dist/index.js +7 -1
  16. package/dist/read.cjs +286 -36
  17. package/dist/read.d.cts +1 -1
  18. package/dist/read.d.ts +1 -1
  19. package/dist/read.js +287 -37
  20. package/dist/stream/page.cjs +47 -0
  21. package/dist/stream/page.d.cts +20 -0
  22. package/dist/stream/page.d.ts +20 -0
  23. package/dist/stream/page.js +35 -0
  24. package/dist/stream/style.cjs +80 -0
  25. package/dist/stream/style.d.cts +16 -0
  26. package/dist/stream/style.d.ts +16 -0
  27. package/dist/stream/style.js +71 -0
  28. package/dist/stream/tab.cjs +33 -0
  29. package/dist/stream/tab.d.cts +12 -0
  30. package/dist/stream/tab.d.ts +12 -0
  31. package/dist/stream/tab.js +31 -0
  32. package/dist/stream/table.cjs +160 -0
  33. package/dist/stream/table.d.cts +43 -0
  34. package/dist/stream/table.d.ts +43 -0
  35. package/dist/stream/table.js +146 -0
  36. package/dist/stream/units.cjs +11 -0
  37. package/dist/stream/units.d.cts +6 -0
  38. package/dist/stream/units.d.ts +6 -0
  39. package/dist/stream/units.js +8 -0
  40. package/package.json +1 -1
  41. package/dist/diagnostics-Cp0HKphg.d.cts +0 -15
  42. package/dist/diagnostics-Cp0HKphg.d.ts +0 -15
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  > Hand-written, read-only WordPerfect 6.x-X6 (`.wpd`) reading into `document-schema.js`'s `ContentDocument`, from Corel's own published File Format SDK — part of the [documents.js family](../../README.md). Worker-isomorphic: the same code runs under Node and inside a Cloudflare Workers isolate.
6
6
 
7
- **Status: under active development.** The read path below is tested against the specification's own worked examples and against hand-built fixtures derived from its field tables, but not yet against a corpus of real WordPerfect documents. See [Remaining scope](#remaining-scope) for what is deliberately not handled yet, and [What is not yet proven](#what-is-not-yet-proven) for the honest limits of the current evidence.
7
+ **Status: under active development.** The read path below is tested against the specification's own worked examples, against hand-built fixtures derived from its field tables, and since the corpus check described in [Evidence](#evidence) — against 93 real WordPerfect documents, 90 of which read and 3 of which are refused correctly. See [Remaining scope](#remaining-scope) for what is deliberately not handled yet.
8
8
 
9
9
  Created for [documents.js#819](https://github.com/ExaDev/documents.js/issues/819). The premise that made the issue worth acting on is that WordPerfect is not a reverse-engineered format: Corel shipped a File Format SDK as a supported developer product, and one specification covers the entire modern lineage — its own document-structure page states outright that "Files created in WordPerfect 6.x, through X6 are structured the same", so 1993 through 2012 is one format, not a family of them. There is also no JavaScript or TypeScript reader for it at all: [libwpd](https://libwpd.sourceforge.net/) is LGPL C++, WP_Reader is C#, and the SDK's own surviving mirror ships an Ada implementation.
10
10
 
@@ -12,16 +12,19 @@ Created for [documents.js#819](https://github.com/ExaDev/documents.js/issues/819
12
12
 
13
13
  Everything this package does is derived from the vendor's own documentation, and every non-obvious decision in the source cites the page it comes from.
14
14
 
15
- | Source | What it gives |
16
- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
17
- | [WordPerfect File Format SDK help](https://github.com/OneWingedShark/WordPerfect/tree/master/doc/SDK_Help/FileFormats) | The specification itself, mirrored in full: document structure, the prefix packet catalogue, single-byte characters and functions, every variable-length function group, the fixed-length functions, and table formulas. |
18
- | [WPFF Document Structure](https://github.com/OneWingedShark/WordPerfect/blob/master/doc/SDK_Help/FileFormats/WPFF_DocumentStructure.htm) | The file header, the index and packet data areas, the function-code stream's shape, the units glossary, and a complete annotated hex dump of a conforming generic prefix. |
19
- | [WPFF Single-Byte Characters and Functions](https://github.com/OneWingedShark/WordPerfect/blob/master/doc/SDK_Help/FileFormats/WPFF_SingleByte.htm) | The character model and the eighty single-byte function codes. |
20
- | [WPFF D0 EOL Functions](https://github.com/OneWingedShark/WordPerfect/blob/master/doc/SDK_Help/FileFormats/WPFF_D0-EOL.htm) | The End-of-Line group and, crucially, its "Conversion/Search mappings" column — the specification stating what a converting application should turn each break code into. |
21
- | [WPFF Fixed-Length Multi-Byte Functions](https://github.com/OneWingedShark/WordPerfect/blob/master/doc/SDK_Help/FileFormats/WPFF_xFixedLength.htm) | Attribute On/Off, the Extended Character function, and the size of every fixed-length code. |
22
- | [WPFF D3 Paragraph](https://github.com/OneWingedShark/WordPerfect/blob/master/doc/SDK_Help/FileFormats/WPFF_D3-Paragraph.htm) and [D4 Character](https://github.com/OneWingedShark/WordPerfect/blob/master/doc/SDK_Help/FileFormats/WPFF_D4-Character.htm) | Justification, font face and size changes, colour, and the rest of the paragraph- and character-oriented functions. |
23
- | [WPFF prefix packet catalogue](https://github.com/OneWingedShark/WordPerfect/blob/master/doc/SDK_Help/FileFormats/WPFF_PrefixPkt0-32.htm) | The packet types, including the font typeface descriptor layout this package reads a run's font family out of. |
24
- | [Corel's File Format SDK product page](https://web.archive.org/web/20120125025312/http://apps.corel.com/partners_developers/csp/wordperfect_fileformatsdk.htm) | The provenance: a supported Corel developer product documenting "the entire document format, document prefix and document codes". |
15
+ | Source | What it gives |
16
+ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
17
+ | [WordPerfect File Format SDK help](https://github.com/OneWingedShark/WordPerfect/tree/master/doc/SDK_Help/FileFormats) | The specification itself, mirrored in full: document structure, the prefix packet catalogue, single-byte characters and functions, every variable-length function group, the fixed-length functions, and table formulas. |
18
+ | [WPFF Document Structure](https://github.com/OneWingedShark/WordPerfect/blob/master/doc/SDK_Help/FileFormats/WPFF_DocumentStructure.htm) | The file header, the index and packet data areas, the function-code stream's shape, the units glossary, and a complete annotated hex dump of a conforming generic prefix. |
19
+ | [WPFF Single-Byte Characters and Functions](https://github.com/OneWingedShark/WordPerfect/blob/master/doc/SDK_Help/FileFormats/WPFF_SingleByte.htm) | The character model and the eighty single-byte function codes. |
20
+ | [WPFF D0 EOL Functions](https://github.com/OneWingedShark/WordPerfect/blob/master/doc/SDK_Help/FileFormats/WPFF_D0-EOL.htm) | The End-of-Line group and, crucially, its "Conversion/Search mappings" column — the specification stating what a converting application should turn each break code into. |
21
+ | [WPFF Fixed-Length Multi-Byte Functions](https://github.com/OneWingedShark/WordPerfect/blob/master/doc/SDK_Help/FileFormats/WPFF_xFixedLength.htm) | Attribute On/Off, the Extended Character function, and the size of every fixed-length code. |
22
+ | [WPFF D3 Paragraph](https://github.com/OneWingedShark/WordPerfect/blob/master/doc/SDK_Help/FileFormats/WPFF_D3-Paragraph.htm) and [D4 Character](https://github.com/OneWingedShark/WordPerfect/blob/master/doc/SDK_Help/FileFormats/WPFF_D4-Character.htm) | Justification, font face and size changes, colour, and the rest of the paragraph- and character-oriented functions. |
23
+ | [WPFF D1 Page](https://github.com/OneWingedShark/WordPerfect/blob/master/doc/SDK_Help/FileFormats/WPFF_D1-Page.htm) and [D2 Column](https://github.com/OneWingedShark/WordPerfect/blob/master/doc/SDK_Help/FileFormats/WPFF_D2-Column.htm) | Page geometry: the Form function's own page size, and the four margin functions split across the two groups. |
24
+ | [WPFF DD Style](https://github.com/OneWingedShark/WordPerfect/blob/master/doc/SDK_Help/FileFormats/WPFF_DD-Style.htm) and [DA Display Number](https://github.com/OneWingedShark/WordPerfect/blob/master/doc/SDK_Help/FileFormats/WPFF_DA-DisplayNumber.htm) | The system style number enumeration this package's heading and outline-level recovery rests on, and the paragraph-number display pair. |
25
+ | [WPFF E0 Tab](https://github.com/OneWingedShark/WordPerfect/blob/master/doc/SDK_Help/FileFormats/WPFF_E0-Tab.htm) | The tab definition bitfield, which this group carries in place of a subfunction number. |
26
+ | [WPFF prefix packet catalogue](https://github.com/OneWingedShark/WordPerfect/blob/master/doc/SDK_Help/FileFormats/WPFF_PrefixPkt0-32.htm) | The packet types, including the font typeface descriptor layout this package reads a run's font family out of, and the Extended Document Summary the document's own metadata comes from. |
27
+ | [Corel's File Format SDK product page](https://web.archive.org/web/20120125025312/http://apps.corel.com/partners_developers/csp/wordperfect_fileformatsdk.htm) | The provenance: a supported Corel developer product documenting "the entire document format, document prefix and document codes". |
25
28
 
26
29
  ## Getting started
27
30
 
@@ -49,7 +52,9 @@ import { readWpdContent } from "wpd-codec";
49
52
  // WordPerfect 6.x file, and a WP7-and-later OLE compound file whose
50
53
  // PerfectOffice_MAIN stream holds the identical byte stream.
51
54
  const document = readWpdContent(bytes);
52
- document.sections[0].blocks; // paragraphs, page breaks
55
+ document.sections[0].pageSize; // the document's own form, not an assumed default
56
+ document.sections[0].blocks; // paragraphs, tables, page breaks
57
+ document.metadata; // from the document's own Extended Document Summary
53
58
  ```
54
59
 
55
60
  `readWpd` is the same read one level up, returning the tree-form `DocumentTree` every other codec in the family also offers. `wpdContentCodec` states the read half as `document-schema.js`'s own `ContentCodec` port, so a consumer dispatching over formats treats WordPerfect uniformly with the rest.
@@ -81,10 +86,16 @@ Every module is importable by package-relative path as well as through the barre
81
86
  | `container/container` | `openWpdDocument` (container, header, packets, and document-area bounds in one), `PERFECT_OFFICE_MAIN_STREAM`, `PERFECT_OFFICE_OBJECTS_STORAGE` |
82
87
  | `container/header` | `readFileHeader`, `hasWordPerfectFileId`, `WPD_FILE_ID`, `WPD_PREFIX_HEADER_SIZE` |
83
88
  | `container/prefix` | `readPrefixPackets`, `packetByPrefixId`, `readTypefaceName`, `WPD_INDEX_RECORD_SIZE`, `PACKET_TYPE_DESIRED_FONT_DESCRIPTOR` |
89
+ | `container/summary` | `readDocumentSummary` (the Extended Document Summary packet as a `LayoutMetadata`), `PACKET_TYPE_EXTENDED_DOCUMENT_SUMMARY` |
84
90
  | `stream/tokenise` | `tokeniseDocumentArea` and the four token types |
85
91
  | `stream/characters` | `decodeWpCharacter`, `decodeSingleByteCharacter`, `decodeWordString`, `UNMAPPED_CHARACTER` |
86
92
  | `stream/eol` | `eolMappingForSubfunction`, `subfunctionForSingleByteEol`, `isSingleByteEol` |
87
93
  | `stream/attributes` | `decodeAttributeByte`, `runAttributesFrom`, `WpdAttribute` |
94
+ | `stream/units` | `pointsFromWpu`, `WPU_PER_INCH`, `POINTS_PER_INCH` |
95
+ | `stream/page` | `readPageForm`, `readMarginPt`, the Page and Column group constants, and the WordPerfect default page |
96
+ | `stream/style` | `styleSemanticsFor`, `readSystemStyleNumber`, the style scope predicates, and the Display Number group's paragraph-number pair |
97
+ | `stream/table` | `readEmbeddedSubfunctions`, `readTableColumnWidthPt`, `readRowInformation`, `readCellInformation`, `readCellSpanning`, `readCellFill` |
98
+ | `stream/tab` | `tabEffectFor`, `TAB_GROUP`, `WpdTabEffect` |
88
99
 
89
100
  The container and stream layers are public deliberately, not by accident: a consumer inspecting a WordPerfect file — a migration audit, a forensic tool, a reader for a construct this package does not yet lift into the shared schema — needs the parsed prefix and the raw function stream, not only the document they fold into.
90
101
 
@@ -96,7 +107,19 @@ A WordPerfect 6.x-X6 file is a **prefix** followed by a **document area**, optio
96
107
 
97
108
  **The tokeniser** (`src/stream/tokenise.ts`) walks the document area. Bytes at or below 0x7F are characters; above it, four ranges of function codes — single-byte (0x80-0xCF), variable-length multi-byte (0xD0-0xEF, self-describing through a size field), fixed-length multi-byte (0xF0-0xFE, sized by a table), and 0xFF, which cannot appear at all. Every multi-byte function is bracketed by matching begin and end gates, and the variable form repeats its size before the end gate; this package verifies all three redundancies, because they are the format's own integrity check — a stream that has gone out of step fails at the very next function rather than decoding rubbish for the rest of the file.
98
109
 
99
- **The fold** (`src/read.ts`) turns tokens into a `ContentDocument`. Characters accumulate into the current run, an attribute or font change closes that run and opens another, and an end-of-line function closes the paragraph. Nothing recurses and nothing looks ahead, which is what makes a hand-written reader for this format tractable at all.
110
+ **The fold** (`src/read.ts`) turns tokens into a `ContentDocument`. Characters accumulate into the current run, an attribute or font change closes that run and opens another, and an end-of-line function closes the paragraph. Nothing recurses and nothing looks ahead, which is what makes a hand-written reader for this format tractable at all. It has exactly one nesting concept, and it is not recursion: while a table definition is open a closed paragraph joins the cell being built rather than the section's own block list, which is as deep as this format's own grid model goes.
111
+
112
+ ### A table is stated in two halves, in two different groups
113
+
114
+ Neither half is nested inside the other, and knowing that is most of what reading one takes. The **definition** opens with Table Definition (`0xD42A`, "Table On"), is followed by one Table Column function (`0xD42C`) per column, and closes with Define Table End (`0xD42B`) — column widths and gutters, no content. The **content** follows as ordinary document text delimited by End-of-Line codes: subfunction 10 ends a cell, 11 through 16 end a cell and its row, 17 through 19 end the table. That is also why a reader with no table support recovers a table's text in reading order anyway: every boundary is a line break too.
115
+
116
+ The per-cell facts are not functions of their own. Spanning, justification, background fill and fixed row height ride **inside** the End-of-Line function that ends the cell, as "embedded subfunctions" in its non-deletable data — a layout unique to this group ("This format is unique in that the non-deletable data area also contains deletable data"), so that region opens with a size word for the deletable half and the documented subfunctions sit after it. Each embedded subfunction is gated by its own code the way every multi-byte function is, and the SDK prints a size against each; `src/stream/table.ts` holds that column as a table, because a record whose size the specification does not state cannot be stepped over — the walk stops there and says so rather than guessing a length and decoding the rest as rubbish.
117
+
118
+ ### A heading is a heading because the file says which style it is
119
+
120
+ WordPerfect states a style's identity twice: as a prefix ID naming the style's own packet, and — for a style the product defines rather than the user — as a **system style number** in the function's own data. The SDK enumerates that number, and its entries include "68 = heading level 1 style" through "75 = heading level 8 style", "52 = level 1 style (indented)" through "67 = level 8 style (not indented)", "31 = list" and "48 = bullets". Those are the whole basis for this package's heading and list recovery: nothing here infers a heading from a short line or a large font.
121
+
122
+ One detail of the fold exists for this. A style region ends at its own closing code, which in a real document sits **before** the hard return that ends the paragraph — so a paragraph's heading level is captured when its first character arrives, not when it closes, which would find the scope already popped.
100
123
 
101
124
  ### Two containers, one document
102
125
 
@@ -131,28 +154,48 @@ The only mature reader for this format is [libwpd](https://libwpd.sourceforge.ne
131
154
  - Font family, from the Desired Font Descriptor packet a Font Face Change names; font size, from a Font Size Change; character colour.
132
155
  - Paragraph justification.
133
156
  - The Start/End of Text to Skip pair, whose contents the formatter does not display and this reader drops.
157
+ - **Page geometry**: the page size from the Form function (0xD111) and all four margins from their own two groups — the vertical pair in the Page group (0xD100/0xD101), the horizontal pair in the Column group (0xD200/0xD201). Each dimension falls back to the WordPerfect default independently, so a document overriding only its top margin keeps US Letter and the other three inches rather than the whole default set.
158
+ - **Tables**, as real `ContentTable` grids: column widths from the Table Column functions, cells and rows from the End-of-Line boundaries, and — from the embedded subfunctions riding inside those boundaries — merged cells as `colSpan`/`rowSpan` with the positions they cover dropped, cell background colour, per-cell justification applied to the paragraphs it holds, and fixed row heights.
159
+ - **Heading levels and outline list levels**, from the Style group's system style numbers.
160
+ - **Outline numbering**, from the Display Number group's Paragraph Number Display pair: the level becomes a list membership and the pair's rendered digits are dropped in favour of it, since a counter's display is generated content rather than typed text.
161
+ - **Document metadata**, from the Extended Document Summary prefix packet (type 0x12): the Descriptive Name as the title, plus author, subject, keywords, and the creation and revision dates.
162
+ - **Tabs and line-scoped alignment**, from the Tab group (0xE0), whose byte in the subfunction position is the tab definition itself rather than a subfunction number. A type that advances to a tab stop becomes a tab character; centre-on-margins, centre-on-current-position and flush-right instead begin the line-scoped alignment the single-byte End of Center Align functions already terminate.
134
163
 
135
164
  ### Remaining scope
136
165
 
137
- 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.
138
-
139
- - **Tables.** Cell and row boundaries become paragraph breaks so a table's text survives in reading order; the grid, cell attributes, and the table's own formula language (`WPFF_TableFormulas`) are not reconstructed. Reported through `wpd/table-flattened`.
140
- - **Boxes and graphics** (the 0xDF group): figures, text boxes, and equations, and the WPG graphics they carry.
141
- - **Embedded OLE objects**, stored under the compound file's `PerfectOffice_OBJECTS` storage.
142
- - **Headers, footers, footnotes, and endnotes** (the 0xD6 and 0xD7 groups).
143
- - **Styles** (the 0xDD group) beyond the document's own Open Style: a run's directly-applied attributes are read, but a style packet's own definitions are not resolved onto the runs that reference them.
144
- - **Lists and outline numbering** (the 0xD8-0xDC groups).
145
- - **Merge codes** (the 0xDE group) and **cross-references** (0xD5).
146
- - **Page geometry** (the 0xD1 group): the section's page size and margins are the WordPerfect default (US Letter, one inch), not what a document that overrides them states.
147
- - **Document metadata**, which lives in prefix packets this reader does not yet interpret, so `metadata` is an empty envelope rather than fields invented from the file's structure.
148
- - **Character sets 2 and above**, and the part of set 1 the mirrored SDK pages do not tabulate. An unmapped character renders as U+FFFD and is reported through `wpd/unmapped-character` rather than dropped.
166
+ 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.
167
+
168
+ - **Boxes and graphics** (the 0xDF group): figures, text boxes, equations, and the WPG graphics they carry. Reported through `wpd/box-dropped`. Two things make this genuinely larger than it looks rather than merely unfinished: which of a box's prefix IDs is its contents, its caption, its border or its fill is not stated positionally but decided by a nested tree of override flags, each of which is itself a mask-plus-data record whose presence depends on the flag above it — so the PID list cannot be read at all without walking that tree correctly, and there is no real file here to walk it against. And a box whose content type is image carries WPG vector graphics, which `ContentImageBlock`'s own `png`/`jpeg` pair cannot hold whatever the walk recovers.
169
+ - **Embedded OLE objects**, stored under the compound file's `PerfectOffice_OBJECTS` storage. `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, so recovering one is a scoping question in its own right rather than a wiring job.
170
+ - **Headers, footers, footnotes, and endnotes** (the 0xD6 and 0xD7 groups). Reported through `wpd/header-footer-dropped` and `wpd/note-dropped`. The text is genuinely recoverable — each function names a General WP Text packet (type 0x08) holding its own function-code stream, which this package's tokeniser and fold would read — but the flat `ContentDocument` has no page-furniture position for a header or footer and no note position for a footnote body. That body's real home is `document-schema.js`'s tree-only `definitions` table, which a codec producing the flat form cannot reach; it is the same gap `rtf-codec` documents for its own equivalent constructs, and it closes at the schema boundary rather than here.
171
+ - **Styles** (the 0xDD group) beyond their system style numbers: a run's directly-applied attributes are read and a style region's own heading or outline level is recovered, but a style packet's own definitions (type 0x30) are not resolved onto the runs that reference them.
172
+ - **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.
173
+ - **Merge codes** (the 0xDE group) and **cross-references** (0xD5). A cross-reference's displayed text survives as ordinary text; its target binding does not. Reported through `wpd/merge-code-dropped` and `wpd/cross-reference-flattened`.
174
+ - **Table formulas** (`WPFF_TableFormulas`): the New Cell Formula embedded subfunction is walked past by its own length so the cells around it still read, but its tokenised formula is not decoded.
175
+ - **Character sets 2 and above**, and the part of set 1 the mirrored SDK pages do not tabulate — the largest remaining fidelity gap, and the one the corpus check measured (see [Evidence](#evidence)): two thirds of the extended characters in real documents name **character set 4** alone, with set 1's untabulated part next, then sets 13, 6, 5, 8, 12 and 3. This is a missing source rather than unfinished work: the mirrored SDK Help states the mechanism — "The high byte is the number of the WordPerfect character set. The low byte contains an offset value into the character set" — and tabulates the thirty-two Default Extended International Characters, but carries no character-set table of its own for any set. It closes when one is transcribed against a citable source, not by inference. An unmapped character renders as U+FFFD and is reported through `wpd/unmapped-character` rather than dropped.
149
176
  - **Encrypted documents**, which throw: the specification states that nothing beyond the file header is intelligible without the password, so there is no partial read to offer.
150
177
 
151
- ### What is not yet proven
178
+ ## Evidence
179
+
180
+ Two independent kinds, which answer different questions.
181
+
182
+ **The specification's own worked examples and field tables** answer "does this match what Corel documented". Every unit test is built either from a worked example — the annotated generic-prefix hex dump, the `can't` extended-character example, the `com<0x83>ment` soft-hyphen example — or from a byte sequence assembled directly from a field table, so each expectation is checkable against the page it cites without a file to hand.
183
+
184
+ **A real corpus** answers "does what Corel documented match what WordPerfect actually wrote", which the first kind cannot. The check ran against every WordPerfect 6.x-X6 file (file ID `FF 57 50 43`, major version 2) in [libwpd's own oss-fuzz seed corpus](https://sourceforge.net/projects/libwpd/files/corpus/) — 93 files: hand-authored feature tests, real bug-report attachments from the AbiWord, OpenOffice, LibreOffice and freedesktop trackers, and a batch of anonymised real-world documents — alongside the two WordPerfect 6 samples in the [Open Preservation Foundation's format corpus](https://github.com/openpreserve/format-corpus/tree/master/office/wordprocessing/WordPerfect6).
185
+
186
+ Ninety read. Three are refused, and each refusal is the right answer rather than a failure: a WordPerfect 3 for Macintosh file is rejected on its major-version byte (a separate format that shares the file ID — see Scope), an encrypted document is rejected because nothing past its header is intelligible without the password, and one file's function gates genuinely do not match — an AbiWord bug-report attachment, in a corpus whose whole purpose is to collect files that broke something. That last one is the tokeniser's own design working: it fails at the first byte the format says cannot be there, naming the offset, rather than decoding rubbish for the rest of the file.
187
+
188
+ What the corpus settles:
189
+
190
+ - **The character model is right.** This was the assumption most likely to make every real document read back as nonsense — bytes 1 through 32 are accented-letter shorthands rather than ASCII, so byte `0x20` is the sharp s and a space is the Soft Space function. Real documents read back as correct prose, which they could not do if that were wrong.
191
+ - **Page geometry, tables, metadata, and outline numbering are exercised by real files**, not only by fixtures: a quarter of the corpus states a page size other than US Letter, a third contains a table, and a sixth carries a document summary.
192
+ - **The heading mapping is not.** No document in the corpus uses WordPerfect's own heading styles (system style numbers 68 through 75), so heading recovery is still evidenced by the specification's enumeration alone. The style group itself is heavily exercised — footnote-number, endnote-number, document and hypertext system styles all appear, and all correctly carry no structure.
193
+ - **Character sets 2 and above are the largest remaining fidelity gap, and set 4 is most of it.** Two thirds of the extended characters in the corpus name character set 4, with the untabulated part of set 1 next, then sets 13, 6, 5, 8, 12 and 3. Each renders as U+FFFD today. That makes the missing character-set tables a concrete, measurable piece of work rather than a theoretical one.
152
194
 
153
- Stated plainly, because it is the difference between this package being correct and being consistent with its own tests:
195
+ ### What is still not proven
154
196
 
155
- - **No real-world corpus.** Every test here is built either from the specification's own worked examples the annotated generic-prefix hex dump, the `can't` extended-character example, the `com<0x83>ment` soft-hyphen example or from byte sequences assembled directly from its field tables. That is strong evidence for the container, prefix, tokeniser, and the specific constructs covered; it is not evidence about what real WordPerfect documents in the wild actually contain, particularly around deletable data and the constructs listed above.
197
+ - **Two readings the specification does not settle, both chosen deliberately.** The Form function states its desired width and its desired length as two independent fields and its orientation as a third, and says nothing about whether the pair is written before or after the rotation — so a landscape form's dimensions go through exactly as written and the flag is reported through `wpd/landscape-orientation-unmapped` rather than rotated on this package's own inference. And a Table Column's `[width]` is the one horizontal dimension in the format the SDK does not tag `(WPU)`, so it is read as WordPerfect Units on the strength of the two gutter fields immediately after it, which are.
198
+ - **Reading a document is not rendering one.** The corpus check confirms that each file reads, that its text is prose, and that the structures above are recovered; it does not compare the result against what WordPerfect itself would display, which would need a reference renderer this family does not have.
156
199
  - **The document-area's own file-size bound.** The header's file-size field is honoured only when self-consistent, because the SDK itself warns that a third-party writer failing to update it is a common real-world defect whose symptom is a document reading back blank.
157
200
 
158
201
  ## Conventions
@@ -0,0 +1,78 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_bytes_view = require("../bytes/view.cjs");
3
+ const require_stream_characters = require("../stream/characters.cjs");
4
+ //#region src/container/summary.ts
5
+ const PACKET_TYPE_EXTENDED_DOCUMENT_SUMMARY = 18;
6
+ const TAG_AUTHOR = 5;
7
+ const TAG_CREATION_DATE = 14;
8
+ const TAG_DESCRIPTIVE_NAME = 17;
9
+ const TAG_KEYWORDS = 26;
10
+ const TAG_REVISION_DATE = 39;
11
+ const TAG_SUBJECT = 46;
12
+ const TYPE_DATE = 4;
13
+ const GROUP_HEADER_SIZE = 6;
14
+ const DATE_FIELD_SIZE = 10;
15
+ const MAX_SUMMARY_GROUPS = 100;
16
+ function pad(value, width) {
17
+ return value.toString().padStart(width, "0");
18
+ }
19
+ function readDateField(bytes, offset) {
20
+ if (offset + DATE_FIELD_SIZE > bytes.length) return;
21
+ const year = require_bytes_view.uint16At(bytes, offset);
22
+ const month = bytes[offset + 2];
23
+ const day = bytes[offset + 3];
24
+ const hour = bytes[offset + 4];
25
+ const minute = bytes[offset + 5];
26
+ const second = bytes[offset + 6];
27
+ if (month === void 0 || day === void 0 || hour === void 0 || minute === void 0 || second === void 0) return;
28
+ if (year === 0 || month === 0 || day === 0) return;
29
+ return `${pad(year, 4)}-${pad(month, 2)}-${pad(day, 2)}T${pad(hour, 2)}:${pad(minute, 2)}:${pad(second, 2)}`;
30
+ }
31
+ function splitKeywords(value) {
32
+ return value.split(",").map((keyword) => keyword.trim()).filter((keyword) => keyword.length > 0);
33
+ }
34
+ function readDocumentSummary(packet) {
35
+ const metadata = {};
36
+ let cursor = 0;
37
+ for (let group = 0; group < MAX_SUMMARY_GROUPS; group += 1) {
38
+ if (cursor + GROUP_HEADER_SIZE > packet.length) break;
39
+ const size = require_bytes_view.uint16At(packet, cursor);
40
+ const tag = require_bytes_view.uint16At(packet, cursor + 2);
41
+ const type = require_bytes_view.uint16At(packet, cursor + 4);
42
+ if (size < GROUP_HEADER_SIZE || cursor + size > packet.length) break;
43
+ const availableWords = Math.floor((size - GROUP_HEADER_SIZE) / 2);
44
+ const name = require_stream_characters.decodeWordString(packet, cursor + GROUP_HEADER_SIZE, availableWords);
45
+ const dataOffset = cursor + GROUP_HEADER_SIZE + name.wordsRead * 2;
46
+ if ((type & TYPE_DATE) !== 0) {
47
+ const iso = readDateField(packet, dataOffset);
48
+ if (iso !== void 0) {
49
+ if (tag === TAG_CREATION_DATE) metadata.createdIso = iso;
50
+ else if (tag === TAG_REVISION_DATE) metadata.modifiedIso = iso;
51
+ }
52
+ cursor += size;
53
+ continue;
54
+ }
55
+ const { text } = require_stream_characters.decodeWordString(packet, dataOffset, availableWords - name.wordsRead);
56
+ if (text.length > 0) switch (tag) {
57
+ case TAG_DESCRIPTIVE_NAME:
58
+ metadata.title = text;
59
+ break;
60
+ case TAG_AUTHOR:
61
+ metadata.author = text;
62
+ break;
63
+ case TAG_SUBJECT:
64
+ metadata.subject = text;
65
+ break;
66
+ case TAG_KEYWORDS: {
67
+ const keywords = splitKeywords(text);
68
+ if (keywords.length > 0) metadata.keywords = keywords;
69
+ break;
70
+ }
71
+ }
72
+ cursor += size;
73
+ }
74
+ return metadata;
75
+ }
76
+ //#endregion
77
+ exports.PACKET_TYPE_EXTENDED_DOCUMENT_SUMMARY = PACKET_TYPE_EXTENDED_DOCUMENT_SUMMARY;
78
+ exports.readDocumentSummary = readDocumentSummary;
@@ -0,0 +1,6 @@
1
+ import { LayoutMetadata } from "document-schema.js";
2
+ //#region src/container/summary.d.ts
3
+ declare const PACKET_TYPE_EXTENDED_DOCUMENT_SUMMARY = 18;
4
+ declare function readDocumentSummary(packet: Uint8Array): LayoutMetadata;
5
+ //#endregion
6
+ export { PACKET_TYPE_EXTENDED_DOCUMENT_SUMMARY, readDocumentSummary };
@@ -0,0 +1,6 @@
1
+ import { LayoutMetadata } from "document-schema.js";
2
+ //#region src/container/summary.d.ts
3
+ declare const PACKET_TYPE_EXTENDED_DOCUMENT_SUMMARY = 18;
4
+ declare function readDocumentSummary(packet: Uint8Array): LayoutMetadata;
5
+ //#endregion
6
+ export { PACKET_TYPE_EXTENDED_DOCUMENT_SUMMARY, readDocumentSummary };
@@ -0,0 +1,76 @@
1
+ import { uint16At } from "../bytes/view.js";
2
+ import { decodeWordString } from "../stream/characters.js";
3
+ //#region src/container/summary.ts
4
+ const PACKET_TYPE_EXTENDED_DOCUMENT_SUMMARY = 18;
5
+ const TAG_AUTHOR = 5;
6
+ const TAG_CREATION_DATE = 14;
7
+ const TAG_DESCRIPTIVE_NAME = 17;
8
+ const TAG_KEYWORDS = 26;
9
+ const TAG_REVISION_DATE = 39;
10
+ const TAG_SUBJECT = 46;
11
+ const TYPE_DATE = 4;
12
+ const GROUP_HEADER_SIZE = 6;
13
+ const DATE_FIELD_SIZE = 10;
14
+ const MAX_SUMMARY_GROUPS = 100;
15
+ function pad(value, width) {
16
+ return value.toString().padStart(width, "0");
17
+ }
18
+ function readDateField(bytes, offset) {
19
+ if (offset + DATE_FIELD_SIZE > bytes.length) return;
20
+ const year = uint16At(bytes, offset);
21
+ const month = bytes[offset + 2];
22
+ const day = bytes[offset + 3];
23
+ const hour = bytes[offset + 4];
24
+ const minute = bytes[offset + 5];
25
+ const second = bytes[offset + 6];
26
+ if (month === void 0 || day === void 0 || hour === void 0 || minute === void 0 || second === void 0) return;
27
+ if (year === 0 || month === 0 || day === 0) return;
28
+ return `${pad(year, 4)}-${pad(month, 2)}-${pad(day, 2)}T${pad(hour, 2)}:${pad(minute, 2)}:${pad(second, 2)}`;
29
+ }
30
+ function splitKeywords(value) {
31
+ return value.split(",").map((keyword) => keyword.trim()).filter((keyword) => keyword.length > 0);
32
+ }
33
+ function readDocumentSummary(packet) {
34
+ const metadata = {};
35
+ let cursor = 0;
36
+ for (let group = 0; group < MAX_SUMMARY_GROUPS; group += 1) {
37
+ if (cursor + GROUP_HEADER_SIZE > packet.length) break;
38
+ const size = uint16At(packet, cursor);
39
+ const tag = uint16At(packet, cursor + 2);
40
+ const type = uint16At(packet, cursor + 4);
41
+ if (size < GROUP_HEADER_SIZE || cursor + size > packet.length) break;
42
+ const availableWords = Math.floor((size - GROUP_HEADER_SIZE) / 2);
43
+ const name = decodeWordString(packet, cursor + GROUP_HEADER_SIZE, availableWords);
44
+ const dataOffset = cursor + GROUP_HEADER_SIZE + name.wordsRead * 2;
45
+ if ((type & TYPE_DATE) !== 0) {
46
+ const iso = readDateField(packet, dataOffset);
47
+ if (iso !== void 0) {
48
+ if (tag === TAG_CREATION_DATE) metadata.createdIso = iso;
49
+ else if (tag === TAG_REVISION_DATE) metadata.modifiedIso = iso;
50
+ }
51
+ cursor += size;
52
+ continue;
53
+ }
54
+ const { text } = decodeWordString(packet, dataOffset, availableWords - name.wordsRead);
55
+ if (text.length > 0) switch (tag) {
56
+ case TAG_DESCRIPTIVE_NAME:
57
+ metadata.title = text;
58
+ break;
59
+ case TAG_AUTHOR:
60
+ metadata.author = text;
61
+ break;
62
+ case TAG_SUBJECT:
63
+ metadata.subject = text;
64
+ break;
65
+ case TAG_KEYWORDS: {
66
+ const keywords = splitKeywords(text);
67
+ if (keywords.length > 0) metadata.keywords = keywords;
68
+ break;
69
+ }
70
+ }
71
+ cursor += size;
72
+ }
73
+ return metadata;
74
+ }
75
+ //#endregion
76
+ export { PACKET_TYPE_EXTENDED_DOCUMENT_SUMMARY, readDocumentSummary };
@@ -0,0 +1,25 @@
1
+ //#region src/diagnostics.d.ts
2
+ interface WpdDiagnostic {
3
+ readonly code: string;
4
+ readonly message: string;
5
+ }
6
+ type WpdDiagnosticSink = (diagnostic: WpdDiagnostic) => void;
7
+ declare const WpdDiagnosticCodes: {
8
+ readonly UnmappedCharacter: "wpd/unmapped-character";
9
+ readonly MissingPrefixPacket: "wpd/missing-prefix-packet";
10
+ readonly TableFlattened: "wpd/table-flattened";
11
+ readonly ColumnBreakFlattened: "wpd/column-break-flattened";
12
+ readonly TableAttributesTruncated: "wpd/table-attributes-truncated";
13
+ readonly CellFillBlended: "wpd/cell-fill-blended";
14
+ readonly LandscapeOrientationUnmapped: "wpd/landscape-orientation-unmapped";
15
+ readonly PageGeometryChanged: "wpd/page-geometry-changed";
16
+ readonly OutlineNumberRegenerated: "wpd/outline-number-regenerated";
17
+ readonly BoxDropped: "wpd/box-dropped";
18
+ readonly NoteDropped: "wpd/note-dropped";
19
+ readonly HeaderFooterDropped: "wpd/header-footer-dropped";
20
+ readonly CrossReferenceFlattened: "wpd/cross-reference-flattened";
21
+ readonly MergeCodeDropped: "wpd/merge-code-dropped";
22
+ };
23
+ declare const NOOP_WPD_DIAGNOSTIC_SINK: WpdDiagnosticSink;
24
+ //#endregion
25
+ export { WpdDiagnosticSink as i, WpdDiagnostic as n, WpdDiagnosticCodes as r, NOOP_WPD_DIAGNOSTIC_SINK as t };
@@ -0,0 +1,25 @@
1
+ //#region src/diagnostics.d.ts
2
+ interface WpdDiagnostic {
3
+ readonly code: string;
4
+ readonly message: string;
5
+ }
6
+ type WpdDiagnosticSink = (diagnostic: WpdDiagnostic) => void;
7
+ declare const WpdDiagnosticCodes: {
8
+ readonly UnmappedCharacter: "wpd/unmapped-character";
9
+ readonly MissingPrefixPacket: "wpd/missing-prefix-packet";
10
+ readonly TableFlattened: "wpd/table-flattened";
11
+ readonly ColumnBreakFlattened: "wpd/column-break-flattened";
12
+ readonly TableAttributesTruncated: "wpd/table-attributes-truncated";
13
+ readonly CellFillBlended: "wpd/cell-fill-blended";
14
+ readonly LandscapeOrientationUnmapped: "wpd/landscape-orientation-unmapped";
15
+ readonly PageGeometryChanged: "wpd/page-geometry-changed";
16
+ readonly OutlineNumberRegenerated: "wpd/outline-number-regenerated";
17
+ readonly BoxDropped: "wpd/box-dropped";
18
+ readonly NoteDropped: "wpd/note-dropped";
19
+ readonly HeaderFooterDropped: "wpd/header-footer-dropped";
20
+ readonly CrossReferenceFlattened: "wpd/cross-reference-flattened";
21
+ readonly MergeCodeDropped: "wpd/merge-code-dropped";
22
+ };
23
+ declare const NOOP_WPD_DIAGNOSTIC_SINK: WpdDiagnosticSink;
24
+ //#endregion
25
+ export { WpdDiagnosticSink as i, WpdDiagnostic as n, WpdDiagnosticCodes as r, NOOP_WPD_DIAGNOSTIC_SINK as t };
@@ -4,7 +4,17 @@ const WpdDiagnosticCodes = {
4
4
  UnmappedCharacter: "wpd/unmapped-character",
5
5
  MissingPrefixPacket: "wpd/missing-prefix-packet",
6
6
  TableFlattened: "wpd/table-flattened",
7
- ColumnBreakFlattened: "wpd/column-break-flattened"
7
+ ColumnBreakFlattened: "wpd/column-break-flattened",
8
+ TableAttributesTruncated: "wpd/table-attributes-truncated",
9
+ CellFillBlended: "wpd/cell-fill-blended",
10
+ LandscapeOrientationUnmapped: "wpd/landscape-orientation-unmapped",
11
+ PageGeometryChanged: "wpd/page-geometry-changed",
12
+ OutlineNumberRegenerated: "wpd/outline-number-regenerated",
13
+ BoxDropped: "wpd/box-dropped",
14
+ NoteDropped: "wpd/note-dropped",
15
+ HeaderFooterDropped: "wpd/header-footer-dropped",
16
+ CrossReferenceFlattened: "wpd/cross-reference-flattened",
17
+ MergeCodeDropped: "wpd/merge-code-dropped"
8
18
  };
9
19
  const NOOP_WPD_DIAGNOSTIC_SINK = () => {};
10
20
  //#endregion
@@ -1,2 +1,2 @@
1
- import { i as WpdDiagnosticSink, n as WpdDiagnostic, r as WpdDiagnosticCodes, t as NOOP_WPD_DIAGNOSTIC_SINK } from "./diagnostics-Cp0HKphg.cjs";
1
+ import { i as WpdDiagnosticSink, n as WpdDiagnostic, r as WpdDiagnosticCodes, t as NOOP_WPD_DIAGNOSTIC_SINK } from "./diagnostics-CR0pXOyb.cjs";
2
2
  export { NOOP_WPD_DIAGNOSTIC_SINK, WpdDiagnostic, WpdDiagnosticCodes, WpdDiagnosticSink };
@@ -1,2 +1,2 @@
1
- import { i as WpdDiagnosticSink, n as WpdDiagnostic, r as WpdDiagnosticCodes, t as NOOP_WPD_DIAGNOSTIC_SINK } from "./diagnostics-Cp0HKphg.js";
1
+ import { i as WpdDiagnosticSink, n as WpdDiagnostic, r as WpdDiagnosticCodes, t as NOOP_WPD_DIAGNOSTIC_SINK } from "./diagnostics-CR0pXOyb.js";
2
2
  export { NOOP_WPD_DIAGNOSTIC_SINK, WpdDiagnostic, WpdDiagnosticCodes, WpdDiagnosticSink };
@@ -3,7 +3,17 @@ const WpdDiagnosticCodes = {
3
3
  UnmappedCharacter: "wpd/unmapped-character",
4
4
  MissingPrefixPacket: "wpd/missing-prefix-packet",
5
5
  TableFlattened: "wpd/table-flattened",
6
- ColumnBreakFlattened: "wpd/column-break-flattened"
6
+ ColumnBreakFlattened: "wpd/column-break-flattened",
7
+ TableAttributesTruncated: "wpd/table-attributes-truncated",
8
+ CellFillBlended: "wpd/cell-fill-blended",
9
+ LandscapeOrientationUnmapped: "wpd/landscape-orientation-unmapped",
10
+ PageGeometryChanged: "wpd/page-geometry-changed",
11
+ OutlineNumberRegenerated: "wpd/outline-number-regenerated",
12
+ BoxDropped: "wpd/box-dropped",
13
+ NoteDropped: "wpd/note-dropped",
14
+ HeaderFooterDropped: "wpd/header-footer-dropped",
15
+ CrossReferenceFlattened: "wpd/cross-reference-flattened",
16
+ MergeCodeDropped: "wpd/merge-code-dropped"
7
17
  };
8
18
  const NOOP_WPD_DIAGNOSTIC_SINK = () => {};
9
19
  //#endregion
package/dist/index.cjs CHANGED
@@ -4,15 +4,34 @@ const require_container_header = require("./container/header.cjs");
4
4
  const require_stream_characters = require("./stream/characters.cjs");
5
5
  const require_container_prefix = require("./container/prefix.cjs");
6
6
  const require_container_container = require("./container/container.cjs");
7
+ const require_container_summary = require("./container/summary.cjs");
7
8
  const require_diagnostics = require("./diagnostics.cjs");
8
9
  const require_stream_attributes = require("./stream/attributes.cjs");
9
10
  const require_stream_eol = require("./stream/eol.cjs");
11
+ const require_stream_units = require("./stream/units.cjs");
12
+ const require_stream_page = require("./stream/page.cjs");
13
+ const require_stream_style = require("./stream/style.cjs");
14
+ const require_stream_table = require("./stream/table.cjs");
15
+ const require_stream_tab = require("./stream/tab.cjs");
10
16
  const require_stream_tokenise = require("./stream/tokenise.cjs");
11
17
  const require_read = require("./read.cjs");
12
18
  const require_codec = require("./codec.cjs");
13
19
  const require_format = require("./format.cjs");
14
20
  exports.ATTRIBUTE_OFF = require_stream_attributes.ATTRIBUTE_OFF;
15
21
  exports.ATTRIBUTE_ON = require_stream_attributes.ATTRIBUTE_ON;
22
+ exports.CELL_FILL_COLORS_SUBFUNCTION = require_stream_table.CELL_FILL_COLORS_SUBFUNCTION;
23
+ exports.CELL_INFORMATION_SUBFUNCTION = require_stream_table.CELL_INFORMATION_SUBFUNCTION;
24
+ exports.CELL_SPANNING_SUBFUNCTION = require_stream_table.CELL_SPANNING_SUBFUNCTION;
25
+ exports.CHARACTER_DEFINE_TABLE_END = require_stream_table.CHARACTER_DEFINE_TABLE_END;
26
+ exports.CHARACTER_TABLE_COLUMN = require_stream_table.CHARACTER_TABLE_COLUMN;
27
+ exports.CHARACTER_TABLE_DEFINITION = require_stream_table.CHARACTER_TABLE_DEFINITION;
28
+ exports.COLUMN_GROUP = require_stream_page.COLUMN_GROUP;
29
+ exports.COLUMN_LEFT_MARGIN_SET = require_stream_page.COLUMN_LEFT_MARGIN_SET;
30
+ exports.COLUMN_RIGHT_MARGIN_SET = require_stream_page.COLUMN_RIGHT_MARGIN_SET;
31
+ exports.DEFAULT_MARGIN_PT = require_stream_page.DEFAULT_MARGIN_PT;
32
+ exports.DEFAULT_PAGE_HEIGHT_PT = require_stream_page.DEFAULT_PAGE_HEIGHT_PT;
33
+ exports.DEFAULT_PAGE_WIDTH_PT = require_stream_page.DEFAULT_PAGE_WIDTH_PT;
34
+ exports.DISPLAY_NUMBER_GROUP = require_stream_style.DISPLAY_NUMBER_GROUP;
16
35
  exports.EOL_GROUP = require_stream_eol.EOL_GROUP;
17
36
  exports.FIRST_ASCII_CHARACTER = require_stream_characters.FIRST_ASCII_CHARACTER;
18
37
  exports.FIRST_FIXED_FUNCTION = require_stream_tokenise.FIRST_FIXED_FUNCTION;
@@ -23,14 +42,24 @@ exports.LAST_CHARACTER = require_stream_characters.LAST_CHARACTER;
23
42
  exports.LAST_SINGLE_BYTE_EOL = require_stream_eol.LAST_SINGLE_BYTE_EOL;
24
43
  exports.NOOP_WPD_DIAGNOSTIC_SINK = require_diagnostics.NOOP_WPD_DIAGNOSTIC_SINK;
25
44
  exports.PACKET_TYPE_DESIRED_FONT_DESCRIPTOR = require_container_prefix.PACKET_TYPE_DESIRED_FONT_DESCRIPTOR;
45
+ exports.PACKET_TYPE_EXTENDED_DOCUMENT_SUMMARY = require_container_summary.PACKET_TYPE_EXTENDED_DOCUMENT_SUMMARY;
46
+ exports.PAGE_BOTTOM_MARGIN_SET = require_stream_page.PAGE_BOTTOM_MARGIN_SET;
47
+ exports.PAGE_FORM = require_stream_page.PAGE_FORM;
48
+ exports.PAGE_GROUP = require_stream_page.PAGE_GROUP;
49
+ exports.PAGE_TOP_MARGIN_SET = require_stream_page.PAGE_TOP_MARGIN_SET;
26
50
  exports.PERFECT_OFFICE_MAIN_STREAM = require_container_container.PERFECT_OFFICE_MAIN_STREAM;
27
51
  exports.PERFECT_OFFICE_OBJECTS_STORAGE = require_container_container.PERFECT_OFFICE_OBJECTS_STORAGE;
52
+ exports.POINTS_PER_INCH = require_stream_units.POINTS_PER_INCH;
53
+ exports.ROW_INFORMATION_SUBFUNCTION = require_stream_table.ROW_INFORMATION_SUBFUNCTION;
54
+ exports.STYLE_GROUP = require_stream_style.STYLE_GROUP;
55
+ exports.TAB_GROUP = require_stream_tab.TAB_GROUP;
28
56
  exports.UNMAPPED_CHARACTER = require_stream_characters.UNMAPPED_CHARACTER;
29
57
  exports.WPD_FILE_EXTENSION = require_format.WPD_FILE_EXTENSION;
30
58
  exports.WPD_FILE_ID = require_container_header.WPD_FILE_ID;
31
59
  exports.WPD_INDEX_RECORD_SIZE = require_container_prefix.WPD_INDEX_RECORD_SIZE;
32
60
  exports.WPD_MEDIA_TYPE = require_format.WPD_MEDIA_TYPE;
33
61
  exports.WPD_PREFIX_HEADER_SIZE = require_container_header.WPD_PREFIX_HEADER_SIZE;
62
+ exports.WPU_PER_INCH = require_stream_units.WPU_PER_INCH;
34
63
  exports.WpdAttribute = require_stream_attributes.WpdAttribute;
35
64
  exports.WpdBytesSchema = require_codec.WpdBytesSchema;
36
65
  exports.WpdDiagnosticCodes = require_diagnostics.WpdDiagnosticCodes;
@@ -43,16 +72,35 @@ exports.decodeSingleByteCharacter = require_stream_characters.decodeSingleByteCh
43
72
  exports.decodeWordString = require_stream_characters.decodeWordString;
44
73
  exports.decodeWpCharacter = require_stream_characters.decodeWpCharacter;
45
74
  exports.eolMappingForSubfunction = require_stream_eol.eolMappingForSubfunction;
75
+ exports.findEmbeddedSubfunction = require_stream_table.findEmbeddedSubfunction;
46
76
  exports.hasWordPerfectFileId = require_container_header.hasWordPerfectFileId;
77
+ exports.isParagraphNumberDisplayOff = require_stream_style.isParagraphNumberDisplayOff;
78
+ exports.isParagraphNumberDisplayOn = require_stream_style.isParagraphNumberDisplayOn;
47
79
  exports.isSingleByteEol = require_stream_eol.isSingleByteEol;
80
+ exports.isStyleScopeCloser = require_stream_style.isStyleScopeCloser;
81
+ exports.isStyleScopeOpener = require_stream_style.isStyleScopeOpener;
48
82
  exports.openWpdDocument = require_container_container.openWpdDocument;
49
83
  exports.packetByPrefixId = require_container_prefix.packetByPrefixId;
84
+ exports.pointsFromWpu = require_stream_units.pointsFromWpu;
85
+ exports.readCellFill = require_stream_table.readCellFill;
86
+ exports.readCellInformation = require_stream_table.readCellInformation;
87
+ exports.readCellSpanning = require_stream_table.readCellSpanning;
88
+ exports.readDisplayNumberLevel = require_stream_style.readDisplayNumberLevel;
89
+ exports.readDocumentSummary = require_container_summary.readDocumentSummary;
90
+ exports.readEmbeddedSubfunctions = require_stream_table.readEmbeddedSubfunctions;
50
91
  exports.readFileHeader = require_container_header.readFileHeader;
92
+ exports.readMarginPt = require_stream_page.readMarginPt;
93
+ exports.readPageForm = require_stream_page.readPageForm;
51
94
  exports.readPrefixPackets = require_container_prefix.readPrefixPackets;
95
+ exports.readRowInformation = require_stream_table.readRowInformation;
96
+ exports.readSystemStyleNumber = require_stream_style.readSystemStyleNumber;
97
+ exports.readTableColumnWidthPt = require_stream_table.readTableColumnWidthPt;
52
98
  exports.readTypefaceName = require_container_prefix.readTypefaceName;
53
99
  exports.readWpd = require_read.readWpd;
54
100
  exports.readWpdContent = require_read.readWpdContent;
55
101
  exports.runAttributesFrom = require_stream_attributes.runAttributesFrom;
102
+ exports.styleSemanticsFor = require_stream_style.styleSemanticsFor;
56
103
  exports.subfunctionForSingleByteEol = require_stream_eol.subfunctionForSingleByteEol;
104
+ exports.tabEffectFor = require_stream_tab.tabEffectFor;
57
105
  exports.tokeniseDocumentArea = require_stream_tokenise.tokeniseDocumentArea;
58
106
  exports.wpdContentCodec = require_codec.wpdContentCodec;
package/dist/index.d.cts CHANGED
@@ -1,13 +1,19 @@
1
- import { i as WpdDiagnosticSink, n as WpdDiagnostic, r as WpdDiagnosticCodes, t as NOOP_WPD_DIAGNOSTIC_SINK } from "./diagnostics-Cp0HKphg.cjs";
1
+ import { i as WpdDiagnosticSink, n as WpdDiagnostic, r as WpdDiagnosticCodes, t as NOOP_WPD_DIAGNOSTIC_SINK } from "./diagnostics-CR0pXOyb.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";
5
5
  import { PACKET_TYPE_DESIRED_FONT_DESCRIPTOR, WPD_INDEX_RECORD_SIZE, WpdPrefixPacket, packetByPrefixId, readPrefixPackets, readTypefaceName } from "./container/prefix.cjs";
6
6
  import { PERFECT_OFFICE_MAIN_STREAM, PERFECT_OFFICE_OBJECTS_STORAGE, WpdDocumentContainer, openWpdDocument } from "./container/container.cjs";
7
+ import { PACKET_TYPE_EXTENDED_DOCUMENT_SUMMARY, readDocumentSummary } from "./container/summary.cjs";
7
8
  import { WpdEncryptedDocumentError, WpdFormatError, WpdNotAWordPerfectFileError, WpdUnsupportedVersionError } from "./errors.cjs";
8
9
  import { WPD_FILE_EXTENSION, WPD_MEDIA_TYPE } from "./format.cjs";
9
10
  import { FIRST_FIXED_FUNCTION, FIRST_SINGLE_BYTE_FUNCTION, FIRST_VARIABLE_FUNCTION, WpdCharacterToken, WpdFixedFunctionToken, WpdSingleByteFunctionToken, WpdToken, WpdVariableFunctionToken, tokeniseDocumentArea } from "./stream/tokenise.cjs";
10
11
  import { EOL_GROUP, FIRST_SINGLE_BYTE_EOL, LAST_SINGLE_BYTE_EOL, WpdEolMapping, eolMappingForSubfunction, isSingleByteEol, subfunctionForSingleByteEol } from "./stream/eol.cjs";
11
12
  import { ATTRIBUTE_OFF, ATTRIBUTE_ON, WpdAttribute, WpdAttributeCode, WpdRunAttributes, decodeAttributeByte, runAttributesFrom } from "./stream/attributes.cjs";
12
13
  import { FIRST_ASCII_CHARACTER, LAST_CHARACTER, UNMAPPED_CHARACTER, decodeSingleByteCharacter, decodeWordString, decodeWpCharacter } from "./stream/characters.cjs";
13
- export { ATTRIBUTE_OFF, ATTRIBUTE_ON, EOL_GROUP, FIRST_ASCII_CHARACTER, FIRST_FIXED_FUNCTION, FIRST_SINGLE_BYTE_EOL, FIRST_SINGLE_BYTE_FUNCTION, FIRST_VARIABLE_FUNCTION, LAST_CHARACTER, LAST_SINGLE_BYTE_EOL, NOOP_WPD_DIAGNOSTIC_SINK, PACKET_TYPE_DESIRED_FONT_DESCRIPTOR, PERFECT_OFFICE_MAIN_STREAM, PERFECT_OFFICE_OBJECTS_STORAGE, type ReadWpdOptions, UNMAPPED_CHARACTER, WPD_FILE_EXTENSION, WPD_FILE_ID, WPD_INDEX_RECORD_SIZE, WPD_MEDIA_TYPE, WPD_PREFIX_HEADER_SIZE, WpdAttribute, type WpdAttributeCode, WpdBytesSchema, type WpdCharacterToken, type WpdDiagnostic, WpdDiagnosticCodes, type WpdDiagnosticSink, type WpdDocumentContainer, WpdEncryptedDocumentError, type WpdEolMapping, type WpdFileHeader, type WpdFixedFunctionToken, WpdFormatError, WpdNotAWordPerfectFileError, type WpdPrefixPacket, type WpdRunAttributes, type WpdSingleByteFunctionToken, type WpdToken, WpdUnsupportedVersionError, type WpdVariableFunctionToken, decodeAttributeByte, decodeSingleByteCharacter, decodeWordString, decodeWpCharacter, eolMappingForSubfunction, hasWordPerfectFileId, isSingleByteEol, openWpdDocument, packetByPrefixId, readFileHeader, readPrefixPackets, readTypefaceName, readWpd, readWpdContent, runAttributesFrom, subfunctionForSingleByteEol, tokeniseDocumentArea, wpdContentCodec };
14
+ import { POINTS_PER_INCH, WPU_PER_INCH, pointsFromWpu } from "./stream/units.cjs";
15
+ import { COLUMN_GROUP, COLUMN_LEFT_MARGIN_SET, COLUMN_RIGHT_MARGIN_SET, DEFAULT_MARGIN_PT, DEFAULT_PAGE_HEIGHT_PT, DEFAULT_PAGE_WIDTH_PT, PAGE_BOTTOM_MARGIN_SET, PAGE_FORM, PAGE_GROUP, PAGE_TOP_MARGIN_SET, WpdPageForm, readMarginPt, readPageForm } from "./stream/page.cjs";
16
+ import { TAB_GROUP, WpdTabEffect, tabEffectFor } from "./stream/tab.cjs";
17
+ import { DISPLAY_NUMBER_GROUP, STYLE_GROUP, WpdStyleSemantics, isParagraphNumberDisplayOff, isParagraphNumberDisplayOn, isStyleScopeCloser, isStyleScopeOpener, readDisplayNumberLevel, readSystemStyleNumber, styleSemanticsFor } from "./stream/style.cjs";
18
+ import { CELL_FILL_COLORS_SUBFUNCTION, CELL_INFORMATION_SUBFUNCTION, CELL_SPANNING_SUBFUNCTION, CHARACTER_DEFINE_TABLE_END, CHARACTER_TABLE_COLUMN, CHARACTER_TABLE_DEFINITION, ROW_INFORMATION_SUBFUNCTION, WpdCellFill, WpdCellInformation, WpdCellSpanning, WpdEmbeddedSubfunction, WpdEmbeddedSubfunctions, WpdRowInformation, findEmbeddedSubfunction, readCellFill, readCellInformation, readCellSpanning, readEmbeddedSubfunctions, readRowInformation, readTableColumnWidthPt } from "./stream/table.cjs";
19
+ export { ATTRIBUTE_OFF, ATTRIBUTE_ON, CELL_FILL_COLORS_SUBFUNCTION, CELL_INFORMATION_SUBFUNCTION, CELL_SPANNING_SUBFUNCTION, CHARACTER_DEFINE_TABLE_END, CHARACTER_TABLE_COLUMN, CHARACTER_TABLE_DEFINITION, COLUMN_GROUP, COLUMN_LEFT_MARGIN_SET, COLUMN_RIGHT_MARGIN_SET, DEFAULT_MARGIN_PT, DEFAULT_PAGE_HEIGHT_PT, DEFAULT_PAGE_WIDTH_PT, DISPLAY_NUMBER_GROUP, EOL_GROUP, FIRST_ASCII_CHARACTER, FIRST_FIXED_FUNCTION, FIRST_SINGLE_BYTE_EOL, FIRST_SINGLE_BYTE_FUNCTION, FIRST_VARIABLE_FUNCTION, LAST_CHARACTER, LAST_SINGLE_BYTE_EOL, NOOP_WPD_DIAGNOSTIC_SINK, PACKET_TYPE_DESIRED_FONT_DESCRIPTOR, PACKET_TYPE_EXTENDED_DOCUMENT_SUMMARY, PAGE_BOTTOM_MARGIN_SET, PAGE_FORM, PAGE_GROUP, PAGE_TOP_MARGIN_SET, PERFECT_OFFICE_MAIN_STREAM, PERFECT_OFFICE_OBJECTS_STORAGE, POINTS_PER_INCH, ROW_INFORMATION_SUBFUNCTION, type ReadWpdOptions, STYLE_GROUP, TAB_GROUP, UNMAPPED_CHARACTER, WPD_FILE_EXTENSION, WPD_FILE_ID, WPD_INDEX_RECORD_SIZE, WPD_MEDIA_TYPE, WPD_PREFIX_HEADER_SIZE, WPU_PER_INCH, WpdAttribute, type WpdAttributeCode, WpdBytesSchema, type WpdCellFill, type WpdCellInformation, type WpdCellSpanning, type WpdCharacterToken, type WpdDiagnostic, WpdDiagnosticCodes, type WpdDiagnosticSink, type WpdDocumentContainer, type WpdEmbeddedSubfunction, type WpdEmbeddedSubfunctions, WpdEncryptedDocumentError, type WpdEolMapping, type WpdFileHeader, type WpdFixedFunctionToken, WpdFormatError, WpdNotAWordPerfectFileError, type WpdPageForm, type WpdPrefixPacket, type WpdRowInformation, type WpdRunAttributes, type WpdSingleByteFunctionToken, type WpdStyleSemantics, type WpdTabEffect, type WpdToken, WpdUnsupportedVersionError, type WpdVariableFunctionToken, decodeAttributeByte, decodeSingleByteCharacter, decodeWordString, decodeWpCharacter, eolMappingForSubfunction, findEmbeddedSubfunction, hasWordPerfectFileId, isParagraphNumberDisplayOff, isParagraphNumberDisplayOn, isSingleByteEol, isStyleScopeCloser, isStyleScopeOpener, openWpdDocument, packetByPrefixId, pointsFromWpu, readCellFill, readCellInformation, readCellSpanning, readDisplayNumberLevel, readDocumentSummary, readEmbeddedSubfunctions, readFileHeader, readMarginPt, readPageForm, readPrefixPackets, readRowInformation, readSystemStyleNumber, readTableColumnWidthPt, readTypefaceName, readWpd, readWpdContent, runAttributesFrom, styleSemanticsFor, subfunctionForSingleByteEol, tabEffectFor, tokeniseDocumentArea, wpdContentCodec };