rtf-codec 4.2.5 → 4.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 CHANGED
@@ -6,9 +6,9 @@
6
6
 
7
7
  **Status: under active development.** The read and write paths described below are implemented and tested, including against a real-producer corpus: `pnpm test:corpus` runs the gitignored `test/corpus/` suite against LibreOffice-produced RTF (flat-ODT sources spanning runs, colour, headings, lists, tables, and alignment, converted through Writer's own RTF filter by `scripts/generate-corpus.mjs`) -- the corpus validated the reader cleanly across all eight fixtures with no defects found. [Scope](#scope) states exactly what is handled and what is not; nothing in this README describes work that is planned rather than done.
8
8
 
9
- Every construct that remains unhandled is either a gap in `document-schema.js` rather than in this codec (superscript/subscript and text direction have no field to land in), or something RTF itself does not specify at all beyond its own form-field vocabulary (a docx-style rich-text SDT has no RTF spelling of any kind) — see [Deliberately not handled](#deliberately-not-handled), which says which of the two each row is.
9
+ Every construct that remains unhandled is either a gap in `document-schema.js` rather than in this codec (character scaling, kerning, and run background colour have no field to land in), or something RTF itself does not specify at all beyond its own form-field vocabulary (a docx-style rich-text SDT has no RTF spelling of any kind) — see [Deliberately not handled](#deliberately-not-handled), which says which of the two each row is.
10
10
 
11
- RTF is the cleanest structural fit of any format this family did not already handle. It is a wordprocessing format through and through — paragraphs, runs, character properties, paragraph properties, tables, lists and pictures all have direct `ContentDocument` equivalents — and it can express more of the wordprocessing variant than markdown can, carrying colour, font family, font size and alignment natively. No `document-schema.js` model change was needed for it.
11
+ RTF is the cleanest structural fit of any format this family did not already handle. It is a wordprocessing format through and through — paragraphs, runs, character properties, paragraph properties, tables, lists and pictures all have direct `ContentDocument` equivalents — and it can express more of the wordprocessing variant than markdown can, carrying colour, font family, font size, alignment, vertical position and text direction natively. No `document-schema.js` model change was needed for it.
12
12
 
13
13
  What it is _not_ is another XML format. RTF is tokenised plain text with a brace-nested group and destination model, so none of the XML plumbing `ooxml.js` and `odf.js` share applies here: this package carries its own byte lexer, its own destination state machine, its own `\uN`/`\ucN` Unicode handling with code-page fallback, and its own parsers for the five header mini-formats. The closest relative in this workspace is `markdown-codec`, which is likewise a hand-written scanner and parser for a non-XML text format rather than a wrapper around a document library.
14
14
 
@@ -121,9 +121,10 @@ On the way out, **every non-ASCII character leaves as `\uN`** with a one-charact
121
121
  | `\*\revtbl` | The revision authors `\revauthN` and its siblings index into |
122
122
  | Sections | `\sect`, `\sectd`, the `\pgwsxnN`/`\marg*sxnN` geometry family, and the `\sbk*` break vocabulary |
123
123
  | Paragraphs | `\par`, `\pard`, alignment, indents, spacing, `\slN`/`\slmultN`, `\pagebb` |
124
- | Runs | `\b`, `\i`, `\ul` (every variant), `\strike`, `\fN`, `\fsN`, `\cfN`, `\v` (dropped as hidden) |
124
+ | Runs | `\b`, `\i`, `\ul` (every variant), `\strike`, `\super`/`\sub` and `\upN`/`\dnN` onto `ContentRun.verticalAlign` (`\nosupersub` as the off-spelling), `\fN`, `\fsN`, `\cfN`, `\v` (dropped as hidden) |
125
+ | Text direction | The four scopes RTF states it at: `\rtlch`/`\ltrch` onto `ContentRun.direction`, `\rtlpar`/`\ltrpar` onto `ContentParagraph.direction`, `\rtlrow`/`\ltrrow` onto `ContentTableRow.direction`, and `\rtldoc`/`\ltrdoc` onto `LayoutMetadata.direction`. `\rtlsect`/`\ltrsect` stay unmapped: they state a section's own column-snaking direction, a page-layout fact `ContentSection` carries no field for, not the text-direction scope the four fields name |
125
126
  | Tables | `\trowd`, `\cellxN`, `\trleftN`, `\cell`, `\row`, multi-paragraph cells |
126
- | Table cells | `\clbrdrt`/`l`/`b`/`r` with the whole `<brdr>` production, `\clcbpatN`/`\clcfpatN`/`\clshdngN` shading (a real two-colour pattern fill, not just a flat colour -- see below), and both merge families (`\clvmgf`/`\clvmrg`, `\clmgf`/`\clmrg`) |
127
+ | Table cells | `\clbrdrt`/`l`/`b`/`r` with the whole `<brdr>` production, `\clvertalc`/`\clvertalb` (and `\clvertalt`, whose stated default collapses into the absence `ContentTableCell.verticalAlign` already means) as the cell's vertical alignment, `\clcbpatN`/`\clcfpatN`/`\clshdngN` shading (a real two-colour pattern fill, not just a flat colour -- see below), and both merge families (`\clvmgf`/`\clvmrg`, `\clmgf`/`\clmrg`) |
127
128
  | Bookmarks | `\*\bkmkstart`/`\*\bkmkend` as `anchor` constructs, with `\bkmkcolfN`/`\bkmkcollN` quarantined as residue |
128
129
  | Revision marks | The whole `<chrev>` production as `provenance` constructs: `\revised`, `\deleted`, `\mvf`/`\mvt`, `\crauthN`, with authors and `\revdttmN` dates |
129
130
  | Form fields | A `\field` whose `\*\fldinst` names FORMTEXT/FORMCHECKBOX/FORMDROPDOWN, plus whatever `\*\formfield` data it carries (`\*\ffname` as its tag, `\*\ffhelptext` as its alias when `\ffownhelp` says it is author-set rather than auto-generated, `\ffprot` as its lock, a checkbox's own `\ffres`/`\ffdefres` as its checked state, a dropdown's own `\ffres`/`\ffdefres` as its selected entry alongside its `\*\ffl` option list), as a `contentControl` construct (plainText/checkbox/dropDown). A plainText field's own `\*\ffdeftext` group is recognised but its content is skipped whole rather than captured, since `value` names the control's CURRENT value, which for a text field is the wrapped-run text already carried in the extent's own children, not `\ffdeftext`'s default/reset text (see Write below for the one direction `\ffdeftext` does feed `value`); the same applies to the four other `\*\formfield` destination strings RTF's own Form Fields table names alongside it (`\*\ffformat`, `\*\ffstattext`, `\*\ffentrymcr`, `\*\ffexitmcr`), each skipped whole for the identical reason -- no `ContentControlDescriptor` field exists to carry any of them |
@@ -160,9 +161,8 @@ Each of these is reported through a diagnostic rather than dropped silently —
160
161
  | Nested tables (`\nestcell`/`\nestrow`) | Read as ordinary cell content; the inner table's own structure is not reconstructed. |
161
162
  | Drawing objects (`\do`, `\shp`) | **A schema gap, not a container one.** These are Word's own native in-document vector-drawing layer, not an OLE embed -- there is no raw drawing-shape `ContentBlock` for a wordprocessing section's block flow to land in (only `ContentEmbeddedObjectBlock`, which names a whole embedded document, not a shape), so a `\do`/`\shp` construct is dropped regardless of the container work [Embedded objects](#embedded-objects) below did for `\object`. |
162
163
  | A real, foreign `\object`'s OLE data | This package's own `\objdata` payload round-trips fully (see [Embedded objects](#embedded-objects)); a real Word-authored OLESaveToStream structure (an actual embedded `.xls`/`.doc`/OLE-control payload) has no decoder here and degrades with a diagnostic, recovering `\result`'s own fallback paragraphs when present instead of the real object. |
163
- | Superscript/subscript (`\super`, `\sub`, `\upN`, `\dnN`), character scaling, kerning, background colour | **A schema gap, not an RTF one.** `ContentRun` carries no vertical-alignment field at all -- `epub-codec` reports the identical gap for its own `<sub>`/`<sup>`, and `ooxml.js`'s docx reader has no `w:vertAlign` handling either. Closing it is a change to `document-schema.js` and every codec that would then carry it, not to this one. |
164
- | Right-to-left text (`\rtlch`, `\ltrch`, `\rtlpar`, `\rtlrow`, `\rtldoc`) | The same shape of gap: no `ContentDocument` field carries text direction, at any of the four scopes RTF states it at. |
165
- | Cell vertical alignment (`\clvertalt`/`\clvertalc`/`\clvertalb`), diagonal cell borders (`\cldglu`/`\cldgll`) | `ContentTableCell.borders` has no diagonal member, so a diagonal rule is not a side; there is no vertical-alignment field to carry the other three into either. |
164
+ | Superscript/subscript (`\super`, `\sub`, `\upN`, `\dnN`), character scaling, kerning, background colour | Character scaling (`\charscalexN`), kerning (`\kerningN`), background colour (`\cbN`/`\highlightN`) **A schema gap, not an RTF one.** `ContentRun` carries no field for any of the three. (The vertical-position family that shared this row `\super`/`\sub`/`\upN`/`\dnN` now reads and writes through `ContentRun.verticalAlign`.) |
165
+ | Cell vertical alignment (`\clvertalt`/`\clvertalc`/`\clvertalb`), diagonal cell borders (`\cldglu`/`\cldgll`) | Diagonal cell borders (`\cldglu`/`\cldgll`) `ContentTableCell.borders` has no diagonal member, so a diagonal rule is not a side. (Cell vertical alignment, which shared this row, now reads and writes through `ContentTableCell.verticalAlign`.) |
166
166
  | A bookmark whose two halves straddle a table cell wall | `document-schema.js` ratifies this as a drop rather than a shape to repair: each block list is its own bracket scope, and pairing across two of them would need the marker ids its contract deliberately refuses. |
167
167
  | A contentControl extent that crosses another contentControl extent in the same paragraph | RTF's `\*\formfield` destination can only nest, never cross: a `{\field...}` group nests cleanly inside another one's own `\fldrslt`, but two extents that start-before/end-after each other have no valid brace sequence, since whichever closes second would close the other's own braces instead of its own. |
168
168
  | Two block-scoped bookmarks whose real ranges are disjoint but whose halves both land in the same paragraph (one's `\bkmkend`, the other's `\bkmkstart`) | A paragraph is this reader's finest addressable block-extent unit, so the shared paragraph's own block index gets claimed by both bookmarks' extents even though their real text never overlapped -- a genuine crossing pair, not a nesting one, and `document-schema.js`'s own construct group has no shape for that (a tree would need two subtrees crossing, which no tree holds). The later-starting bookmark is dropped with a diagnostic; the earlier one keeps its extent intact. |
@@ -83,7 +83,8 @@ function newPendingCell() {
83
83
  verticalMergeFirst: false,
84
84
  verticalMergeContinuation: false,
85
85
  horizontalMergeFirst: false,
86
- horizontalMergeContinuation: false
86
+ horizontalMergeContinuation: false,
87
+ verticalAlign: void 0
87
88
  };
88
89
  }
89
90
  function applyCellDefinitionControlWord(name, param, cell) {
@@ -120,6 +121,15 @@ function applyCellDefinitionControlWord(name, param, cell) {
120
121
  case "clshdng":
121
122
  cell.shadingPercent = param === void 0 ? void 0 : param / 100;
122
123
  return true;
124
+ case "clvertalc":
125
+ cell.verticalAlign = "center";
126
+ return true;
127
+ case "clvertalb":
128
+ cell.verticalAlign = "bottom";
129
+ return true;
130
+ case "clvertalt":
131
+ cell.verticalAlign = void 0;
132
+ return true;
123
133
  }
124
134
  const pending = cell.side === void 0 ? void 0 : cell.borders[cell.side];
125
135
  if (pending === void 0) return false;
@@ -18,6 +18,7 @@ interface PendingCell {
18
18
  verticalMergeContinuation: boolean;
19
19
  horizontalMergeFirst: boolean;
20
20
  horizontalMergeContinuation: boolean;
21
+ verticalAlign: "center" | "bottom" | undefined;
21
22
  }
22
23
  declare function newPendingCell(): PendingCell;
23
24
  declare function applyCellDefinitionControlWord(name: string, param: number | undefined, cell: PendingCell): boolean;
@@ -18,6 +18,7 @@ interface PendingCell {
18
18
  verticalMergeContinuation: boolean;
19
19
  horizontalMergeFirst: boolean;
20
20
  horizontalMergeContinuation: boolean;
21
+ verticalAlign: "center" | "bottom" | undefined;
21
22
  }
22
23
  declare function newPendingCell(): PendingCell;
23
24
  declare function applyCellDefinitionControlWord(name: string, param: number | undefined, cell: PendingCell): boolean;
@@ -82,7 +82,8 @@ function newPendingCell() {
82
82
  verticalMergeFirst: false,
83
83
  verticalMergeContinuation: false,
84
84
  horizontalMergeFirst: false,
85
- horizontalMergeContinuation: false
85
+ horizontalMergeContinuation: false,
86
+ verticalAlign: void 0
86
87
  };
87
88
  }
88
89
  function applyCellDefinitionControlWord(name, param, cell) {
@@ -119,6 +120,15 @@ function applyCellDefinitionControlWord(name, param, cell) {
119
120
  case "clshdng":
120
121
  cell.shadingPercent = param === void 0 ? void 0 : param / 100;
121
122
  return true;
123
+ case "clvertalc":
124
+ cell.verticalAlign = "center";
125
+ return true;
126
+ case "clvertalb":
127
+ cell.verticalAlign = "bottom";
128
+ return true;
129
+ case "clvertalt":
130
+ cell.verticalAlign = void 0;
131
+ return true;
122
132
  }
123
133
  const pending = cell.side === void 0 ? void 0 : cell.borders[cell.side];
124
134
  if (pending === void 0) return false;
package/dist/codec.d.cts CHANGED
@@ -7,6 +7,12 @@ declare const rtfCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Arr
7
7
  widthPt: z.ZodNumber;
8
8
  heightPt: z.ZodNumber;
9
9
  }, z.core.$strip>>>;
10
+ fonts: z.ZodOptional<z.ZodArray<z.ZodObject<{
11
+ family: z.ZodString;
12
+ bold: z.ZodBoolean;
13
+ italic: z.ZodBoolean;
14
+ base64: z.ZodString;
15
+ }, z.core.$strip>>>;
10
16
  styles: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
11
17
  paragraph: z.ZodOptional<z.ZodObject<{
12
18
  alignment: z.ZodOptional<z.ZodEnum<{
@@ -156,6 +162,12 @@ declare const rtfCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Arr
156
162
  widthPt: z.ZodNumber;
157
163
  heightPt: z.ZodNumber;
158
164
  }, z.core.$strip>>>;
165
+ fonts: z.ZodOptional<z.ZodArray<z.ZodObject<{
166
+ family: z.ZodString;
167
+ bold: z.ZodBoolean;
168
+ italic: z.ZodBoolean;
169
+ base64: z.ZodString;
170
+ }, z.core.$strip>>>;
159
171
  styles: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
160
172
  paragraph: z.ZodOptional<z.ZodObject<{
161
173
  alignment: z.ZodOptional<z.ZodEnum<{
@@ -310,6 +322,12 @@ declare const rtfCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Arr
310
322
  widthPt: z.ZodNumber;
311
323
  heightPt: z.ZodNumber;
312
324
  }, z.core.$strip>>>;
325
+ fonts: z.ZodOptional<z.ZodArray<z.ZodObject<{
326
+ family: z.ZodString;
327
+ bold: z.ZodBoolean;
328
+ italic: z.ZodBoolean;
329
+ base64: z.ZodString;
330
+ }, z.core.$strip>>>;
313
331
  styles: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
314
332
  paragraph: z.ZodOptional<z.ZodObject<{
315
333
  alignment: z.ZodOptional<z.ZodEnum<{
@@ -459,6 +477,12 @@ declare const rtfCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Arr
459
477
  widthPt: z.ZodNumber;
460
478
  heightPt: z.ZodNumber;
461
479
  }, z.core.$strip>>>;
480
+ fonts: z.ZodOptional<z.ZodArray<z.ZodObject<{
481
+ family: z.ZodString;
482
+ bold: z.ZodBoolean;
483
+ italic: z.ZodBoolean;
484
+ base64: z.ZodString;
485
+ }, z.core.$strip>>>;
462
486
  styles: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
463
487
  paragraph: z.ZodOptional<z.ZodObject<{
464
488
  alignment: z.ZodOptional<z.ZodEnum<{
@@ -667,6 +691,12 @@ declare const rtfCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Arr
667
691
  widthPt: z.ZodNumber;
668
692
  heightPt: z.ZodNumber;
669
693
  }, z.core.$strip>>>;
694
+ fonts: z.ZodOptional<z.ZodArray<z.ZodObject<{
695
+ family: z.ZodString;
696
+ bold: z.ZodBoolean;
697
+ italic: z.ZodBoolean;
698
+ base64: z.ZodString;
699
+ }, z.core.$strip>>>;
670
700
  styles: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
671
701
  paragraph: z.ZodOptional<z.ZodObject<{
672
702
  alignment: z.ZodOptional<z.ZodEnum<{
@@ -867,6 +897,11 @@ declare const rtfContentCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, U
867
897
  even: z.ZodOptional<z.ZodLazy<z.ZodArray<z.ZodType<import("document-schema.js").ContentBlock, import("document-schema.js").ContentBlock, z.core.$ZodTypeInternals<import("document-schema.js").ContentBlock, import("document-schema.js").ContentBlock>>>>>;
868
898
  first: z.ZodOptional<z.ZodLazy<z.ZodArray<z.ZodType<import("document-schema.js").ContentBlock, import("document-schema.js").ContentBlock, z.core.$ZodTypeInternals<import("document-schema.js").ContentBlock, import("document-schema.js").ContentBlock>>>>>;
869
899
  }, z.core.$strip>>;
900
+ watermarks: z.ZodOptional<z.ZodObject<{
901
+ default: z.ZodOptional<z.ZodLazy<z.ZodArray<z.ZodType<import("document-schema.js").ContentBlock, import("document-schema.js").ContentBlock, z.core.$ZodTypeInternals<import("document-schema.js").ContentBlock, import("document-schema.js").ContentBlock>>>>>;
902
+ even: z.ZodOptional<z.ZodLazy<z.ZodArray<z.ZodType<import("document-schema.js").ContentBlock, import("document-schema.js").ContentBlock, z.core.$ZodTypeInternals<import("document-schema.js").ContentBlock, import("document-schema.js").ContentBlock>>>>>;
903
+ first: z.ZodOptional<z.ZodLazy<z.ZodArray<z.ZodType<import("document-schema.js").ContentBlock, import("document-schema.js").ContentBlock, z.core.$ZodTypeInternals<import("document-schema.js").ContentBlock, import("document-schema.js").ContentBlock>>>>>;
904
+ }, z.core.$strip>>;
870
905
  source: z.ZodOptional<z.ZodObject<{
871
906
  format: z.ZodEnum<{
872
907
  docx: "docx";
@@ -1032,6 +1067,7 @@ declare const rtfContentCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, U
1032
1067
  fontScale: z.ZodOptional<z.ZodNumber>;
1033
1068
  lineSpacingReduction: z.ZodOptional<z.ZodNumber>;
1034
1069
  paintOrder: z.ZodOptional<z.ZodNumber>;
1070
+ readingOrder: z.ZodOptional<z.ZodNumber>;
1035
1071
  sourcePath: z.ZodOptional<z.ZodString>;
1036
1072
  source: z.ZodOptional<z.ZodObject<{
1037
1073
  format: z.ZodEnum<{
@@ -2572,6 +2608,7 @@ declare const rtfContentCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, U
2572
2608
  fontScale: z.ZodOptional<z.ZodNumber>;
2573
2609
  lineSpacingReduction: z.ZodOptional<z.ZodNumber>;
2574
2610
  paintOrder: z.ZodOptional<z.ZodNumber>;
2611
+ readingOrder: z.ZodOptional<z.ZodNumber>;
2575
2612
  sourcePath: z.ZodOptional<z.ZodString>;
2576
2613
  source: z.ZodOptional<z.ZodObject<{
2577
2614
  format: z.ZodEnum<{
package/dist/codec.d.ts CHANGED
@@ -7,6 +7,12 @@ declare const rtfCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Arr
7
7
  widthPt: z.ZodNumber;
8
8
  heightPt: z.ZodNumber;
9
9
  }, z.core.$strip>>>;
10
+ fonts: z.ZodOptional<z.ZodArray<z.ZodObject<{
11
+ family: z.ZodString;
12
+ bold: z.ZodBoolean;
13
+ italic: z.ZodBoolean;
14
+ base64: z.ZodString;
15
+ }, z.core.$strip>>>;
10
16
  styles: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
11
17
  paragraph: z.ZodOptional<z.ZodObject<{
12
18
  alignment: z.ZodOptional<z.ZodEnum<{
@@ -156,6 +162,12 @@ declare const rtfCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Arr
156
162
  widthPt: z.ZodNumber;
157
163
  heightPt: z.ZodNumber;
158
164
  }, z.core.$strip>>>;
165
+ fonts: z.ZodOptional<z.ZodArray<z.ZodObject<{
166
+ family: z.ZodString;
167
+ bold: z.ZodBoolean;
168
+ italic: z.ZodBoolean;
169
+ base64: z.ZodString;
170
+ }, z.core.$strip>>>;
159
171
  styles: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
160
172
  paragraph: z.ZodOptional<z.ZodObject<{
161
173
  alignment: z.ZodOptional<z.ZodEnum<{
@@ -310,6 +322,12 @@ declare const rtfCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Arr
310
322
  widthPt: z.ZodNumber;
311
323
  heightPt: z.ZodNumber;
312
324
  }, z.core.$strip>>>;
325
+ fonts: z.ZodOptional<z.ZodArray<z.ZodObject<{
326
+ family: z.ZodString;
327
+ bold: z.ZodBoolean;
328
+ italic: z.ZodBoolean;
329
+ base64: z.ZodString;
330
+ }, z.core.$strip>>>;
313
331
  styles: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
314
332
  paragraph: z.ZodOptional<z.ZodObject<{
315
333
  alignment: z.ZodOptional<z.ZodEnum<{
@@ -459,6 +477,12 @@ declare const rtfCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Arr
459
477
  widthPt: z.ZodNumber;
460
478
  heightPt: z.ZodNumber;
461
479
  }, z.core.$strip>>>;
480
+ fonts: z.ZodOptional<z.ZodArray<z.ZodObject<{
481
+ family: z.ZodString;
482
+ bold: z.ZodBoolean;
483
+ italic: z.ZodBoolean;
484
+ base64: z.ZodString;
485
+ }, z.core.$strip>>>;
462
486
  styles: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
463
487
  paragraph: z.ZodOptional<z.ZodObject<{
464
488
  alignment: z.ZodOptional<z.ZodEnum<{
@@ -667,6 +691,12 @@ declare const rtfCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Arr
667
691
  widthPt: z.ZodNumber;
668
692
  heightPt: z.ZodNumber;
669
693
  }, z.core.$strip>>>;
694
+ fonts: z.ZodOptional<z.ZodArray<z.ZodObject<{
695
+ family: z.ZodString;
696
+ bold: z.ZodBoolean;
697
+ italic: z.ZodBoolean;
698
+ base64: z.ZodString;
699
+ }, z.core.$strip>>>;
670
700
  styles: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
671
701
  paragraph: z.ZodOptional<z.ZodObject<{
672
702
  alignment: z.ZodOptional<z.ZodEnum<{
@@ -867,6 +897,11 @@ declare const rtfContentCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, U
867
897
  even: z.ZodOptional<z.ZodLazy<z.ZodArray<z.ZodType<import("document-schema.js").ContentBlock, import("document-schema.js").ContentBlock, z.core.$ZodTypeInternals<import("document-schema.js").ContentBlock, import("document-schema.js").ContentBlock>>>>>;
868
898
  first: z.ZodOptional<z.ZodLazy<z.ZodArray<z.ZodType<import("document-schema.js").ContentBlock, import("document-schema.js").ContentBlock, z.core.$ZodTypeInternals<import("document-schema.js").ContentBlock, import("document-schema.js").ContentBlock>>>>>;
869
899
  }, z.core.$strip>>;
900
+ watermarks: z.ZodOptional<z.ZodObject<{
901
+ default: z.ZodOptional<z.ZodLazy<z.ZodArray<z.ZodType<import("document-schema.js").ContentBlock, import("document-schema.js").ContentBlock, z.core.$ZodTypeInternals<import("document-schema.js").ContentBlock, import("document-schema.js").ContentBlock>>>>>;
902
+ even: z.ZodOptional<z.ZodLazy<z.ZodArray<z.ZodType<import("document-schema.js").ContentBlock, import("document-schema.js").ContentBlock, z.core.$ZodTypeInternals<import("document-schema.js").ContentBlock, import("document-schema.js").ContentBlock>>>>>;
903
+ first: z.ZodOptional<z.ZodLazy<z.ZodArray<z.ZodType<import("document-schema.js").ContentBlock, import("document-schema.js").ContentBlock, z.core.$ZodTypeInternals<import("document-schema.js").ContentBlock, import("document-schema.js").ContentBlock>>>>>;
904
+ }, z.core.$strip>>;
870
905
  source: z.ZodOptional<z.ZodObject<{
871
906
  format: z.ZodEnum<{
872
907
  docx: "docx";
@@ -1032,6 +1067,7 @@ declare const rtfContentCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, U
1032
1067
  fontScale: z.ZodOptional<z.ZodNumber>;
1033
1068
  lineSpacingReduction: z.ZodOptional<z.ZodNumber>;
1034
1069
  paintOrder: z.ZodOptional<z.ZodNumber>;
1070
+ readingOrder: z.ZodOptional<z.ZodNumber>;
1035
1071
  sourcePath: z.ZodOptional<z.ZodString>;
1036
1072
  source: z.ZodOptional<z.ZodObject<{
1037
1073
  format: z.ZodEnum<{
@@ -2572,6 +2608,7 @@ declare const rtfContentCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, U
2572
2608
  fontScale: z.ZodOptional<z.ZodNumber>;
2573
2609
  lineSpacingReduction: z.ZodOptional<z.ZodNumber>;
2574
2610
  paintOrder: z.ZodOptional<z.ZodNumber>;
2611
+ readingOrder: z.ZodOptional<z.ZodNumber>;
2575
2612
  sourcePath: z.ZodOptional<z.ZodString>;
2576
2613
  source: z.ZodOptional<z.ZodObject<{
2577
2614
  format: z.ZodEnum<{
package/dist/header.cjs CHANGED
@@ -340,6 +340,7 @@ function readRtfHeader(tokens, sink) {
340
340
  marginBottomTwips: require_units.DEFAULT_MARGIN_BOTTOM_TWIPS
341
341
  };
342
342
  let metadata = {};
343
+ let direction;
343
344
  let codepage = require_codepage.DEFAULT_CODEPAGE;
344
345
  let defaultFontIndex;
345
346
  let bodyStartIndex = 0;
@@ -356,6 +357,14 @@ function readRtfHeader(tokens, sink) {
356
357
  codepage = charsetPage;
357
358
  continue;
358
359
  }
360
+ if (token.name === "rtldoc") {
361
+ direction = "rtl";
362
+ continue;
363
+ }
364
+ if (token.name === "ltrdoc") {
365
+ direction = "ltr";
366
+ continue;
367
+ }
359
368
  if (token.param === void 0) continue;
360
369
  switch (token.name) {
361
370
  case "ansicpg":
@@ -424,7 +433,10 @@ function readRtfHeader(tokens, sink) {
424
433
  lists,
425
434
  revisionAuthors,
426
435
  page,
427
- metadata,
436
+ metadata: {
437
+ ...metadata,
438
+ ...direction === void 0 ? {} : { direction }
439
+ },
428
440
  bodyStartIndex
429
441
  };
430
442
  }
package/dist/header.js CHANGED
@@ -339,6 +339,7 @@ function readRtfHeader(tokens, sink) {
339
339
  marginBottomTwips: DEFAULT_MARGIN_BOTTOM_TWIPS
340
340
  };
341
341
  let metadata = {};
342
+ let direction;
342
343
  let codepage = DEFAULT_CODEPAGE;
343
344
  let defaultFontIndex;
344
345
  let bodyStartIndex = 0;
@@ -355,6 +356,14 @@ function readRtfHeader(tokens, sink) {
355
356
  codepage = charsetPage;
356
357
  continue;
357
358
  }
359
+ if (token.name === "rtldoc") {
360
+ direction = "rtl";
361
+ continue;
362
+ }
363
+ if (token.name === "ltrdoc") {
364
+ direction = "ltr";
365
+ continue;
366
+ }
358
367
  if (token.param === void 0) continue;
359
368
  switch (token.name) {
360
369
  case "ansicpg":
@@ -423,7 +432,10 @@ function readRtfHeader(tokens, sink) {
423
432
  lists,
424
433
  revisionAuthors,
425
434
  page,
426
- metadata,
435
+ metadata: {
436
+ ...metadata,
437
+ ...direction === void 0 ? {} : { direction }
438
+ },
427
439
  bodyStartIndex
428
440
  };
429
441
  }
package/dist/read.cjs CHANGED
@@ -186,12 +186,15 @@ function defaultCharacterState() {
186
186
  fontIndex: void 0,
187
187
  sizeHalfPoints: 24,
188
188
  colorIndex: void 0,
189
+ verticalAlign: void 0,
190
+ direction: void 0,
189
191
  revision: require_constructs.NO_REVISION
190
192
  };
191
193
  }
192
194
  function defaultParagraphState() {
193
195
  return {
194
196
  alignment: void 0,
197
+ direction: void 0,
195
198
  indentLeftTwips: 0,
196
199
  indentFirstLineTwips: 0,
197
200
  spaceBeforeTwips: 0,
@@ -235,6 +238,8 @@ function runKey(char, fontName, color, hyperlink) {
235
238
  String(char.sizeHalfPoints),
236
239
  color === void 0 ? "" : `${String(color.r)},${String(color.g)},${String(color.b)}`,
237
240
  hyperlink ?? "",
241
+ char.verticalAlign ?? "",
242
+ char.direction ?? "",
238
243
  JSON.stringify(char.revision)
239
244
  ].join("|");
240
245
  }
@@ -329,6 +334,7 @@ function freshAccumulatorState() {
329
334
  pendingCellDefinitions: [],
330
335
  pendingCell: require_cell_format.newPendingCell(),
331
336
  rowLeftTwips: 0,
337
+ rowDirection: void 0,
332
338
  paragraphSerial: 0,
333
339
  openBookmarks: /* @__PURE__ */ new Map(),
334
340
  sectionBlockExtents: [],
@@ -356,6 +362,7 @@ var ContentBuilder = class {
356
362
  pendingCellDefinitions = [];
357
363
  pendingCell = require_cell_format.newPendingCell();
358
364
  rowLeftTwips = 0;
365
+ rowDirection;
359
366
  paragraphSerial = 0;
360
367
  openBookmarks = /* @__PURE__ */ new Map();
361
368
  sectionBlockExtents = [];
@@ -525,6 +532,7 @@ var ContentBuilder = class {
525
532
  headingLevel
526
533
  },
527
534
  ...para.alignment === void 0 ? {} : { alignment: para.alignment },
535
+ ...para.direction === void 0 ? {} : { direction: para.direction },
528
536
  ...para.indentLeftTwips === 0 ? {} : { indentLeftPt: require_units.twipsToPoints(para.indentLeftTwips) },
529
537
  ...para.indentFirstLineTwips === 0 ? {} : { indentFirstLinePt: require_units.twipsToPoints(para.indentFirstLineTwips) },
530
538
  ...para.spaceBeforeTwips === 0 ? {} : { spacingBeforePt: require_units.twipsToPoints(para.spaceBeforeTwips) },
@@ -560,10 +568,14 @@ var ContentBuilder = class {
560
568
  this.pendingCellDefinitions = [];
561
569
  this.pendingCell = require_cell_format.newPendingCell();
562
570
  this.rowLeftTwips = 0;
571
+ this.rowDirection = void 0;
563
572
  }
564
573
  setRowLeft(twips) {
565
574
  this.rowLeftTwips = twips;
566
575
  }
576
+ setRowDirection(direction) {
577
+ this.rowDirection = direction;
578
+ }
567
579
  applyCellDefinition(name, param) {
568
580
  return require_cell_format.applyCellDefinitionControlWord(name, param, this.pendingCell);
569
581
  }
@@ -591,7 +603,8 @@ var ContentBuilder = class {
591
603
  }
592
604
  this.tableRows.push({
593
605
  cells: this.rowCells,
594
- definitions: this.pendingCellDefinitions
606
+ definitions: this.pendingCellDefinitions,
607
+ direction: this.rowDirection
595
608
  });
596
609
  if (this.tableColumnRights.length === 0) this.tableColumnRights = [...this.pendingCellRights];
597
610
  this.rowCells = [];
@@ -619,30 +632,34 @@ var ContentBuilder = class {
619
632
  }
620
633
  return indices;
621
634
  });
622
- return rows.map((row, rowIndex) => ({ cells: row.cells.map((cell, cellIndex) => ({
623
- cell,
624
- cellIndex
625
- })).filter(({ cellIndex }) => row.definitions[cellIndex]?.horizontalMergeContinuation !== true).map(({ cell, cellIndex }) => {
626
- const definition = row.definitions[cellIndex];
627
- if (definition?.verticalMergeContinuation === true) return { blocks: [] };
628
- const colSpan = horizontalSpanAt(row.definitions, cellIndex);
629
- const column = columnIndices[rowIndex]?.[cellIndex];
630
- let rowSpan = 1;
631
- if (definition?.verticalMergeFirst === true && column !== void 0) for (let next = rowIndex + 1; next < rows.length; next += 1) {
632
- const matchIndex = columnIndices[next]?.indexOf(column) ?? -1;
633
- if ((matchIndex === -1 ? void 0 : rows[next]?.definitions[matchIndex])?.verticalMergeContinuation !== true) break;
634
- rowSpan += 1;
635
- }
636
- const borders = this.resolveBorders(definition);
637
- const background = definition === void 0 ? void 0 : require_cell_format.resolveCellFill(definition, (index) => this.header.colors[index]);
638
- return {
639
- blocks: cell.blocks,
640
- ...colSpan > 1 ? { colSpan } : {},
641
- ...rowSpan > 1 ? { rowSpan } : {},
642
- ...background === void 0 ? {} : { background },
643
- ...borders === void 0 ? {} : { borders }
644
- };
645
- }) }));
635
+ return rows.map((row, rowIndex) => ({
636
+ ...row.direction === void 0 ? {} : { direction: row.direction },
637
+ cells: row.cells.map((cell, cellIndex) => ({
638
+ cell,
639
+ cellIndex
640
+ })).filter(({ cellIndex }) => row.definitions[cellIndex]?.horizontalMergeContinuation !== true).map(({ cell, cellIndex }) => {
641
+ const definition = row.definitions[cellIndex];
642
+ if (definition?.verticalMergeContinuation === true) return { blocks: [] };
643
+ const colSpan = horizontalSpanAt(row.definitions, cellIndex);
644
+ const column = columnIndices[rowIndex]?.[cellIndex];
645
+ let rowSpan = 1;
646
+ if (definition?.verticalMergeFirst === true && column !== void 0) for (let next = rowIndex + 1; next < rows.length; next += 1) {
647
+ const matchIndex = columnIndices[next]?.indexOf(column) ?? -1;
648
+ if ((matchIndex === -1 ? void 0 : rows[next]?.definitions[matchIndex])?.verticalMergeContinuation !== true) break;
649
+ rowSpan += 1;
650
+ }
651
+ const borders = this.resolveBorders(definition);
652
+ const background = definition === void 0 ? void 0 : require_cell_format.resolveCellFill(definition, (index) => this.header.colors[index]);
653
+ return {
654
+ blocks: cell.blocks,
655
+ ...colSpan > 1 ? { colSpan } : {},
656
+ ...rowSpan > 1 ? { rowSpan } : {},
657
+ ...background === void 0 ? {} : { background },
658
+ ...borders === void 0 ? {} : { borders },
659
+ ...definition?.verticalAlign === void 0 ? {} : { verticalAlign: definition.verticalAlign }
660
+ };
661
+ })
662
+ }));
646
663
  }
647
664
  resolveBorders(definition) {
648
665
  if (definition === void 0) return;
@@ -699,6 +716,7 @@ var ContentBuilder = class {
699
716
  pendingCellDefinitions: this.pendingCellDefinitions,
700
717
  pendingCell: this.pendingCell,
701
718
  rowLeftTwips: this.rowLeftTwips,
719
+ rowDirection: this.rowDirection,
702
720
  paragraphSerial: this.paragraphSerial,
703
721
  openBookmarks: this.openBookmarks,
704
722
  sectionBlockExtents: this.sectionBlockExtents,
@@ -723,6 +741,7 @@ var ContentBuilder = class {
723
741
  this.pendingCellDefinitions = saved.pendingCellDefinitions;
724
742
  this.pendingCell = saved.pendingCell;
725
743
  this.rowLeftTwips = saved.rowLeftTwips;
744
+ this.rowDirection = saved.rowDirection;
726
745
  this.paragraphSerial = saved.paragraphSerial;
727
746
  this.openBookmarks = saved.openBookmarks;
728
747
  this.sectionBlockExtents = saved.sectionBlockExtents;
@@ -823,7 +842,9 @@ function buildRunFields(char, fontName, color, hyperlink) {
823
842
  ...fontName === void 0 || fontName.length === 0 ? {} : { fontFamily: fontName },
824
843
  sizePt: require_units.halfPointsToPoints(char.sizeHalfPoints),
825
844
  ...color === void 0 ? {} : { color },
826
- ...hyperlink === void 0 ? {} : { hyperlink }
845
+ ...hyperlink === void 0 ? {} : { hyperlink },
846
+ ...char.verticalAlign === void 0 ? {} : { verticalAlign: char.verticalAlign },
847
+ ...char.direction === void 0 ? {} : { direction: char.direction }
827
848
  };
828
849
  }
829
850
  function buildPicture(picture, sink) {
@@ -1325,6 +1346,27 @@ function applyCharacterControlWord(name, param, state, header) {
1325
1346
  case "uc":
1326
1347
  if (param !== void 0 && param >= 0) state.uc = param;
1327
1348
  return true;
1349
+ case "super":
1350
+ state.char.verticalAlign = "superscript";
1351
+ return true;
1352
+ case "sub":
1353
+ state.char.verticalAlign = "subscript";
1354
+ return true;
1355
+ case "up":
1356
+ state.char.verticalAlign = param === void 0 || param > 0 ? "superscript" : param < 0 ? "subscript" : void 0;
1357
+ return true;
1358
+ case "dn":
1359
+ state.char.verticalAlign = param === void 0 || param > 0 ? "subscript" : param < 0 ? "superscript" : void 0;
1360
+ return true;
1361
+ case "nosupersub":
1362
+ state.char.verticalAlign = void 0;
1363
+ return true;
1364
+ case "rtlch":
1365
+ state.char.direction = "rtl";
1366
+ return true;
1367
+ case "ltrch":
1368
+ state.char.direction = "ltr";
1369
+ return true;
1328
1370
  case "revised":
1329
1371
  state.char.revision = {
1330
1372
  ...state.char.revision,
@@ -1418,6 +1460,12 @@ function applyParagraphControlWord(name, param, state) {
1418
1460
  case "s":
1419
1461
  state.para.styleIndex = param;
1420
1462
  return true;
1463
+ case "rtlpar":
1464
+ state.para.direction = "rtl";
1465
+ return true;
1466
+ case "ltrpar":
1467
+ state.para.direction = "ltr";
1468
+ return true;
1421
1469
  case "outlinelevel":
1422
1470
  state.para.outlineLevel = param === void 0 || param > 8 ? void 0 : param;
1423
1471
  return true;
@@ -1508,6 +1556,12 @@ function applyStructureControlWord(name, param, state, builder, section, sink) {
1508
1556
  case "trleft":
1509
1557
  builder.setRowLeft(param ?? 0);
1510
1558
  return true;
1559
+ case "rtlrow":
1560
+ builder.setRowDirection("rtl");
1561
+ return true;
1562
+ case "ltrrow":
1563
+ builder.setRowDirection("ltr");
1564
+ return true;
1511
1565
  case "cellx":
1512
1566
  if (param !== void 0) builder.addCellBoundary(param);
1513
1567
  return true;
package/dist/read.js CHANGED
@@ -185,12 +185,15 @@ function defaultCharacterState() {
185
185
  fontIndex: void 0,
186
186
  sizeHalfPoints: 24,
187
187
  colorIndex: void 0,
188
+ verticalAlign: void 0,
189
+ direction: void 0,
188
190
  revision: NO_REVISION
189
191
  };
190
192
  }
191
193
  function defaultParagraphState() {
192
194
  return {
193
195
  alignment: void 0,
196
+ direction: void 0,
194
197
  indentLeftTwips: 0,
195
198
  indentFirstLineTwips: 0,
196
199
  spaceBeforeTwips: 0,
@@ -234,6 +237,8 @@ function runKey(char, fontName, color, hyperlink) {
234
237
  String(char.sizeHalfPoints),
235
238
  color === void 0 ? "" : `${String(color.r)},${String(color.g)},${String(color.b)}`,
236
239
  hyperlink ?? "",
240
+ char.verticalAlign ?? "",
241
+ char.direction ?? "",
237
242
  JSON.stringify(char.revision)
238
243
  ].join("|");
239
244
  }
@@ -328,6 +333,7 @@ function freshAccumulatorState() {
328
333
  pendingCellDefinitions: [],
329
334
  pendingCell: newPendingCell(),
330
335
  rowLeftTwips: 0,
336
+ rowDirection: void 0,
331
337
  paragraphSerial: 0,
332
338
  openBookmarks: /* @__PURE__ */ new Map(),
333
339
  sectionBlockExtents: [],
@@ -355,6 +361,7 @@ var ContentBuilder = class {
355
361
  pendingCellDefinitions = [];
356
362
  pendingCell = newPendingCell();
357
363
  rowLeftTwips = 0;
364
+ rowDirection;
358
365
  paragraphSerial = 0;
359
366
  openBookmarks = /* @__PURE__ */ new Map();
360
367
  sectionBlockExtents = [];
@@ -524,6 +531,7 @@ var ContentBuilder = class {
524
531
  headingLevel
525
532
  },
526
533
  ...para.alignment === void 0 ? {} : { alignment: para.alignment },
534
+ ...para.direction === void 0 ? {} : { direction: para.direction },
527
535
  ...para.indentLeftTwips === 0 ? {} : { indentLeftPt: twipsToPoints(para.indentLeftTwips) },
528
536
  ...para.indentFirstLineTwips === 0 ? {} : { indentFirstLinePt: twipsToPoints(para.indentFirstLineTwips) },
529
537
  ...para.spaceBeforeTwips === 0 ? {} : { spacingBeforePt: twipsToPoints(para.spaceBeforeTwips) },
@@ -559,10 +567,14 @@ var ContentBuilder = class {
559
567
  this.pendingCellDefinitions = [];
560
568
  this.pendingCell = newPendingCell();
561
569
  this.rowLeftTwips = 0;
570
+ this.rowDirection = void 0;
562
571
  }
563
572
  setRowLeft(twips) {
564
573
  this.rowLeftTwips = twips;
565
574
  }
575
+ setRowDirection(direction) {
576
+ this.rowDirection = direction;
577
+ }
566
578
  applyCellDefinition(name, param) {
567
579
  return applyCellDefinitionControlWord(name, param, this.pendingCell);
568
580
  }
@@ -590,7 +602,8 @@ var ContentBuilder = class {
590
602
  }
591
603
  this.tableRows.push({
592
604
  cells: this.rowCells,
593
- definitions: this.pendingCellDefinitions
605
+ definitions: this.pendingCellDefinitions,
606
+ direction: this.rowDirection
594
607
  });
595
608
  if (this.tableColumnRights.length === 0) this.tableColumnRights = [...this.pendingCellRights];
596
609
  this.rowCells = [];
@@ -618,30 +631,34 @@ var ContentBuilder = class {
618
631
  }
619
632
  return indices;
620
633
  });
621
- return rows.map((row, rowIndex) => ({ cells: row.cells.map((cell, cellIndex) => ({
622
- cell,
623
- cellIndex
624
- })).filter(({ cellIndex }) => row.definitions[cellIndex]?.horizontalMergeContinuation !== true).map(({ cell, cellIndex }) => {
625
- const definition = row.definitions[cellIndex];
626
- if (definition?.verticalMergeContinuation === true) return { blocks: [] };
627
- const colSpan = horizontalSpanAt(row.definitions, cellIndex);
628
- const column = columnIndices[rowIndex]?.[cellIndex];
629
- let rowSpan = 1;
630
- if (definition?.verticalMergeFirst === true && column !== void 0) for (let next = rowIndex + 1; next < rows.length; next += 1) {
631
- const matchIndex = columnIndices[next]?.indexOf(column) ?? -1;
632
- if ((matchIndex === -1 ? void 0 : rows[next]?.definitions[matchIndex])?.verticalMergeContinuation !== true) break;
633
- rowSpan += 1;
634
- }
635
- const borders = this.resolveBorders(definition);
636
- const background = definition === void 0 ? void 0 : resolveCellFill(definition, (index) => this.header.colors[index]);
637
- return {
638
- blocks: cell.blocks,
639
- ...colSpan > 1 ? { colSpan } : {},
640
- ...rowSpan > 1 ? { rowSpan } : {},
641
- ...background === void 0 ? {} : { background },
642
- ...borders === void 0 ? {} : { borders }
643
- };
644
- }) }));
634
+ return rows.map((row, rowIndex) => ({
635
+ ...row.direction === void 0 ? {} : { direction: row.direction },
636
+ cells: row.cells.map((cell, cellIndex) => ({
637
+ cell,
638
+ cellIndex
639
+ })).filter(({ cellIndex }) => row.definitions[cellIndex]?.horizontalMergeContinuation !== true).map(({ cell, cellIndex }) => {
640
+ const definition = row.definitions[cellIndex];
641
+ if (definition?.verticalMergeContinuation === true) return { blocks: [] };
642
+ const colSpan = horizontalSpanAt(row.definitions, cellIndex);
643
+ const column = columnIndices[rowIndex]?.[cellIndex];
644
+ let rowSpan = 1;
645
+ if (definition?.verticalMergeFirst === true && column !== void 0) for (let next = rowIndex + 1; next < rows.length; next += 1) {
646
+ const matchIndex = columnIndices[next]?.indexOf(column) ?? -1;
647
+ if ((matchIndex === -1 ? void 0 : rows[next]?.definitions[matchIndex])?.verticalMergeContinuation !== true) break;
648
+ rowSpan += 1;
649
+ }
650
+ const borders = this.resolveBorders(definition);
651
+ const background = definition === void 0 ? void 0 : resolveCellFill(definition, (index) => this.header.colors[index]);
652
+ return {
653
+ blocks: cell.blocks,
654
+ ...colSpan > 1 ? { colSpan } : {},
655
+ ...rowSpan > 1 ? { rowSpan } : {},
656
+ ...background === void 0 ? {} : { background },
657
+ ...borders === void 0 ? {} : { borders },
658
+ ...definition?.verticalAlign === void 0 ? {} : { verticalAlign: definition.verticalAlign }
659
+ };
660
+ })
661
+ }));
645
662
  }
646
663
  resolveBorders(definition) {
647
664
  if (definition === void 0) return;
@@ -698,6 +715,7 @@ var ContentBuilder = class {
698
715
  pendingCellDefinitions: this.pendingCellDefinitions,
699
716
  pendingCell: this.pendingCell,
700
717
  rowLeftTwips: this.rowLeftTwips,
718
+ rowDirection: this.rowDirection,
701
719
  paragraphSerial: this.paragraphSerial,
702
720
  openBookmarks: this.openBookmarks,
703
721
  sectionBlockExtents: this.sectionBlockExtents,
@@ -722,6 +740,7 @@ var ContentBuilder = class {
722
740
  this.pendingCellDefinitions = saved.pendingCellDefinitions;
723
741
  this.pendingCell = saved.pendingCell;
724
742
  this.rowLeftTwips = saved.rowLeftTwips;
743
+ this.rowDirection = saved.rowDirection;
725
744
  this.paragraphSerial = saved.paragraphSerial;
726
745
  this.openBookmarks = saved.openBookmarks;
727
746
  this.sectionBlockExtents = saved.sectionBlockExtents;
@@ -822,7 +841,9 @@ function buildRunFields(char, fontName, color, hyperlink) {
822
841
  ...fontName === void 0 || fontName.length === 0 ? {} : { fontFamily: fontName },
823
842
  sizePt: halfPointsToPoints(char.sizeHalfPoints),
824
843
  ...color === void 0 ? {} : { color },
825
- ...hyperlink === void 0 ? {} : { hyperlink }
844
+ ...hyperlink === void 0 ? {} : { hyperlink },
845
+ ...char.verticalAlign === void 0 ? {} : { verticalAlign: char.verticalAlign },
846
+ ...char.direction === void 0 ? {} : { direction: char.direction }
826
847
  };
827
848
  }
828
849
  function buildPicture(picture, sink) {
@@ -1324,6 +1345,27 @@ function applyCharacterControlWord(name, param, state, header) {
1324
1345
  case "uc":
1325
1346
  if (param !== void 0 && param >= 0) state.uc = param;
1326
1347
  return true;
1348
+ case "super":
1349
+ state.char.verticalAlign = "superscript";
1350
+ return true;
1351
+ case "sub":
1352
+ state.char.verticalAlign = "subscript";
1353
+ return true;
1354
+ case "up":
1355
+ state.char.verticalAlign = param === void 0 || param > 0 ? "superscript" : param < 0 ? "subscript" : void 0;
1356
+ return true;
1357
+ case "dn":
1358
+ state.char.verticalAlign = param === void 0 || param > 0 ? "subscript" : param < 0 ? "superscript" : void 0;
1359
+ return true;
1360
+ case "nosupersub":
1361
+ state.char.verticalAlign = void 0;
1362
+ return true;
1363
+ case "rtlch":
1364
+ state.char.direction = "rtl";
1365
+ return true;
1366
+ case "ltrch":
1367
+ state.char.direction = "ltr";
1368
+ return true;
1327
1369
  case "revised":
1328
1370
  state.char.revision = {
1329
1371
  ...state.char.revision,
@@ -1417,6 +1459,12 @@ function applyParagraphControlWord(name, param, state) {
1417
1459
  case "s":
1418
1460
  state.para.styleIndex = param;
1419
1461
  return true;
1462
+ case "rtlpar":
1463
+ state.para.direction = "rtl";
1464
+ return true;
1465
+ case "ltrpar":
1466
+ state.para.direction = "ltr";
1467
+ return true;
1420
1468
  case "outlinelevel":
1421
1469
  state.para.outlineLevel = param === void 0 || param > 8 ? void 0 : param;
1422
1470
  return true;
@@ -1507,6 +1555,12 @@ function applyStructureControlWord(name, param, state, builder, section, sink) {
1507
1555
  case "trleft":
1508
1556
  builder.setRowLeft(param ?? 0);
1509
1557
  return true;
1558
+ case "rtlrow":
1559
+ builder.setRowDirection("rtl");
1560
+ return true;
1561
+ case "ltrrow":
1562
+ builder.setRowDirection("ltr");
1563
+ return true;
1510
1564
  case "cellx":
1511
1565
  if (param !== void 0) builder.addCellBoundary(param);
1512
1566
  return true;
package/dist/write.cjs CHANGED
@@ -353,6 +353,8 @@ var RtfWriter = class {
353
353
  writeHeader(document) {
354
354
  this.raw(`{\\rtf1\\ansi\\ansicpg${String(OUTPUT_CODEPAGE)}\\deff0\\uc1`);
355
355
  this.writeDocumentGeometry(document);
356
+ if (document.metadata.direction === "rtl") this.raw("\\rtldoc");
357
+ else if (document.metadata.direction === "ltr") this.raw("\\ltrdoc");
356
358
  this.writeFontTable();
357
359
  this.writeColorTable();
358
360
  this.writeStyleSheet();
@@ -537,6 +539,8 @@ var RtfWriter = class {
537
539
  if (styleHandle !== void 0 && level !== void 0) out += `\\s${String(styleHandle)}\\outlinelevel${String(level - 1)}`;
538
540
  const alignment = paragraph.alignment === void 0 ? void 0 : ALIGNMENT_CONTROL_WORDS.get(paragraph.alignment);
539
541
  if (alignment !== void 0) out += alignment;
542
+ if (paragraph.direction === "rtl") out += "\\rtlpar";
543
+ else if (paragraph.direction === "ltr") out += "\\ltrpar";
540
544
  const list = paragraph.list;
541
545
  if (list !== void 0) {
542
546
  const numId = list.numId;
@@ -591,6 +595,10 @@ var RtfWriter = class {
591
595
  if (run.italic === true) out += "\\i";
592
596
  if (run.underline === true) out += "\\ul";
593
597
  if (run.strike === true) out += "\\strike";
598
+ if (run.verticalAlign === "superscript") out += "\\super";
599
+ else if (run.verticalAlign === "subscript") out += "\\sub";
600
+ if (run.direction === "rtl") out += "\\rtlch";
601
+ else if (run.direction === "ltr") out += "\\ltrch";
594
602
  const colorIndex = colorIndexOf(run.color, this.tables.colors);
595
603
  if (colorIndex !== void 0) out += `\\cf${String(colorIndex)}`;
596
604
  return out;
@@ -614,7 +622,7 @@ var RtfWriter = class {
614
622
  });
615
623
  }
616
624
  }
617
- const rowDefinition = `\\trowd\\trgaph108\\trleft0${definitions.join("")}`;
625
+ const rowDefinition = `\\trowd\\trgaph108\\trleft0${row.direction === "rtl" ? "\\rtlrow" : row.direction === "ltr" ? "\\ltrrow" : ""}${definitions.join("")}`;
618
626
  this.line(rowDefinition);
619
627
  for (const mark of marks) {
620
628
  this.writeCellBlocks(mark.empty ? [] : mark.cell.blocks);
@@ -630,6 +638,8 @@ var RtfWriter = class {
630
638
  else if ((cell.rowSpan ?? 1) > 1) out += "\\clvmgf";
631
639
  if (horizontal === "mergeFirst") out += "\\clmgf";
632
640
  else if (horizontal === "mergeContinuation") return `${out}\\clmrg\\cellx${String(rightTwips)}`;
641
+ if (cell.verticalAlign === "center") out += "\\clvertalc";
642
+ else if (cell.verticalAlign === "bottom") out += "\\clvertalb";
633
643
  const borders = cell.borders;
634
644
  if (borders !== void 0) for (const side of CELL_BORDER_ORDER) {
635
645
  const border = borders[side];
package/dist/write.js CHANGED
@@ -352,6 +352,8 @@ var RtfWriter = class {
352
352
  writeHeader(document) {
353
353
  this.raw(`{\\rtf1\\ansi\\ansicpg${String(OUTPUT_CODEPAGE)}\\deff0\\uc1`);
354
354
  this.writeDocumentGeometry(document);
355
+ if (document.metadata.direction === "rtl") this.raw("\\rtldoc");
356
+ else if (document.metadata.direction === "ltr") this.raw("\\ltrdoc");
355
357
  this.writeFontTable();
356
358
  this.writeColorTable();
357
359
  this.writeStyleSheet();
@@ -536,6 +538,8 @@ var RtfWriter = class {
536
538
  if (styleHandle !== void 0 && level !== void 0) out += `\\s${String(styleHandle)}\\outlinelevel${String(level - 1)}`;
537
539
  const alignment = paragraph.alignment === void 0 ? void 0 : ALIGNMENT_CONTROL_WORDS.get(paragraph.alignment);
538
540
  if (alignment !== void 0) out += alignment;
541
+ if (paragraph.direction === "rtl") out += "\\rtlpar";
542
+ else if (paragraph.direction === "ltr") out += "\\ltrpar";
539
543
  const list = paragraph.list;
540
544
  if (list !== void 0) {
541
545
  const numId = list.numId;
@@ -590,6 +594,10 @@ var RtfWriter = class {
590
594
  if (run.italic === true) out += "\\i";
591
595
  if (run.underline === true) out += "\\ul";
592
596
  if (run.strike === true) out += "\\strike";
597
+ if (run.verticalAlign === "superscript") out += "\\super";
598
+ else if (run.verticalAlign === "subscript") out += "\\sub";
599
+ if (run.direction === "rtl") out += "\\rtlch";
600
+ else if (run.direction === "ltr") out += "\\ltrch";
593
601
  const colorIndex = colorIndexOf(run.color, this.tables.colors);
594
602
  if (colorIndex !== void 0) out += `\\cf${String(colorIndex)}`;
595
603
  return out;
@@ -613,7 +621,7 @@ var RtfWriter = class {
613
621
  });
614
622
  }
615
623
  }
616
- const rowDefinition = `\\trowd\\trgaph108\\trleft0${definitions.join("")}`;
624
+ const rowDefinition = `\\trowd\\trgaph108\\trleft0${row.direction === "rtl" ? "\\rtlrow" : row.direction === "ltr" ? "\\ltrrow" : ""}${definitions.join("")}`;
617
625
  this.line(rowDefinition);
618
626
  for (const mark of marks) {
619
627
  this.writeCellBlocks(mark.empty ? [] : mark.cell.blocks);
@@ -629,6 +637,8 @@ var RtfWriter = class {
629
637
  else if ((cell.rowSpan ?? 1) > 1) out += "\\clvmgf";
630
638
  if (horizontal === "mergeFirst") out += "\\clmgf";
631
639
  else if (horizontal === "mergeContinuation") return `${out}\\clmrg\\cellx${String(rightTwips)}`;
640
+ if (cell.verticalAlign === "center") out += "\\clvertalc";
641
+ else if (cell.verticalAlign === "bottom") out += "\\clvertalb";
632
642
  const borders = cell.borders;
633
643
  if (borders !== void 0) for (const side of CELL_BORDER_ORDER) {
634
644
  const border = borders[side];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rtf-codec",
3
- "version": "4.2.5",
3
+ "version": "4.4.0",
4
4
  "description": "Hand-written Rich Text Format (RTF 1.9.1) reader and writer against the shared document-schema.js content pivot.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -82,8 +82,8 @@
82
82
  "license": "MIT",
83
83
  "packageManager": "pnpm@11.6.0",
84
84
  "dependencies": {
85
- "archive-codec": "1.10.7",
86
- "document-schema.js": "7.9.1",
85
+ "archive-codec": "1.11.0",
86
+ "document-schema.js": "7.11.0",
87
87
  "zod": "4.4.3"
88
88
  },
89
89
  "devDependencies": {