doc-codec 1.1.2 → 2.0.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.
@@ -11,6 +11,8 @@ const SPRM_T_MERGE = 22052;
11
11
  const SPRM_T_VERT_MERGE = 54827;
12
12
  /** sprmTSetBrc (0xD62F): a TableBrcOperand ([MS-DOC] 2.9.305) restating one cell range's borders on the named sides, with an exact COLORREF rather than TC80's own Ico index. */
13
13
  const SPRM_T_SET_BRC = 54831;
14
+ /** sprmTSetBrc80 (0xD620): the Word 97-era spelling of sprmTSetBrc, a TableBrc80Operand ([MS-DOC] 2.9.304) restating one cell range's borders with a palette-indexed Brc80MayBeNil rather than sprmTSetBrc's own exact-colour Brc -- the identical cb/ItcFirstLim/bordersToApply header, differing only in which four-byte border field follows it. A real Word-97-era producer's own NilBrc80 clear (the same all-bits-set sentinel TC80's own Brc80 fields use) reaches applyBrcToCell exactly like sprmTSetBrc's own NilBrc, recording the cleared side on clearedSides so the row-level cascade never re-fills it ([ExaDev/documents.js#945](https://github.com/ExaDev/documents.js/issues/945)). */
15
+ const SPRM_T_SET_BRC80 = 54816;
14
16
  /** sprmTDefTableShd80 (0xD609): a DefTableShd80Operand ([MS-DOC] 2.9.52), the Word 97-era array of one Shd80 per cell starting at the row's first. */
15
17
  const SPRM_T_DEF_TABLE_SHD80 = 54793;
16
18
  /** sprmTDefTableShd3rd (0xD60C): a DefTableShdOperand ([MS-DOC] 2.9.53) shading cells 45-63 -- index 44 onward. */
@@ -27,6 +29,12 @@ const SPRM_T_SET_SHD_ODD = 54830;
27
29
  const SPRM_T_DEF_TABLE_SHD_RAW = 54896;
28
30
  const SPRM_T_DEF_TABLE_SHD_RAW_2ND = 54897;
29
31
  const SPRM_T_DEF_TABLE_SHD_RAW_3RD = 54898;
32
+ /** sprmTTableBorders80 (0xD605): the Word 97-era spelling of the row/table border cascade, a TableBordersOperand80 ([MS-DOC] 2.9.303) over Brc80MayBeNil fields. */
33
+ const SPRM_T_TABLE_BORDERS_80 = 54789;
34
+ /** sprmTTableBorders (0xD613): a TableBordersOperand ([MS-DOC] 2.9.302) over real Brc fields with an exact COLORREF, the modern spelling of the same cascade. */
35
+ const SPRM_T_TABLE_BORDERS = 54803;
36
+ /** sprmTSetShdTable (0xD660): a SHDOperand ([MS-DOC] 2.9.249) whose own text states it as shading for "the entire table" -- but its own spec text carries none of sprmTTableBorders's "unless modified" exception, so this reader applies it as an ordinary per-row sprm to every cell of whichever row's grpprl states it (folded in grpprl order like every other shading sprm above), rather than resolving it across the whole table the way its name implies: a producer writing it on only one row's grpprl shades, per this implementation, only that row. */
37
+ const SPRM_T_SET_SHD_TABLE = 54880;
30
38
  /** The first cell index each of the three DefTableShdOperand sprms shades, [MS-DOC] 2.6.3: "Cells 1 - 22 are shaded by sprmTDefTableShd, and cells 23 - 44 are shaded by sprmTDefTableShd2nd" and 45-63 by the third -- one-based there, so zero-based here. */
