rtf-codec 3.0.4 → 4.0.1

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
@@ -121,7 +121,7 @@ On the way out, **every non-ASCII character leaves as `\uN`** with a one-charact
121
121
  | Paragraphs | `\par`, `\pard`, alignment, indents, spacing, `\slN`/`\slmultN`, `\pagebb` |
122
122
  | Runs | `\b`, `\i`, `\ul` (every variant), `\strike`, `\fN`, `\fsN`, `\cfN`, `\v` (dropped as hidden) |
123
123
  | Tables | `\trowd`, `\cellxN`, `\trleftN`, `\cell`, `\row`, multi-paragraph cells |
124
- | Table cells | `\clbrdrt`/`l`/`b`/`r` with the whole `<brdr>` production, `\clcbpatN` shading, and both merge families (`\clvmgf`/`\clvmrg`, `\clmgf`/`\clmrg`) |
124
+ | 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`) |
125
125
  | Bookmarks | `\*\bkmkstart`/`\*\bkmkend` as `anchor` constructs, with `\bkmkcolfN`/`\bkmkcollN` quarantined as residue |
126
126
  | Revision marks | The whole `<chrev>` production as `provenance` constructs: `\revised`, `\deleted`, `\mvf`/`\mvt`, `\crauthN`, with authors and `\revdttmN` dates |
127
127
  | 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 |
@@ -161,7 +161,7 @@ Each of these is reported through a diagnostic rather than dropped silently —
161
161
  | 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. |
162
162
  | 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. |
163
163
  | 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. |
164
- | Cell vertical alignment (`\clvertalt`/`\clvertalc`/`\clvertalb`), diagonal cell borders (`\cldglu`/`\cldgll`), `\clshdngN` | `ContentTableCell.borders` has no diagonal member, so a diagonal rule is not a side; `\clshdngN`'s own shading percentage names a genuine two-colour pattern fill, which `document-schema.js`'s `ContentCellFill` can now express (ExaDev/documents.js#951) but this package does not yet resolve either `\clcbpatN`'s own foreground colour or `\clshdngN`'s percentage into one -- `\clcbpatN`'s background colour alone still reads and writes as a `'solid'` fill. |
164
+ | 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. |
165
165
  | 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. |
166
166
  | 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. |
167
167
  | 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. |
@@ -1,6 +1,35 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_units = require("./units.cjs");
3
+ let document_schema_js = require("document-schema.js");
3
4
  //#region src/cell-format.ts
5
+ const PERCENT_STEPS = [
6
+ [5, "percent5"],
7
+ [10, "percent10"],
8
+ [12, "percent12"],
9
+ [15, "percent15"],
10
+ [20, "percent20"],
11
+ [25, "percent25"],
12
+ [30, "percent30"],
13
+ [35, "percent35"],
14
+ [37, "percent37"],
15
+ [40, "percent40"],
16
+ [45, "percent45"],
17
+ [50, "percent50"],
18
+ [55, "percent55"],
19
+ [60, "percent60"],
20
+ [62, "percent62"],
21
+ [65, "percent65"],
22
+ [70, "percent70"],
23
+ [75, "percent75"],
24
+ [80, "percent80"],
25
+ [85, "percent85"],
26
+ [87, "percent87"],
27
+ [90, "percent90"],
28
+ [95, "percent95"]
29
+ ];
30
+ function nearestPercentType(percent) {
31
+ return PERCENT_STEPS.reduce((best, step) => Math.abs(step[0] - percent) < Math.abs(best[0] - percent) ? step : best)[1];
32
+ }
4
33
  const CELL_BORDER_SIDES = /* @__PURE__ */ new Map([
5
34
  ["clbrdrt", "top"],
6
35
  ["clbrdrl", "left"],
@@ -49,6 +78,8 @@ function newPendingCell() {
49
78
  borders: {},
50
79
  side: void 0,
51
80
  backgroundIndex: void 0,
81
+ foregroundIndex: void 0,
82
+ shadingPercent: void 0,
52
83
  verticalMergeFirst: false,
53
84
  verticalMergeContinuation: false,
54
85
  horizontalMergeFirst: false,
@@ -83,6 +114,12 @@ function applyCellDefinitionControlWord(name, param, cell) {
83
114
  case "clcbpat":
84
115
  cell.backgroundIndex = param;
85
116
  return true;
117
+ case "clcfpat":
118
+ cell.foregroundIndex = param;
119
+ return true;
120
+ case "clshdng":
121
+ cell.shadingPercent = param === void 0 ? void 0 : param / 100;
122
+ return true;
86
123
  }
87
124
  const pending = cell.side === void 0 ? void 0 : cell.borders[cell.side];
88
125
  if (pending === void 0) return false;
@@ -121,6 +158,26 @@ function resolveBorder(pending, colorAt) {
121
158
  ...style === void 0 || style === "solid" ? {} : { style }
122
159
  };
123
160
  }
161
+ function resolveCellFill(pending, colorAt) {
162
+ if (pending.backgroundIndex === void 0) return;
163
+ const backgroundColor = colorAt(pending.backgroundIndex);
164
+ const shading = pending.shadingPercent ?? 0;
165
+ if (shading <= 0) return backgroundColor === void 0 ? void 0 : {
166
+ kind: "solid",
167
+ color: backgroundColor
168
+ };
169
+ const foregroundColor = pending.foregroundIndex === void 0 ? void 0 : colorAt(pending.foregroundIndex);
170
+ if (shading >= 100) return foregroundColor === void 0 ? void 0 : {
171
+ kind: "solid",
172
+ color: foregroundColor
173
+ };
174
+ return {
175
+ kind: "pattern",
176
+ patternType: nearestPercentType(shading),
177
+ ...foregroundColor === void 0 ? {} : { foregroundColor },
178
+ ...backgroundColor === void 0 ? {} : { backgroundColor }
179
+ };
180
+ }
124
181
  function borderControlWords(side, border, colorIndex) {
125
182
  const sideWord = [...CELL_BORDER_SIDES].find(([, value]) => value === side)?.[0];
126
183
  if (sideWord === void 0) return "";
@@ -134,9 +191,29 @@ const BORDER_STYLE_CONTROL_WORDS = {
134
191
  dotted: "brdrdot",
135
192
  double: "brdrdb"
136
193
  };
194
+ /** The inverse of PERCENT_STEPS, keyed by patternType. */
195
+ const PERCENT_TYPE_TO_VALUE = new Map(PERCENT_STEPS.map(([percent, patternType]) => [patternType, percent]));
196
+ function cellFillControlWords(fill, colorIndexOf) {
197
+ switch (fill.kind) {
198
+ case "solid": {
199
+ const index = colorIndexOf(fill.color);
200
+ return index === void 0 ? "" : `\\clcbpat${String(index)}`;
201
+ }
202
+ case "pattern": {
203
+ const percent = PERCENT_TYPE_TO_VALUE.get(fill.patternType);
204
+ if (percent === void 0) throw new Error(`rtf-codec cannot write a '${fill.patternType}' cell fill: RTF's own \\clshdngN is a flat two-colour percentage blend with no named stripe/cross/grid pattern of its own, and that pattern name belongs only to the ST_Shd/ST_PatternType half of ContentCellPatternType's shared vocabulary`);
205
+ const backgroundIndex = fill.backgroundColor === void 0 ? void 0 : colorIndexOf(fill.backgroundColor);
206
+ const foregroundIndex = fill.foregroundColor === void 0 ? void 0 : colorIndexOf(fill.foregroundColor);
207
+ return (backgroundIndex === void 0 ? "" : `\\clcbpat${String(backgroundIndex)}`) + (foregroundIndex === void 0 ? "" : `\\clcfpat${String(foregroundIndex)}`) + `\\clshdng${String(percent * 100)}`;
208
+ }
209
+ default: throw new Error(`rtf-codec cannot write a cell fill with kind '${(0, document_schema_js.unrecognizedFillKind)(fill)}': ContentCellFillSchema's discriminated union only defines 'solid' and 'pattern'`);
210
+ }
211
+ }
137
212
  //#endregion
