@docx4j/core-ts 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/NOTICE +4 -0
  2. package/README.md +34 -3
  3. package/dist/html/convert.mjs +40 -15
  4. package/dist/html/elements.mjs +2 -1
  5. package/dist/model/color/ColorMath.d.mts +29 -0
  6. package/dist/model/color/ColorMath.mjs +146 -0
  7. package/dist/model/color/ColorTables.d.mts +6 -0
  8. package/dist/model/color/ColorTables.mjs +83 -0
  9. package/dist/model/color/DrawingMLColors.d.mts +52 -0
  10. package/dist/model/color/DrawingMLColors.mjs +374 -0
  11. package/dist/model/color/ResolvedColor.d.mts +36 -0
  12. package/dist/model/color/ResolvedColor.mjs +65 -0
  13. package/dist/model/color/SpreadsheetColors.d.mts +36 -0
  14. package/dist/model/color/SpreadsheetColors.mjs +84 -0
  15. package/dist/model/color/StyleFill.d.mts +28 -0
  16. package/dist/model/color/StyleFill.mjs +21 -0
  17. package/dist/model/color/ThemeColors.d.mts +86 -0
  18. package/dist/model/color/ThemeColors.mjs +304 -0
  19. package/dist/model/color/WordColors.d.mts +71 -0
  20. package/dist/model/color/WordColors.mjs +121 -0
  21. package/dist/model/color/index.d.mts +6 -0
  22. package/dist/model/color/index.mjs +30 -0
  23. package/dist/model/content/Body.d.mts +153 -19
  24. package/dist/model/content/Body.mjs +570 -131
  25. package/dist/model/content/Comment.d.mts +21 -3
  26. package/dist/model/content/Comment.mjs +202 -19
  27. package/dist/model/content/ContentControl.d.mts +197 -18
  28. package/dist/model/content/ContentControl.mjs +664 -95
  29. package/dist/model/content/Font.d.mts +67 -17
  30. package/dist/model/content/Font.mjs +114 -35
  31. package/dist/model/content/InlinePicture.d.mts +82 -7
  32. package/dist/model/content/InlinePicture.mjs +221 -28
  33. package/dist/model/content/List.mjs +13 -1
  34. package/dist/model/content/Paragraph.d.mts +213 -14
  35. package/dist/model/content/Paragraph.mjs +936 -100
  36. package/dist/model/content/Range.d.mts +242 -11
  37. package/dist/model/content/Range.mjs +975 -119
  38. package/dist/model/content/ResolvedView.d.mts +104 -0
  39. package/dist/model/content/ResolvedView.mjs +596 -0
  40. package/dist/model/content/Table.d.mts +29 -3
  41. package/dist/model/content/Table.mjs +109 -33
  42. package/dist/model/content/TrackedChange.d.mts +301 -16
  43. package/dist/model/content/TrackedChange.mjs +1284 -158
  44. package/dist/model/content/binder.d.mts +2 -1
  45. package/dist/model/content/binder.mjs +48 -1
  46. package/dist/model/content/comments.d.mts +20 -6
  47. package/dist/model/content/comments.mjs +67 -57
  48. package/dist/model/content/generalException.d.mts +18 -0
  49. package/dist/model/content/generalException.mjs +24 -0
  50. package/dist/model/content/index.d.mts +8 -3
  51. package/dist/model/content/index.mjs +7 -2
  52. package/dist/model/content/mathText.d.mts +21 -0
  53. package/dist/model/content/mathText.mjs +267 -0
  54. package/dist/model/content/officeText.d.mts +27 -0
  55. package/dist/model/content/officeText.mjs +74 -0
  56. package/dist/model/content/outline.d.mts +5 -0
  57. package/dist/model/content/outline.mjs +13 -0
  58. package/dist/model/content/placeholders.d.mts +110 -0
  59. package/dist/model/content/placeholders.mjs +253 -0
  60. package/dist/model/content/relationships.d.mts +30 -0
  61. package/dist/model/content/relationships.mjs +133 -0
  62. package/dist/model/content/search.d.mts +72 -0
  63. package/dist/model/content/search.mjs +204 -2
  64. package/dist/model/content/spanEdits.d.mts +159 -0
  65. package/dist/model/content/spanEdits.mjs +728 -0
  66. package/dist/model/content/story.d.mts +44 -0
  67. package/dist/model/content/story.mjs +92 -0
  68. package/dist/model/content/tableColumns.d.mts +1 -1
  69. package/dist/model/content/tableColumns.mjs +108 -80
  70. package/dist/model/content/tableRevisions.d.mts +11 -3
  71. package/dist/model/content/tableRevisions.mjs +72 -42
  72. package/dist/model/content/tableRules.d.mts +30 -0
  73. package/dist/model/content/tableRules.mjs +100 -0
  74. package/dist/model/content/textBoxes.d.mts +18 -0
  75. package/dist/model/content/textBoxes.mjs +105 -0
  76. package/dist/model/content/trackedChangeFilter.d.mts +39 -0
  77. package/dist/model/content/trackedChangeFilter.mjs +81 -0
  78. package/dist/model/content/tracking.d.mts +28 -0
  79. package/dist/model/content/tracking.mjs +149 -10
  80. package/dist/model/content/tree.d.mts +212 -13
  81. package/dist/model/content/tree.mjs +356 -35
  82. package/dist/model/customxml/CustomXmlPartCollection.d.mts +8 -0
  83. package/dist/model/customxml/CustomXmlPartCollection.mjs +34 -11
  84. package/dist/model/customxml/XmlMapping.d.mts +1 -1
  85. package/dist/model/customxml/XmlMapping.mjs +11 -0
  86. package/dist/model/customxml/bindings.d.mts +21 -2
  87. package/dist/model/customxml/bindings.mjs +76 -32
  88. package/dist/model/customxml/kinds.d.mts +8 -3
  89. package/dist/model/customxml/kinds.mjs +26 -5
  90. package/dist/model/customxml/picturePlaceholder.d.mts +17 -0
  91. package/dist/model/customxml/picturePlaceholder.mjs +32 -0
  92. package/dist/model/customxml/pictures.d.mts +9 -5
  93. package/dist/model/customxml/pictures.mjs +31 -13
  94. package/dist/model/customxml/xhtml.mjs +8 -2
  95. package/dist/model/fonts/families.generated.mjs +2 -2
  96. package/dist/model/fonts/registry.mjs +7 -1
  97. package/dist/model/fonts/substitutions.generated.mjs +2 -2
  98. package/dist/model/fonts/themes.generated.mjs +1 -1
  99. package/dist/model/index.d.mts +2 -0
  100. package/dist/model/index.mjs +4 -0
  101. package/dist/model/listnumbering/Emulator.d.mts +37 -5
  102. package/dist/model/listnumbering/Emulator.mjs +63 -9
  103. package/dist/model/listnumbering/definitions.mjs +18 -0
  104. package/dist/model/properties/PropertyResolver.d.mts +87 -7
  105. package/dist/model/properties/PropertyResolver.mjs +197 -17
  106. package/dist/model/properties/catalogue.d.mts +32 -4
  107. package/dist/model/properties/catalogue.mjs +115 -36
  108. package/dist/model/properties/log.d.mts +1 -1
  109. package/dist/model/properties/log.mjs +5 -5
  110. package/dist/model/properties/table.d.mts +16 -2
  111. package/dist/model/properties/table.mjs +17 -5
  112. package/dist/model/text/Extractor.d.mts +289 -0
  113. package/dist/model/text/Extractor.mjs +2162 -0
  114. package/dist/model/text/LinkTargets.d.mts +45 -0
  115. package/dist/model/text/LinkTargets.mjs +170 -0
  116. package/dist/model/text/MarkdownExporter.d.mts +140 -0
  117. package/dist/model/text/MarkdownExporter.mjs +255 -0
  118. package/dist/model/text/PlainText.d.mts +29 -0
  119. package/dist/model/text/PlainText.mjs +539 -0
  120. package/dist/model/text/TextContext.d.mts +161 -0
  121. package/dist/model/text/TextContext.mjs +425 -0
  122. package/dist/model/text/TextOptions.d.mts +150 -0
  123. package/dist/model/text/TextOptions.mjs +278 -0
  124. package/dist/model/text/TextResult.d.mts +504 -0
  125. package/dist/model/text/TextResult.mjs +526 -0
  126. package/dist/model/text/Texts.d.mts +99 -0
  127. package/dist/model/text/Texts.mjs +187 -0
  128. package/dist/model/text/WmlToMarkdown.d.mts +120 -0
  129. package/dist/model/text/WmlToMarkdown.mjs +911 -0
  130. package/dist/model/text/commonmark.d.mts +121 -0
  131. package/dist/model/text/commonmark.mjs +655 -0
  132. package/dist/model/text/index.d.mts +7 -0
  133. package/dist/model/text/index.mjs +8 -0
  134. package/dist/model/text/normalize.d.mts +8 -0
  135. package/dist/model/text/normalize.mjs +13 -0
  136. package/dist/model/text/outline.d.mts +4 -0
  137. package/dist/model/text/outline.mjs +71 -0
  138. package/dist/office-js/fragment.d.mts +7 -5
  139. package/dist/office-js/fragment.mjs +63 -14
  140. package/dist/office-js/proxy.d.mts +8 -0
  141. package/dist/office-js/proxy.mjs +29 -0
  142. package/dist/office-js/run.mjs +2 -0
  143. package/dist/office-js/supported.generated.mjs +16 -0
  144. package/dist/office-js/toApiScript.mjs +21 -5
  145. package/dist/opc/Load.d.mts +9 -2
  146. package/dist/opc/Save.d.mts +17 -0
  147. package/dist/opc/Save.mjs +12 -2
  148. package/dist/opc/index.d.mts +3 -3
  149. package/dist/opc/index.mjs +2 -2
  150. package/dist/opc/mce/preprocessor.d.mts +88 -7
  151. package/dist/opc/mce/preprocessor.mjs +144 -37
  152. package/dist/opc/mce/understood.d.mts +34 -0
  153. package/dist/opc/mce/understood.mjs +55 -3
  154. package/dist/packages/OpcPackage.d.mts +20 -0
  155. package/dist/packages/OpcPackage.mjs +16 -3
  156. package/dist/packages/WordprocessingMLPackage.d.mts +77 -7
  157. package/dist/packages/WordprocessingMLPackage.mjs +242 -11
  158. package/dist/parts/DefaultXmlPart.mjs +2 -2
  159. package/dist/parts/Part.d.mts +10 -2
  160. package/dist/parts/XmlPart.d.mts +35 -32
  161. package/dist/parts/XmlPart.mjs +107 -107
  162. package/dist/parts/wml/comments.mjs +30 -0
  163. package/dist/parts/wml/index.mjs +12 -0
  164. package/dist/xml/dom.d.mts +7 -0
  165. package/dist/xml/dom.mjs +18 -0
  166. package/package.json +2 -2
