doc-codec 1.0.2 → 1.1.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.
Files changed (112) hide show
  1. package/README.md +192 -50
  2. package/dist/color.cjs +166 -0
  3. package/dist/color.d.cts +20 -0
  4. package/dist/color.d.ts +20 -0
  5. package/dist/color.js +160 -0
  6. package/dist/detect.cjs +3 -0
  7. package/dist/detect.d.cts +3 -1
  8. package/dist/detect.d.ts +3 -1
  9. package/dist/detect.js +3 -1
  10. package/dist/fib/fib.cjs +9 -1
  11. package/dist/fib/fib.d.cts +1 -28
  12. package/dist/fib/fib.d.ts +1 -28
  13. package/dist/fib/fib.js +9 -1
  14. package/dist/fib/offsets.cjs +9 -1
  15. package/dist/fib/offsets.d.cts +8 -0
  16. package/dist/fib/offsets.d.ts +8 -0
  17. package/dist/fib/offsets.js +9 -1
  18. package/dist/fib/write.cjs +43 -0
  19. package/dist/fib/write.d.cts +21 -0
  20. package/dist/fib/write.d.ts +21 -0
  21. package/dist/fib/write.js +42 -0
  22. package/dist/fib-CtsLtF96.d.cts +37 -0
  23. package/dist/fib-CtsLtF96.d.ts +37 -0
  24. package/dist/index.cjs +31 -0
  25. package/dist/index.d.cts +18 -7
  26. package/dist/index.d.ts +18 -7
  27. package/dist/index.js +14 -3
  28. package/dist/list/numbering.cjs +206 -0
  29. package/dist/list/numbering.d.cts +2 -0
  30. package/dist/list/numbering.d.ts +2 -0
  31. package/dist/list/numbering.js +205 -0
  32. package/dist/metadata.cjs +14 -0
  33. package/dist/metadata.d.cts +6 -0
  34. package/dist/metadata.d.ts +6 -0
  35. package/dist/metadata.js +13 -0
  36. package/dist/numbering-Cir-uGdc.d.cts +22 -0
  37. package/dist/numbering-DF1z1PyG.d.ts +22 -0
  38. package/dist/pap-BkFo8dZO.d.ts +29 -0
  39. package/dist/pap-BmelEYEP.d.cts +29 -0
  40. package/dist/prop/chp-write.cjs +47 -0
  41. package/dist/prop/chp-write.d.cts +5 -0
  42. package/dist/prop/chp-write.d.ts +5 -0
  43. package/dist/prop/chp-write.js +46 -0
  44. package/dist/prop/chp.cjs +15 -107
  45. package/dist/prop/chp.d.cts +3 -2
  46. package/dist/prop/chp.d.ts +3 -2
  47. package/dist/prop/chp.js +15 -107
  48. package/dist/prop/fkp-write.cjs +139 -0
  49. package/dist/prop/fkp-write.d.cts +21 -0
  50. package/dist/prop/fkp-write.d.ts +21 -0
  51. package/dist/prop/fkp-write.js +135 -0
  52. package/dist/prop/pap-write.cjs +67 -0
  53. package/dist/prop/pap-write.d.cts +5 -0
  54. package/dist/prop/pap-write.d.ts +5 -0
  55. package/dist/prop/pap-write.js +66 -0
  56. package/dist/prop/pap.cjs +10 -0
  57. package/dist/prop/pap.d.cts +1 -24
  58. package/dist/prop/pap.d.ts +1 -24
  59. package/dist/prop/pap.js +11 -1
  60. package/dist/prop/sep-write.cjs +63 -0
  61. package/dist/prop/sep-write.d.cts +9 -0
  62. package/dist/prop/sep-write.d.ts +9 -0
  63. package/dist/prop/sep-write.js +60 -0
  64. package/dist/prop/sep.cjs +63 -0
  65. package/dist/prop/sep.d.cts +16 -0
  66. package/dist/prop/sep.d.ts +16 -0
  67. package/dist/prop/sep.js +61 -0
  68. package/dist/prop/sprm.d.cts +1 -25
  69. package/dist/prop/sprm.d.ts +1 -25
  70. package/dist/read.cjs +44 -18
  71. package/dist/read.d.cts +21 -4
  72. package/dist/read.d.ts +21 -4
  73. package/dist/read.js +46 -20
  74. package/dist/sprm-Djg5RNiq.d.cts +26 -0
  75. package/dist/sprm-Djg5RNiq.d.ts +26 -0
  76. package/dist/style/fonts.cjs +68 -0
  77. package/dist/style/fonts.d.cts +5 -0
  78. package/dist/style/fonts.d.ts +5 -0
  79. package/dist/style/fonts.js +66 -0
  80. package/dist/style/stsh.cjs +23 -0
  81. package/dist/style/stsh.d.cts +2 -1
  82. package/dist/style/stsh.d.ts +2 -1
  83. package/dist/style/stsh.js +23 -1
  84. package/dist/table/decoration.cjs +220 -0
  85. package/dist/table/decoration.d.cts +41 -0
  86. package/dist/table/decoration.d.ts +41 -0
  87. package/dist/table/decoration.js +205 -0
  88. package/dist/table/read.cjs +192 -0
  89. package/dist/table/read.d.cts +6 -0
  90. package/dist/table/read.d.ts +6 -0
  91. package/dist/table/read.js +191 -0
  92. package/dist/table/tap-write.cjs +125 -0
  93. package/dist/table/tap-write.d.cts +13 -0
  94. package/dist/table/tap-write.d.ts +13 -0
  95. package/dist/table/tap-write.js +124 -0
  96. package/dist/table/tap.cjs +217 -0
  97. package/dist/table/tap.d.cts +29 -0
  98. package/dist/table/tap.d.ts +29 -0
  99. package/dist/table/tap.js +213 -0
  100. package/dist/table/write.cjs +136 -0
  101. package/dist/table/write.d.cts +13 -0
  102. package/dist/table/write.d.ts +13 -0
  103. package/dist/table/write.js +135 -0
  104. package/dist/text/piece-table-write.cjs +24 -0
  105. package/dist/text/piece-table-write.d.cts +4 -0
  106. package/dist/text/piece-table-write.d.ts +4 -0
  107. package/dist/text/piece-table-write.js +23 -0
  108. package/dist/write.cjs +175 -0
  109. package/dist/write.d.cts +5 -0
  110. package/dist/write.d.ts +5 -0
  111. package/dist/write.js +174 -0
  112. package/package.json +3 -3