31
39
  const SHD_ARRAY_FIRST_CELL = {
32
40
  [SPRM_T_DEF_TABLE_SHD]: 0,
@@ -87,10 +95,11 @@ function readTdefTableOperand(operand) {
87
95
  function withCells(definition, next) {
88
96
  return {
89
97
  columnBoundariesTwips: definition.columnBoundariesTwips,
90
- cells: definition.cells.map(next)
98
+ cells: definition.cells.map(next),
99
+ rowBorders: definition.rowBorders
91
100
  };
92
101
  }
93
- /** Overlays the sides one TableBrcOperand names onto a cell's existing borders, leaving every side it does not name as TC80's own Brc80 stated it. A named side whose Brc is a NilBrc removes that side's border, which is how a producer states "this cell has no top border" over a row-level one it would otherwise inherit. */
102
+ /** Overlays the sides one TableBrcOperand(80) names onto a cell's existing borders, leaving every side it does not name as TC80's own Brc80 stated it. Shared verbatim by both SPRM_T_SET_BRC and SPRM_T_SET_BRC80's own switch cases, since the two operands differ only in which border field their caller has already decoded (readBrc's exact Brc vs readBrc80's palette-indexed Brc80) -- by the time `border` reaches here, both are the identical ContentBorder|undefined shape. A named side whose Brc/Brc80 is a NilBrc/NilBrc80 explicitly clears that side -- how a producer states "this cell has no top border" over a row-level one it would otherwise inherit -- and is recorded in `clearedSides` so table/read.ts's own row-level cascade can tell that apart from a side this cell has simply never mentioned. TC80's own Brc80 fields cannot state this distinction on their own (they are mandatory for every cell, so "no border" and "never stated" are the identical bytes -- table/read.ts's own applyRowLevelBorderCascade note); only an explicit sprmTSetBrc/sprmTSetBrc80 naming the side carries an unambiguous "clear" signal. A later sprmTSetBrc/sprmTSetBrc80 restating a real border on a previously cleared side un-clears it, matching the ordinary last-Prl-wins fold every sprm in this module already follows. */
94
103
  function applyBrcToCell(cell, bordersToApply, border) {
95
104
  const sides = {
96
105
  top: cell.borders?.top,
@@ -98,11 +107,18 @@ function applyBrcToCell(cell, bordersToApply, border) {
98
107
  bottom: cell.borders?.bottom,
99
108
  right: cell.borders?.right
100
109
  };
101
- for (const side of require_table_decoration.CELL_BORDER_SIDES) if ((bordersToApply & require_table_decoration.BORDERS_TO_APPLY[side]) !== 0) sides[side] = border;
110
+ const clearedSides = new Set(cell.clearedSides);
111
+ for (const side of require_table_decoration.CELL_BORDER_SIDES) {
112
+ if ((bordersToApply & require_table_decoration.BORDERS_TO_APPLY[side]) === 0) continue;
113
+ sides[side] = border;
114
+ if (border === void 0) clearedSides.add(side);
115
+ else clearedSides.delete(side);
116
+ }
102
117
  return {
103
118
  horzMerge: cell.horzMerge,
104
119
  vertMerge: cell.vertMerge,
105
120
  borders: require_table_decoration.cellBordersFrom(sides),
121
+ clearedSides: clearedSides.size > 0 ? clearedSides : void 0,
106
122
  background: cell.background
107
123
  };
108
124
  }
@@ -111,17 +127,18 @@ function withBackground(cell, background) {
111
127
  horzMerge: cell.horzMerge,
112
128
  vertMerge: cell.vertMerge,
113
129
  borders: cell.borders,
130
+ clearedSides: cell.clearedSides,
114
131
  background
115
132
  };
116
133
  }
117
134
  /** Applies a DefTableShdOperand's ([MS-DOC] 2.9.53) rgShd array from `firstCell` onward. The array carries "only ... elements necessary to define all shaded cells in the row", so cells past its end keep whatever an earlier sprm left them -- an entry that is present and states ShdAuto/ShdNil genuinely clears its own cell, which is how a real producer's full-length array turns a row's unshaded cells back off. */
118
- function applyShdArray(definition, operand, firstCell, entrySize, colorAt) {
135
+ function applyShdArray(definition, operand, firstCell, entrySize, fillAt) {
119
136
  const cb = require_bytes.readUint8(operand, 0);
120
137
  const count = Math.floor(cb / entrySize);
121
138
  return withCells(definition, (cell, index) => {
122
139
  const entry = index - firstCell;
123
140
  if (entry < 0 || entry >= count) return cell;
124
- return withBackground(cell, colorAt(operand, 1 + entry * entrySize));
141
+ return withBackground(cell, fillAt(operand, 1 + entry * entrySize));
125
142
  });
126
143
  }
127
144
  /** Applies a TableShadeOperand ([MS-DOC] 2.9.308): cb, an ItcFirstLim naming the range, then a single Shd applied to every cell the range covers -- not one Shd per cell. `step` is 1 for sprmTSetShd and 2 for sprmTSetShdOdd, whose own range covers every other cell from itcFirst. */
@@ -157,6 +174,7 @@ function applyTableSprms(prls, into) {
157
174
  horzMerge: index === itcFirst ? 2 : 1,
158
175
  vertMerge: cell.vertMerge,
159
176
  borders: cell.borders,
177
+ clearedSides: cell.clearedSides,
160
178
  background: cell.background
161
179
  };
162
180
  });
@@ -170,6 +188,7 @@ function applyTableSprms(prls, into) {
170
188
  horzMerge: cell.horzMerge,
171
189
  vertMerge: vertMergeFlags,
172
190
  borders: cell.borders,
191
+ clearedSides: cell.clearedSides,
173
192
  background: cell.background
174
193
  } : cell);
175
194
  break;
@@ -183,6 +202,15 @@ function applyTableSprms(prls, into) {
183
202
  into.definition = withCells(into.definition, (cell, index) => index < itcFirst || index >= itcLim ? cell : applyBrcToCell(cell, bordersToApply, border));
184
203
  break;
185
204
  }
205
+ case SPRM_T_SET_BRC80: {
206
+ if (into.definition === void 0) break;
207
+ const itcFirst = require_bytes.readUint8(prl.operand, 1);
208
+ const itcLim = require_bytes.readUint8(prl.operand, 2);
209
+ const bordersToApply = require_bytes.readUint8(prl.operand, 3);
210
+ const border = require_table_decoration.readBrc80(prl.operand, 4);
211
+ into.definition = withCells(into.definition, (cell, index) => index < itcFirst || index >= itcLim ? cell : applyBrcToCell(cell, bordersToApply, border));
212
+ break;
213
+ }
186
214
  case SPRM_T_DEF_TABLE_SHD:
187
215
  case SPRM_T_DEF_TABLE_SHD_2ND:
188
216
  case SPRM_T_DEF_TABLE_SHD_3RD:
@@ -206,6 +234,27 @@ function applyTableSprms(prls, into) {
206
234
  case SPRM_T_SET_SHD_ODD:
207
235
  if (into.definition === void 0) break;
208
236
  into.definition = applyTableShade(into.definition, prl.operand, 2);
237
+ break;
238
+ case SPRM_T_TABLE_BORDERS_80:
239
+ if (into.definition === void 0) break;
240
+ into.definition = {
241
+ ...into.definition,
242
+ rowBorders: require_table_decoration.readTableBordersOperand80(prl.operand)
243
+ };
244
+ break;
245
+ case SPRM_T_TABLE_BORDERS:
246
+ if (into.definition === void 0) break;
247
+ into.definition = {
248
+ ...into.definition,
249
+ rowBorders: require_table_decoration.readTableBordersOperand(prl.operand)
250
+ };
251
+ break;
252
+ case SPRM_T_SET_SHD_TABLE: {
253
+ if (into.definition === void 0) break;
254
+ const background = require_table_decoration.readShd(prl.operand, 1);
255
+ into.definition = withCells(into.definition, (cell) => withBackground(cell, background));
256
+ break;
257
+ }
209
258
  }
