@portone/docx-editor 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/dist/DocxEditor.js +1 -0
  3. package/dist/core.d.ts +1 -1
  4. package/dist/docx/comments/writing.js +14 -8
  5. package/dist/docx/exportDocx.js +20 -4
  6. package/dist/docx/formatting/attrs.d.ts +20 -2
  7. package/dist/docx/formatting/attrs.js +57 -2
  8. package/dist/docx/formatting/context.d.ts +10 -2
  9. package/dist/docx/formatting/context.js +23 -5
  10. package/dist/docx/formatting/resolve.d.ts +10 -4
  11. package/dist/docx/formatting/resolve.js +31 -11
  12. package/dist/docx/formatting/styles.d.ts +25 -2
  13. package/dist/docx/formatting/styles.js +84 -3
  14. package/dist/docx/hyperlink.d.ts +14 -4
  15. package/dist/docx/hyperlink.js +4 -1
  16. package/dist/docx/importDocx.js +46 -37
  17. package/dist/docx/importTable.d.ts +2 -2
  18. package/dist/docx/importTable.js +52 -55
  19. package/dist/docx/invariants.js +26 -13
  20. package/dist/docx/newLists.d.ts +30 -0
  21. package/dist/docx/newLists.js +24 -1
  22. package/dist/docx/numberingPlanner.d.ts +6 -3
  23. package/dist/docx/numberingPlanner.js +38 -7
  24. package/dist/docx/session.d.ts +1 -1
  25. package/dist/docx/session.js +11 -1
  26. package/dist/docx/tableFormatting/conditions.d.ts +76 -0
  27. package/dist/docx/tableFormatting/conditions.js +196 -0
  28. package/dist/docx/tableFormatting/editing.d.ts +7 -3
  29. package/dist/docx/tableFormatting/editing.js +4 -5
  30. package/dist/docx/tableFormatting/reading.d.ts +49 -19
  31. package/dist/docx/tableFormatting/reading.js +109 -35
  32. package/dist/docx/tableFormatting.d.ts +1 -0
  33. package/dist/docx/tableFormatting.js +1 -0
  34. package/dist/docx/tableTemplate.js +12 -12
  35. package/dist/editor/commands/formatting/propertyCommands.js +6 -1
  36. package/dist/editor/commands/listCommands.d.ts +6 -3
  37. package/dist/editor/commands/listCommands.js +73 -28
  38. package/dist/editor/commands/paragraphCommands.js +6 -1
  39. package/dist/editor/createEditor.js +1 -1
  40. package/dist/editor/editorDocument.d.ts +7 -6
  41. package/dist/editor/editorDocument.js +18 -6
  42. package/dist/editor/externalClipboard.d.ts +13 -2
  43. package/dist/editor/externalClipboard.js +64 -34
  44. package/dist/editor/paragraphEdits.d.ts +16 -1
  45. package/dist/editor/paragraphEdits.js +13 -5
  46. package/dist/editor/paragraphPlacement.d.ts +11 -0
  47. package/dist/editor/paragraphPlacement.js +18 -0
  48. package/dist/editor/plugins/imagePaste.js +7 -4
  49. package/dist/editor/plugins/numberingDecorations.d.ts +10 -4
  50. package/dist/editor/plugins/numberingDecorations.js +43 -2
  51. package/dist/editor/plugins/paragraphDisplay.js +9 -3
  52. package/dist/editor/plugins/tableDisplay.js +19 -5
  53. package/dist/model/format.d.ts +50 -0
  54. package/dist/model/format.js +65 -0
  55. package/dist/numbering/listRegistry.d.ts +37 -0
  56. package/dist/numbering/listRegistry.js +112 -0
  57. package/dist/numbering/listTemplate.d.ts +26 -17
  58. package/dist/numbering/listTemplate.js +66 -20
  59. package/dist/numbering/markers.d.ts +15 -6
  60. package/dist/numbering/markers.js +26 -59
  61. package/dist/numbering/parseNumbering.d.ts +70 -5
  62. package/dist/numbering/parseNumbering.js +100 -26
  63. package/dist/numbering/spellers.d.ts +15 -0
  64. package/dist/numbering/spellers.js +96 -0
  65. package/dist/numbering/writeNumbering.d.ts +5 -3
  66. package/dist/numbering/writeNumbering.js +15 -15
  67. package/dist/ooxml/conformance.d.ts +28 -0
  68. package/dist/ooxml/conformance.js +20 -0
  69. package/dist/ooxml/errors.d.ts +5 -5
  70. package/dist/ooxml/names.d.ts +9 -0
  71. package/dist/ooxml/names.js +8 -1
  72. package/dist/ooxml/partSplice.d.ts +1 -1
  73. package/dist/ooxml/partSplice.js +10 -3
  74. package/dist/ooxml/xml.d.ts +8 -9
  75. package/dist/ooxml/xml.js +6 -6
  76. package/dist/page/PageGuides.d.ts +1 -1
  77. package/dist/page/PageGuides.js +0 -13
  78. package/dist/page/pageLayout.d.ts +0 -10
  79. package/dist/page/pageLayout.js +3 -10
  80. package/dist/page/usePageLayout.d.ts +0 -7
  81. package/dist/page/usePageLayout.js +2 -9
  82. package/dist/schema/attrRoles.js +8 -1
  83. package/dist/schema/docxSchema.js +32 -3
  84. package/dist/styles/classNames.d.ts +13 -2
  85. package/dist/styles/classNames.js +13 -2
  86. package/dist/styles.css +16 -18
  87. package/dist/table/cellFormatting.js +1 -2
  88. package/dist/table/gridBorders.d.ts +11 -15
  89. package/dist/table/gridBorders.js +32 -18
  90. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,51 @@