@@ -0,0 +1,220 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_errors = require("../errors.cjs");
3
+ const require_bytes = require("../bytes.cjs");
4
+ const require_color = require("../color.cjs");
5
+ //#region src/table/decoration.ts
6
+ /** The four sides of a cell, in the order TC80 declares them ([MS-DOC] 2.9.313: brcTop, brcLeft, brcBottom, brcRight). Used as the iteration order for both directions, so a side can never be read from one offset and written to another. */
7
+ const CELL_BORDER_SIDES = [
8
+ "top",
9
+ "left",
10
+ "bottom",
11
+ "right"
12
+ ];
13
+ /** TableBrcOperand.bordersToApply, [MS-DOC] 2.9.305: which edges one operand formats, as the bitwise OR of any subset. Only the four rectangular sides are listed -- 0x10/0x20 are the two diagonals, which ContentCellBorders has no member for and this package neither reads nor writes. */
14
+ const BORDERS_TO_APPLY = {
15
+ top: 1,
16
+ left: 2,
17
+ bottom: 4,
18
+ right: 8
19
+ };
20
+ /** Brc80's own fixed size, [MS-DOC] 2.9.17: dptLineWidth (1) + brcType (1) + ico (1) + dptSpace/fShadow/fFrame/reserved (1). */
21
+ const BRC80_SIZE = 4;
22
+ /** Brc's own fixed size, [MS-DOC] 2.9.16: cv (4) + dptLineWidth (1) + brcType (1) + dptSpace/fShadow/fFrame/fReserved (2). */
23
+ const BRC_SIZE = 8;
24
+ /** Shd's own fixed size, [MS-DOC] 2.9.247: cvFore (4) + cvBack (4) + ipat (2). */
25
+ const SHD_SIZE = 10;
26
+ /** Shd80's own fixed size, [MS-DOC] 2.9.248: one 16-bit word packing icoFore (5 bits), icoBack (5 bits) and ipat (6 bits). */
27
+ const SHD80_SIZE = 2;
28
+ /** BrcType 0x00, [MS-DOC] 2.9.22: "No border." Distinct from the Brc80MayBeNil/BrcMayBeNil all-bits-set sentinel, and the spelling a real producer (LibreOffice) uses for an undecorated cell -- both mean the same thing and both are read as no border. */
29
+ const BRC_TYPE_NONE = 0;
30
+ const BRC_TYPE_SINGLE = 1;
31
+ const BRC_TYPE_DOUBLE = 3;
32
+ const BRC_TYPE_DOTTED = 6;
33
+ const BRC_TYPE_DASHED = 7;
34
+ /**
35
+ * Every BrcType [MS-DOC] 2.9.22 defines for a cell border (0x00 through 0x1B), mapped onto the four members ContentStrokeStyle has. Values 0x02 and 0x04 are absent from the published enumeration entirely and so have no entry here; 0x00 is handled by the caller before this table is consulted.
36
+ *
37
+ * Three families collapse, each stated rather than silently folded -- the same deliberate narrowing xls-codec's own BIFF_BORDER_STYLE makes for BIFF8's dash tokens. The dash family (dotDash 0x08, dotDotDash 0x09, dashSmallGap 0x16, dashDotStroked 0x17) collapses to 'dashed', since ContentStrokeStyle names one dashed pattern rather than a vocabulary of them. Every genuinely multi-line border collapses to 'double': the triple line (0x0A), the nine thinThick/thickThin gap variants (0x0B-0x13), the double wave (0x15), the two three-dimensional borders (0x18/0x19), and outset/inset (0x1A/0x1B, which Brc80 forbids but Brc permits). And the single wavy line (0x14) collapses to 'solid', being one continuous stroke.
38
+ *
39
+ * Values from 0x40 to 0xE3 are the art/image page borders, which [MS-DOC] 2.9.22 permits only "if they describe a page border" -- never a cell border -- so they have no entry and read as no border at all rather than as an invented approximation of a picture. 0xFF ("This MUST be ignored") is likewise absent.
40
+ */
41
+ const BRC_TYPE_STYLE = {
42
+ [BRC_TYPE_SINGLE]: "solid",
43
+ [BRC_TYPE_DOUBLE]: "double",
44
+ 5: "solid",
45
+ [BRC_TYPE_DOTTED]: "dotted",
46
+ [BRC_TYPE_DASHED]: "dashed",
47
+ 8: "dashed",
48
+ 9: "dashed",
49
+ 10: "double",
50
+ 11: "double",
51
+ 12: "double",
52
+ 13: "double",
53
+ 14: "double",
54
+ 15: "double",
55
+ 16: "double",
56
+ 17: "double",
57
+ 18: "double",
58
+ 19: "double",
59
+ 20: "solid",
60
+ 21: "double",
61
+ 22: "dashed",
62
+ 23: "dashed",
63
+ 24: "double",
64
+ 25: "double",
65
+ 26: "double",
66
+ 27: "double"
67
+ };
68
+ /** The inverse of BRC_TYPE_STYLE: the one BrcType each ContentStrokeStyle member is written as. Every collapsed family above writes back as its own family's plainest member, since that is the only one the collapsed value can honestly claim to be. */
69
+ const STYLE_BRC_TYPE = {
70
+ solid: BRC_TYPE_SINGLE,
71
+ dashed: BRC_TYPE_DASHED,
72
+ dotted: BRC_TYPE_DOTTED,
73
+ double: BRC_TYPE_DOUBLE
74
+ };
75
+ /** dptLineWidth's own unit, [MS-DOC] 2.9.16/2.9.17: "the width of the border in 1/8-point increments" for every brcType below 0x40, which is every type a cell border can carry. */
76
+ const EIGHTHS_PER_POINT = 8;
77
+ /** "Values of less than 2 are considered to be equivalent to 2" -- the specification's own floor, applied on read so a border stating 0 or 1 resolves to the width it actually renders at rather than to a widthPt ContentBorderSchema would reject for not being positive. */
78
+ const MIN_DPT_LINE_WIDTH = 2;
79
+ /** dptLineWidth is a single byte, so 255 eighths (31.875pt) is the widest border the format can state at all. */
80
+ const MAX_DPT_LINE_WIDTH = 255;
81
+ /** The colour a border with no colour of its own resolves to. [MS-DOC]'s automatic colour (Ico 0x00, or a COLORREF with fAuto set) "designates the default color for the application" and names no components, but ContentBorder.color is required, so a border stating one has to resolve to something. Black is what an automatic border renders as against a default background, and resolving to it keeps the border itself -- which genuinely exists and genuinely renders -- rather than dropping the border outright to avoid stating a colour for it. */
82
+ const AUTOMATIC_BORDER_COLOR = {
83
+ r: 0,
84
+ g: 0,
85
+ b: 0
86
+ };
87
+ function borderFrom(dptLineWidth, brcType, color) {
88
+ const style = BRC_TYPE_STYLE[brcType];
89
+ if (style === void 0) return void 0;
90
+ const border = {
91
+ color: color ?? AUTOMATIC_BORDER_COLOR,
92
+ widthPt: Math.max(dptLineWidth, MIN_DPT_LINE_WIDTH) / EIGHTHS_PER_POINT
93
+ };
94
+ if (style !== "solid") border.style = style;
95
+ return border;
96
+ }
97
+ /** Whether all `length` bytes from `offset` are 0xFF -- the all-bits-set form both nil-border spellings are defined in terms of. */
98
+ function allBitsSet(bytes, offset, length) {
99
+ for (let index = 0; index < length; index += 1) if (require_bytes.readUint8(bytes, offset + index) !== 255) return false;
100
+ return true;
101
+ }
102
+ /** One TC80 border field: a Brc80MayBeNil ([MS-DOC] 2.9.18) -- a Brc80 whose all-bits-set value "specifies that the region in question has no border". Returns undefined for that sentinel, for brcType 0x00 ("No border", the spelling LibreOffice writes instead), and for any brcType outside the cell-border range BRC_TYPE_STYLE covers. An ico outside the palette's own bound resolves through decorativeIcoColor to the automatic-colour fallback (borderFrom's own AUTOMATIC_BORDER_COLOR) rather than aborting the whole document read over one cosmetic byte -- see decorativeIcoColor's own note in color.ts. */
103
+ function readBrc80(bytes, offset) {
104
+ if (allBitsSet(bytes, offset, 4)) return void 0;
105
+ const brcType = require_bytes.readUint8(bytes, offset + 1);
106
+ if (brcType === BRC_TYPE_NONE) return void 0;
107
+ return borderFrom(require_bytes.readUint8(bytes, offset), brcType, require_color.decorativeIcoColor(require_bytes.readUint8(bytes, offset + 2)));
108
+ }
109
+ /** The eight bytes of one TableBrcOperand.brc field: a BrcMayBeNil ([MS-DOC] 2.9.20) -- "If the last four bytes are 0xFFFFFFFF, the BrcMayBeNil is a NilBrc that specifies that the table cells in question have no border", otherwise a Brc ([MS-DOC] 2.9.16) whose own cv states the colour exactly. */
110
+ function readBrc(bytes, offset) {
111
+ if (allBitsSet(bytes, offset + 4, 4)) return void 0;
112
+ const brcType = require_bytes.readUint8(bytes, offset + 5);
113
+ if (brcType === BRC_TYPE_NONE) return void 0;
114
+ return borderFrom(require_bytes.readUint8(bytes, offset + 4), brcType, require_color.readColorRef(bytes, offset));
115
+ }
116
+ /** Brc80MayBeNil's own no-border value, [MS-DOC] 2.9.18: "When all bits are set (0xFFFFFFFF when interpreted as a 4-byte unsigned integer), this structure specifies that the region in question has no border." */
117
+ const NIL_BRC80 = [
118
+ 255,
119
+ 255,
120
+ 255,
121
+ 255
122
+ ];
123
+ /** dptLineWidth for a border of `widthPt`, in the 1/8-point increments [MS-DOC] states it in. Refuses a width the single-byte field cannot hold rather than silently clamping it to a thinner border than the caller asked for, matching how every other out-of-range operand in this writer is handled. */
124
+ function dptLineWidthFor(widthPt) {
125
+ const eighths = Math.round(widthPt * EIGHTHS_PER_POINT);
126
+ if (eighths < MIN_DPT_LINE_WIDTH || eighths > MAX_DPT_LINE_WIDTH) throw new require_errors.DocFormatError(`a table cell border is ${widthPt}pt, outside the ${MIN_DPT_LINE_WIDTH / EIGHTHS_PER_POINT}..${MAX_DPT_LINE_WIDTH / EIGHTHS_PER_POINT}pt range [MS-DOC]'s own single-byte dptLineWidth can state in 1/8-point increments`);
127
+ return eighths;
128
+ }
129
+ /** One TC80 border field's own four bytes: the Brc80MayBeNil no-border sentinel for an absent border, otherwise a real Brc80 whose colour is the nearest Ico the fixed palette offers (see color.ts's nearestIco, and borderNeedsExactColor for how the exact colour still reaches the file). dptSpace, fShadow and fFrame are always zero -- ContentBorder models none of the three, so writing anything else would be inventing a fact the input never stated. */
130
+ function writeBrc80(border) {
131
+ if (border === void 0) return [...NIL_BRC80];
132
+ return [
133
+ dptLineWidthFor(border.widthPt),
134
+ STYLE_BRC_TYPE[border.style ?? "solid"],
135
+ require_color.nearestIco(border.color),
136
+ 0
137
+ ];
138
+ }
139
+ /** One TableBrcOperand.brc field's own eight bytes: a real Brc carrying the border's colour exactly, as a COLORREF rather than a palette index. Only ever called for a border that exists, since a TableBrcOperand naming no sides is never emitted at all. */
140
+ function writeBrc(border) {
141
+ return [
142
+ ...require_color.colorRefBytes(border.color),
143
+ dptLineWidthFor(border.widthPt),
144
+ STYLE_BRC_TYPE[border.style ?? "solid"],
145
+ 0,
146
+ 0
147
+ ];
148
+ }
149
+ /** Whether this border's colour survives the Ico palette Brc80 is limited to. When it does, TC80's own Brc80 already states the border exactly and the sprmTSetBrc precision layer would be pure duplication; when it does not, that layer is the only place the real colour can be stated. Compared on the written byte values rather than the floating-point components, so a colour that round-trips through colorRefBytes to the identical palette entry counts as exact. */
150
+ function borderNeedsExactColor(border) {
151
+ const palette = require_color.icoColor(require_color.nearestIco(border.color));
152
+ if (palette === void 0) return true;
153
+ const wanted = require_color.colorRefBytes(border.color);
154
+ const approximated = require_color.colorRefBytes(palette);
155
+ return wanted[0] !== approximated[0] || wanted[1] !== approximated[1] || wanted[2] !== approximated[2];
156
+ }
157
+ /** ipatAuto, [MS-DOC] 2.9.121: "Clear, ST_Shd: clear" -- the pattern under which a cell simply shows its own cvBack, which is how both Word and LibreOffice spell a flat background colour, and the only pattern this package writes. */
158
+ const IPAT_AUTO = 0;
159
+ /** ipatSolid, [MS-DOC] 2.9.121: "Solid ST_Shd: solid" -- the cell is filled entirely with cvFore. */
160
+ const IPAT_SOLID = 1;
161
+ /**
162
+ * One Shd ([MS-DOC] 2.9.247) as a flat background colour, or undefined where it states none.
163
+ *
164
+ * Only the two patterns that genuinely produce a flat fill resolve: ipatAuto, under which the cell shows cvBack (ECMA-376's own `clear` shading with a fill colour, which is what a real producer writes for a plain cell background), and ipatSolid, under which it shows cvFore. Every other Ipat -- the fourteen percentage fills, the stripe and crosshatch families, and ipatNil -- is a genuine pattern that Color cannot express, and reads as no background rather than as one of its two colours: reporting a 50% grey crosshatch as its own foreground colour would misstate what the cell actually shows. This is the same deliberate judgment xls-codec makes for BIFF8's own FillPattern enumeration, for the same reason, and it costs nothing on a round trip because this package's own writer emits ipatAuto and nothing else.
165
+ *
166
+ * A cvAuto colour under either pattern is likewise no background: it designates the application's own default, which for a cell background is "not shaded" rather than a colour to state. ShdAuto and ShdNil -- the two special values [MS-DOC] 2.9.247 names for "no shading is applied" -- both fall out of exactly that, with no separate check: each is a pair of cvAuto colours under ipatAuto.
167
+ */
168
+ function readShd(bytes, offset) {
169
+ const ipat = require_bytes.readUint16LE(bytes, offset + 8);
170
+ if (ipat === IPAT_AUTO) return require_color.readColorRef(bytes, offset + 4);
171
+ if (ipat === IPAT_SOLID) return require_color.readColorRef(bytes, offset);
172
+ }
173
+ /** One Shd's own ten bytes: cvFore left automatic and the background stated as cvBack under ipatAuto, which is exactly how LibreOffice 26.2.5.2 writes a cell fill (confirmed against its own `.doc` output: a #ffff00 cell came back as cvFore cvAuto, cvBack `ff ff 00 00`, ipat 0x0000). An absent background writes ShdAuto -- the all-automatic value [MS-DOC] 2.9.247 defines as "no shading is applied" -- so an undecorated cell inside a row that has decorated ones still states its own lack of shading rather than inheriting a neighbour's. */
174
+ function writeShd(background) {
175
+ return [
176
+ ...require_color.autoColorRefBytes(),
177
+ ...background === void 0 ? require_color.autoColorRefBytes() : require_color.colorRefBytes(background),
178
+ 0,
179
+ 0
180
+ ];
181
+ }
182
+ /** Shd80Nil, [MS-DOC] 2.9.248: icoFore 0x1F, icoBack 0x1F, ipat 0x3F -- every bit set, "specifies that no shading is applied", and explicitly exempt from the Ico and Ipat bounds the fields otherwise carry. */
183
+ const SHD80_NIL = 65535;
184
+ /** One Shd80 ([MS-DOC] 2.9.248) as a flat background colour: the same ipatAuto/ipatSolid reading readShd applies, over the Ico palette rather than COLORREFs. This is the Word 97-era spelling of cell shading, superseded by Shd but still written -- alongside it -- by a real producer, so a file carrying only this one still reads. Never written by this package, which states shading through Shd alone. icoFore/icoBack are each a 5-bit field, so a value the 17-entry palette cannot hold is a real possibility rather than a format-level impossibility; decorativeIcoColor resolves that case to no background instead of aborting the whole document read. */
185
+ function readShd80(value) {
186
+ if (value === SHD80_NIL) return void 0;
187
+ const icoFore = value & 31;
188
+ const icoBack = value >> 5 & 31;
189
+ const ipat = value >> 10 & 63;
190
+ if (ipat === IPAT_AUTO) return require_color.decorativeIcoColor(icoBack);
191
+ if (ipat === IPAT_SOLID) return require_color.decorativeIcoColor(icoFore);
192
+ }
193
+ /** A cell's four sides as a ContentCellBorders, or undefined when it has none -- the shape ContentTableCell.borders carries, with an absent side meaning that side has no border rather than an explicitly-null one. */
194
+ function cellBordersFrom(sides) {
195
+ const borders = {};
196
+ let any = false;
197
+ for (const side of CELL_BORDER_SIDES) {
198
+ const border = sides[side];
199
+ if (border === void 0) continue;
200
+ borders[side] = border;
201
+ any = true;
202
+ }
203
+ return any ? borders : void 0;
204
+ }
205
+ //#endregion
206
+ exports.BORDERS_TO_APPLY = BORDERS_TO_APPLY;
207
+ exports.BRC80_SIZE = BRC80_SIZE;
208
+ exports.BRC_SIZE = BRC_SIZE;
209
+ exports.CELL_BORDER_SIDES = CELL_BORDER_SIDES;
210
+ exports.SHD80_SIZE = SHD80_SIZE;
211
+ exports.SHD_SIZE = SHD_SIZE;
212
+ exports.borderNeedsExactColor = borderNeedsExactColor;
213
+ exports.cellBordersFrom = cellBordersFrom;
214
+ exports.readBrc = readBrc;
215
+ exports.readBrc80 = readBrc80;
216
+ exports.readShd = readShd;
217
+ exports.readShd80 = readShd80;
218
+ exports.writeBrc = writeBrc;
219
+ exports.writeBrc80 = writeBrc80;
220
+ exports.writeShd = writeShd;
@@ -0,0 +1,41 @@
1
+ import { Color, ContentBorder, ContentCellBorders } from "document-schema.js";
2
+ //#region src/table/decoration.d.ts
3
+ /** The four sides of a cell, in the order TC80 declares them ([MS-DOC] 2.9.313: brcTop, brcLeft, brcBottom, brcRight). Used as the iteration order for both directions, so a side can never be read from one offset and written to another. */
4
+ declare const CELL_BORDER_SIDES: readonly ["top", "left", "bottom", "right"];
5
+ type CellBorderSide = (typeof CELL_BORDER_SIDES)[number];
6
+ /** TableBrcOperand.bordersToApply, [MS-DOC] 2.9.305: which edges one operand formats, as the bitwise OR of any subset. Only the four rectangular sides are listed -- 0x10/0x20 are the two diagonals, which ContentCellBorders has no member for and this package neither reads nor writes. */
7
+ declare const BORDERS_TO_APPLY: Readonly<Record<CellBorderSide, number>>;
8
+ /** Brc80's own fixed size, [MS-DOC] 2.9.17: dptLineWidth (1) + brcType (1) + ico (1) + dptSpace/fShadow/fFrame/reserved (1). */
9
+ declare const BRC80_SIZE = 4;
10
+ /** Brc's own fixed size, [MS-DOC] 2.9.16: cv (4) + dptLineWidth (1) + brcType (1) + dptSpace/fShadow/fFrame/fReserved (2). */
11
+ declare const BRC_SIZE = 8;
12
+ /** Shd's own fixed size, [MS-DOC] 2.9.247: cvFore (4) + cvBack (4) + ipat (2). */
13
+ declare const SHD_SIZE = 10;
14
+ /** Shd80's own fixed size, [MS-DOC] 2.9.248: one 16-bit word packing icoFore (5 bits), icoBack (5 bits) and ipat (6 bits). */
15
+ declare const SHD80_SIZE = 2;
16
+ /** One TC80 border field: a Brc80MayBeNil ([MS-DOC] 2.9.18) -- a Brc80 whose all-bits-set value "specifies that the region in question has no border". Returns undefined for that sentinel, for brcType 0x00 ("No border", the spelling LibreOffice writes instead), and for any brcType outside the cell-border range BRC_TYPE_STYLE covers. An ico outside the palette's own bound resolves through decorativeIcoColor to the automatic-colour fallback (borderFrom's own AUTOMATIC_BORDER_COLOR) rather than aborting the whole document read over one cosmetic byte -- see decorativeIcoColor's own note in color.ts. */
17
+ declare function readBrc80(bytes: Uint8Array, offset: number): ContentBorder | undefined;
18
+ /** The eight bytes of one TableBrcOperand.brc field: a BrcMayBeNil ([MS-DOC] 2.9.20) -- "If the last four bytes are 0xFFFFFFFF, the BrcMayBeNil is a NilBrc that specifies that the table cells in question have no border", otherwise a Brc ([MS-DOC] 2.9.16) whose own cv states the colour exactly. */
19
+ declare function readBrc(bytes: Uint8Array, offset: number): ContentBorder | undefined;
20
+ /** One TC80 border field's own four bytes: the Brc80MayBeNil no-border sentinel for an absent border, otherwise a real Brc80 whose colour is the nearest Ico the fixed palette offers (see color.ts's nearestIco, and borderNeedsExactColor for how the exact colour still reaches the file). dptSpace, fShadow and fFrame are always zero -- ContentBorder models none of the three, so writing anything else would be inventing a fact the input never stated. */
21
+ declare function writeBrc80(border: ContentBorder | undefined): number[];
22
+ /** One TableBrcOperand.brc field's own eight bytes: a real Brc carrying the border's colour exactly, as a COLORREF rather than a palette index. Only ever called for a border that exists, since a TableBrcOperand naming no sides is never emitted at all. */
23
+ declare function writeBrc(border: ContentBorder): number[];
24
+ /** Whether this border's colour survives the Ico palette Brc80 is limited to. When it does, TC80's own Brc80 already states the border exactly and the sprmTSetBrc precision layer would be pure duplication; when it does not, that layer is the only place the real colour can be stated. Compared on the written byte values rather than the floating-point components, so a colour that round-trips through colorRefBytes to the identical palette entry counts as exact. */
25
+ declare function borderNeedsExactColor(border: ContentBorder): boolean;
26
+ /**
27
+ * One Shd ([MS-DOC] 2.9.247) as a flat background colour, or undefined where it states none.
28
+ *
29
+ * Only the two patterns that genuinely produce a flat fill resolve: ipatAuto, under which the cell shows cvBack (ECMA-376's own `clear` shading with a fill colour, which is what a real producer writes for a plain cell background), and ipatSolid, under which it shows cvFore. Every other Ipat -- the fourteen percentage fills, the stripe and crosshatch families, and ipatNil -- is a genuine pattern that Color cannot express, and reads as no background rather than as one of its two colours: reporting a 50% grey crosshatch as its own foreground colour would misstate what the cell actually shows. This is the same deliberate judgment xls-codec makes for BIFF8's own FillPattern enumeration, for the same reason, and it costs nothing on a round trip because this package's own writer emits ipatAuto and nothing else.
30
+ *
31
+ * A cvAuto colour under either pattern is likewise no background: it designates the application's own default, which for a cell background is "not shaded" rather than a colour to state. ShdAuto and ShdNil -- the two special values [MS-DOC] 2.9.247 names for "no shading is applied" -- both fall out of exactly that, with no separate check: each is a pair of cvAuto colours under ipatAuto.
32
+ */
33
+ declare function readShd(bytes: Uint8Array, offset: number): Color | undefined;
34
+ /** One Shd's own ten bytes: cvFore left automatic and the background stated as cvBack under ipatAuto, which is exactly how LibreOffice 26.2.5.2 writes a cell fill (confirmed against its own `.doc` output: a #ffff00 cell came back as cvFore cvAuto, cvBack `ff ff 00 00`, ipat 0x0000). An absent background writes ShdAuto -- the all-automatic value [MS-DOC] 2.9.247 defines as "no shading is applied" -- so an undecorated cell inside a row that has decorated ones still states its own lack of shading rather than inheriting a neighbour's. */
35
+ declare function writeShd(background: Color | undefined): number[];
36
+ /** One Shd80 ([MS-DOC] 2.9.248) as a flat background colour: the same ipatAuto/ipatSolid reading readShd applies, over the Ico palette rather than COLORREFs. This is the Word 97-era spelling of cell shading, superseded by Shd but still written -- alongside it -- by a real producer, so a file carrying only this one still reads. Never written by this package, which states shading through Shd alone. icoFore/icoBack are each a 5-bit field, so a value the 17-entry palette cannot hold is a real possibility rather than a format-level impossibility; decorativeIcoColor resolves that case to no background instead of aborting the whole document read. */
37
+ declare function readShd80(value: number): Color | undefined;
38
+ /** A cell's four sides as a ContentCellBorders, or undefined when it has none -- the shape ContentTableCell.borders carries, with an absent side meaning that side has no border rather than an explicitly-null one. */
39
+ declare function cellBordersFrom(sides: Readonly<Record<CellBorderSide, ContentBorder | undefined>>): ContentCellBorders | undefined;
40
+ //#endregion
41
+ export { BORDERS_TO_APPLY, BRC80_SIZE, BRC_SIZE, CELL_BORDER_SIDES, CellBorderSide, SHD80_SIZE, SHD_SIZE, borderNeedsExactColor, cellBordersFrom, readBrc, readBrc80, readShd, readShd80, writeBrc, writeBrc80, writeShd };
@@ -0,0 +1,41 @@
1
+ import { Color, ContentBorder, ContentCellBorders } from "document-schema.js";
2
+ //#region src/table/decoration.d.ts
3
+ /** The four sides of a cell, in the order TC80 declares them ([MS-DOC] 2.9.313: brcTop, brcLeft, brcBottom, brcRight). Used as the iteration order for both directions, so a side can never be read from one offset and written to another. */
4
+ declare const CELL_BORDER_SIDES: readonly ["top", "left", "bottom", "right"];
5
+ type CellBorderSide = (typeof CELL_BORDER_SIDES)[number];
6
+ /** TableBrcOperand.bordersToApply, [MS-DOC] 2.9.305: which edges one operand formats, as the bitwise OR of any subset. Only the four rectangular sides are listed -- 0x10/0x20 are the two diagonals, which ContentCellBorders has no member for and this package neither reads nor writes. */
7
+ declare const BORDERS_TO_APPLY: Readonly<Record<CellBorderSide, number>>;
8
+ /** Brc80's own fixed size, [MS-DOC] 2.9.17: dptLineWidth (1) + brcType (1) + ico (1) + dptSpace/fShadow/fFrame/reserved (1). */
9
+ declare const BRC80_SIZE = 4;
10
+ /** Brc's own fixed size, [MS-DOC] 2.9.16: cv (4) + dptLineWidth (1) + brcType (1) + dptSpace/fShadow/fFrame/fReserved (2). */
11
+ declare const BRC_SIZE = 8;
12
+ /** Shd's own fixed size, [MS-DOC] 2.9.247: cvFore (4) + cvBack (4) + ipat (2). */
13
+ declare const SHD_SIZE = 10;
14
+ /** Shd80's own fixed size, [MS-DOC] 2.9.248: one 16-bit word packing icoFore (5 bits), icoBack (5 bits) and ipat (6 bits). */
15
+ declare const SHD80_SIZE = 2;
16
+ /** One TC80 border field: a Brc80MayBeNil ([MS-DOC] 2.9.18) -- a Brc80 whose all-bits-set value "specifies that the region in question has no border". Returns undefined for that sentinel, for brcType 0x00 ("No border", the spelling LibreOffice writes instead), and for any brcType outside the cell-border range BRC_TYPE_STYLE covers. An ico outside the palette's own bound resolves through decorativeIcoColor to the automatic-colour fallback (borderFrom's own AUTOMATIC_BORDER_COLOR) rather than aborting the whole document read over one cosmetic byte -- see decorativeIcoColor's own note in color.ts. */
17
+ declare function readBrc80(bytes: Uint8Array, offset: number): ContentBorder | undefined;
18
+ /** The eight bytes of one TableBrcOperand.brc field: a BrcMayBeNil ([MS-DOC] 2.9.20) -- "If the last four bytes are 0xFFFFFFFF, the BrcMayBeNil is a NilBrc that specifies that the table cells in question have no border", otherwise a Brc ([MS-DOC] 2.9.16) whose own cv states the colour exactly. */
19
+ declare function readBrc(bytes: Uint8Array, offset: number): ContentBorder | undefined;
20
+ /** One TC80 border field's own four bytes: the Brc80MayBeNil no-border sentinel for an absent border, otherwise a real Brc80 whose colour is the nearest Ico the fixed palette offers (see color.ts's nearestIco, and borderNeedsExactColor for how the exact colour still reaches the file). dptSpace, fShadow and fFrame are always zero -- ContentBorder models none of the three, so writing anything else would be inventing a fact the input never stated. */
21
+ declare function writeBrc80(border: ContentBorder | undefined): number[];
22
+ /** One TableBrcOperand.brc field's own eight bytes: a real Brc carrying the border's colour exactly, as a COLORREF rather than a palette index. Only ever called for a border that exists, since a TableBrcOperand naming no sides is never emitted at all. */
23
+ declare function writeBrc(border: ContentBorder): number[];
24
+ /** Whether this border's colour survives the Ico palette Brc80 is limited to. When it does, TC80's own Brc80 already states the border exactly and the sprmTSetBrc precision layer would be pure duplication; when it does not, that layer is the only place the real colour can be stated. Compared on the written byte values rather than the floating-point components, so a colour that round-trips through colorRefBytes to the identical palette entry counts as exact. */
25
+ declare function borderNeedsExactColor(border: ContentBorder): boolean;
26
+ /**
27
+ * One Shd ([MS-DOC] 2.9.247) as a flat background colour, or undefined where it states none.
28
+ *
29
+ * Only the two patterns that genuinely produce a flat fill resolve: ipatAuto, under which the cell shows cvBack (ECMA-376's own `clear` shading with a fill colour, which is what a real producer writes for a plain cell background), and ipatSolid, under which it shows cvFore. Every other Ipat -- the fourteen percentage fills, the stripe and crosshatch families, and ipatNil -- is a genuine pattern that Color cannot express, and reads as no background rather than as one of its two colours: reporting a 50% grey crosshatch as its own foreground colour would misstate what the cell actually shows. This is the same deliberate judgment xls-codec makes for BIFF8's own FillPattern enumeration, for the same reason, and it costs nothing on a round trip because this package's own writer emits ipatAuto and nothing else.
30
+ *
31
+ * A cvAuto colour under either pattern is likewise no background: it designates the application's own default, which for a cell background is "not shaded" rather than a colour to state. ShdAuto and ShdNil -- the two special values [MS-DOC] 2.9.247 names for "no shading is applied" -- both fall out of exactly that, with no separate check: each is a pair of cvAuto colours under ipatAuto.
32
+ */
33
+ declare function readShd(bytes: Uint8Array, offset: number): Color | undefined;
34
+ /** One Shd's own ten bytes: cvFore left automatic and the background stated as cvBack under ipatAuto, which is exactly how LibreOffice 26.2.5.2 writes a cell fill (confirmed against its own `.doc` output: a #ffff00 cell came back as cvFore cvAuto, cvBack `ff ff 00 00`, ipat 0x0000). An absent background writes ShdAuto -- the all-automatic value [MS-DOC] 2.9.247 defines as "no shading is applied" -- so an undecorated cell inside a row that has decorated ones still states its own lack of shading rather than inheriting a neighbour's. */
35
+ declare function writeShd(background: Color | undefined): number[];
36
+ /** One Shd80 ([MS-DOC] 2.9.248) as a flat background colour: the same ipatAuto/ipatSolid reading readShd applies, over the Ico palette rather than COLORREFs. This is the Word 97-era spelling of cell shading, superseded by Shd but still written -- alongside it -- by a real producer, so a file carrying only this one still reads. Never written by this package, which states shading through Shd alone. icoFore/icoBack are each a 5-bit field, so a value the 17-entry palette cannot hold is a real possibility rather than a format-level impossibility; decorativeIcoColor resolves that case to no background instead of aborting the whole document read. */
37
+ declare function readShd80(value: number): Color | undefined;
38
+ /** A cell's four sides as a ContentCellBorders, or undefined when it has none -- the shape ContentTableCell.borders carries, with an absent side meaning that side has no border rather than an explicitly-null one. */
39
+ declare function cellBordersFrom(sides: Readonly<Record<CellBorderSide, ContentBorder | undefined>>): ContentCellBorders | undefined;
40
+ //#endregion
41
+ export { BORDERS_TO_APPLY, BRC80_SIZE, BRC_SIZE, CELL_BORDER_SIDES, CellBorderSide, SHD80_SIZE, SHD_SIZE, borderNeedsExactColor, cellBordersFrom, readBrc, readBrc80, readShd, readShd80, writeBrc, writeBrc80, writeShd };
@@ -0,0 +1,205 @@
1
+ import { DocFormatError } from "../errors.js";
2
+ import { readUint16LE, readUint8 } from "../bytes.js";
3
+ import { autoColorRefBytes, colorRefBytes, decorativeIcoColor, icoColor, nearestIco, readColorRef } from "../color.js";
4
+ //#region src/table/decoration.ts
5
+ /** The four sides of a cell, in the order TC80 declares them ([MS-DOC] 2.9.313: brcTop, brcLeft, brcBottom, brcRight). Used as the iteration order for both directions, so a side can never be read from one offset and written to another. */
6
+ const CELL_BORDER_SIDES = [
7
+ "top",
8
+ "left",
9
+ "bottom",
10
+ "right"
11
+ ];
12
+ /** TableBrcOperand.bordersToApply, [MS-DOC] 2.9.305: which edges one operand formats, as the bitwise OR of any subset. Only the four rectangular sides are listed -- 0x10/0x20 are the two diagonals, which ContentCellBorders has no member for and this package neither reads nor writes. */
13
+ const BORDERS_TO_APPLY = {
14
+ top: 1,
15
+ left: 2,
16
+ bottom: 4,
17
+ right: 8
18
+ };
19
+ /** Brc80's own fixed size, [MS-DOC] 2.9.17: dptLineWidth (1) + brcType (1) + ico (1) + dptSpace/fShadow/fFrame/reserved (1). */
20
+ const BRC80_SIZE = 4;
21
+ /** Brc's own fixed size, [MS-DOC] 2.9.16: cv (4) + dptLineWidth (1) + brcType (1) + dptSpace/fShadow/fFrame/fReserved (2). */
22
+ const BRC_SIZE = 8;
23
+ /** Shd's own fixed size, [MS-DOC] 2.9.247: cvFore (4) + cvBack (4) + ipat (2). */
24
+ const SHD_SIZE = 10;
25
+ /** Shd80's own fixed size, [MS-DOC] 2.9.248: one 16-bit word packing icoFore (5 bits), icoBack (5 bits) and ipat (6 bits). */
26
+ const SHD80_SIZE = 2;
27
+ /** BrcType 0x00, [MS-DOC] 2.9.22: "No border." Distinct from the Brc80MayBeNil/BrcMayBeNil all-bits-set sentinel, and the spelling a real producer (LibreOffice) uses for an undecorated cell -- both mean the same thing and both are read as no border. */
28
+ const BRC_TYPE_NONE = 0;
29
+ const BRC_TYPE_SINGLE = 1;
30
+ const BRC_TYPE_DOUBLE = 3;
31
+ const BRC_TYPE_DOTTED = 6;
32
+ const BRC_TYPE_DASHED = 7;
33
+ /**
34
+ * Every BrcType [MS-DOC] 2.9.22 defines for a cell border (0x00 through 0x1B), mapped onto the four members ContentStrokeStyle has. Values 0x02 and 0x04 are absent from the published enumeration entirely and so have no entry here; 0x00 is handled by the caller before this table is consulted.
35
+ *
36
+ * Three families collapse, each stated rather than silently folded -- the same deliberate narrowing xls-codec's own BIFF_BORDER_STYLE makes for BIFF8's dash tokens. The dash family (dotDash 0x08, dotDotDash 0x09, dashSmallGap 0x16, dashDotStroked 0x17) collapses to 'dashed', since ContentStrokeStyle names one dashed pattern rather than a vocabulary of them. Every genuinely multi-line border collapses to 'double': the triple line (0x0A), the nine thinThick/thickThin gap variants (0x0B-0x13), the double wave (0x15), the two three-dimensional borders (0x18/0x19), and outset/inset (0x1A/0x1B, which Brc80 forbids but Brc permits). And the single wavy line (0x14) collapses to 'solid', being one continuous stroke.
37
+ *
38
+ * Values from 0x40 to 0xE3 are the art/image page borders, which [MS-DOC] 2.9.22 permits only "if they describe a page border" -- never a cell border -- so they have no entry and read as no border at all rather than as an invented approximation of a picture. 0xFF ("This MUST be ignored") is likewise absent.
39
+ */
40
+ const BRC_TYPE_STYLE = {
41
+ [BRC_TYPE_SINGLE]: "solid",
42
+ [BRC_TYPE_DOUBLE]: "double",
43
+ 5: "solid",
44
+ [BRC_TYPE_DOTTED]: "dotted",
45
+ [BRC_TYPE_DASHED]: "dashed",
46
+ 8: "dashed",
47
+ 9: "dashed",
48
+ 10: "double",
49
+ 11: "double",
50
+ 12: "double",
51
+ 13: "double",
52
+ 14: "double",
53
+ 15: "double",
54
+ 16: "double",
55
+ 17: "double",
56
+ 18: "double",
57
+ 19: "double",
58
+ 20: "solid",
59
+ 21: "double",
60
+ 22: "dashed",
61
+ 23: "dashed",
62
+ 24: "double",
63
+ 25: "double",
64
+ 26: "double",
65
+ 27: "double"
66
+ };
67
+ /** The inverse of BRC_TYPE_STYLE: the one BrcType each ContentStrokeStyle member is written as. Every collapsed family above writes back as its own family's plainest member, since that is the only one the collapsed value can honestly claim to be. */
68
+ const STYLE_BRC_TYPE = {
69
+ solid: BRC_TYPE_SINGLE,
70
+ dashed: BRC_TYPE_DASHED,
71
+ dotted: BRC_TYPE_DOTTED,
72
+ double: BRC_TYPE_DOUBLE
73
+ };
74
+ /** dptLineWidth's own unit, [MS-DOC] 2.9.16/2.9.17: "the width of the border in 1/8-point increments" for every brcType below 0x40, which is every type a cell border can carry. */
75
+ const EIGHTHS_PER_POINT = 8;
76
+ /** "Values of less than 2 are considered to be equivalent to 2" -- the specification's own floor, applied on read so a border stating 0 or 1 resolves to the width it actually renders at rather than to a widthPt ContentBorderSchema would reject for not being positive. */
77
+ const MIN_DPT_LINE_WIDTH = 2;
78
+ /** dptLineWidth is a single byte, so 255 eighths (31.875pt) is the widest border the format can state at all. */
79
+ const MAX_DPT_LINE_WIDTH = 255;
80
+ /** The colour a border with no colour of its own resolves to. [MS-DOC]'s automatic colour (Ico 0x00, or a COLORREF with fAuto set) "designates the default color for the application" and names no components, but ContentBorder.color is required, so a border stating one has to resolve to something. Black is what an automatic border renders as against a default background, and resolving to it keeps the border itself -- which genuinely exists and genuinely renders -- rather than dropping the border outright to avoid stating a colour for it. */
81
+ const AUTOMATIC_BORDER_COLOR = {
82
+ r: 0,
83
+ g: 0,
84
+ b: 0
85
+ };
86
+ function borderFrom(dptLineWidth, brcType, color) {
87
+ const style = BRC_TYPE_STYLE[brcType];
88
+ if (style === void 0) return void 0;
89
+ const border = {
90
+ color: color ?? AUTOMATIC_BORDER_COLOR,
91
+ widthPt: Math.max(dptLineWidth, MIN_DPT_LINE_WIDTH) / EIGHTHS_PER_POINT
92
+ };
93
+ if (style !== "solid") border.style = style;
94
+ return border;
95
+ }
96
+ /** Whether all `length` bytes from `offset` are 0xFF -- the all-bits-set form both nil-border spellings are defined in terms of. */
97
+ function allBitsSet(bytes, offset, length) {
98
+ for (let index = 0; index < length; index += 1) if (readUint8(bytes, offset + index) !== 255) return false;
99
+ return true;
100
+ }
101
+ /** One TC80 border field: a Brc80MayBeNil ([MS-DOC] 2.9.18) -- a Brc80 whose all-bits-set value "specifies that the region in question has no border". Returns undefined for that sentinel, for brcType 0x00 ("No border", the spelling LibreOffice writes instead), and for any brcType outside the cell-border range BRC_TYPE_STYLE covers. An ico outside the palette's own bound resolves through decorativeIcoColor to the automatic-colour fallback (borderFrom's own AUTOMATIC_BORDER_COLOR) rather than aborting the whole document read over one cosmetic byte -- see decorativeIcoColor's own note in color.ts. */
102
+ function readBrc80(bytes, offset) {
103
+ if (allBitsSet(bytes, offset, 4)) return void 0;
104
+ const brcType = readUint8(bytes, offset + 1);
105
+ if (brcType === BRC_TYPE_NONE) return void 0;
106
+ return borderFrom(readUint8(bytes, offset), brcType, decorativeIcoColor(readUint8(bytes, offset + 2)));
107
+ }
108
+ /** The eight bytes of one TableBrcOperand.brc field: a BrcMayBeNil ([MS-DOC] 2.9.20) -- "If the last four bytes are 0xFFFFFFFF, the BrcMayBeNil is a NilBrc that specifies that the table cells in question have no border", otherwise a Brc ([MS-DOC] 2.9.16) whose own cv states the colour exactly. */
109
+ function readBrc(bytes, offset) {
110
+ if (allBitsSet(bytes, offset + 4, 4)) return void 0;
111
+ const brcType = readUint8(bytes, offset + 5);
112
+ if (brcType === BRC_TYPE_NONE) return void 0;
113
+ return borderFrom(readUint8(bytes, offset + 4), brcType, readColorRef(bytes, offset));
114
+ }
115
+ /** Brc80MayBeNil's own no-border value, [MS-DOC] 2.9.18: "When all bits are set (0xFFFFFFFF when interpreted as a 4-byte unsigned integer), this structure specifies that the region in question has no border." */
116
+ const NIL_BRC80 = [
117
+ 255,
118
+ 255,
119
+ 255,
120
+ 255
121
+ ];
122
+ /** dptLineWidth for a border of `widthPt`, in the 1/8-point increments [MS-DOC] states it in. Refuses a width the single-byte field cannot hold rather than silently clamping it to a thinner border than the caller asked for, matching how every other out-of-range operand in this writer is handled. */
123
+ function dptLineWidthFor(widthPt) {
124
+ const eighths = Math.round(widthPt * EIGHTHS_PER_POINT);
125
+ if (eighths < MIN_DPT_LINE_WIDTH || eighths > MAX_DPT_LINE_WIDTH) throw new DocFormatError(`a table cell border is ${widthPt}pt, outside the ${MIN_DPT_LINE_WIDTH / EIGHTHS_PER_POINT}..${MAX_DPT_LINE_WIDTH / EIGHTHS_PER_POINT}pt range [MS-DOC]'s own single-byte dptLineWidth can state in 1/8-point increments`);
126
+ return eighths;
127
+ }
128
+ /** One TC80 border field's own four bytes: the Brc80MayBeNil no-border sentinel for an absent border, otherwise a real Brc80 whose colour is the nearest Ico the fixed palette offers (see color.ts's nearestIco, and borderNeedsExactColor for how the exact colour still reaches the file). dptSpace, fShadow and fFrame are always zero -- ContentBorder models none of the three, so writing anything else would be inventing a fact the input never stated. */
129
+ function writeBrc80(border) {
130
+ if (border === void 0) return [...NIL_BRC80];
131
+ return [
132
+ dptLineWidthFor(border.widthPt),
133
+ STYLE_BRC_TYPE[border.style ?? "solid"],
134
+ nearestIco(border.color),
135
+ 0
136
+ ];
137
+ }
138
+ /** One TableBrcOperand.brc field's own eight bytes: a real Brc carrying the border's colour exactly, as a COLORREF rather than a palette index. Only ever called for a border that exists, since a TableBrcOperand naming no sides is never emitted at all. */
139
+ function writeBrc(border) {
140
+ return [
141
+ ...colorRefBytes(border.color),
142
+ dptLineWidthFor(border.widthPt),
143
+ STYLE_BRC_TYPE[border.style ?? "solid"],
144
+ 0,
145
+ 0
146
+ ];
147
+ }
148
+ /** Whether this border's colour survives the Ico palette Brc80 is limited to. When it does, TC80's own Brc80 already states the border exactly and the sprmTSetBrc precision layer would be pure duplication; when it does not, that layer is the only place the real colour can be stated. Compared on the written byte values rather than the floating-point components, so a colour that round-trips through colorRefBytes to the identical palette entry counts as exact. */
149
+ function borderNeedsExactColor(border) {
150
+ const palette = icoColor(nearestIco(border.color));
151
+ if (palette === void 0) return true;
152
+ const wanted = colorRefBytes(border.color);
153
+ const approximated = colorRefBytes(palette);
154
+ return wanted[0] !== approximated[0] || wanted[1] !== approximated[1] || wanted[2] !== approximated[2];
155
+ }
156
+ /** ipatAuto, [MS-DOC] 2.9.121: "Clear, ST_Shd: clear" -- the pattern under which a cell simply shows its own cvBack, which is how both Word and LibreOffice spell a flat background colour, and the only pattern this package writes. */
157
+ const IPAT_AUTO = 0;
158
+ /** ipatSolid, [MS-DOC] 2.9.121: "Solid ST_Shd: solid" -- the cell is filled entirely with cvFore. */
159
+ const IPAT_SOLID = 1;
160
+ /**
161
+ * One Shd ([MS-DOC] 2.9.247) as a flat background colour, or undefined where it states none.
162
+ *
163
+ * Only the two patterns that genuinely produce a flat fill resolve: ipatAuto, under which the cell shows cvBack (ECMA-376's own `clear` shading with a fill colour, which is what a real producer writes for a plain cell background), and ipatSolid, under which it shows cvFore. Every other Ipat -- the fourteen percentage fills, the stripe and crosshatch families, and ipatNil -- is a genuine pattern that Color cannot express, and reads as no background rather than as one of its two colours: reporting a 50% grey crosshatch as its own foreground colour would misstate what the cell actually shows. This is the same deliberate judgment xls-codec makes for BIFF8's own FillPattern enumeration, for the same reason, and it costs nothing on a round trip because this package's own writer emits ipatAuto and nothing else.
164
+ *
165
+ * A cvAuto colour under either pattern is likewise no background: it designates the application's own default, which for a cell background is "not shaded" rather than a colour to state. ShdAuto and ShdNil -- the two special values [MS-DOC] 2.9.247 names for "no shading is applied" -- both fall out of exactly that, with no separate check: each is a pair of cvAuto colours under ipatAuto.
166
+ */
167
+ function readShd(bytes, offset) {
168
+ const ipat = readUint16LE(bytes, offset + 8);
169
+ if (ipat === IPAT_AUTO) return readColorRef(bytes, offset + 4);
170
+ if (ipat === IPAT_SOLID) return readColorRef(bytes, offset);
171
+ }
172
+ /** One Shd's own ten bytes: cvFore left automatic and the background stated as cvBack under ipatAuto, which is exactly how LibreOffice 26.2.5.2 writes a cell fill (confirmed against its own `.doc` output: a #ffff00 cell came back as cvFore cvAuto, cvBack `ff ff 00 00`, ipat 0x0000). An absent background writes ShdAuto -- the all-automatic value [MS-DOC] 2.9.247 defines as "no shading is applied" -- so an undecorated cell inside a row that has decorated ones still states its own lack of shading rather than inheriting a neighbour's. */
173
+ function writeShd(background) {
174
+ return [
175
+ ...autoColorRefBytes(),
176
+ ...background === void 0 ? autoColorRefBytes() : colorRefBytes(background),
177
+ 0,
178
+ 0
179
+ ];
180
+ }
181
+ /** Shd80Nil, [MS-DOC] 2.9.248: icoFore 0x1F, icoBack 0x1F, ipat 0x3F -- every bit set, "specifies that no shading is applied", and explicitly exempt from the Ico and Ipat bounds the fields otherwise carry. */
182
+ const SHD80_NIL = 65535;
183
+ /** One Shd80 ([MS-DOC] 2.9.248) as a flat background colour: the same ipatAuto/ipatSolid reading readShd applies, over the Ico palette rather than COLORREFs. This is the Word 97-era spelling of cell shading, superseded by Shd but still written -- alongside it -- by a real producer, so a file carrying only this one still reads. Never written by this package, which states shading through Shd alone. icoFore/icoBack are each a 5-bit field, so a value the 17-entry palette cannot hold is a real possibility rather than a format-level impossibility; decorativeIcoColor resolves that case to no background instead of aborting the whole document read. */
184
+ function readShd80(value) {
185
+ if (value === SHD80_NIL) return void 0;
186
+ const icoFore = value & 31;
187
+ const icoBack = value >> 5 & 31;
188
+ const ipat = value >> 10 & 63;
189
+ if (ipat === IPAT_AUTO) return decorativeIcoColor(icoBack);
190
+ if (ipat === IPAT_SOLID) return decorativeIcoColor(icoFore);
191
+ }
192
+ /** A cell's four sides as a ContentCellBorders, or undefined when it has none -- the shape ContentTableCell.borders carries, with an absent side meaning that side has no border rather than an explicitly-null one. */
193
+ function cellBordersFrom(sides) {
194
+ const borders = {};
195
+ let any = false;
196
+ for (const side of CELL_BORDER_SIDES) {
197
+ const border = sides[side];
198
+ if (border === void 0) continue;
199
+ borders[side] = border;
200
+ any = true;
201
+ }
202
+ return any ? borders : void 0;
203
+ }
204
+ //#endregion
205
+ export { BORDERS_TO_APPLY, BRC80_SIZE, BRC_SIZE, CELL_BORDER_SIDES, SHD80_SIZE, SHD_SIZE, borderNeedsExactColor, cellBordersFrom, readBrc, readBrc80, readShd, readShd80, writeBrc, writeBrc80, writeShd };