210
259
  }
211
260
  return into;
@@ -1,5 +1,6 @@
1
1
  import { t as Prl } from "../sprm-Djg5RNiq.cjs";
2
- import { Color, ContentCellBorders } from "document-schema.js";
2
+ import { CellBorderSide, TableBordersSet } from "./decoration.cjs";
3
+ import { ContentCellBorders, ContentCellFill } from "document-schema.js";
3
4
  //#region src/table/tap.d.ts
4
5
  /** TCGRF.horzMerge, [MS-DOC] 2.9.317: 0 not merged, 1 a continuation cell (contributes its layout region, its own contents are not rendered), 2 or 3 the first cell of a horizontally merged set. */
5
6
  declare const HORZ_MERGE_CONTINUATION = 1;
@@ -11,14 +12,18 @@ interface TableCellProperties {
11
12
  readonly vertMerge: number;
12
13
  /** The cell's own four borders, absent when it states none on any side. */
13
14
  readonly borders?: ContentCellBorders;
14
- /** The cell's own flat background colour, absent when it states no shading or states a pattern Color cannot express (see decoration.ts's readShd). */
15
- readonly background?: Color;
15
+ /** Sides sprmTSetBrc or sprmTSetBrc80 has explicitly named with a NilBrc/NilBrc80 -- a real "this cell has no border here" statement, distinct from a side this cell's own TAP has simply never mentioned. `borders` alone cannot carry that distinction (an absent side means the same thing either way once cellBordersFrom has dropped it), so table/read.ts's own row-level border cascade (applyRowLevelBorderCascade) consults this set too: a side listed here is never filled from the row's cascade, no matter what `borders` says. See applyBrcToCell's own note for why only sprmTSetBrc/sprmTSetBrc80, never TC80's own Brc80 fields, can state this. */
16
+ readonly clearedSides?: ReadonlySet<CellBorderSide>;
17
+ /** The cell's own background fill, absent when it states no shading or states a pattern this reader cannot resolve at all (see decoration.ts's readShd). */
18
+ readonly background?: ContentCellFill;
16
19
  }
17
20
  interface TableRowDefinition {
18
21
  /** rgdxaCenter, in twips: one more entry than there are physical cells in the row -- column i's width is boundaries[i + 1] - boundaries[i]. */
19
22
  readonly columnBoundariesTwips: readonly number[];
20
23
  /** One entry per physical cell in the row, in document order -- every physical cell the row's own cell marks delimit, horizontally- and vertically-merged-away cells included, exactly as [MS-DOC]'s own model keeps them all present. */
21
24
  readonly cells: readonly TableCellProperties[];
25
+ /** This row's own sprmTTableBorders/sprmTTableBorders80 cascade, unresolved onto any cell -- see this module's own top-of-file note on why table/read.ts's applyRowLevelBorderCascade, not this function, is what actually applies it. */
26
+ readonly rowBorders?: TableBordersSet;
22
27
  }