138
213
  exports.CELL_BORDER_SIDES = CELL_BORDER_SIDES;
139
214
  exports.applyCellDefinitionControlWord = applyCellDefinitionControlWord;
140
215
  exports.borderControlWords = borderControlWords;
216
+ exports.cellFillControlWords = cellFillControlWords;
141
217
  exports.newPendingCell = newPendingCell;
142
218
  exports.resolveBorder = resolveBorder;
219
+ exports.resolveCellFill = resolveCellFill;
@@ -1,4 +1,4 @@
1
- import { Color, ContentBorder, ContentStrokeStyle } from "document-schema.js";
1
+ import { Color, ContentBorder, ContentCellFill, ContentStrokeStyle } from "document-schema.js";
2
2
  //#region src/cell-format.d.ts
3
3
  type CellBorderSide = "top" | "left" | "bottom" | "right";
4
4
  declare const CELL_BORDER_SIDES: ReadonlyMap<string, CellBorderSide>;
@@ -12,6 +12,8 @@ interface PendingCell {
12
12
  borders: Partial<Record<CellBorderSide, PendingBorder>>;
13
13
  side: CellBorderSide | undefined;
14
14
  backgroundIndex: number | undefined;
15
+ foregroundIndex: number | undefined;
16
+ shadingPercent: number | undefined;
15
17
  verticalMergeFirst: boolean;
16
18
  verticalMergeContinuation: boolean;
17
19
  horizontalMergeFirst: boolean;
@@ -20,6 +22,8 @@ interface PendingCell {
20
22
  declare function newPendingCell(): PendingCell;
21
23
  declare function applyCellDefinitionControlWord(name: string, param: number | undefined, cell: PendingCell): boolean;
22
24
  declare function resolveBorder(pending: PendingBorder, colorAt: (index: number) => Color | undefined): ContentBorder | undefined;
25
+ declare function resolveCellFill(pending: PendingCell, colorAt: (index: number) => Color | undefined): ContentCellFill | undefined;
23
26
  declare function borderControlWords(side: CellBorderSide, border: ContentBorder, colorIndex: number | undefined): string;
27
+ declare function cellFillControlWords(fill: ContentCellFill, colorIndexOf: (color: Color) => number | undefined): string;
24
28
  //#endregion
25
- export { CELL_BORDER_SIDES, CellBorderSide, PendingBorder, PendingCell, applyCellDefinitionControlWord, borderControlWords, newPendingCell, resolveBorder };
29
+ export { CELL_BORDER_SIDES, CellBorderSide, PendingBorder, PendingCell, applyCellDefinitionControlWord, borderControlWords, cellFillControlWords, newPendingCell, resolveBorder, resolveCellFill };
@@ -1,4 +1,4 @@
1
- import { Color, ContentBorder, ContentStrokeStyle } from "document-schema.js";
1
+ import { Color, ContentBorder, ContentCellFill, ContentStrokeStyle } from "document-schema.js";
2
2
  //#region src/cell-format.d.ts
3
3
  type CellBorderSide = "top" | "left" | "bottom" | "right";
4
4
  declare const CELL_BORDER_SIDES: ReadonlyMap<string, CellBorderSide>;
@@ -12,6 +12,8 @@ interface PendingCell {
12
12
  borders: Partial<Record<CellBorderSide, PendingBorder>>;
13
13
  side: CellBorderSide | undefined;
14
14
  backgroundIndex: number | undefined;
15
+ foregroundIndex: number | undefined;
16
+ shadingPercent: number | undefined;
15
17
  verticalMergeFirst: boolean;
16
18
  verticalMergeContinuation: boolean;
17
19
  horizontalMergeFirst: boolean;
@@ -20,6 +22,8 @@ interface PendingCell {
20
22
  declare function newPendingCell(): PendingCell;
21
23
  declare function applyCellDefinitionControlWord(name: string, param: number | undefined, cell: PendingCell): boolean;
22
24
  declare function resolveBorder(pending: PendingBorder, colorAt: (index: number) => Color | undefined): ContentBorder | undefined;
25
+ declare function resolveCellFill(pending: PendingCell, colorAt: (index: number) => Color | undefined): ContentCellFill | undefined;
23
26
  declare function borderControlWords(side: CellBorderSide, border: ContentBorder, colorIndex: number | undefined): string;
27
+ declare function cellFillControlWords(fill: ContentCellFill, colorIndexOf: (color: Color) => number | undefined): string;
24
28
  //#endregion
25
- export { CELL_BORDER_SIDES, CellBorderSide, PendingBorder, PendingCell, applyCellDefinitionControlWord, borderControlWords, newPendingCell, resolveBorder };
29
+ export { CELL_BORDER_SIDES, CellBorderSide, PendingBorder, PendingCell, applyCellDefinitionControlWord, borderControlWords, cellFillControlWords, newPendingCell, resolveBorder, resolveCellFill };
@@ -1,5 +1,34 @@
1
1
  import { pointsToTwips, twipsToPoints } from "./units.js";
2
+ import { unrecognizedFillKind } from "document-schema.js";
2
3
  //#region src/cell-format.ts
4
+ const PERCENT_STEPS = [
5
+ [5, "percent5"],
6
+ [10, "percent10"],
7
+ [12, "percent12"],
8
+ [15, "percent15"],
9
+ [20, "percent20"],
10
+ [25, "percent25"],
11
+ [30, "percent30"],
12
+ [35, "percent35"],
13
+ [37, "percent37"],
14
+ [40, "percent40"],
15
+ [45, "percent45"],
16
+ [50, "percent50"],
17
+ [55, "percent55"],
18
+ [60, "percent60"],
19
+ [62, "percent62"],
20
+ [65, "percent65"],
21
+ [70, "percent70"],
22
+ [75, "percent75"],
23
+ [80, "percent80"],
24
+ [85, "percent85"],
25
+ [87, "percent87"],
26
+ [90, "percent90"],
27
+ [95, "percent95"]
28
+ ];
29
+ function nearestPercentType(percent) {
30
+ return PERCENT_STEPS.reduce((best, step) => Math.abs(step[0] - percent) < Math.abs(best[0] - percent) ? step : best)[1];
31
+ }
3
32
  const CELL_BORDER_SIDES = /* @__PURE__ */ new Map([
4
33
  ["clbrdrt", "top"],
5
34
  ["clbrdrl", "left"],
@@ -48,6 +77,8 @@ function newPendingCell() {
48
77
  borders: {},
49
78
  side: void 0,
50
79
  backgroundIndex: void 0,
80
+ foregroundIndex: void 0,
81
+ shadingPercent: void 0,
51
82
  verticalMergeFirst: false,
52
83
  verticalMergeContinuation: false,
53
84
  horizontalMergeFirst: false,
@@ -82,6 +113,12 @@ function applyCellDefinitionControlWord(name, param, cell) {
82
113
  case "clcbpat":
83
114
  cell.backgroundIndex = param;
84
115
  return true;
116
+ case "clcfpat":
117
+ cell.foregroundIndex = param;
118
+ return true;
119
+ case "clshdng":
120
+ cell.shadingPercent = param === void 0 ? void 0 : param / 100;
121
+ return true;
85
122
  }
86
123
  const pending = cell.side === void 0 ? void 0 : cell.borders[cell.side];
87
124
  if (pending === void 0) return false;
@@ -120,6 +157,26 @@ function resolveBorder(pending, colorAt) {
120
157
  ...style === void 0 || style === "solid" ? {} : { style }
121
158
  };
122
159
  }
160
+ function resolveCellFill(pending, colorAt) {
161
+ if (pending.backgroundIndex === void 0) return;
162
+ const backgroundColor = colorAt(pending.backgroundIndex);
163
+ const shading = pending.shadingPercent ?? 0;
164
+ if (shading <= 0) return backgroundColor === void 0 ? void 0 : {
165
+ kind: "solid",
166
+ color: backgroundColor
167
+ };
168
+ const foregroundColor = pending.foregroundIndex === void 0 ? void 0 : colorAt(pending.foregroundIndex);
169
+ if (shading >= 100) return foregroundColor === void 0 ? void 0 : {
170
+ kind: "solid",
171
+ color: foregroundColor
172
+ };
173
+ return {
174
+ kind: "pattern",
175
+ patternType: nearestPercentType(shading),
176
+ ...foregroundColor === void 0 ? {} : { foregroundColor },
177
+ ...backgroundColor === void 0 ? {} : { backgroundColor }
178
+ };
179
+ }
123
180
  function borderControlWords(side, border, colorIndex) {
124
181
  const sideWord = [...CELL_BORDER_SIDES].find(([, value]) => value === side)?.[0];
125
182
  if (sideWord === void 0) return "";
@@ -133,5 +190,23 @@ const BORDER_STYLE_CONTROL_WORDS = {
133
190
  dotted: "brdrdot",
134
191
  double: "brdrdb"
135
192
  };
193
+ /** The inverse of PERCENT_STEPS, keyed by patternType. */
194
+ const PERCENT_TYPE_TO_VALUE = new Map(PERCENT_STEPS.map(([percent, patternType]) => [patternType, percent]));
195
+ function cellFillControlWords(fill, colorIndexOf) {
196
+ switch (fill.kind) {
197
+ case "solid": {
198
+ const index = colorIndexOf(fill.color);
199
+ return index === void 0 ? "" : `\\clcbpat${String(index)}`;
200
+ }
201
+ case "pattern": {
202
+ const percent = PERCENT_TYPE_TO_VALUE.get(fill.patternType);
203
+ if (percent === void 0) throw new Error(`rtf-codec cannot write a '${fill.patternType}' cell fill: RTF's own \\clshdngN is a flat two-colour percentage blend with no named stripe/cross/grid pattern of its own, and that pattern name belongs only to the ST_Shd/ST_PatternType half of ContentCellPatternType's shared vocabulary`);
204
+ const backgroundIndex = fill.backgroundColor === void 0 ? void 0 : colorIndexOf(fill.backgroundColor);
205
+ const foregroundIndex = fill.foregroundColor === void 0 ? void 0 : colorIndexOf(fill.foregroundColor);
206
+ return (backgroundIndex === void 0 ? "" : `\\clcbpat${String(backgroundIndex)}`) + (foregroundIndex === void 0 ? "" : `\\clcfpat${String(foregroundIndex)}`) + `\\clshdng${String(percent * 100)}`;
207
+ }
208
+ default: throw new Error(`rtf-codec cannot write a cell fill with kind '${unrecognizedFillKind(fill)}': ContentCellFillSchema's discriminated union only defines 'solid' and 'pattern'`);
209
+ }
210
+ }
136
211
  //#endregion
137
- export { CELL_BORDER_SIDES, applyCellDefinitionControlWord, borderControlWords, newPendingCell, resolveBorder };
212
+ export { CELL_BORDER_SIDES, applyCellDefinitionControlWord, borderControlWords, cellFillControlWords, newPendingCell, resolveBorder, resolveCellFill };
package/dist/codec.cjs CHANGED
@@ -1,8 +1,8 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_read = require("./read.cjs");
3
3
  const require_write = require("./write.cjs");
4
- let zod = require("zod");
5
4
  let document_schema_js = require("document-schema.js");
5
+ let zod = require("zod");
6
6
  //#region src/codec.ts
7
7
  const RTF_MAGIC = [
8
8
  123,
package/dist/codec.d.cts CHANGED
@@ -1425,10 +1425,10 @@ declare const rtfContentCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, U
1425
1425
  endColumn: z.ZodNumber;
1426
1426
  }, z.core.$strip>>;
1427
1427
  type: z.ZodEnum<{
1428
- decimal: "decimal";
1429
- list: "list";
1430
1428
  date: "date";
1431
1429
  custom: "custom";
1430
+ decimal: "decimal";
1431
+ list: "list";
1432
1432
  time: "time";
1433
1433
  whole: "whole";
1434
1434
  textLength: "textLength";
package/dist/codec.d.ts CHANGED
@@ -1425,10 +1425,10 @@ declare const rtfContentCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, U
1425
1425
  endColumn: z.ZodNumber;
1426
1426
  }, z.core.$strip>>;
1427
1427
  type: z.ZodEnum<{
1428
- decimal: "decimal";
1429
- list: "list";
1430
1428
  date: "date";
1431
1429
  custom: "custom";
1430
+ decimal: "decimal";
1431
+ list: "list";
1432
1432
  time: "time";
1433
1433
  whole: "whole";
1434
1434
  textLength: "textLength";
package/dist/codec.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { readRtf, readRtfContent } from "./read.js";
2
2
  import { writeRtf, writeRtfContent } from "./write.js";
3
- import { z } from "zod";
4
3
  import { ContentDocumentSchema, DocumentTreeSchema } from "document-schema.js";
4
+ import { z } from "zod";
5
5
  //#region src/codec.ts
6
6
  const RTF_MAGIC = [
7
7
  123,
package/dist/read.cjs CHANGED
@@ -634,15 +634,12 @@ var ContentBuilder = class {
634
634
  rowSpan += 1;
635
635
  }
636
636
  const borders = this.resolveBorders(definition);
637
- const backgroundColor = definition?.backgroundIndex === void 0 ? void 0 : this.header.colors[definition.backgroundIndex];
637
+ const background = definition === void 0 ? void 0 : require_cell_format.resolveCellFill(definition, (index) => this.header.colors[index]);
638
638
  return {
639
639
  blocks: cell.blocks,
640
640
  ...colSpan > 1 ? { colSpan } : {},
641
641
  ...rowSpan > 1 ? { rowSpan } : {},
642
- ...backgroundColor === void 0 ? {} : { background: {
643
- kind: "solid",
644
- color: backgroundColor
645
- } },
642
+ ...background === void 0 ? {} : { background },
646
643
  ...borders === void 0 ? {} : { borders }
647
644
  };
648
645
  }) }));
package/dist/read.js CHANGED
@@ -2,7 +2,7 @@ import { bytesToBase64, hexToBytes } from "./base64.js";
2
2
  import { RtfDiagnosticCodes, RtfInputTooLargeError, RtfNestingLimitExceededError, RtfNotAnRtfDocumentError } from "./diagnostics.js";
3
3
  import { appendBytes, asciiStringFromBytes, rtfBytesFromLatin1 } from "./bytes.js";
4
4
  import { halfPointsToPoints, pixelsToPoints, twipsToPoints } from "./units.js";
5
- import { applyCellDefinitionControlWord, newPendingCell, resolveBorder } from "./cell-format.js";
5
+ import { applyCellDefinitionControlWord, newPendingCell, resolveBorder, resolveCellFill } from "./cell-format.js";
6
6
  import { NO_REVISION, bookmarkAnchorDescriptor, coalesceRunConstructs, formFieldContentControl, formFieldControlType, provenanceDescriptors } from "./constructs.js";
7
7
  import { readEmbeddedObjectData } from "./embedded-object.js";
8
8
  import { decodeCodepageBytes } from "./codepage.js";
@@ -633,15 +633,12 @@ var ContentBuilder = class {
633
633
  rowSpan += 1;
634
634
  }
635
635
  const borders = this.resolveBorders(definition);
636
- const backgroundColor = definition?.backgroundIndex === void 0 ? void 0 : this.header.colors[definition.backgroundIndex];
636
+ const background = definition === void 0 ? void 0 : resolveCellFill(definition, (index) => this.header.colors[index]);
637
637
  return {
638
638
  blocks: cell.blocks,
639
639
  ...colSpan > 1 ? { colSpan } : {},
640
640
  ...rowSpan > 1 ? { rowSpan } : {},
641
- ...backgroundColor === void 0 ? {} : { background: {
642
- kind: "solid",
643
- color: backgroundColor
644
- } },
641
+ ...background === void 0 ? {} : { background },
645
642
  ...borders === void 0 ? {} : { borders }
646
643
  };
647
644
  }) }));