package/NOTICE CHANGED
@@ -10,3 +10,7 @@ resolution utilities follow docx4j's design and names. It depends on @docx4j/gen
10
10
  (https://github.com/plutext/docx4j-generated-objects-ts) for the Office Open XML object model and on
11
11
  @docx4j/jsonix (https://github.com/plutext/jsonix) for XML binding; see their NOTICE files for the
12
12
  material they derive from (ECMA-376, Microsoft Open Specifications, Jsonix).
13
+
14
+ The Markdown writer (src/model/text/commonmark.mts) is a port of the Markdown renderer of
15
+ commonmark-java (https://github.com/commonmark/commonmark-java), Copyright (c) 2015, Atlassian Pty
16
+ Ltd, under the BSD 2-Clause License; its notice and conditions are kept in that file.
package/README.md CHANGED
@@ -43,8 +43,13 @@ const ooxml = await pkg.saveFlatOpc(); // pkg:package, for insertOo
43
43
  ```
44
44
 
45
45
  A part that is never touched is written back byte for byte; `getContents()` (or a view over
46
- it) marks a part for re-marshalling. `mc:AlternateContent` is resolved when a part is
47
- unmarshalled, as Word does on open (`{ mcePreprocess: false }` to keep it). Subpaths
46
+ it) marks a part for re-marshalling. `mc:AlternateContent` is kept with every branch, as docx4j
47
+ keeps it: the content API reads the branch Word would (the first `mc:Choice` whose namespaces are
48
+ understood, else the `mc:Fallback`) at every level, and a save writes them all (`{ mcePreprocess: true }`
49
+ resolves the element when a part is unmarshalled instead, as Word does on open). Among a paragraph's runs
50
+ the branch read is edited in place and the others kept (CR-012 section 12). At block, row and cell level a
51
+ view in a branch is read-only (`NotSupportedError`), while resolving tracked changes, `replaceText`, a
52
+ tracked deletion, the column edits and the binding passes reach every branch (CR-013). Subpaths
48
53
  `@docx4j/core-ts/opc`, `/parts`, `/packages` and `/model` give the layers separately,
49
54
  `/office-js` the `Word` shim (below), `/html` HTML to WordprocessingML (a converter to an
50
55
  intermediate form and a builder from it, CR-005 section 8; the HTML parser is yours, a browser's
@@ -235,7 +240,33 @@ body.acceptAll(); // or rejectAll(), or accept()/reject() one at a time
235
240
  `getTrackedChanges()` is also on `Paragraph` and `Range`. Text and searches read the accepted
236
241
  view throughout, so an agent sees the document as it will read once the changes are taken. A moved
237
242
  paragraph is listed as its two halves, and accepting or rejecting either resolves the whole move,
238
- as Word's Review tab does.
243
+ as Word's Review tab does. `accept()` and `reject()` return whether they acted: a change already
244
+ resolved since it was listed, by its own view or another, is left alone and gives `false` (Office
245
+ JS's return nothing).
246
+
247
+ A body is one story. Every story at once - the main document, each footnote, endnote and comment,
248
+ the headers and footers in the order the sections reference them, and the text boxes in each - is
249
+ the package's, as docx4j lists it (`TrackedChanges.in`), with docx4j's kind and the part of each
250
+ change. The listing and the resolve-all verbs take a filter: docx4j's conditions as an object, which
251
+ travels as JSON, or a function over each single change. A group of touching changes is listed whole
252
+ when any of its pieces matches, and resolved only in the pieces that match:
253
+
254
+ ```ts
255
+ for (const change of await pkg.getAllTrackedChanges()) {
256
+ change.kind; // docx4j's: 'RUN_INSERTION', 'PARAGRAPH_MARK_DELETION', 'TABLE_PROPERTIES', ...
257
+ change.part.partName.name; // '/word/document.xml', '/word/footnotes.xml', '/word/header1.xml', ...
258
+ }
259
+ await pkg.rejectAllRevisions({ authors: ['Author B'] });
260
+ await pkg.acceptAllRevisions({ types: ['Deleted'], from: '2026-10-01', parts: ['/word/document.xml'] });
261
+ await pkg.acceptAllRevisions((change) => change.author === 'Claude');
262
+ body.acceptAll({ kinds: ['RUN_DELETION'] }); // one story; a deleted paragraph's text goes, its mark stays
263
+ ```
264
+
265
+ The conditions are `authors`, `types` (Office JS's names or docx4j's `ADDED`, `DELETED`,
266
+ `FORMATTED`), `kinds`, `from` (inclusive) and `to` (exclusive), and `parts` (a part or its name).
267
+ A key or a value the filter does not know throws a `TypeError`, before anything is resolved. With a
268
+ filter, what no change owns (a `w:cellMerge`, a `w:numberingChange`) is left alone; without one it is
269
+ swept, as before. Each verb returns how many listed changes had a matching piece.
239
270
 
240
271
  To record one caller's edits without touching the document's own setting - an agent working in a
241
272
  document whose `w:trackRevisions` belongs to its owner - use `withTracking`. Nothing is written
@@ -415,7 +415,8 @@ function cellFrom(el, w) {
415
415
  var _a, _b;
416
416
  const blocks = [];
417
417
  walkBlocks(el, blocks, w);
418
- if (blocks.length === 0) {
418
+ // a cell ends with a paragraph, as Word reads it, after a table too (CR-002 section 55)
419
+ if (blocks.length === 0 || blocks[blocks.length - 1].kind === 'table') {
419
420
  blocks.push({ kind: 'paragraph', style: null, inlines: [] });
420
421
  w.report.paragraphs++;
421
422
  }
@@ -454,19 +455,37 @@ function tableFrom(el, w) {
454
455
  w.report.tables++;
455
456
  return { kind: 'table', rows, style: w.tableStyle };
456
457
  }
457
- /** Block-level content in document order: paragraphs, headings, list items, tables; containers descended into. */
458
+ /** Elements a line holds: gathered with the text beside them into one paragraph when they stand under a container. */
459
+ const INLINE_TAGS = new Set(['span', 'a', 'b', 'strong', 'i', 'em', 'u', 's', 'strike', 'del', 'ins', 'sub', 'sup', 'code', 'font', 'small', 'big', 'mark', 'abbr', 'cite', 'q', 'kbd', 'var', 'samp', 'tt', 'time', 'label', 'wbr', 'br']);
460
+ /** What makes an inline element a container after all: a block inside it (`<span><p>` in Word's HTML, `<a><div>`). */
461
+ const BLOCK_INSIDE = 'p,h1,h2,h3,h4,h5,h6,li,pre,blockquote,ul,ol,dl,table,div,section,article,header,footer,nav,aside,main,figure,figcaption,hr,img';
462
+ /**
463
+ * Block-level content in document order: paragraphs, headings, list items, tables; containers descended into.
464
+ * Text and inline elements met under a container (a cell without a paragraph; ChatGPT's note after a table, a
465
+ * `span` holding a `span` per word) are one paragraph together, up to the next block or the container's end,
466
+ * as a browser lays them out: a paragraph per text node had made a paragraph of every word.
467
+ */
458
468
  function walkBlocks(parent, out, w) {
459
469
  var _a;
460
- const pending = null;
461
- void pending;
470
+ let pending = null;
471
+ const flush = () => {
472
+ if (pending === null)
473
+ return;
474
+ while (pending.length > 0 && 'br' in pending[pending.length - 1])
475
+ pending.pop();
476
+ trimEdges(pending);
477
+ if (pending.length > 0) {
478
+ out.push({ kind: 'paragraph', style: null, inlines: pending });
479
+ w.report.paragraphs++;
480
+ }
481
+ pending = null;
482
+ };
462
483
  for (const node of Array.from(parent.childNodes)) {
463
484
  if (node.nodeType === 3) {
464
- // Bare text under a container (a cell without a paragraph): its own paragraph.
465
- const text = ((_a = node.textContent) !== null && _a !== void 0 ? _a : '').replace(/[ \t\r\n]+/g, ' ').trim();
466
- if (text !== '') {
467
- out.push({ kind: 'paragraph', style: null, inlines: [{ text }] });
468
- w.report.paragraphs++;
469
- }
485
+ if (pending === null && ((_a = node.textContent) !== null && _a !== void 0 ? _a : '').trim() === '')
486
+ continue; // the markup's own line ends between blocks
487
+ pending !== null && pending !== void 0 ? pending : (pending = []);
488
+ walkInline(node, pending, {}, w, false);
470
489
  continue;
471
490
  }
472
491
  if (node.nodeType !== 1)
@@ -475,6 +494,14 @@ function walkBlocks(parent, out, w) {
475
494
  const tag = el.tagName.toLowerCase();
476
495
  if (tag === 'script' || tag === 'style' || tag === 'head' || tag === 'meta' || tag === 'link' || tag === 'title' || tag === 'o:p')
477
496
  continue;
497
+ if (INLINE_TAGS.has(tag) && el.querySelector(BLOCK_INSIDE) === null) {
498
+ if (tag === 'br' && pending === null)
499
+ continue; // a break between blocks starts no paragraph
500
+ pending !== null && pending !== void 0 ? pending : (pending = []);
501
+ walkInline(el, pending, {}, w, false);
502
+ continue;
503
+ }
504
+ flush();
478
505
  if (tag === 'p' || HEADING.test(tag) || tag === 'li' || tag === 'pre' || tag === 'blockquote' && el.querySelector('p') === null) {
479
506
  out.push(paragraphFrom(el, w));
480
507
  continue;
@@ -486,19 +513,17 @@ function walkBlocks(parent, out, w) {
486
513
  continue;
487
514
  }
488
515
  if (tag === 'table') {
489
- if (w.listStack.length === 0 || true)
490
- out.push(tableFrom(el, w));
516
+ out.push(tableFrom(el, w));
491
517
  continue;
492
518
  }
493
519
  if (tag === 'img') {
494
520
  w.drop('images');
495
521
  continue;
496
522
  }
497
- if (tag === 'br')
498
- continue;
499
- // A container (div, body, html, span at block level, blockquote with paragraphs, td outside a table): its blocks.
523
+ // A container (div, body, html, a span holding blocks, blockquote with paragraphs, td outside a table): its blocks.
500
524
  walkBlocks(el, out, w);
501
525
  }
526
+ flush();
502
527
  }
503
528
  /**
504
529
  * Where a parsed fragment's elements are: a browser's `DOMParser` wraps a bare fragment (the text
@@ -142,7 +142,8 @@ function tableXml(t, resolve) {
142
142
  w += (_a = widths[col + k]) !== null && _a !== void 0 ? _a : widths[0]; return w; };
143
143
  const tcPr = (col, span, merge) => `<w:tcPr><w:tcW w:w="${widthOf(col, span)}" w:type="dxa"/>${span > 1 ? `<w:gridSpan w:val="${span}"/>` : ''}${merge === 'restart' ? '<w:vMerge w:val="restart"/>' : merge === 'continue' ? '<w:vMerge/>' : ''}</w:tcPr>`;
144
144
  const cellXml = (c, col) => {
145
- const blocks = c.blocks.length > 0 ? blocksToXml(c.blocks, resolve) : '<w:p/>';
145
+ const last = c.blocks[c.blocks.length - 1];
146
+ const blocks = last === undefined ? '<w:p/>' : blocksToXml(c.blocks, resolve) + (last.kind === 'table' ? '<w:p/>' : '');
146
147
  return `<w:tc>${tcPr(col, c.colspan, c.rowspan > 1 ? 'restart' : undefined)}${blocks}</w:tc>`;
147
148
  };
148
149
  // A cell spanning rows holds its place in the rows below with a continuation cell, as WordprocessingML writes a vertical merge.
@@ -0,0 +1,29 @@
1
+ /** sRGB (0 to 1) to linear RGB (IEC 61966-2-1). */
2
+ export declare function toLinear(c: number): number;
3
+ /** linear RGB to sRGB, clamped to 0 to 1. */
4
+ export declare function toSrgb(l: number): number;
5
+ export declare function clamp01(v: number): number;
6
+ /** a channel, 0 to 1, as 0 to 255, rounded half up */
7
+ export declare function channel(v: number): number;
8
+ /** a channel, 0 to 1, as 0 to 255, rounded half down (Office's luminance transforms) */
9
+ export declare function channelHalfDown(v: number): number;
10
+ /** [h, s, l], each 0 to 1, from sRGB channels 0 to 1; a grey's hue is 0 */
11
+ export declare function rgbToHsl(r: number, g: number, b: number): [number, number, number];
12
+ /**
13
+ * sRGB channels (0 to 1, clamped) from HSL; the saturation is not clamped, as Office does not clamp it (a
14
+ * saturation above 1 pushes a channel past its range, which is then clamped).
15
+ */
16
+ export declare function hslToRgb(h: number, s: number, l: number): [number, number, number];
17
+ /**
18
+ * sRGB channels (0 to 1, not clamped) from HSL as Foley and van Dam give it: the same colour as {@link hslToRgb}
19
+ * but not the same rounding error, and the one Office's luminance transforms round as.
20
+ */
21
+ export declare function hslToRgbFoley(h: number, s: number, l: number): [number, number, number];
22
+ export declare const HLSMAX = 240;
23
+ /** [h, l, s] on 0 to 240, as Windows' ColorRGBToHLS (KB29240) computes them */
24
+ export declare function rgbToHls240(r: number, g: number, b: number): [number, number, number];
25
+ /**
26
+ * RGB from {@link rgbToHls240}'s values, as Windows' ColorHLSToRGB, except that a grey is rounded rather than
27
+ * truncated, which is what Excel's fills show (docx4j CR-034 section 11, P4).
28
+ */
29
+ export declare function hls240ToRgb(h: number, l: number, s: number): [number, number, number];
@@ -0,0 +1,146 @@
1
+ // The colour arithmetic Office uses, as docx4j CR-034's probes measured it (section 11): sRGB's transfer function,
2
+ // HSL with an unclamped saturation, and Windows' integer HLS for SpreadsheetML tints. docx4j
3
+ // `org.docx4j.model.color.ColorMath`, expression for expression: JS numbers are IEEE doubles as Java's are, so the
4
+ // same expressions in the same order give the same bits, which the luminance transforms' rounding depends on
5
+ // (CR-008 section 3). Java's integer division is `Math.trunc` of the quotient here; nothing is simplified.
6
+ // ------------------------------------------------------------------ sRGB and linear RGB
7
+ /** sRGB (0 to 1) to linear RGB (IEC 61966-2-1). */
8
+ export function toLinear(c) {
9
+ return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
10
+ }
11
+ /** linear RGB to sRGB, clamped to 0 to 1. */
12
+ export function toSrgb(l) {
13
+ const c = clamp01(l);
14
+ return c <= 0.0031308 ? c * 12.92 : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
15
+ }
16
+ export function clamp01(v) {
17
+ return v < 0 ? 0 : v > 1 ? 1 : v;
18
+ }
19
+ /** a channel, 0 to 1, as 0 to 255, rounded half up */
20
+ export function channel(v) {
21
+ return Math.floor(clamp01(v) * 255 + 0.5);
22
+ }
23
+ /** a channel, 0 to 1, as 0 to 255, rounded half down (Office's luminance transforms) */
24
+ export function channelHalfDown(v) {
25
+ // Math.ceil(-0.5) is -0 in JS, 0 in Java's (int) cast; ResolvedColor's clamp makes either 0
26
+ return Math.ceil(clamp01(v) * 255 - 0.5);
27
+ }
28
+ // ------------------------------------------------------------------ HSL
29
+ /** [h, s, l], each 0 to 1, from sRGB channels 0 to 1; a grey's hue is 0 */
30
+ export function rgbToHsl(r, g, b) {
31
+ const max = Math.max(r, Math.max(g, b)), min = Math.min(r, Math.min(g, b));
32
+ const l = (max + min) / 2;
33
+ if (max === min)
34
+ return [0, 0, l];
35
+ const d = max - min;
36
+ const s = l <= 0.5 ? d / (max + min) : d / (2 - max - min);
37
+ let h;
38
+ if (max === r) {
39
+ h = ((g - b) / d) % 6;
40
+ if (h < 0)
41
+ h += 6;
42
+ }
43
+ else if (max === g) {
44
+ h = (b - r) / d + 2;
45
+ }
46
+ else {
47
+ h = (r - g) / d + 4;
48
+ }
49
+ return [h / 6, s, l];
50
+ }
51
+ /**
52
+ * sRGB channels (0 to 1, clamped) from HSL; the saturation is not clamped, as Office does not clamp it (a
53
+ * saturation above 1 pushes a channel past its range, which is then clamped).
54
+ */
55
+ export function hslToRgb(h, s, l) {
56
+ return [hslChannel(0, h, s, l), hslChannel(8, h, s, l), hslChannel(4, h, s, l)];
57
+ }
58
+ /**
59
+ * sRGB channels (0 to 1, not clamped) from HSL as Foley and van Dam give it: the same colour as {@link hslToRgb}
60
+ * but not the same rounding error, and the one Office's luminance transforms round as.
61
+ */
62
+ export function hslToRgbFoley(h, s, l) {
63
+ // keep this form and its order of operations, and don't fold it into hslToRgb: a channel that is exactly half a
64
+ // unit rounds (channelHalfDown) the way this form's floating-point error leans, which is the way Office rounds it
65
+ // (docx4j CR-034 section 11, follow-up: 29 of 30 halves; hslToRgb's error leans otherwise)
66
+ const m2 = l <= 0.5 ? l * (1 + s) : l + s - l * s, m1 = 2 * l - m2;
67
+ return [foley(m1, m2, h + 1 / 3.0), foley(m1, m2, h), foley(m1, m2, h - 1 / 3.0)];
68
+ }
69
+ function foley(m1, m2, h) {
70
+ h -= Math.floor(h);
71
+ if (h < 1 / 6.0)
72
+ return m1 + (m2 - m1) * 6 * h;
73
+ if (h < 1 / 2.0)
74
+ return m2;
75
+ if (h < 2 / 3.0)
76
+ return m1 + (m2 - m1) * (2 / 3.0 - h) * 6;
77
+ return m1;
78
+ }
79
+ function hslChannel(n, h, s, l) {
80
+ let k = (n + h * 12) % 12;
81
+ if (k < 0)
82
+ k += 12;
83
+ const a = s * Math.min(l, 1 - l);
84
+ return clamp01(l - a * Math.max(-1, Math.min(Math.min(k - 3, 9 - k), 1)));
85
+ }
86
+ // ------------------------------------------------------------------ Windows' integer HLS (Excel)
87
+ export const HLSMAX = 240;
88
+ const RGBMAX = 255;
89
+ /** Java's int division. */
90
+ const div = (a, b) => Math.trunc(a / b);
91
+ /** [h, l, s] on 0 to 240, as Windows' ColorRGBToHLS (KB29240) computes them */
92
+ export function rgbToHls240(r, g, b) {
93
+ const max = Math.max(r, Math.max(g, b)), min = Math.min(r, Math.min(g, b));
94
+ const l = div((max + min) * HLSMAX + RGBMAX, 2 * RGBMAX);
95
+ if (max === min)
96
+ return [div(HLSMAX * 2, 3), l, 0];
97
+ const s = l <= div(HLSMAX, 2)
98
+ ? div((max - min) * HLSMAX + div(max + min, 2), max + min)
99
+ : div((max - min) * HLSMAX + div(2 * RGBMAX - max - min, 2), 2 * RGBMAX - max - min);
100
+ const d = max - min;
101
+ const rd = div((max - r) * div(HLSMAX, 6) + div(d, 2), d);
102
+ const gd = div((max - g) * div(HLSMAX, 6) + div(d, 2), d);
103
+ const bd = div((max - b) * div(HLSMAX, 6) + div(d, 2), d);
104
+ let h;
105
+ if (r === max)
106
+ h = bd - gd;
107
+ else if (g === max)
108
+ h = div(HLSMAX, 3) + rd - bd;
109
+ else
110
+ h = div(2 * HLSMAX, 3) + gd - rd;
111
+ if (h < 0)
112
+ h += HLSMAX;
113
+ if (h > HLSMAX)
114
+ h -= HLSMAX;
115
+ return [h, l, s];
116
+ }
117
+ /**
118
+ * RGB from {@link rgbToHls240}'s values, as Windows' ColorHLSToRGB, except that a grey is rounded rather than
119
+ * truncated, which is what Excel's fills show (docx4j CR-034 section 11, P4).
120
+ */
121
+ export function hls240ToRgb(h, l, s) {
122
+ if (s === 0) {
123
+ const v = div(l * RGBMAX + div(HLSMAX, 2), HLSMAX);
124
+ return [v, v, v];
125
+ }
126
+ const m2 = l <= div(HLSMAX, 2) ? div(l * (HLSMAX + s) + div(HLSMAX, 2), HLSMAX) : l + s - div(l * s + div(HLSMAX, 2), HLSMAX);
127
+ const m1 = 2 * l - m2;
128
+ return [
129
+ div(hueToRgb(m1, m2, h + div(HLSMAX, 3)) * RGBMAX + div(HLSMAX, 2), HLSMAX),
130
+ div(hueToRgb(m1, m2, h) * RGBMAX + div(HLSMAX, 2), HLSMAX),
131
+ div(hueToRgb(m1, m2, h - div(HLSMAX, 3)) * RGBMAX + div(HLSMAX, 2), HLSMAX)
132
+ ];
133
+ }
134
+ function hueToRgb(n1, n2, hue) {
135
+ if (hue < 0)
136
+ hue += HLSMAX;
137
+ if (hue > HLSMAX)
138
+ hue -= HLSMAX;
139
+ if (hue < div(HLSMAX, 6))
140
+ return n1 + div((n2 - n1) * hue + div(HLSMAX, 12), div(HLSMAX, 6));
141
+ if (hue < div(HLSMAX, 2))
142
+ return n2;
143
+ if (hue < div(HLSMAX * 2, 3))
144
+ return n1 + div((n2 - n1) * (div(HLSMAX * 2, 3) - hue) + div(HLSMAX, 12), div(HLSMAX, 6));
145
+ return n1;
146
+ }
@@ -0,0 +1,6 @@
1
+ /** `a:prstClr` names to RRGGBB. */
2
+ export declare const PRESET: ReadonlyMap<string, string>;
3
+ /** `a:sysClr` names to Windows' default RRGGBB. */
4
+ export declare const SYSTEM: ReadonlyMap<string, string>;
5
+ /** SpreadsheetML indexed colours 0 to 65. */
6
+ export declare const INDEXED: readonly string[];
@@ -0,0 +1,83 @@
1
+ // The fixed colour tables: DrawingML's preset colours (`a:prstClr`), Windows' default system colours (`a:sysClr`,
2
+ // which Office draws as the machine's colour, not `lastClr`: docx4j CR-034 section 11), and SpreadsheetML's legacy
3
+ // indexed palette. docx4j `org.docx4j.model.color.ColorTables`, copied entry for entry.
4
+ //
5
+ // The preset and system values are Apache POI's `org.apache.poi.sl.usermodel.PresetColor` (trunk ae62bb5116,
6
+ // Apache-2.0), keyed by the names docx4j's schema gives `ST_PresetColorVal` and `ST_SystemColorVal`. The indexed
7
+ // palette is Excel 365's, read from its PDF of CR-034's probe tc-p4-spreadsheet.xlsx (2026-10-12): entries 0 to 63,
8
+ // then 64 and 65, the system foreground and background.
9
+ /** `a:prstClr` names to RRGGBB. */
10
+ export const PRESET = new Map(Object.entries({
11
+ aliceBlue: 'F0F8FF', antiqueWhite: 'FAEBD7', aqua: '00FFFF',
12
+ aquamarine: '7FFFD4', azure: 'F0FFFF', beige: 'F5F5DC',
13
+ bisque: 'FFE4C4', black: '000000', blanchedAlmond: 'FFEBCD',
14
+ blue: '0000FF', blueViolet: '8A2BE2', brown: 'A52A2A',
15
+ burlyWood: 'DEB887', cadetBlue: '5F9EA0', chartreuse: '7FFF00',
16
+ chocolate: 'D2691E', coral: 'FF7F50', cornflowerBlue: '6495ED',
17
+ cornsilk: 'FFF8DC', crimson: 'DC143C', cyan: '00FFFF',
18
+ dkBlue: '00008B', dkCyan: '008B8B', dkGoldenrod: 'B8860B',
19
+ dkGray: 'A9A9A9', dkGreen: '006400', dkKhaki: 'BDB76B',
20
+ dkMagenta: '8B008B', dkOliveGreen: '556B2F', dkOrange: 'FF8C00',
21
+ dkOrchid: '9932CC', dkRed: '8B0000', dkSalmon: 'E9967A',
22
+ dkSeaGreen: '8FBC8B', dkSlateBlue: '483D8B', dkSlateGray: '2F4F4F',
23
+ dkTurquoise: '00CED1', dkViolet: '9400D3', deepPink: 'FF1493',
24
+ deepSkyBlue: '00BFFF', dimGray: '696969', dodgerBlue: '1E90FF',
25
+ firebrick: 'B22222', floralWhite: 'FFFAF0', forestGreen: '228B22',
26
+ fuchsia: 'FF00FF', gainsboro: 'DCDCDC', ghostWhite: 'F8F8FF',
27
+ gold: 'FFD700', goldenrod: 'DAA520', gray: '808080',
28
+ green: '008000', greenYellow: 'ADFF2F', honeydew: 'F0FFF0',
29
+ hotPink: 'FF69B4', indianRed: 'CD5C5C', indigo: '4B0082',
30
+ ivory: 'FFFFF0', khaki: 'F0E68C', lavender: 'E6E6FA',
31
+ lavenderBlush: 'FFF0F5', lawnGreen: '7CFC00', lemonChiffon: 'FFFACD',
32
+ ltBlue: 'ADD8E6', ltCoral: 'F08080', ltCyan: 'E0FFFF',
33
+ ltGoldenrodYellow: 'FAFA78', ltGray: 'D3D3D3', ltGreen: '90EE90',
34
+ ltPink: 'FFB6C1', ltSalmon: 'FFA07A', ltSeaGreen: '20B2AA',
35
+ ltSkyBlue: '87CEFA', ltSlateGray: '778899', ltSteelBlue: 'B0C4DE',
36
+ ltYellow: 'FFFFE0', lime: '00FF00', limeGreen: '32CD32',
37
+ linen: 'FAF0E6', magenta: 'FF00FF', maroon: '800000',
38
+ medAquamarine: '66CDAA', medBlue: '0000CD', medOrchid: 'BA55D3',
39
+ medPurple: '9370DB', medSeaGreen: '3CB371', medSlateBlue: '7B68EE',
40
+ medSpringGreen: '00FA9A', medTurquoise: '48D1CC', medVioletRed: 'C71585',
41
+ midnightBlue: '191970', mintCream: 'F5FFFA', mistyRose: 'FFE4E1',
42
+ moccasin: 'FFE4B5', navajoWhite: 'FFDEAD', navy: '000080',
43
+ oldLace: 'FDF5E6', olive: '808000', oliveDrab: '6B8E23',
44
+ orange: 'FFA500', orangeRed: 'FF4500', orchid: 'DA70D6',
45
+ paleGoldenrod: 'EEE8AA', paleGreen: '98FB98', paleTurquoise: 'AFEEEE',
46
+ paleVioletRed: 'DB7093', papayaWhip: 'FFEFD5', peachPuff: 'FFDAB9',
47
+ peru: 'CD853F', pink: 'FFC0CB', plum: 'DDA0DD',
48
+ powderBlue: 'B0E0E6', purple: '800080', red: 'FF0000',
49
+ rosyBrown: 'BC8F8F', royalBlue: '4169E1', saddleBrown: '8B4513',
50
+ salmon: 'FA8072', sandyBrown: 'F4A460', seaGreen: '2E8B57',
51
+ seaShell: 'FFF5EE', sienna: 'A0522D', silver: 'C0C0C0',
52
+ skyBlue: '87CEEB', slateBlue: '6A5ACD', slateGray: '708090',
53
+ snow: 'FFFAFA', springGreen: '00FF7F', steelBlue: '4682B4',
54
+ tan: 'D2B48C', teal: '008080', thistle: 'D8BFD8',
55
+ tomato: 'FF6347', turquoise: '40E0D0', violet: 'EE82EE',
56
+ wheat: 'F5DEB3', white: 'FFFFFF', whiteSmoke: 'F5F5F5',
57
+ yellow: 'FFFF00', yellowGreen: '9ACD32',
58
+ }));
59
+ /** `a:sysClr` names to Windows' default RRGGBB. */
60
+ export const SYSTEM = new Map(Object.entries({
61
+ scrollBar: 'C8C8C8', background: '000000', activeCaption: '99B4D1',
62
+ inactiveCaption: 'BFCDDB', menu: 'F0F0F0', window: 'FFFFFF',
63
+ windowFrame: '646464', menuText: '000000', windowText: '000000',
64
+ captionText: '000000', activeBorder: 'B4B4B4', inactiveBorder: 'F4F7FC',
65
+ appWorkspace: 'ABABAB', highlight: '3399FF', highlightText: 'FFFFFF',
66
+ btnFace: 'F0F0F0', btnShadow: '696969', grayText: '6D6D6D',
67
+ btnText: '000000', inactiveCaptionText: '000000', btnHighlight: 'E3E3E3',
68
+ '3dDkShadow': '000000', '3dLight': 'E3E3E3', infoText: '000000',
69
+ infoBk: 'FFFFE1', hotLight: '0066CC', gradientActiveCaption: 'B9D1EA',
70
+ gradientInactiveCaption: 'D7E4F2', menuHighlight: '3399FF', menuBar: 'F0F0F0',
71
+ }));
72
+ /** SpreadsheetML indexed colours 0 to 65. */
73
+ export const INDEXED = Object.freeze([
74
+ '000000', 'FFFFFF', 'FF0000', '00FF00', '0000FF', 'FFFF00', 'FF00FF', '00FFFF',
75
+ '000000', 'FFFFFF', 'FF0000', '00FF00', '0000FF', 'FFFF00', 'FF00FF', '00FFFF',
76
+ '800000', '008000', '000080', '808000', '800080', '008080', 'C0C0C0', '808080',
77
+ '9999FF', '993366', 'FFFFCC', 'CCFFFF', '660066', 'FF8080', '0066CC', 'CCCCFF',
78
+ '000080', 'FF00FF', 'FFFF00', '00FFFF', '800080', '800000', '008080', '0000FF',
79
+ '00CCFF', 'CCFFFF', 'CCFFCC', 'FFFF99', '99CCFF', 'FF99CC', 'CC99FF', 'FFCC99',
80
+ '3366FF', '33CCCC', '99CC00', 'FFCC00', 'FF9900', 'FF6600', '666699', '969696',
81
+ '003366', '339966', '003300', '333300', '993300', '993366', '333399', '333333',
82
+ '000000', 'FFFFFF',
83
+ ]);
@@ -0,0 +1,52 @@
1
+ import type * as dml from '@docx4j/generated-objects-ts/modules/org_docx4j_dml';
2
+ import { ResolvedColor } from './ResolvedColor.mjs';
3
+ import { StyleFill } from './StyleFill.mjs';
4
+ import type { ThemeColors } from './ThemeColors.mjs';
5
+ /** hexBinary as the objects model has it at run time (byte numbers), or as its declarations say (hex digits). */
6
+ export declare function bytesOf(v: unknown): number[] | undefined;
7
+ /**
8
+ * DrawingML colours (ISO/IEC 29500-1 section 20.1.2.3) resolved as Office draws them. The six kinds: `srgbClr`,
9
+ * `scrgbClr` (linear percentages), `hslClr`, `sysClr` (the machine's colour by name, not `lastClr`), `prstClr` and
10
+ * `schemeClr` (through {@link ThemeColors}). Their transforms apply in document order, in the spaces Word and
11
+ * PowerPoint use, measured on 403 cases each (docx4j CR-034 section 11, P3):
12
+ * - `tint`, `shade`, `inv`, `red`/`green`/`blue` and their `Off`/`Mod`: linear RGB; `gamma` and `invGamma`: the
13
+ * sRGB transfer function each way;
14
+ * - `hue`, `sat`, `lum` and their `Off`/`Mod`, `comp`: HSL of the sRGB colour, the saturation not clamped (the
15
+ * channels are, afterwards); a colour whose last change is a luminance transform converted back as Foley and van
16
+ * Dam do and rounded half down, as Office rounds it (so `tx1` at `lumMod 50000 lumOff 50000` is 7F7F7F);
17
+ * - `gray`: Rec.709 luma of the sRGB channels;
18
+ * - `alpha`, `alphaOff`, `alphaMod`: the alpha.
19
+ *
20
+ * MS-OI29500 says `lumOff` and `satOff` are not supported by Office; Office 365 applies both as above in every case
21
+ * measured. Known differences from Office: a saturation set on a grey (Office gives a grey's hue about 40 degrees),
22
+ * and some hue, saturation and luminance cases one unit off.
23
+ *
24
+ * An object is known by its `TYPE_NAME` (which the objects package's unmarshal and factories give it), or, wrapped
25
+ * as `{ name, value }`, by its element name; a holder of a colour also by its `srgbClr`, `schemeClr`, ... member.
26
+ * docx4j's `null` is `undefined` here.
27
+ */
28
+ export declare const DrawingMLColors: {
29
+ /**
30
+ * The colour a DrawingML colour element, or an element holding one, resolves to.
31
+ *
32
+ * @param colour one of the six kinds (`CTSchemeColor`, `CTSRgbColor`, ...), or a holder of one: `CTColor` (a
33
+ * scheme slot), `CTSolidColorFillProperties`, `CTStyleMatrixReference`, `CTFontReference`; `{ name, value }`
34
+ * is unwrapped
35
+ * @param theme the theme and colour map for `schemeClr`; may be undefined where there is none
36
+ * @returns undefined where there is no colour, or a `schemeClr` cannot be resolved
37
+ */
38
+ resolve(colour: unknown, theme: ThemeColors | undefined): ResolvedColor | undefined;
39
+ /** A fill: `a:noFill`, `a:solidFill` (its colour resolved), or a gradient, pattern, picture or group fill reported by kind. */
40
+ fill(fillProperties: unknown, theme: ThemeColors | undefined): StyleFill;
41
+ /** A line's fill: none, solid, gradient or pattern. */
42
+ lineFill(ln: dml.CTLineProperties | undefined | null, theme: ThemeColors | undefined): StyleFill;
43
+ /**
44
+ * A shape style's `a:fillRef`: index 0 is no fill, 1 to 999 the theme's `a:fillStyleLst`, 1001 and up its
45
+ * `a:bgFillStyleLst`, with `phClr` the reference's own colour.
46
+ */
47
+ fillReference(ref: dml.CTStyleMatrixReference | undefined | null, theme: ThemeColors | undefined): StyleFill;
48
+ /** A shape style's `a:lnRef`: index 0 is no line, else the theme's `a:lnStyleLst` entry's fill. */
49
+ lineReference(ref: dml.CTStyleMatrixReference | undefined | null, theme: ThemeColors | undefined): StyleFill;
50
+ /** A shape style's `a:fontRef`: its colour, the text's where the text states none. */
51
+ fontReference(ref: dml.CTFontReference | undefined | null, theme: ThemeColors | undefined): ResolvedColor | undefined;
52
+ };