23
28
  interface TableRowProperties {
24
29
  definition?: TableRowDefinition;
@@ -1,5 +1,6 @@
1
1
  import { t as Prl } from "../sprm-Djg5RNiq.js";
2
- import { Color, ContentCellBorders } from "document-schema.js";
2
+ import { CellBorderSide, TableBordersSet } from "./decoration.js";
3
+ import { ContentCellBorders, ContentCellFill } from "document-schema.js";
3
4
  //#region src/table/tap.d.ts
4
5
  /** TCGRF.horzMerge, [MS-DOC] 2.9.317: 0 not merged, 1 a continuation cell (contributes its layout region, its own contents are not rendered), 2 or 3 the first cell of a horizontally merged set. */
5
6
  declare const HORZ_MERGE_CONTINUATION = 1;
@@ -11,14 +12,18 @@ interface TableCellProperties {
11
12
  readonly vertMerge: number;
12
13
  /** The cell's own four borders, absent when it states none on any side. */
13
14
  readonly borders?: ContentCellBorders;
14
- /** The cell's own flat background colour, absent when it states no shading or states a pattern Color cannot express (see decoration.ts's readShd). */
15
- readonly background?: Color;
15
+ /** Sides sprmTSetBrc or sprmTSetBrc80 has explicitly named with a NilBrc/NilBrc80 -- a real "this cell has no border here" statement, distinct from a side this cell's own TAP has simply never mentioned. `borders` alone cannot carry that distinction (an absent side means the same thing either way once cellBordersFrom has dropped it), so table/read.ts's own row-level border cascade (applyRowLevelBorderCascade) consults this set too: a side listed here is never filled from the row's cascade, no matter what `borders` says. See applyBrcToCell's own note for why only sprmTSetBrc/sprmTSetBrc80, never TC80's own Brc80 fields, can state this. */
16
+ readonly clearedSides?: ReadonlySet<CellBorderSide>;
17
+ /** The cell's own background fill, absent when it states no shading or states a pattern this reader cannot resolve at all (see decoration.ts's readShd). */
18
+ readonly background?: ContentCellFill;
16
19
  }
17
20
  interface TableRowDefinition {
18
21
  /** rgdxaCenter, in twips: one more entry than there are physical cells in the row -- column i's width is boundaries[i + 1] - boundaries[i]. */
19
22
  readonly columnBoundariesTwips: readonly number[];
20
23
  /** One entry per physical cell in the row, in document order -- every physical cell the row's own cell marks delimit, horizontally- and vertically-merged-away cells included, exactly as [MS-DOC]'s own model keeps them all present. */
21
24
  readonly cells: readonly TableCellProperties[];
25
+ /** This row's own sprmTTableBorders/sprmTTableBorders80 cascade, unresolved onto any cell -- see this module's own top-of-file note on why table/read.ts's applyRowLevelBorderCascade, not this function, is what actually applies it. */
26
+ readonly rowBorders?: TableBordersSet;
22
27
  }
23
28
  interface TableRowProperties {
24
29
  definition?: TableRowDefinition;
package/dist/table/tap.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { readInt16LE, readUint16LE, readUint8 } from "../bytes.js";
2
2
  import { SGC } from "../prop/sprm.js";
3
- import { BORDERS_TO_APPLY, CELL_BORDER_SIDES, cellBordersFrom, readBrc, readBrc80, readShd, readShd80 } from "./decoration.js";
3
+ import { BORDERS_TO_APPLY, CELL_BORDER_SIDES, cellBordersFrom, readBrc, readBrc80, readShd, readShd80, readTableBordersOperand, readTableBordersOperand80 } from "./decoration.js";
4
4
  //#region src/table/tap.ts
5
5
  const SPRM_T_DEF_TABLE = 54792;
6
6
  const SPRM_T_DYA_ROW_HEIGHT = 37895;
@@ -10,6 +10,8 @@ const SPRM_T_MERGE = 22052;
10
10
  const SPRM_T_VERT_MERGE = 54827;
11
11
  /** sprmTSetBrc (0xD62F): a TableBrcOperand ([MS-DOC] 2.9.305) restating one cell range's borders on the named sides, with an exact COLORREF rather than TC80's own Ico index. */
12
12
  const SPRM_T_SET_BRC = 54831;
13
+ /** sprmTSetBrc80 (0xD620): the Word 97-era spelling of sprmTSetBrc, a TableBrc80Operand ([MS-DOC] 2.9.304) restating one cell range's borders with a palette-indexed Brc80MayBeNil rather than sprmTSetBrc's own exact-colour Brc -- the identical cb/ItcFirstLim/bordersToApply header, differing only in which four-byte border field follows it. A real Word-97-era producer's own NilBrc80 clear (the same all-bits-set sentinel TC80's own Brc80 fields use) reaches applyBrcToCell exactly like sprmTSetBrc's own NilBrc, recording the cleared side on clearedSides so the row-level cascade never re-fills it ([ExaDev/documents.js#945](https://github.com/ExaDev/documents.js/issues/945)). */
14
+ const SPRM_T_SET_BRC80 = 54816;
13
15
  /** sprmTDefTableShd80 (0xD609): a DefTableShd80Operand ([MS-DOC] 2.9.52), the Word 97-era array of one Shd80 per cell starting at the row's first. */
14
16
  const SPRM_T_DEF_TABLE_SHD80 = 54793;
15
17
  /** sprmTDefTableShd3rd (0xD60C): a DefTableShdOperand ([MS-DOC] 2.9.53) shading cells 45-63 -- index 44 onward. */
@@ -26,6 +28,12 @@ const SPRM_T_SET_SHD_ODD = 54830;
26
28
  const SPRM_T_DEF_TABLE_SHD_RAW = 54896;
27
29
  const SPRM_T_DEF_TABLE_SHD_RAW_2ND = 54897;
28
30
  const SPRM_T_DEF_TABLE_SHD_RAW_3RD = 54898;
31
+ /** sprmTTableBorders80 (0xD605): the Word 97-era spelling of the row/table border cascade, a TableBordersOperand80 ([MS-DOC] 2.9.303) over Brc80MayBeNil fields. */
32
+ const SPRM_T_TABLE_BORDERS_80 = 54789;
33
+ /** sprmTTableBorders (0xD613): a TableBordersOperand ([MS-DOC] 2.9.302) over real Brc fields with an exact COLORREF, the modern spelling of the same cascade. */
34
+ const SPRM_T_TABLE_BORDERS = 54803;
35
+ /** sprmTSetShdTable (0xD660): a SHDOperand ([MS-DOC] 2.9.249) whose own text states it as shading for "the entire table" -- but its own spec text carries none of sprmTTableBorders's "unless modified" exception, so this reader applies it as an ordinary per-row sprm to every cell of whichever row's grpprl states it (folded in grpprl order like every other shading sprm above), rather than resolving it across the whole table the way its name implies: a producer writing it on only one row's grpprl shades, per this implementation, only that row. */
36
+ const SPRM_T_SET_SHD_TABLE = 54880;
29
37
  /** The first cell index each of the three DefTableShdOperand sprms shades, [MS-DOC] 2.6.3: "Cells 1 - 22 are shaded by sprmTDefTableShd, and cells 23 - 44 are shaded by sprmTDefTableShd2nd" and 45-63 by the third -- one-based there, so zero-based here. */
30
38
  const SHD_ARRAY_FIRST_CELL = {
31
39
  [SPRM_T_DEF_TABLE_SHD]: 0,
@@ -86,10 +94,11 @@ function readTdefTableOperand(operand) {
86
94
  function withCells(definition, next) {
87
95
  return {
88
96
  columnBoundariesTwips: definition.columnBoundariesTwips,
89
- cells: definition.cells.map(next)
97
+ cells: definition.cells.map(next),
98
+ rowBorders: definition.rowBorders
90
99
  };
91
100
  }
92
- /** Overlays the sides one TableBrcOperand names onto a cell's existing borders, leaving every side it does not name as TC80's own Brc80 stated it. A named side whose Brc is a NilBrc removes that side's border, which is how a producer states "this cell has no top border" over a row-level one it would otherwise inherit. */
101
+ /** Overlays the sides one TableBrcOperand(80) names onto a cell's existing borders, leaving every side it does not name as TC80's own Brc80 stated it. Shared verbatim by both SPRM_T_SET_BRC and SPRM_T_SET_BRC80's own switch cases, since the two operands differ only in which border field their caller has already decoded (readBrc's exact Brc vs readBrc80's palette-indexed Brc80) -- by the time `border` reaches here, both are the identical ContentBorder|undefined shape. A named side whose Brc/Brc80 is a NilBrc/NilBrc80 explicitly clears that side -- how a producer states "this cell has no top border" over a row-level one it would otherwise inherit -- and is recorded in `clearedSides` so table/read.ts's own row-level cascade can tell that apart from a side this cell has simply never mentioned. TC80's own Brc80 fields cannot state this distinction on their own (they are mandatory for every cell, so "no border" and "never stated" are the identical bytes -- table/read.ts's own applyRowLevelBorderCascade note); only an explicit sprmTSetBrc/sprmTSetBrc80 naming the side carries an unambiguous "clear" signal. A later sprmTSetBrc/sprmTSetBrc80 restating a real border on a previously cleared side un-clears it, matching the ordinary last-Prl-wins fold every sprm in this module already follows. */
93
102
  function applyBrcToCell(cell, bordersToApply, border) {
94
103
  const sides = {
95
104
  top: cell.borders?.top,
@@ -97,11 +106,18 @@ function applyBrcToCell(cell, bordersToApply, border) {
97
106
  bottom: cell.borders?.bottom,
98
107
  right: cell.borders?.right
99
108
  };
100
- for (const side of CELL_BORDER_SIDES) if ((bordersToApply & BORDERS_TO_APPLY[side]) !== 0) sides[side] = border;
109
+ const clearedSides = new Set(cell.clearedSides);
110
+ for (const side of CELL_BORDER_SIDES) {
111
+ if ((bordersToApply & BORDERS_TO_APPLY[side]) === 0) continue;
112
+ sides[side] = border;
113
+ if (border === void 0) clearedSides.add(side);
114
+ else clearedSides.delete(side);
115
+ }
101
116
  return {
102
117
  horzMerge: cell.horzMerge,
103
118
  vertMerge: cell.vertMerge,
104
119
  borders: cellBordersFrom(sides),
120
+ clearedSides: clearedSides.size > 0 ? clearedSides : void 0,
105
121
  background: cell.background
106
122
  };
107
123
  }
@@ -110,17 +126,18 @@ function withBackground(cell, background) {
110
126
  horzMerge: cell.horzMerge,
111
127
  vertMerge: cell.vertMerge,
112
128
  borders: cell.borders,
129
+ clearedSides: cell.clearedSides,
113
130
  background
114
131
  };
115
132
  }
116
133
  /** Applies a DefTableShdOperand's ([MS-DOC] 2.9.53) rgShd array from `firstCell` onward. The array carries "only ... elements necessary to define all shaded cells in the row", so cells past its end keep whatever an earlier sprm left them -- an entry that is present and states ShdAuto/ShdNil genuinely clears its own cell, which is how a real producer's full-length array turns a row's unshaded cells back off. */
117
- function applyShdArray(definition, operand, firstCell, entrySize, colorAt) {
134
+ function applyShdArray(definition, operand, firstCell, entrySize, fillAt) {
118
135
  const cb = readUint8(operand, 0);
119
136
  const count = Math.floor(cb / entrySize);
120
137
  return withCells(definition, (cell, index) => {
121
138
  const entry = index - firstCell;
122
139
  if (entry < 0 || entry >= count) return cell;
123
- return withBackground(cell, colorAt(operand, 1 + entry * entrySize));
140
+ return withBackground(cell, fillAt(operand, 1 + entry * entrySize));
124
141
  });
125
142
  }
126
143
  /** Applies a TableShadeOperand ([MS-DOC] 2.9.308): cb, an ItcFirstLim naming the range, then a single Shd applied to every cell the range covers -- not one Shd per cell. `step` is 1 for sprmTSetShd and 2 for sprmTSetShdOdd, whose own range covers every other cell from itcFirst. */
@@ -156,6 +173,7 @@ function applyTableSprms(prls, into) {
156
173
  horzMerge: index === itcFirst ? 2 : 1,
157
174
  vertMerge: cell.vertMerge,
158
175
  borders: cell.borders,
176
+ clearedSides: cell.clearedSides,
159
177
  background: cell.background
160
178
  };
161
179
  });
@@ -169,6 +187,7 @@ function applyTableSprms(prls, into) {
169
187
  horzMerge: cell.horzMerge,
170
188
  vertMerge: vertMergeFlags,
171
189
  borders: cell.borders,
190
+ clearedSides: cell.clearedSides,
172
191
  background: cell.background
173
192
  } : cell);
174
193
  break;
@@ -182,6 +201,15 @@ function applyTableSprms(prls, into) {
182
201
  into.definition = withCells(into.definition, (cell, index) => index < itcFirst || index >= itcLim ? cell : applyBrcToCell(cell, bordersToApply, border));
183
202
  break;
184
203
  }
204
+ case SPRM_T_SET_BRC80: {
205
+ if (into.definition === void 0) break;
206
+ const itcFirst = readUint8(prl.operand, 1);
207
+ const itcLim = readUint8(prl.operand, 2);
208
+ const bordersToApply = readUint8(prl.operand, 3);
209
+ const border = readBrc80(prl.operand, 4);
210
+ into.definition = withCells(into.definition, (cell, index) => index < itcFirst || index >= itcLim ? cell : applyBrcToCell(cell, bordersToApply, border));
211
+ break;
212
+ }
185
213
  case SPRM_T_DEF_TABLE_SHD:
186
214
  case SPRM_T_DEF_TABLE_SHD_2ND:
187
215
  case SPRM_T_DEF_TABLE_SHD_3RD:
@@ -205,6 +233,27 @@ function applyTableSprms(prls, into) {
205
233
  case SPRM_T_SET_SHD_ODD:
206
234
  if (into.definition === void 0) break;
207
235
  into.definition = applyTableShade(into.definition, prl.operand, 2);
236
+ break;
237
+ case SPRM_T_TABLE_BORDERS_80:
238
+ if (into.definition === void 0) break;
239
+ into.definition = {
240
+ ...into.definition,
241
+ rowBorders: readTableBordersOperand80(prl.operand)
242
+ };
243
+ break;
244
+ case SPRM_T_TABLE_BORDERS:
245
+ if (into.definition === void 0) break;
246
+ into.definition = {
247
+ ...into.definition,
248
+ rowBorders: readTableBordersOperand(prl.operand)
249
+ };
250
+ break;
251
+ case SPRM_T_SET_SHD_TABLE: {
252
+ if (into.definition === void 0) break;
253
+ const background = readShd(prl.operand, 1);
254
+ into.definition = withCells(into.definition, (cell) => withBackground(cell, background));
255
+ break;
256
+ }
208
257
  }
209
258
  }
210
259
  return into;
@@ -1,6 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_errors = require("../errors.cjs");
3
3
  require("../text/special.cjs");
4
+ const require_prop_fkp_write = require("../prop/fkp-write.cjs");
4
5
  const require_table_tap_write = require("./tap-write.cjs");
5
6
  //#region src/table/write.ts
6
7
  /** sprmPFInTable (0x2416): a Bool8, "MUST be 1 any time the table depth is greater than zero". */
@@ -48,7 +49,64 @@ function columnBoundariesTwips(columnWidthsPt) {
48
49
  }
49
50
  return boundaries;
50
51
  }