package/dist/write.cjs CHANGED
@@ -79,7 +79,11 @@ function collectTables(document) {
79
79
  return;
80
80
  }
81
81
  if (block.kind === "table") for (const row of block.rows) for (const cell of row.cells) {
82
- noteColor(cell.background === void 0 ? void 0 : (0, document_schema_js.resolveCellFillColor)(cell.background));
82
+ if (cell.background?.kind === "solid") noteColor(cell.background.color);
83
+ else if (cell.background?.kind === "pattern") {
84
+ noteColor(cell.background.foregroundColor);
85
+ noteColor(cell.background.backgroundColor);
86
+ }
83
87
  for (const side of CELL_BORDER_ORDER) noteColor(cell.borders?.[side]?.color);
84
88
  for (const inner of cell.blocks) noteBlock(inner);
85
89
  }
@@ -631,11 +635,7 @@ var RtfWriter = class {
631
635
  const border = borders[side];
632
636
  if (border !== void 0) out += require_cell_format.borderControlWords(side, border, colorIndexOf(border.color, this.tables.colors));
633
637
  }
634
- const cellBackgroundColor = cell.background === void 0 ? void 0 : (0, document_schema_js.resolveCellFillColor)(cell.background);
635
- if (cellBackgroundColor !== void 0) {
636
- const index = colorIndexOf(cellBackgroundColor, this.tables.colors);
637
- if (index !== void 0) out += `\\clcbpat${String(index)}`;
638
- }
638
+ if (cell.background !== void 0) out += require_cell_format.cellFillControlWords(cell.background, (color) => colorIndexOf(color, this.tables.colors));
639
639
  return `${out}\\cellx${String(rightTwips)}`;