1
1
  # @portone/docx-editor
2
2
 
3
+ ## 0.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#102](https://github.com/portone-io/docx-editor/pull/102) [`4258530`](https://github.com/portone-io/docx-editor/commit/4258530aaf391ba546a282f73dff7b45f7063829) Thanks [@Deea222](https://github.com/Deea222)! - Draw the table styles and list numbering a document defines, and let a document that never held a list take one.
8
+
9
+ A table style is now displayed part by part: the header row, the closing row, the first and last column, the corners and the banded rows take the shading, the lines and the text formatting the style dresses them with (`w:tblStylePr`), and `w:tblLook` decides which of those parts a table takes.
10
+ The bands are as many rows or columns wide as the style says (`w:tblStyleRowBandSize`, `w:tblStyleColBandSize`), and the header row and the closing row are not banded with the rest.
11
+ Text already in a cell follows its new position after a row edit, and the conditional-formatting markup a document arrived with goes back out untouched.
12
+
13
+ A list that takes its numbers from a numbering style now draws them.
14
+ Such a list holds no numbers of its own: it names a style (`w:numStyleLink`), the style names a list, and that list's definition is where the numbers are, which is how Word writes a list built from its gallery.
15
+ Alongside decimal, bullets, upper and lower letters and lower Roman numerals, a list may now count in upper Roman numerals, decimal numbers with a leading zero, Ganada, Korean digits and the Chinese counting system; a format past those is still shown with decimal numbers.
16
+ Each level is drawn the way it asks to be: where its counting starts over (`w:lvlRestart`), whether the numbers in its text are all spelled as decimals (`w:isLgl`), what stands between its number and the paragraph text (`w:suff`), and where the number sits in the room kept for it (`w:lvlJc`).
17
+ A marker is also drawn in the character formatting its own level writes down (`lvl/rPr`), so bold, italic, color, size and typeface reach the number and never the text of the paragraph it stands in front of.
18
+ All of this is read; the numbering part goes back out exactly as it arrived.
19
+
20
+ A new list no longer needs the document to have arrived with a numbering part.
21
+ The export writes `word/numbering.xml`, relates it from the main part and declares it in the content types, so a document that never held a list can take one; the list commands, which used to report that they did not apply in such a document, now do.
22
+ If an existing numbering relationship points to a missing part, the new definition is written at that location so the document can find the list when it is reopened.
23
+
24
+ A new or pasted list is exported with the definition it was started with, rather than a format inferred from its numbering IDs, and undo restores that registration along with the edit.
25
+ A list whose definition is missing or unsupported is refused as `unsupported-content`, and a package with no `[Content_Types].xml` for the new numbering part to be declared in is refused as `missing-content-types`; `exportProblems` reports either ahead of the write.
26
+ List and abstract definition IDs stay distinct in a document already using the largest safely representable integer, and a value that cannot be written faithfully is rejected rather than silently changed or discarded.
27
+
28
+ An ECMA-376 Strict package is refused with the new `unsupported-conformance` code, and a document whose main part root does not bind `w` to Transitional WordprocessingML is refused as `unsupported-content`.
29
+ Word saves neither by default, and both used to open far enough to fail later and obscurely: a Strict package as `missing-part`, and a document under another prefix as a `malformed-xml` about bookmarks on its first edit.
30
+ A write needing a prefix already bound to a different namespace is refused instead of silently giving new markup the wrong meaning, and a new hyperlink relationship attribute is checked for a declaration that shadows the root binding.
31
+ The comment and hyperlink writers now have the part's root declare the prefix they write under rather than declaring it on every element written, and editing an existing numbering or comment extension part that uses another prefix adds the writer's declaration at the root, so an untouched document still exports byte for byte and the exported XML stays readable.
32
+
33
+ `DocxImportErrorCode` gains `unsupported-conformance`.
34
+ `DocxExportErrorCode` loses `missing-numbering-part`, since nothing can reach it any more, and that is a compile error for a `switch` over the code written to be exhaustive; drop the branch.
35
+ `missing-content-types` now also covers the numbering part a new list is defined in.
36
+ On `./core`, `Numbering` gains `added`, and the new `NewList` and `NewListLevel` types describe the definitions the editor registered.
37
+ Level maps are read-only, and a registered definition carries a restart, legal numbering and a suffix, while marker run formatting and custom tab stops stay outside what can be registered.
38
+
39
+ ### Patch Changes
40
+
41
+ - [#101](https://github.com/portone-io/docx-editor/pull/101) [`1b6c723`](https://github.com/portone-io/docx-editor/commit/1b6c723982eed9c10656d57aca1d496e6baad5ec) Thanks [@Deea222](https://github.com/Deea222)! - Stop drawing the page number on the corner of each page.
42
+
43
+ The page guides laid a small grey number inside the top right corner of every page, over the paper the text sits on.
44
+ It is gone, and the guides now draw the gaps between pages and the header and footer stories alone.
45
+ A document that prints its own page number through a `PAGE` field in a header or footer is unaffected: that number is the document's, not the editor's.
46
+
47
+ The `docx-editor-page-badge` class the number carried is no longer emitted, so a rule of your own written against it in the published stylesheet no longer matches anything.
48
+
3
49
  ## 0.3.0
4
50
 
5
51
  ### Minor Changes
@@ -115,6 +115,7 @@ var IMPORT_REJECTION_REASON = {
115
115
  "missing-part": "This document is missing the part that holds its body.",
116
116
  "missing-body": "This document has no body.",
117
117
  "malformed-xml": "The XML inside this document cannot be read.",
118
+ "unsupported-conformance": "This document is saved in a format this editor cannot open. Save it as a Word document and try again.",
118
119
  "unsupported-content": "This document holds content that could not be kept as it is."
119
120
  };
120
121
  function ImportRejection({ error }) {
package/dist/core.d.ts CHANGED
@@ -27,7 +27,7 @@ export type { DocxSession } from "./docx/session";
27
27
  export { documentNumbering, documentPartPath } from "./docx/session";
28
28
  export type { CellFormat, CellVerticalAlign, DocumentDefaults, HighlightName, LineSpacing, NumberingRef, ParagraphAlign, ParagraphFormat, RowFormat, RowHeight, RunFormat, TableFormat, TableWidth, TableWidthType, UnderlineKind, VerticalAlign, } from "./model/format";
29
29
  export { toCellFormat, toParagraphFormat, toRowFormat, toRunFormat, toTableFormat, toTableWidth, } from "./model/format";
30
- export type { LevelIndent, NumberFormat, Numbering, NumberingLevel, NumberingList, NumberingOptions, } from "./numbering/parseNumbering";
30
+ export type { LevelAlign, LevelIndent, LevelSuffix, NewList, NewListLevel, NumberFormat, Numbering, NumberingLevel, NumberingList, NumberingOptions, NumberingStyleLinks, ReadLevelRun, } from "./numbering/parseNumbering";
31
31
  export { parseNumbering } from "./numbering/parseNumbering";
32
32
  export type { DocxExportErrorCode, DocxImportErrorCode, } from "./ooxml/errors";
33
33
  export { DocxExportError, DocxImportError } from "./ooxml/errors";
@@ -1,5 +1,5 @@
1
1
  // src/docx/comments/writing.ts
2
- import { elementXml, xmlnsAttr } from "../../ooxml/element.js";
2
+ import { elementXml } from "../../ooxml/element.js";
3
3
  import { NAMESPACES, wName, xmlnsDecl } from "../../ooxml/names.js";
4
4
  import {
5
5
  ensureRootDeclarations,
@@ -74,10 +74,13 @@ function extensionsXml(references, comments, originallyReferenced) {
74
74
  if (comments.extendedXml === null) {
75
75
  return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w15:commentsEx ${xmlnsDecl("w15")}>${pieces.join("")}</w15:commentsEx>`;
76
76
  }
77
- return splicePart(comments.extendedXml, {
78
- root: EXTENSIONS_ROOT,
79
- replaceChildren: pieces.join("")
80
- });
77
+ return ensureRootDeclarations(
78
+ splicePart(comments.extendedXml, {
79
+ root: EXTENSIONS_ROOT,
80
+ replaceChildren: pieces.join("")
81
+ }),
82
+ { namespaces: { w15: NAMESPACES.w15 } }
83
+ );
81
84
  }
82
85
  var COMMENTS_ROOT = "comments";
83
86
  var EXTENSIONS_ROOT = "commentsEx";
@@ -117,7 +120,6 @@ function renderedComment(comment, arrivedKeyed, arrived) {
117
120
  ) : comment.commentXml;
118
121
  }
119
122
  const attrs = [
120
- xmlnsAttr("w"),
121
123
  [wName("id"), comment.id],
122
124
  comment.author === null ? null : [wName("author"), comment.author],
123
125
  comment.date === null ? null : [wName("date"), comment.date],
@@ -131,8 +133,9 @@ function renderedComment(comment, arrivedKeyed, arrived) {
131
133
  [body]
132
134
  );
133
135
  }
136
+ var COMMENT_MARKUP = { namespaces: { w: NAMESPACES.w } };
134
137
  var THREAD_MARKUP = {
135
- namespaces: { w14: NAMESPACES.w14, mc: NAMESPACES.mc },
138
+ namespaces: { w: NAMESPACES.w, w14: NAMESPACES.w14, mc: NAMESPACES.mc },
136
139
  ignorable: ["w14"]
137
140
  };
138
141
  function currentCommentBodies(references) {
@@ -196,7 +199,10 @@ function commentsXml(references, comments, originallyReferenced) {
196
199
  root: COMMENTS_ROOT,
197
200
  replaceChildren: pieces.join("")
198
201
  });
199
- return hasThreadMetadata ? ensureRootDeclarations(rewritten, THREAD_MARKUP) : rewritten;
202
+ return ensureRootDeclarations(
203
+ rewritten,
204
+ hasThreadMetadata ? THREAD_MARKUP : COMMENT_MARKUP
205
+ );
200
206
  }
201
207
  function planCommentParts(doc, session, context) {
202
208
  const bodyChanged = commentsChanged(doc, session);
@@ -1,8 +1,12 @@
1
1
  // src/docx/exportDocx.ts
2
2
  import { DocxExportError } from "../ooxml/errors.js";
3
+ import {
4
+ ensureRootDeclarations
5
+ } from "../ooxml/partSplice.js";
3
6
  import {
4
7
  encodeUtf8,
5
8
  parseXml,
9
+ R_NS,
6
10
  W_NS,
7
11
  withXmlParser
8
12
  } from "../ooxml/xml.js";
@@ -48,7 +52,7 @@ function buildDocumentXml(doc, session, refs) {
48
52
  function localAttribute(el, name) {
49
53
  return Array.from(el.attributes).find((attribute) => attribute.localName === name)?.value ?? null;
50
54
  }
51
- function assertBookmarkPairs(documentXml) {
55
+ function assertMainPart(documentXml, wroteLinks) {
52
56
  let root;
53
57
  try {
54
58
  root = parseXml(documentXml).documentElement;
@@ -63,6 +67,15 @@ function assertBookmarkPairs(documentXml) {
63
67
  const used = /* @__PURE__ */ new Set();
64
68
  for (const element of root.getElementsByTagName("*")) {
65
69
  if (element.namespaceURI !== W_NS) continue;
70
+ if (wroteLinks && element.localName === "hyperlink") {
71
+ const id2 = element.getAttributeNode("r:id");
72
+ if (id2 && id2.namespaceURI !== R_NS) {
73
+ throw new DocxExportError(
74
+ "unsupported-content",
75
+ "a hyperlink's r:id is shadowed by a different relationship namespace"
76
+ );
77
+ }
78
+ }
66
79
  if (element.localName !== "bookmarkStart" && element.localName !== "bookmarkEnd") {
67
80
  continue;
68
81
  }
@@ -97,6 +110,7 @@ function assertBookmarkPairs(documentXml) {
97
110
  );
98
111
  }
99
112
  }
113
+ var LINK_MARKUP = { namespaces: { r: R_NS } };
100
114
  var PART_PLANNERS = [
101
115
  numberingPlanner,
102
116
  commentsPlanner
@@ -135,12 +149,14 @@ function writeDocx(doc, store, notes, planners) {
135
149
  contentTypes: contentTypeWriter(store.parts)
136
150
  };
137
151
  const media = planImageMedia(doc, store, context);
138
- const documentXml = buildDocumentXml(doc, store, {
152
+ const links = hyperlinkRefs(context.relationships);
153
+ const body = buildDocumentXml(doc, store, {
139
154
  images: media?.refs ?? NO_IMAGE_REFS,
140
- links: hyperlinkRefs(context.relationships),
155
+ links,
141
156
  notes
142
157
  });
143
- assertBookmarkPairs(documentXml);
158
+ const documentXml = links.addedRelId() ? ensureRootDeclarations(body, LINK_MARKUP) : body;
159
+ assertMainPart(documentXml, links.addedRelId());
144
160
  const parts = runPartPlanners(planners, doc, store, context, media?.parts);
145
161
  const rels = context.relationships.part(store.parts.get(relsPath));
146
162
  if (rels) parts.set(relsPath, rels);
@@ -4,8 +4,8 @@
4
4
  * and the clipboard reader all take them from here, so a paragraph is drawn the same whichever of
5
5
  * them last touched it.
6
6
  */
7
- import type { Mark } from "prosemirror-model";
8
- import type { ParagraphFormat, RunFormat } from "../../model/format";
7
+ import { type Mark, type Node as PMNode } from "prosemirror-model";
8
+ import { type ParagraphFormat, type RunFormat } from "../../model/format";
9
9
  import type { FormattingContext } from "./context";
10
10
  import { type ParagraphPlacement, type ResolvedParagraph } from "./resolve";
11
11
  /** The two derived paragraph attrs (`format` and `styleRun` in `schema`) */
@@ -25,3 +25,21 @@ export declare function paragraphAttrsFor(pPr: string | null, context: Formattin
25
25
  * An inline that is not text and carries no mark is left as it is.
26
26
  */
27
27
  export declare function runMarkUnder(mark: Mark | null, isText: boolean, paragraph: ResolvedParagraph, context: FormattingContext): Mark | null;
28
+ /**
29
+ * The paragraph with the values the hierarchy gives it, and the text inside it wearing the run
30
+ * mark that goes with them.
31
+ *
32
+ * The style's run values are also laid on the paragraph itself, so that text carrying no run of
33
+ * its own - typed in the editor - is drawn in them (`styleRun` in `schema`).
34
+ * Where the paragraph stands in a table cell, `placement` is the part of the table it belongs to,
35
+ * which the table style dresses on top of everything else it lays down.
36
+ */
37
+ export declare function styledParagraph(node: PMNode, context: FormattingContext, placement?: ParagraphPlacement | null): PMNode;
38
+ /** Table display inputs, refreshed from the same style context as the paragraphs inside it. */
39
+ export declare function tableStyleAttrs(tblPr: Element | null, context: FormattingContext): {
40
+ format: import("../..").TableFormat | null;
41
+ styleInside: import("../tableFormatting").InsideBorders | null;
42
+ styleCellMargins: import("../tableFormatting").CellMargins | null;
43
+ styleConditions: Readonly<Partial<Record<"band1Horz" | "band1Vert" | "band2Horz" | "band2Vert" | "firstCol" | "firstRow" | "lastCol" | "lastRow" | "neCell" | "nwCell" | "seCell" | "swCell" | "wholeTable", import("../tableFormatting").CellStyleFormat>>> | null;
44
+ styleBands: import("../tableFormatting").BandSizes | null;
45
+ };
@@ -1,9 +1,26 @@
1
1
  // src/docx/formatting/attrs.ts
2
+ import { Fragment } from "prosemirror-model";
3
+ import {
4
+ toBandSizes,
5
+ toCellMargins,
6
+ toInsideBorders,
7
+ toTableStyleConditions
8
+ } from "../../model/format.js";
2
9
  import { docxSchema } from "../../schema/index.js";
10
+ import {
11
+ cellConditionsOf,
12
+ layerBandSizes,
13
+ NO_BAND_SIZES,
14
+ readBandSizes,
15
+ readTableFormat,
16
+ tblStyleIdOf
17
+ } from "../tableFormatting.js";
3
18
  import {
4
19
  resolveParagraph,
5
- resolveRun
20
+ resolveRun,
21
+ tableStyleFor
6
22
  } from "./resolve.js";
23
+ import { layerTableFormat } from "./styles.js";
7
24
  function paragraphAttrsOf(paragraph) {
8
25
  return { format: paragraph.format, styleRun: paragraph.styleRun };
9
26
  }
@@ -24,8 +41,46 @@ function runMarkUnder(mark, isText, paragraph, context) {
24
41
  const format = resolveRun(null, paragraph, context);
25
42
  return format === null ? null : docxSchema.marks.run.create({ rPr: null, rAttrs: null, format });
26
43
  }
44
+ function styledParagraph(node, context, placement = null) {
45
+ const pPr = node.attrs.pPr;
46
+ const paragraph = resolveParagraph(
47
+ typeof pPr === "string" ? pPr : null,
48
+ context,
49
+ placement
50
+ );
51
+ const inline = node.children.map((child) => {
52
+ const mark = runMarkUnder(
53
+ child.marks.find((entry) => entry.type === docxSchema.marks.run) ?? null,
54
+ child.isText,
55
+ paragraph,
56
+ context
57
+ );
58
+ return mark ? child.mark(mark.addToSet(child.marks)) : child;
59
+ });
60
+ return node.type.create(
61
+ { ...node.attrs, ...paragraphAttrsOf(paragraph) },
62
+ Fragment.fromArray(inline),
63
+ node.marks
64
+ );
65
+ }
66
+ function tableStyleAttrs(tblPr, context) {
67
+ const style = tableStyleFor(tblStyleIdOf(tblPr), context);
68
+ return {
69
+ format: layerTableFormat(style?.table ?? {}, readTableFormat(tblPr)),
70
+ styleInside: toInsideBorders(style?.tableInside),
71
+ styleCellMargins: toCellMargins(style?.tableCellMargins),
72
+ styleConditions: toTableStyleConditions(
73
+ cellConditionsOf(style?.tableConditions ?? {})
74
+ ),
75
+ styleBands: toBandSizes(
76
+ layerBandSizes(style?.tableBands ?? NO_BAND_SIZES, readBandSizes(tblPr))
77
+ )
78
+ };
79
+ }
27
80
  export {
28
81
  paragraphAttrsFor,
29
82
  paragraphAttrsOf,
30
- runMarkUnder
83
+ runMarkUnder,
84
+ styledParagraph,
85
+ tableStyleAttrs
31
86
  };
@@ -1,5 +1,5 @@
1
1
  import type { RunFormat } from "../../model/format";
2
- import { type Numbering } from "../../numbering/parseNumbering";
2
+ import { type Numbering, type NumberingOptions } from "../../numbering/parseNumbering";
3
3
  import { type CompatSettings } from "../documentSettings";
4
4
  import { type ThemeFonts } from "../theme";
5
5
  import { type StyleTable } from "./styles";
@@ -27,5 +27,13 @@ export interface FormattingContext {
27
27
  compat: CompatSettings;
28
28
  }
29
29
  export declare const NO_FORMATTING: FormattingContext;
30
+ /**
31
+ * How a document's numbering part is read against the rest of what the document lays down.
32
+ *
33
+ * The numbering folder cannot reach styles.xml, so the links a definition follows are handed to it
34
+ * from here, and the two callers that read a numbering part - opening a document and asking an
35
+ * open one for its lists - resolve the same links.
36
+ */
37
+ export declare function numberingOptionsFor(styles: StyleTable, themeFonts: ThemeFonts): NumberingOptions;
30
38
  /** The context an opened document resolves against. A document without a styles part lays down nothing */
31
- export declare function formattingContextOf(styles: Document | null, numbering: Numbering, themeFonts: ThemeFonts, compat?: CompatSettings): FormattingContext;
39
+ export declare function formattingContextOf(styles: Document | null, numberingXml: string | null, themeFonts: ThemeFonts, compat?: CompatSettings): FormattingContext;
@@ -1,14 +1,20 @@
1
1
  // src/docx/formatting/context.ts
2
2
  import {
3
- EMPTY_NUMBERING
3
+ EMPTY_NUMBERING,
4
+ parseNumbering
4
5
  } from "../../numbering/parseNumbering.js";
5
6
  import { NO_COMPAT } from "../documentSettings.js";
6
7
  import { NO_THEME_FONTS } from "../theme.js";
7
- import { readDefaultParagraphFormat, readRunDefaults } from "./direct.js";
8
+ import {
9
+ readDefaultParagraphFormat,
10
+ readRunDefaults,
11
+ readRunFormat
12
+ } from "./direct.js";
8
13
  import {
9
14
  defaultParagraphStyleIdOf,
10
15
  defaultTableStyleIdOf,
11
16
  NO_STYLES,
17
+ numberingStyleLinks,
12
18
  readStyles
13
19
  } from "./styles.js";
14
20
  var NO_FORMATTING = {
@@ -21,12 +27,23 @@ var NO_FORMATTING = {
21
27
  themeFonts: NO_THEME_FONTS,
22
28
  compat: NO_COMPAT
23
29
  };
24
- function formattingContextOf(styles, numbering, themeFonts, compat = NO_COMPAT) {
30
+ function numberingOptionsFor(styles, themeFonts) {
31
+ return {
32
+ links: numberingStyleLinks(styles),
33
+ readRun: (rPr) => readRunFormat(rPr, themeFonts)
34
+ };
35
+ }
36
+ function formattingContextOf(styles, numberingXml, themeFonts, compat = NO_COMPAT) {
37
+ const table = styles === null ? NO_STYLES : readStyles(styles, themeFonts);
38
+ const numbering = parseNumbering(
39
+ numberingXml,
40
+ numberingOptionsFor(table, themeFonts)
41
+ );
25
42
  if (styles === null) {
26
43
  return { ...NO_FORMATTING, numbering, themeFonts, compat };
27
44
  }
28
45
  return {
29
- styles: readStyles(styles, themeFonts),
46
+ styles: table,
30
47
  defaultParagraphStyleId: defaultParagraphStyleIdOf(styles),
31
48
  defaultTableStyleId: defaultTableStyleIdOf(styles),
32
49
  paragraphDefaults: readDefaultParagraphFormat(styles),
@@ -38,5 +55,6 @@ function formattingContextOf(styles, numbering, themeFonts, compat = NO_COMPAT)
38
55
  }
39
56
  export {
40
57
  NO_FORMATTING,
41
- formattingContextOf
58
+ formattingContextOf,
59
+ numberingOptionsFor
42
60
  };
@@ -9,13 +9,12 @@ import { type ParagraphFormat, type RunFormat } from "../../model/format";
9
9
  import type { TabStop } from "../../model/tabStops";
10
10
  import type { TableStyleOverrideType } from "../tableFormatting";
11
11
  import type { FormattingContext } from "./context";
12
+ import { type StyleFormat } from "./styles";
12
13
  /** Where a paragraph sits when it is inside a table cell. null for body text */
13
14
  export interface ParagraphPlacement {
15
+ /** What the table's `w:tblStyle` names. null for a table naming none, which wears the default one */
14
16
  tableStyleId: string | null;
15
- /**
16
- * The conditional formats the cell takes, lowest first. Not read yet: a table style
17
- * contributes the values it lays down for the whole table alone
18
- */
17
+ /** The parts of the table the cell belongs to, lowest first (`docx/tableFormatting/conditions`) */
19
18
  conditions: readonly TableStyleOverrideType[];
20
19
  }
21
20
  /**
@@ -38,6 +37,13 @@ export interface ResolvedParagraph {
38
37
  /** The stops `format.tabStops` holds, each with the layer that laid it down */
39
38
  tabStops: readonly LayeredTabStop[];
40
39
  }
40
+ /**
41
+ * The table style an object wearing this one is dressed by.
42
+ *
43
+ * A table that points at no style, or at one that is not defined, falls back on the document's
44
+ * default table style, which is what OOXML applies to an object with no style of its own.
45
+ */
46
+ export declare function tableStyleFor(styleId: string | null, context: FormattingContext): StyleFormat | undefined;
41
47
  export declare function resolveParagraph(pPr: string | null, context: FormattingContext, placement?: ParagraphPlacement | null): ResolvedParagraph;
42
48
  /** `format` on a run mark: paragraph.styleRun < character style < the run's own rPr */
43
49
  export declare function resolveRun(rPr: string | null, paragraph: ResolvedParagraph, context: FormattingContext): RunFormat | null;
@@ -2,6 +2,7 @@
2
2
  import {
3
3
  toParagraphFormat
4
4
  } from "../../model/format.js";
5
+ import { listFor } from "../../numbering/listTemplate.js";
5
6
  import {
6
7
  levelIndentPt
7
8
  } from "../../numbering/parseNumbering.js";
@@ -14,9 +15,17 @@ import {
14
15
  function directLayer(pPr) {
15
16
  return pPr === null ? {} : readParagraphFormat(parsePropsXml(pPr)) ?? {};
16
17
  }
17
- function tableStyleOf(placement, context) {
18
- const id = placement?.tableStyleId ?? null;
19
- return id === null ? void 0 : context.styles.get(id);
18
+ function tableStyleFor(styleId, context) {
19
+ const named = styleId === null ? void 0 : context.styles.get(styleId);
20
+ const fallback = context.defaultTableStyleId === null ? void 0 : context.styles.get(context.defaultTableStyleId);
21
+ return named ?? fallback;
22
+ }
23
+ function conditionsOf(style, placement) {
24
+ if (!style || !placement) return [];
25
+ return placement.conditions.flatMap((type) => {
26
+ const format = style.tableConditions[type];
27
+ return format ? [format] : [];
28
+ });
20
29
  }
21
30
  function numberingLevelOf(layers, context) {
22
31
  const ref = layers.reduce(
@@ -24,7 +33,7 @@ function numberingLevelOf(layers, context) {
24
33
  void 0
25
34
  );
26
35
  if (!ref) return void 0;
27
- return context.numbering.lists.get(ref.numId)?.levels.get(ref.ilvl);
36
+ return listFor(context.numbering, ref.numId)?.levels.get(ref.ilvl);
28
37
  }
29
38
  function numberingLayer(level) {
30
39
  return level?.tabStops ? { tabStops: level.tabStops } : {};
@@ -66,16 +75,23 @@ function orNull(values) {
66
75
  return Object.keys(values).length === 0 ? null : values;
67
76
  }
68
77
  function resolveParagraph(pPr, context, placement = null) {
69
- const tableStyle = tableStyleOf(placement, context);
78
+ const tableStyle = placement === null ? void 0 : tableStyleFor(placement.tableStyleId, context);
79
+ const conditions = conditionsOf(tableStyle, placement);
70
80
  const style = paragraphStyleFormat(
71
81
  pPr,
72
82
  context.styles,
73
83
  context.defaultParagraphStyleId
74
84
  );
75
85
  const direct = directLayer(pPr);
86
+ const table = [
87
+ { values: tableStyle?.paragraph ?? {}, stops: "style" },
88
+ ...conditions.map(
89
+ (condition) => ({ values: condition.paragraph, stops: "style" })
90
+ )
91
+ ];
76
92
  const explicit = [
77
93
  { values: context.paragraphDefaults, stops: "style" },
78
- { values: tableStyle?.paragraph ?? {}, stops: "style" },
94
+ ...table,
79
95
  { values: style?.paragraph ?? {}, stops: "style" },
80
96
  { values: direct, stops: "direct" }
81
97
  ];
@@ -83,10 +99,11 @@ function resolveParagraph(pPr, context, placement = null) {
83
99
  explicit.map((layer) => layer.values),
84
100
  context
85
101
  );
102
+ const belowNumbering = 1 + table.length;
86
103
  const layers = [
87
- ...explicit.slice(0, 2),
104
+ ...explicit.slice(0, belowNumbering),
88
105
  { values: numberingLayer(level), stops: "numbering" },
89
- ...explicit.slice(2)
106
+ ...explicit.slice(belowNumbering)
90
107
  ];
91
108
  const values = layers.reduce(
92
109
  (base, layer) => layerValues(base, layer.values),
@@ -107,8 +124,10 @@ function resolveParagraph(pPr, context, placement = null) {
107
124
  ...displayDefaults
108
125
  } = context.runDefaults;
109
126
  const styleRun = {
110
- ...displayDefaults,
111
- ...tableStyle?.run,
127
+ ...conditions.reduce(
128
+ (run, condition) => ({ ...run, ...condition.run }),
129
+ { ...displayDefaults, ...tableStyle?.run }
130
+ ),
112
131
  ...style?.run
113
132
  };
114
133
  return {
@@ -136,5 +155,6 @@ function inheritedRunFormat(rPr, paragraph, context) {
136
155
  export {
137
156
  inheritedRunFormat,
138
157
  resolveParagraph,
139
- resolveRun
158
+ resolveRun,
159
+ tableStyleFor
140
160
  };
@@ -1,9 +1,16 @@
1
1
  /** Reads and layers styles.xml values used by the display model. */
2
- import { type CellMargins, type InsideBorders, type ParagraphFormat, type RunFormat, type TableFormat } from "../../model/format";
2
+ import { type BandSizes, type CellMargins, type InsideBorders, type ParagraphFormat, type RunFormat, type TableFormat, type TableStyleOverrideType } from "../../model/format";
3
+ import type { NumberingStyleLinks } from "../../numbering/parseNumbering";
4
+ import { type ConditionalTableFormat } from "../tableFormatting";
3
5
  import { type ThemeFonts } from "../theme";
4
6
  import { type ParagraphFormatLayer } from "./tabStops";
7
+ /** The kind of object a style dresses (`w:style/@w:type`, ST_StyleType) */
8
+ export type StyleType = "paragraph" | "character" | "table" | "numbering";
9
+ /** What a table style dresses each part of a table with, by the part it covers */
10
+ export type TableStyleConditionFormats = Readonly<Partial<Record<TableStyleOverrideType, ConditionalTableFormat>>>;
5
11
  /** The display values one style passes down to paragraphs, to text, and to tables */
6
12
  export interface StyleFormat {
13
+ type: StyleType;
7
14
  paragraph: ParagraphFormatLayer;
8
15
  run: RunFormat;
9
16
  table: TableFormat;
@@ -11,6 +18,14 @@ export interface StyleFormat {
11
18
  tableInside: InsideBorders;
12
19
  /** The cell margins the style laid down. A side it says nothing about is null */
13
20
  tableCellMargins: CellMargins;
21
+ /** How many rows and columns one band of this table style is made of */
22
+ tableBands: BandSizes;
23
+ /**
24
+ * The conditional formats it wrote (`w:tblStylePr`). Each part is layered down the `basedOn`
25
+ * chain on its own, so a style that redresses the header row alone leaves the parts the styles
26
+ * it is based on dressed standing.
27
+ */
28
+ tableConditions: TableStyleConditionFormats;
14
29
  }
15
30
  /** A table looked up by style name. The `basedOn` chain has already been layered into the values */
16
31
  export type StyleTable = ReadonlyMap<string, StyleFormat>;
@@ -19,10 +34,18 @@ export declare const NO_STYLES: StyleTable;
19
34
  * Reads the style chain from styles.xml into effective values for display.
20
35
  *
21
36
  * A style name is unique across all kinds (paragraph, character, table), so we take them in
22
- * without telling the kinds apart.
37
+ * without telling the kinds apart, and each one remembers the kind it is.
23
38
  * The names a paragraph points at are only ever paragraph styles, so the other kinds are never looked up.
24
39
  */
25
40
  export declare function readStyles(styles: Document, themeFonts?: ThemeFonts): StyleTable;
41
+ /**
42
+ * The list each numbering style names.
43
+ *
44
+ * A numbering style carries its list in the `w:numPr` of its paragraph properties, and that is the
45
+ * only way a numbering definition deferring to a style (`w:numStyleLink`) finds the levels it
46
+ * draws. A style naming no list, or naming the empty list `w:numId` 0, links to nothing.
47
+ */
48
+ export declare function numberingStyleLinks(styles: StyleTable): NumberingStyleLinks;
26
49
  /**
27
50
  * The default table style, which for most documents is where a plain table gets its cell margins
28
51
  * and its lines from