51
- function flattenRow(cells, columnCount, boundaries, active) {
52
+ /** A deep copy of `active` -- a fresh Map holding a fresh object per entry, never the same ActiveVerticalMerge instances. flattenTable's own per-row budget check (see its own note below) has to try flattening a row's lost-boundary split as a dry run before committing to it, and placeCell mutates `covered.remaining` on the object a Map entry already holds -- a shallow copy would let that dry run's own mutation bleed into the real, committed state for every row after it. */
53
+ function cloneActive(active) {
54
+ return new Map(Array.from(active, ([column, merge]) => [column, { ...merge }]));
55
+ }
56
+ /** Where one logical cell lands on the row's own grid, before any lost-boundary splitting: its span in grid columns, and whether it is a vertical-merge continuation of a cell above. `active`'s own cross-row state must evolve identically wherever this runs, since recoverableBoundaries and flattenRow each walk every row with their own fresh map and have to land on the same columns for the second pass's splitting decisions to mean anything. */
57
+ function placeCell(cell, column, active) {
58
+ const covered = active.get(column);
59
+ if (cell.blocks.length === 0 && covered !== void 0 && covered.remaining > 0) {
60
+ covered.remaining -= 1;
61
+ return {
62
+ span: covered.span,
63
+ isContinuation: true
64
+ };
65
+ }
66
+ const span = cell.colSpan ?? 1;
67
+ const rowSpan = cell.rowSpan ?? 1;
68
+ if (rowSpan > 1) active.set(column, {
69
+ span,
70
+ remaining: rowSpan - 1
71
+ });
72
+ return {
73
+ span,
74
+ isContinuation: false
75
+ };
76
+ }
77
+ function recoverableBoundaries(rows) {
78
+ const stated = /* @__PURE__ */ new Set();
79
+ const active = /* @__PURE__ */ new Map();
80
+ for (const row of rows) {
81
+ let column = 0;
82
+ for (const cell of row.cells) {
83
+ const { span } = placeCell(cell, column, active);
84
+ column += span;
85
+ stated.add(column);
86
+ }
87
+ }
88
+ return stated;
89
+ }
90
+ function distributeLostBoundaries(lostBoundaries, rowCount) {
91
+ const perRow = Array.from({ length: rowCount }, () => /* @__PURE__ */ new Set());
92
+ lostBoundaries.forEach((boundary, index) => {
93
+ const bucket = perRow[index % rowCount];
94
+ if (bucket === void 0) throw new require_errors.DocFormatError("internal defect: distributeLostBoundaries built fewer row buckets than the row count it was given");
95
+ bucket.add(boundary);
96
+ });
97
+ return perRow;
98
+ }
99
+ function splitAtLostBoundaries(column, span, lostBoundaries) {
100
+ const subSpans = [];
101
+ let start = column;
102
+ for (let position = column + 1; position < column + span; position += 1) if (lostBoundaries.has(position)) {
103
+ subSpans.push(position - start);
104
+ start = position;
105
+ }
106
+ subSpans.push(column + span - start);
107
+ return subSpans;
108
+ }
109
+ function flattenRow(cells, columnCount, boundaries, active, lostBoundaries) {
52
110
  const paragraphs = [];
53
111
  const cellsToWrite = [];
54
112
  const firstBoundary = boundaries[0];
@@ -56,36 +114,31 @@ function flattenRow(cells, columnCount, boundaries, active) {
56
114
  const rowBoundariesTwips = [firstBoundary];
57
115
  let column = 0;
58
116
  for (const cell of cells) {
59
- const covered = active.get(column);
60
- const isContinuation = cell.blocks.length === 0 && covered !== void 0 && covered.remaining > 0;
61
- let span;
62
- let vertMerge;
63
- let blocks;
64
- if (isContinuation) {
65
- covered.remaining -= 1;
66
- span = covered.span;
67
- vertMerge = 1;
68
- blocks = [];
69
- } else {
70
- span = cell.colSpan ?? 1;
71
- const rowSpan = cell.rowSpan ?? 1;
72
- vertMerge = rowSpan > 1 ? 3 : 0;
73
- if (rowSpan > 1) active.set(column, {
74
- span,
75
- remaining: rowSpan - 1
117
+ const startColumn = column;
118
+ const { span, isContinuation } = placeCell(cell, startColumn, active);
119
+ const rowSpan = cell.rowSpan ?? 1;
120
+ const vertMerge = isContinuation ? 1 : rowSpan > 1 ? 3 : 0;
121
+ const blocks = isContinuation ? [] : cell.blocks;
122
+ const subSpans = splitAtLostBoundaries(startColumn, span, lostBoundaries);
123
+ subSpans.forEach((subSpan, subIndex) => {
124
+ paragraphs.push(...cellParagraphs(subIndex === 0 ? blocks : []));
125
+ cellsToWrite.push(subIndex === 0 ? isContinuation ? {
126
+ vertMerge,
127
+ horzMerge: subSpans.length > 1 ? 2 : 0
128
+ } : {
129
+ vertMerge,
130
+ horzMerge: subSpans.length > 1 ? 2 : 0,
131
+ borders: cell.borders,
132
+ background: cell.background
133
+ } : {
134
+ vertMerge,
135
+ horzMerge: 1
76
136
  });
77
- blocks = cell.blocks;
78
- }
79
- paragraphs.push(...cellParagraphs(blocks));
80
- cellsToWrite.push(isContinuation ? { vertMerge } : {
81
- vertMerge,
82
- borders: cell.borders,
83
- background: cell.background
137
+ column += subSpan;
138
+ const rightBoundary = boundaries[column];
139
+ if (rightBoundary === void 0) throw new require_errors.DocFormatError(`a table cell's own colSpan runs past the table's ${columnCount}-column grid`);
140
+ rowBoundariesTwips.push(rightBoundary);
84
141
  });
85
- column += span;
86
- const rightBoundary = boundaries[column];
87
- if (rightBoundary === void 0) throw new require_errors.DocFormatError(`a table cell's own colSpan runs past the table's ${columnCount}-column grid`);
88
- rowBoundariesTwips.push(rightBoundary);
89
142
  }
90
143
  if (column !== columnCount) throw new require_errors.DocFormatError(`a table row's own cells cover ${column} columns (via colSpan), but the table declares ${columnCount} in columnWidthsPt`);
91
144
  return {
@@ -94,27 +147,51 @@ function flattenRow(cells, columnCount, boundaries, active) {
94
147
  rowBoundariesTwips
95
148
  };
96
149
  }
97
- function flattenTable(table) {
150
+ function rowMarkParagraph(rowBoundariesTwips, cellsToWrite, heightPt) {
151
+ return {
152
+ runs: [],
153
+ properties: {},
154
+ extraGrpprl: rowMarkExtraGrpprl(rowBoundariesTwips, cellsToWrite, heightPt),
155
+ terminator: 7
156
+ };
157
+ }
158
+ function rowSplitFits(row, columnCount, boundaries, active, candidateBoundaries, heightPt) {
159
+ const trial = flattenRow(row.cells, columnCount, boundaries, cloneActive(active), candidateBoundaries);
160
+ if (trial.cellsToWrite.length > 63) return false;
161
+ const trialGrpprl = rowMarkExtraGrpprl(trial.rowBoundariesTwips, trial.cellsToWrite, heightPt);
162
+ return require_prop_fkp_write.fitsAloneOnPapxPage(trialGrpprl);
163
+ }
164
+ function flattenTable(table, blockIndex, onWarning) {
98
165
  const columnCount = table.columnWidthsPt.length;
99
166
  if (columnCount === 0 || table.rows.length === 0) throw new require_errors.DocFormatError("a table must have at least one column and one row to write");
100
167
  const boundaries = columnBoundariesTwips(table.columnWidthsPt);
168
+ const stated = recoverableBoundaries(table.rows);
169
+ const lostBoundaries = [];
170
+ for (let index = 1; index < columnCount; index += 1) if (!stated.has(index)) lostBoundaries.push(index);
171
+ const lostBoundariesByRow = distributeLostBoundaries(lostBoundaries, table.rows.length);
101
172
  const active = /* @__PURE__ */ new Map();
102
173
  const output = [];
103
- for (const row of table.rows) {
104
- const { paragraphs, cellsToWrite, rowBoundariesTwips } = flattenRow(row.cells, columnCount, boundaries, active);
174
+ table.rows.forEach((row, rowIndex) => {
175
+ const rowLostBoundaries = lostBoundariesByRow[rowIndex];
176
+ if (rowLostBoundaries === void 0) throw new require_errors.DocFormatError("internal defect: distributeLostBoundaries returned fewer buckets than the table has rows");
177
+ let rowLostBoundariesToApply = rowLostBoundaries;
178
+ if (rowLostBoundaries.size > 0 && !rowSplitFits(row, columnCount, boundaries, active, rowLostBoundaries, row.heightPt)) {
179
+ const ordered = Array.from(rowLostBoundaries);
180
+ let kept = ordered;
181
+ while (kept.length > 0 && !rowSplitFits(row, columnCount, boundaries, active, new Set(kept), row.heightPt)) kept = kept.slice(0, -1);
182
+ rowLostBoundariesToApply = new Set(kept);
183
+ const droppedCount = ordered.length - kept.length;
184
+ onWarning?.(kept.length === 0 ? `doc-codec: table at block ${blockIndex}, row ${rowIndex} could not state ${rowLostBoundaries.size === 1 ? "its assigned lost column boundary" : `any of its ${rowLostBoundaries.size} assigned lost column boundaries`} without exceeding a PapxInFkp record's own byte budget or the format's own 63-cell-per-row ceiling; attempting to write it unsplit instead, which narrows columnWidthsPt on read for this table exactly as this writer's own pre-#992 behaviour did -- if this row's own unsplit encoding also overflows this same budget, writeDocContent can still throw its usual DocFormatError further down this same pipeline, after any remaining rows have reported their own warnings` : `doc-codec: table at block ${blockIndex}, row ${rowIndex} could only state ${kept.length} of its ${ordered.length} assigned lost column boundaries without exceeding a PapxInFkp record's own byte budget or the format's own 63-cell-per-row ceiling; dropping the other ${droppedCount} (narrowing columnWidthsPt on read for those boundaries alone)`);
185
+ }
186
+ const { paragraphs, cellsToWrite, rowBoundariesTwips } = flattenRow(row.cells, columnCount, boundaries, active, rowLostBoundariesToApply);
105
187
  output.push(...paragraphs);
106
- output.push({
107
- runs: [],
108
- properties: {},
109
- extraGrpprl: rowMarkExtraGrpprl(rowBoundariesTwips, cellsToWrite, row.heightPt),
110
- terminator: 7
111
- });
112
- }
188
+ output.push(rowMarkParagraph(rowBoundariesTwips, cellsToWrite, row.heightPt));
189
+ });
113
190
  return output;
114
191
  }
115
- function flattenSectionBlocks(blocks) {
192
+ function flattenSectionBlocks(blocks, onWarning) {
116
193
  const output = [];
117
- for (const block of blocks) {
194
+ blocks.forEach((block, blockIndex) => {
118
195
  if (block.kind === "paragraph") {
119
196
  output.push({
120
197
  runs: block.runs,
@@ -122,14 +199,14 @@ function flattenSectionBlocks(blocks) {
122
199
  extraGrpprl: [],
123
200
  terminator: 13
124
201
  });
125
- continue;
202
+ return;
126
203
  }
127
204
  if (block.kind === "table") {
128
- output.push(...flattenTable(block));
129
- continue;
205
+ output.push(...flattenTable(block, blockIndex, onWarning));
206
+ return;
130
207
  }
131
208
  throw new require_errors.DocUnsupportedError(`doc-codec's writer does not yet support '${block.kind}' blocks (see README's scope note)`);
132
- }
209
+ });
133
210
  return output;
134
211
  }
135
212
  //#endregion
@@ -1,13 +1,2 @@
1
- import { ContentBlock, ContentParagraph, ContentRun } from "document-schema.js";
2
- //#region src/table/write.d.ts
3
- interface WriteParagraph {
4
- readonly runs: readonly ContentRun[];
5
- readonly properties: Pick<ContentParagraph, "alignment" | "indentLeftPt" | "indentFirstLinePt" | "spacingBeforePt" | "spacingAfterPt" | "lineSpacing" | "pageBreakBefore">;
6
- /** Extra grpprl bytes appended after encodeParagraphGrpprl's own output -- sprmPFInTable on every table paragraph, plus sprmPFTtp and the row's own TAP on a row's trailing mark. */
7
- readonly extraGrpprl: readonly number[];
8
- /** The character terminating this paragraph in the text stream: PARAGRAPH_MARK normally, CELL_MARK for a table cell or row mark. */
9
- readonly terminator: number;
10
- }
11
- declare function flattenSectionBlocks(blocks: readonly ContentBlock[]): WriteParagraph[];
12
- //#endregion
13
- export { WriteParagraph, flattenSectionBlocks };
1
+ import { n as WriteWarning, r as flattenSectionBlocks, t as WriteParagraph } from "../write-C_vJizAM.cjs";
2
+ export { WriteParagraph, WriteWarning, flattenSectionBlocks };
@@ -1,13 +1,2 @@
1
- import { ContentBlock, ContentParagraph, ContentRun } from "document-schema.js";
2
- //#region src/table/write.d.ts
3
- interface WriteParagraph {
4
- readonly runs: readonly ContentRun[];
5
- readonly properties: Pick<ContentParagraph, "alignment" | "indentLeftPt" | "indentFirstLinePt" | "spacingBeforePt" | "spacingAfterPt" | "lineSpacing" | "pageBreakBefore">;
6
- /** Extra grpprl bytes appended after encodeParagraphGrpprl's own output -- sprmPFInTable on every table paragraph, plus sprmPFTtp and the row's own TAP on a row's trailing mark. */
7
- readonly extraGrpprl: readonly number[];
8
- /** The character terminating this paragraph in the text stream: PARAGRAPH_MARK normally, CELL_MARK for a table cell or row mark. */
9
- readonly terminator: number;
10
- }
11
- declare function flattenSectionBlocks(blocks: readonly ContentBlock[]): WriteParagraph[];
12
- //#endregion
13
- export { WriteParagraph, flattenSectionBlocks };
1
+ import { n as WriteWarning, r as flattenSectionBlocks, t as WriteParagraph } from "../write-C_vJizAM.js";
2
+ export { WriteParagraph, WriteWarning, flattenSectionBlocks };