640
640
  }
641
641
  writeCellBlocks(blocks) {
package/dist/write.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import { base64ToBytes, bytesToHex } from "./base64.js";
2
2
  import { RtfDiagnosticCodes, RtfUnsupportedDocumentKindError } from "./diagnostics.js";
3
3
  import { pointsToHalfPoints, pointsToTwips } from "./units.js";
4
- import { borderControlWords } from "./cell-format.js";
4
+ import { borderControlWords, cellFillControlWords } from "./cell-format.js";
5
5
  import { bookmarkResidueControlWords, dttmFromIso, isBookmarkAnchor } from "./constructs.js";
6
6
  import { writeEmbeddedObjectData } from "./embedded-object.js";
7
7
  import { parseRtfListNumId } from "./list-id.js";
8
- import { clampHeadingLevel, colorToRgbHex, flattenTree, resolveCellFillColor } from "document-schema.js";
8
+ import { clampHeadingLevel, colorToRgbHex, flattenTree } from "document-schema.js";
9
9
  //#region src/write.ts
10
10
  const LEVEL_NUMBER_FORMAT_BULLET = 23;
11
11
  const LEVEL_NUMBER_FORMAT_ARABIC = 0;
@@ -78,7 +78,11 @@ function collectTables(document) {
78
78
  return;
79
79
  }
80
80
  if (block.kind === "table") for (const row of block.rows) for (const cell of row.cells) {
81
- noteColor(cell.background === void 0 ? void 0 : resolveCellFillColor(cell.background));
81
+ if (cell.background?.kind === "solid") noteColor(cell.background.color);
82
+ else if (cell.background?.kind === "pattern") {
83
+ noteColor(cell.background.foregroundColor);
84
+ noteColor(cell.background.backgroundColor);
85
+ }
82
86
  for (const side of CELL_BORDER_ORDER) noteColor(cell.borders?.[side]?.color);
83
87
  for (const inner of cell.blocks) noteBlock(inner);
84
88
  }
@@ -630,11 +634,7 @@ var RtfWriter = class {
630
634
  const border = borders[side];
631
635
  if (border !== void 0) out += borderControlWords(side, border, colorIndexOf(border.color, this.tables.colors));
632
636
  }
633
- const cellBackgroundColor = cell.background === void 0 ? void 0 : resolveCellFillColor(cell.background);
634
- if (cellBackgroundColor !== void 0) {
635
- const index = colorIndexOf(cellBackgroundColor, this.tables.colors);
636
- if (index !== void 0) out += `\\clcbpat${String(index)}`;
637
- }
637
+ if (cell.background !== void 0) out += cellFillControlWords(cell.background, (color) => colorIndexOf(color, this.tables.colors));
638
638
  return `${out}\\cellx${String(rightTwips)}`;
639
639
  }
640
640
  writeCellBlocks(blocks) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rtf-codec",
3
- "version": "3.0.4",
3
+ "version": "4.0.1",
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": {
@@ -80,8 +80,8 @@
80
80
  "license": "MIT",
81
81
  "packageManager": "pnpm@11.6.0",
82
82
  "dependencies": {
83
- "archive-codec": "^1.6.4",
84
- "document-schema.js": "^6.2.3",
83
+ "archive-codec": "^1.6.5",
84
+ "document-schema.js": "^7.0.0",
85
85
  "zod": "^4.4.3"
86
86
  },
87
87
  "devDependencies": {