@cj-tech-master/excelts 9.6.1 → 10.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/README.md +18 -3
  2. package/README_zh.md +18 -3
  3. package/dist/browser/modules/excel/cell.d.ts +4 -0
  4. package/dist/browser/modules/excel/note.js +5 -1
  5. package/dist/browser/modules/excel/row.js +35 -2
  6. package/dist/browser/modules/excel/stream/workbook-writer.browser.d.ts +8 -1
  7. package/dist/browser/modules/excel/stream/workbook-writer.browser.js +22 -2
  8. package/dist/browser/modules/excel/types.d.ts +81 -0
  9. package/dist/browser/modules/excel/utils/drawing-utils.d.ts +8 -0
  10. package/dist/browser/modules/excel/utils/drawing-utils.js +19 -2
  11. package/dist/browser/modules/excel/workbook.browser.d.ts +16 -0
  12. package/dist/browser/modules/excel/workbook.browser.js +32 -2
  13. package/dist/browser/modules/excel/worksheet.d.ts +31 -1
  14. package/dist/browser/modules/excel/worksheet.js +83 -0
  15. package/dist/browser/modules/excel/xlsx/xform/comment/vml-shape-xform.d.ts +7 -0
  16. package/dist/browser/modules/excel/xlsx/xform/comment/vml-shape-xform.js +42 -8
  17. package/dist/browser/modules/excel/xlsx/xform/core/content-types-xform.js +3 -1
  18. package/dist/browser/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +5 -0
  19. package/dist/browser/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +18 -1
  20. package/dist/browser/modules/excel/xlsx/xform/drawing/blip-xform.d.ts +6 -0
  21. package/dist/browser/modules/excel/xlsx/xform/drawing/blip-xform.js +38 -11
  22. package/dist/browser/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.d.ts +1 -0
  23. package/dist/browser/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.js +5 -0
  24. package/dist/browser/modules/excel/xlsx/xform/drawing/pic-xform.d.ts +2 -0
  25. package/dist/browser/modules/excel/xlsx/xform/drawing/pic-xform.js +2 -1
  26. package/dist/browser/modules/excel/xlsx/xform/drawing/shape-xform.d.ts +47 -0
  27. package/dist/browser/modules/excel/xlsx/xform/drawing/shape-xform.js +109 -0
  28. package/dist/browser/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +10 -1
  29. package/dist/browser/modules/excel/xlsx/xform/sheet/worksheet-xform.js +64 -1
  30. package/dist/browser/modules/pdf/builder/document-builder.js +22 -49
  31. package/dist/browser/modules/pdf/builder/pdf-editor.js +1 -1
  32. package/dist/browser/modules/pdf/core/pdf-stream.d.ts +28 -1
  33. package/dist/browser/modules/pdf/core/pdf-stream.js +38 -2
  34. package/dist/browser/modules/pdf/font/font-manager.d.ts +26 -0
  35. package/dist/browser/modules/pdf/font/font-manager.js +35 -18
  36. package/dist/browser/modules/pdf/render/page-renderer.d.ts +51 -3
  37. package/dist/browser/modules/pdf/render/page-renderer.js +111 -18
  38. package/dist/browser/modules/word/advanced/field-engine.js +45 -20
  39. package/dist/browser/modules/word/advanced/glossary.d.ts +10 -36
  40. package/dist/browser/modules/word/advanced/glossary.js +8 -9
  41. package/dist/browser/modules/word/advanced/math-convert.js +94 -12
  42. package/dist/browser/modules/word/advanced/ole-objects.d.ts +28 -0
  43. package/dist/browser/modules/word/advanced/ole-objects.js +122 -19
  44. package/dist/browser/modules/word/advanced/style-map.js +31 -10
  45. package/dist/browser/modules/word/builder/run-builders.d.ts +7 -1
  46. package/dist/browser/modules/word/builder/run-builders.js +7 -1
  47. package/dist/browser/modules/word/constants.d.ts +4 -0
  48. package/dist/browser/modules/word/constants.js +5 -1
  49. package/dist/browser/modules/word/convert/docx-to-semantic.d.ts +2 -1
  50. package/dist/browser/modules/word/convert/docx-to-semantic.js +135 -1
  51. package/dist/browser/modules/word/convert/html/html-import.d.ts +32 -1
  52. package/dist/browser/modules/word/convert/html/html-import.js +167 -14
  53. package/dist/browser/modules/word/convert/html/html.d.ts +2 -2
  54. package/dist/browser/modules/word/convert/html/html.js +1 -1
  55. package/dist/browser/modules/word/convert/markdown/markdown-import.d.ts +48 -18
  56. package/dist/browser/modules/word/convert/markdown/markdown-import.js +279 -69
  57. package/dist/browser/modules/word/convert/markdown/markdown.d.ts +1 -1
  58. package/dist/browser/modules/word/convert/odt/odt.js +407 -56
  59. package/dist/browser/modules/word/html.d.ts +2 -2
  60. package/dist/browser/modules/word/html.js +1 -1
  61. package/dist/browser/modules/word/index.base.d.ts +3 -3
  62. package/dist/browser/modules/word/index.base.js +1 -1
  63. package/dist/browser/modules/word/layout/layout-full.js +326 -19
  64. package/dist/browser/modules/word/layout/render-page.js +35 -8
  65. package/dist/browser/modules/word/markdown.d.ts +1 -1
  66. package/dist/browser/modules/word/query/compat.d.ts +10 -2
  67. package/dist/browser/modules/word/query/compat.js +29 -21
  68. package/dist/browser/modules/word/reader/docx-reader.js +105 -2
  69. package/dist/browser/modules/word/reader/math-parser.js +8 -2
  70. package/dist/browser/modules/word/security/cfb-reader.js +5 -5
  71. package/dist/browser/modules/word/types.d.ts +96 -1
  72. package/dist/browser/modules/word/writer/docx-packager.js +108 -2
  73. package/dist/browser/modules/word/writer/glossary-writer.d.ts +28 -0
  74. package/dist/browser/modules/word/writer/glossary-writer.js +121 -0
  75. package/dist/browser/modules/word/writer/header-footer-writer.js +105 -20
  76. package/dist/browser/modules/word/writer/math-writer.js +7 -2
  77. package/dist/browser/utils/font-metrics.d.ts +8 -0
  78. package/dist/browser/utils/font-metrics.js +43 -0
  79. package/dist/browser/utils/theme-colors.js +4 -1
  80. package/dist/cjs/modules/excel/note.js +5 -1
  81. package/dist/cjs/modules/excel/row.js +35 -2
  82. package/dist/cjs/modules/excel/stream/workbook-writer.browser.js +22 -2
  83. package/dist/cjs/modules/excel/utils/drawing-utils.js +19 -2
  84. package/dist/cjs/modules/excel/workbook.browser.js +31 -1
  85. package/dist/cjs/modules/excel/worksheet.js +83 -0
  86. package/dist/cjs/modules/excel/xlsx/xform/comment/vml-shape-xform.js +42 -8
  87. package/dist/cjs/modules/excel/xlsx/xform/core/content-types-xform.js +3 -1
  88. package/dist/cjs/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +5 -0
  89. package/dist/cjs/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +18 -1
  90. package/dist/cjs/modules/excel/xlsx/xform/drawing/blip-xform.js +38 -11
  91. package/dist/cjs/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.js +5 -0
  92. package/dist/cjs/modules/excel/xlsx/xform/drawing/pic-xform.js +2 -1
  93. package/dist/cjs/modules/excel/xlsx/xform/drawing/shape-xform.js +112 -0
  94. package/dist/cjs/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +10 -1
  95. package/dist/cjs/modules/excel/xlsx/xform/sheet/worksheet-xform.js +64 -1
  96. package/dist/cjs/modules/pdf/builder/document-builder.js +21 -48
  97. package/dist/cjs/modules/pdf/builder/pdf-editor.js +1 -1
  98. package/dist/cjs/modules/pdf/core/pdf-stream.js +38 -2
  99. package/dist/cjs/modules/pdf/font/font-manager.js +35 -18
  100. package/dist/cjs/modules/pdf/render/page-renderer.js +112 -18
  101. package/dist/cjs/modules/word/advanced/field-engine.js +45 -20
  102. package/dist/cjs/modules/word/advanced/glossary.js +8 -9
  103. package/dist/cjs/modules/word/advanced/math-convert.js +94 -12
  104. package/dist/cjs/modules/word/advanced/ole-objects.js +123 -19
  105. package/dist/cjs/modules/word/advanced/style-map.js +31 -10
  106. package/dist/cjs/modules/word/builder/run-builders.js +7 -1
  107. package/dist/cjs/modules/word/constants.js +5 -1
  108. package/dist/cjs/modules/word/convert/docx-to-semantic.js +135 -1
  109. package/dist/cjs/modules/word/convert/html/html-import.js +168 -14
  110. package/dist/cjs/modules/word/convert/html/html.js +2 -1
  111. package/dist/cjs/modules/word/convert/markdown/markdown-import.js +279 -69
  112. package/dist/cjs/modules/word/convert/odt/odt.js +407 -56
  113. package/dist/cjs/modules/word/html.js +2 -1
  114. package/dist/cjs/modules/word/index.base.js +4 -3
  115. package/dist/cjs/modules/word/layout/layout-full.js +325 -18
  116. package/dist/cjs/modules/word/layout/render-page.js +35 -8
  117. package/dist/cjs/modules/word/query/compat.js +29 -21
  118. package/dist/cjs/modules/word/reader/docx-reader.js +104 -1
  119. package/dist/cjs/modules/word/reader/math-parser.js +8 -2
  120. package/dist/cjs/modules/word/security/cfb-reader.js +5 -5
  121. package/dist/cjs/modules/word/writer/docx-packager.js +108 -2
  122. package/dist/cjs/modules/word/writer/glossary-writer.js +124 -0
  123. package/dist/cjs/modules/word/writer/header-footer-writer.js +105 -20
  124. package/dist/cjs/modules/word/writer/math-writer.js +7 -2
  125. package/dist/cjs/utils/font-metrics.js +44 -0
  126. package/dist/cjs/utils/theme-colors.js +4 -1
  127. package/dist/esm/modules/excel/note.js +5 -1
  128. package/dist/esm/modules/excel/row.js +35 -2
  129. package/dist/esm/modules/excel/stream/workbook-writer.browser.js +22 -2
  130. package/dist/esm/modules/excel/utils/drawing-utils.js +19 -2
  131. package/dist/esm/modules/excel/workbook.browser.js +32 -2
  132. package/dist/esm/modules/excel/worksheet.js +83 -0
  133. package/dist/esm/modules/excel/xlsx/xform/comment/vml-shape-xform.js +42 -8
  134. package/dist/esm/modules/excel/xlsx/xform/core/content-types-xform.js +3 -1
  135. package/dist/esm/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +5 -0
  136. package/dist/esm/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +18 -1
  137. package/dist/esm/modules/excel/xlsx/xform/drawing/blip-xform.js +38 -11
  138. package/dist/esm/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.js +5 -0
  139. package/dist/esm/modules/excel/xlsx/xform/drawing/pic-xform.js +2 -1
  140. package/dist/esm/modules/excel/xlsx/xform/drawing/shape-xform.js +109 -0
  141. package/dist/esm/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +10 -1
  142. package/dist/esm/modules/excel/xlsx/xform/sheet/worksheet-xform.js +64 -1
  143. package/dist/esm/modules/pdf/builder/document-builder.js +22 -49
  144. package/dist/esm/modules/pdf/builder/pdf-editor.js +1 -1
  145. package/dist/esm/modules/pdf/core/pdf-stream.js +38 -2
  146. package/dist/esm/modules/pdf/font/font-manager.js +35 -18
  147. package/dist/esm/modules/pdf/render/page-renderer.js +111 -18
  148. package/dist/esm/modules/word/advanced/field-engine.js +45 -20
  149. package/dist/esm/modules/word/advanced/glossary.js +8 -9
  150. package/dist/esm/modules/word/advanced/math-convert.js +94 -12
  151. package/dist/esm/modules/word/advanced/ole-objects.js +122 -19
  152. package/dist/esm/modules/word/advanced/style-map.js +31 -10
  153. package/dist/esm/modules/word/builder/run-builders.js +7 -1
  154. package/dist/esm/modules/word/constants.js +5 -1
  155. package/dist/esm/modules/word/convert/docx-to-semantic.js +135 -1
  156. package/dist/esm/modules/word/convert/html/html-import.js +167 -14
  157. package/dist/esm/modules/word/convert/html/html.js +1 -1
  158. package/dist/esm/modules/word/convert/markdown/markdown-import.js +279 -69
  159. package/dist/esm/modules/word/convert/odt/odt.js +407 -56
  160. package/dist/esm/modules/word/html.js +1 -1
  161. package/dist/esm/modules/word/index.base.js +1 -1
  162. package/dist/esm/modules/word/layout/layout-full.js +326 -19
  163. package/dist/esm/modules/word/layout/render-page.js +35 -8
  164. package/dist/esm/modules/word/query/compat.js +29 -21
  165. package/dist/esm/modules/word/reader/docx-reader.js +105 -2
  166. package/dist/esm/modules/word/reader/math-parser.js +8 -2
  167. package/dist/esm/modules/word/security/cfb-reader.js +5 -5
  168. package/dist/esm/modules/word/writer/docx-packager.js +108 -2
  169. package/dist/esm/modules/word/writer/glossary-writer.js +121 -0
  170. package/dist/esm/modules/word/writer/header-footer-writer.js +105 -20
  171. package/dist/esm/modules/word/writer/math-writer.js +7 -2
  172. package/dist/esm/utils/font-metrics.js +43 -0
  173. package/dist/esm/utils/theme-colors.js +4 -1
  174. package/dist/iife/excelts.iife.js +496 -59
  175. package/dist/iife/excelts.iife.js.map +1 -1
  176. package/dist/iife/excelts.iife.min.js +39 -39
  177. package/dist/types/modules/excel/cell.d.ts +4 -0
  178. package/dist/types/modules/excel/stream/workbook-writer.browser.d.ts +8 -1
  179. package/dist/types/modules/excel/types.d.ts +81 -0
  180. package/dist/types/modules/excel/utils/drawing-utils.d.ts +8 -0
  181. package/dist/types/modules/excel/workbook.browser.d.ts +16 -0
  182. package/dist/types/modules/excel/worksheet.d.ts +31 -1
  183. package/dist/types/modules/excel/xlsx/xform/comment/vml-shape-xform.d.ts +7 -0
  184. package/dist/types/modules/excel/xlsx/xform/drawing/blip-xform.d.ts +6 -0
  185. package/dist/types/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.d.ts +1 -0
  186. package/dist/types/modules/excel/xlsx/xform/drawing/pic-xform.d.ts +2 -0
  187. package/dist/types/modules/excel/xlsx/xform/drawing/shape-xform.d.ts +47 -0
  188. package/dist/types/modules/pdf/core/pdf-stream.d.ts +28 -1
  189. package/dist/types/modules/pdf/font/font-manager.d.ts +26 -0
  190. package/dist/types/modules/pdf/render/page-renderer.d.ts +51 -3
  191. package/dist/types/modules/word/advanced/glossary.d.ts +10 -36
  192. package/dist/types/modules/word/advanced/ole-objects.d.ts +28 -0
  193. package/dist/types/modules/word/builder/run-builders.d.ts +7 -1
  194. package/dist/types/modules/word/constants.d.ts +4 -0
  195. package/dist/types/modules/word/convert/docx-to-semantic.d.ts +2 -1
  196. package/dist/types/modules/word/convert/html/html-import.d.ts +32 -1
  197. package/dist/types/modules/word/convert/html/html.d.ts +2 -2
  198. package/dist/types/modules/word/convert/markdown/markdown-import.d.ts +48 -18
  199. package/dist/types/modules/word/convert/markdown/markdown.d.ts +1 -1
  200. package/dist/types/modules/word/html.d.ts +2 -2
  201. package/dist/types/modules/word/index.base.d.ts +3 -3
  202. package/dist/types/modules/word/markdown.d.ts +1 -1
  203. package/dist/types/modules/word/query/compat.d.ts +10 -2
  204. package/dist/types/modules/word/types.d.ts +96 -1
  205. package/dist/types/modules/word/writer/glossary-writer.d.ts +28 -0
  206. package/dist/types/utils/font-metrics.d.ts +8 -0
  207. package/package.json +3 -1
@@ -20,6 +20,7 @@ exports.layoutDocumentFull = layoutDocumentFull;
20
20
  const font_metrics_1 = require("../../../utils/font-metrics.js");
21
21
  const math_convert_1 = require("../advanced/math-convert");
22
22
  const text_utils_1 = require("../core/text-utils");
23
+ const style_resolve_1 = require("../query/style-resolve");
23
24
  const units_1 = require("../units");
24
25
  const layout_1 = require("./layout");
25
26
  const layout_constants_1 = require("./layout-constants");
@@ -33,6 +34,33 @@ const layout_constants_1 = require("./layout-constants");
33
34
  function layoutDocumentFull(doc, options) {
34
35
  // First pass: get page assignments via the existing lightweight layout
35
36
  const layoutResult = (0, layout_1.layoutDocument)(doc, options);
37
+ // Resolve list markers once over the whole document so ordered-list
38
+ // counters increment correctly across pages. Stored in a module-level
39
+ // context so that every `layoutParagraph` call — including those reached
40
+ // through tables, text boxes, SDTs, footnotes, etc. — can render markers
41
+ // without threading the map through every container function. Layout runs
42
+ // fully synchronously (no `await`), so a single shared slot is safe.
43
+ const listMarkers = computeListMarkers(doc);
44
+ activeListMarkers = listMarkers;
45
+ activeDoc = doc;
46
+ try {
47
+ return layoutDocumentFullInner(doc, options, layoutResult, listMarkers);
48
+ }
49
+ finally {
50
+ activeListMarkers = undefined;
51
+ activeDoc = undefined;
52
+ }
53
+ }
54
+ /** Active list-marker map for the in-flight layout (see layoutDocumentFull). */
55
+ let activeListMarkers;
56
+ /**
57
+ * Active document for the in-flight layout, so `layoutParagraph` can resolve
58
+ * paragraph-style run properties (size/color/font) via `resolveStyle` without
59
+ * threading `doc` through every container function. Layout is synchronous so a
60
+ * single shared slot is safe.
61
+ */
62
+ let activeDoc;
63
+ function layoutDocumentFullInner(doc, options, layoutResult, listMarkers) {
36
64
  // Second pass: compute precise positions for each page. Footnote
37
65
  // ids that don't fit on a given page are carried over to the next
38
66
  // (a later page may still have room thanks to less body content
@@ -41,7 +69,7 @@ function layoutDocumentFull(doc, options) {
41
69
  const bodyPageCount = layoutResult.pageCount;
42
70
  let pendingFootnoteIds = [];
43
71
  for (let pageNum = 1; pageNum <= bodyPageCount; pageNum++) {
44
- const result = buildPage(doc, pageNum, layoutResult, options, pendingFootnoteIds);
72
+ const result = buildPage(doc, pageNum, layoutResult, options, pendingFootnoteIds, listMarkers);
45
73
  pages.push(result.page);
46
74
  pendingFootnoteIds = result.deferredFootnoteIds;
47
75
  }
@@ -55,7 +83,7 @@ function layoutDocumentFull(doc, options) {
55
83
  // entries for already-placed body items still point at earlier
56
84
  // pages, so the synthetic page won't pick up extra body
57
85
  // content; only the carried footnote queue renders.
58
- layoutResult, options, pendingFootnoteIds);
86
+ layoutResult, options, pendingFootnoteIds, listMarkers);
59
87
  pages.push(overflowResult.page);
60
88
  }
61
89
  return {
@@ -173,7 +201,7 @@ function availableSlotForLine(ctx, lineY, lineHeight) {
173
201
  function twipsToPt(twips) {
174
202
  return twips / 20;
175
203
  }
176
- function buildPage(doc, pageNumber, layout, options, pendingFootnoteIds) {
204
+ function buildPage(doc, pageNumber, layout, options, pendingFootnoteIds, listMarkers) {
177
205
  const sectionProps = doc.sectionProperties;
178
206
  const geometry = computePageGeometry(sectionProps, options?.pageGeometry);
179
207
  const content = [];
@@ -208,7 +236,7 @@ function buildPage(doc, pageNumber, layout, options, pendingFootnoteIds) {
208
236
  };
209
237
  switch (item.type) {
210
238
  case "paragraph": {
211
- const laid = layoutParagraph(item, cursorY, geometry.contentWidth, options, pageContext, imageMap);
239
+ const laid = layoutParagraph(item, cursorY, geometry.contentWidth, options, pageContext, imageMap, listMarkers);
212
240
  content.push({ ...laid, sourceIndex: i });
213
241
  cursorY = laid.rect.y + laid.rect.height;
214
242
  break;
@@ -701,13 +729,219 @@ function computeSectionBreaks(layout) {
701
729
  }
702
730
  return breaks;
703
731
  }
704
- // =============================================================================
705
- // Internal: Paragraph Layout
706
- // =============================================================================
707
- function layoutParagraph(para, startY, contentWidth, options, pageContext, imageMap) {
732
+ /**
733
+ * Resolve list markers for every numbered / bulleted paragraph in the
734
+ * document, in reading order, so ordered-list counters increment correctly
735
+ * across paragraphs (and reset when a lower level reappears). Returns a map
736
+ * keyed by the paragraph object.
737
+ *
738
+ * Markers are derived from `paragraph.properties.numbering` → the matching
739
+ * `NumberingInstance` → its `AbstractNumbering` level definition. Bullet
740
+ * levels emit their symbol; ordered levels emit a counter formatted per the
741
+ * level's `NumberFormat` (decimal / lower-upper letter / lower-upper roman),
742
+ * falling back to decimal for formats we don't render numerically.
743
+ */
744
+ function computeListMarkers(doc) {
745
+ const markers = new Map();
746
+ const instances = doc.numberingInstances;
747
+ const abstracts = doc.abstractNumberings;
748
+ if (!instances || !abstracts || instances.length === 0 || abstracts.length === 0) {
749
+ return markers;
750
+ }
751
+ const instById = new Map(instances.map(n => [n.numId, n]));
752
+ const absById = new Map(abstracts.map(a => [a.abstractNumId, a]));
753
+ // Per (numId) counters, one slot per level. Counters reset at deeper
754
+ // levels when a shallower level advances.
755
+ const counters = new Map();
756
+ // numIds whose list was interrupted by non-list content since their last
757
+ // item; the next item with that numId restarts its numbering. This makes
758
+ // two visually separate ordered lists (sharing a numId, separated by a
759
+ // plain paragraph) each start at 1 — matching user expectation rather than
760
+ // running a single continuous sequence.
761
+ const interrupted = new Set();
762
+ // numIds seen at least once, so we know which to mark interrupted.
763
+ const seenNumIds = new Set();
764
+ // Flatten paragraphs into document reading order (descending into tables),
765
+ // so list continuity is judged across the whole body, not per-cell.
766
+ const orderedParagraphs = [];
767
+ const walk = (items) => {
768
+ for (const item of items) {
769
+ if (item.type === "paragraph") {
770
+ orderedParagraphs.push(item);
771
+ }
772
+ else if (item.type === "table") {
773
+ for (const row of item.rows) {
774
+ for (const cell of row.cells) {
775
+ walk(cell.content);
776
+ }
777
+ }
778
+ }
779
+ }
780
+ };
781
+ const resolveParagraphMarker = (para) => {
782
+ const numbering = para.properties?.numbering;
783
+ if (!numbering) {
784
+ // Non-list paragraph: any list seen so far is now interrupted, so a
785
+ // later paragraph reusing the same numId restarts its sequence.
786
+ for (const id of seenNumIds) {
787
+ interrupted.add(id);
788
+ }
789
+ return;
790
+ }
791
+ const inst = instById.get(numbering.numId);
792
+ if (!inst) {
793
+ return;
794
+ }
795
+ const abs = absById.get(inst.abstractNumId);
796
+ if (!abs) {
797
+ return;
798
+ }
799
+ const level = numbering.level ?? 0;
800
+ const levelDef = inst.overrides?.find(o => o.level === level)?.levelDef ??
801
+ abs.levels.find(l => l.level === level);
802
+ if (!levelDef) {
803
+ return;
804
+ }
805
+ seenNumIds.add(numbering.numId);
806
+ const indentPt = (level + 1) * 36; // 0.5" per level
807
+ if (levelDef.format === "bullet") {
808
+ // Bullet symbol. Word authors bullets with Symbol/Wingdings private-use
809
+ // code points (e.g. U+F0B7 ·, U+F0A7 ▪) that PDF standard fonts can't
810
+ // render. Normalize the common ones to WinAnsi-renderable equivalents;
811
+ // fall back to a round bullet when empty or unknown.
812
+ const symbol = normalizeBulletGlyph(levelDef.text);
813
+ markers.set(para, { text: `${symbol} `, indentPt });
814
+ // A bullet item does not clear the interruption flag for ordered
815
+ // siblings, but it is itself a list item — keep it out of `interrupted`.
816
+ interrupted.delete(numbering.numId);
817
+ return;
818
+ }
819
+ // Ordered list: advance this level's counter and reset deeper levels.
820
+ let levelCounts = counters.get(numbering.numId);
821
+ if (!levelCounts) {
822
+ levelCounts = [];
823
+ counters.set(numbering.numId, levelCounts);
824
+ }
825
+ // If this numId's run was interrupted by non-list content, restart it.
826
+ if (interrupted.has(numbering.numId)) {
827
+ levelCounts.length = 0;
828
+ interrupted.delete(numbering.numId);
829
+ }
830
+ const startOverride = inst.overrides?.find(o => o.level === level)?.startOverride;
831
+ const start = startOverride ?? levelDef.start ?? 1;
832
+ if (levelCounts[level] === undefined) {
833
+ levelCounts[level] = start;
834
+ }
835
+ else {
836
+ levelCounts[level] += 1;
837
+ }
838
+ // Reset any deeper levels.
839
+ for (let l = level + 1; l < levelCounts.length; l++) {
840
+ levelCounts[l] = undefined;
841
+ }
842
+ const counter = levelCounts[level];
843
+ const numeral = formatListCounter(counter, levelDef.format);
844
+ // Honour the level's `text` template (e.g. "%1.") when present; else
845
+ // fall back to "<n>.".
846
+ const text = levelDef.text ? levelDef.text.replace(/%\d+/g, numeral) : `${numeral}.`;
847
+ markers.set(para, { text: `${text} `, indentPt });
848
+ };
849
+ walk(doc.body);
850
+ for (const para of orderedParagraphs) {
851
+ resolveParagraphMarker(para);
852
+ }
853
+ return markers;
854
+ }
855
+ /** Normalize a Word bullet glyph to a WinAnsi-renderable equivalent. */
856
+ function normalizeBulletGlyph(text) {
857
+ if (!text || text.length === 0) {
858
+ return "\u2022"; // round bullet
859
+ }
860
+ const cp = text.codePointAt(0);
861
+ switch (cp) {
862
+ // Symbol-font private-use code points Word emits for default bullets.
863
+ case 0xf0b7: // Symbol "·" → round bullet
864
+ case 0x00b7: // middle dot
865
+ return "\u2022";
866
+ case 0xf0a7: // Symbol filled small square
867
+ case 0xf0a8:
868
+ return "\u25aa";
869
+ case 0xf0fc: // Wingdings check
870
+ return "\u2713";
871
+ default:
872
+ // Already a renderable glyph (e.g. "o", "-", "•") — keep it.
873
+ return text;
874
+ }
875
+ }
876
+ /** Format an ordered-list counter per its OOXML number format. */
877
+ function formatListCounter(n, format) {
878
+ switch (format) {
879
+ case "lowerLetter":
880
+ return toAlpha(n).toLowerCase();
881
+ case "upperLetter":
882
+ return toAlpha(n).toUpperCase();
883
+ case "lowerRoman":
884
+ return toRoman(n).toLowerCase();
885
+ case "upperRoman":
886
+ return toRoman(n).toUpperCase();
887
+ case "decimalZero":
888
+ return n < 10 ? `0${n}` : String(n);
889
+ default:
890
+ // decimal and any non-numeric/locale formats we don't render.
891
+ return String(n);
892
+ }
893
+ }
894
+ /** 1 → "A", 26 → "Z", 27 → "AA" (spreadsheet-style alpha). */
895
+ function toAlpha(n) {
896
+ let s = "";
897
+ let v = n;
898
+ while (v > 0) {
899
+ const rem = (v - 1) % 26;
900
+ s = String.fromCharCode(65 + rem) + s;
901
+ v = Math.floor((v - 1) / 26);
902
+ }
903
+ return s || "A";
904
+ }
905
+ /** Convert a positive integer to a Roman numeral (uppercase). */
906
+ function toRoman(n) {
907
+ if (n <= 0) {
908
+ return String(n);
909
+ }
910
+ const table = [
911
+ [1000, "M"],
912
+ [900, "CM"],
913
+ [500, "D"],
914
+ [400, "CD"],
915
+ [100, "C"],
916
+ [90, "XC"],
917
+ [50, "L"],
918
+ [40, "XL"],
919
+ [10, "X"],
920
+ [9, "IX"],
921
+ [5, "V"],
922
+ [4, "IV"],
923
+ [1, "I"]
924
+ ];
925
+ let v = n;
926
+ let s = "";
927
+ for (const [val, sym] of table) {
928
+ while (v >= val) {
929
+ s += sym;
930
+ v -= val;
931
+ }
932
+ }
933
+ return s;
934
+ }
935
+ function layoutParagraph(para, startY, contentWidth, options, pageContext, imageMap, listMarkers) {
708
936
  const props = para.properties;
709
937
  const spacing = props?.spacing;
710
- const headingScale = getHeadingFontScale(getHeadingLevel(props));
938
+ // Resolve effective run properties from the paragraph's style chain. When
939
+ // the style supplies a concrete font size we honour it; only when it does
940
+ // not do we fall back to the heuristic heading scale so headings stay
941
+ // distinct in documents lacking a styles table.
942
+ const styleRunProps = activeDoc ? (0, style_resolve_1.resolveStyle)(activeDoc, para).runProperties : undefined;
943
+ const styleHasSize = styleRunProps?.size != null;
944
+ const headingScale = styleHasSize ? 1 : getHeadingFontScale(getHeadingLevel(props));
711
945
  // Space before
712
946
  let spaceBefore = 0;
713
947
  if (spacing?.beforeAutoSpacing) {
@@ -717,7 +951,15 @@ function layoutParagraph(para, startY, contentWidth, options, pageContext, image
717
951
  spaceBefore = twipsToPt(spacing.before);
718
952
  }
719
953
  const indent = props?.indent;
720
- const leftIndentPt = indent?.left ? twipsToPt(indent.left) : 0;
954
+ // Prefer an explicitly threaded map; fall back to the active layout's
955
+ // shared map so list markers also render inside tables, text boxes, SDTs,
956
+ // footnotes, etc. (whose layoutParagraph calls don't thread it through).
957
+ const marker = (listMarkers ?? activeListMarkers)?.get(para);
958
+ // List paragraphs are indented by their numbering level; the marker text
959
+ // is injected as a leading run below. An explicit paragraph indent (rare on
960
+ // list items) still wins when larger.
961
+ const markerIndentPt = marker ? marker.indentPt : 0;
962
+ const leftIndentPt = Math.max(indent?.left ? twipsToPt(indent.left) : 0, markerIndentPt);
721
963
  const firstLineIndentPt = indent?.firstLine ? twipsToPt(indent.firstLine) : 0;
722
964
  const alignment = props?.alignment ?? "left";
723
965
  // Line height
@@ -738,7 +980,20 @@ function layoutParagraph(para, startY, contentWidth, options, pageContext, image
738
980
  }
739
981
  lineHeightPt *= headingScale;
740
982
  // Collect runs
741
- const segments = collectParagraphSegments(para);
983
+ const segments = mergeStyleRunProps(collectParagraphSegments(para), styleRunProps);
984
+ // Inject the list marker (bullet / number) as a leading text run so it
985
+ // renders inline at the start of the first line, inheriting the first
986
+ // text run's formatting (font / size) for visual consistency.
987
+ if (marker) {
988
+ let firstRunProps;
989
+ for (const s of segments) {
990
+ if (!("type" in s) || s.type === undefined) {
991
+ firstRunProps = s.properties;
992
+ break;
993
+ }
994
+ }
995
+ segments.unshift({ text: marker.text, properties: firstRunProps });
996
+ }
742
997
  const fullAvailableWidth = contentWidth - leftIndentPt;
743
998
  // When a page has wrap exclusions (square / tight / through floats)
744
999
  // we wrap line-by-line, asking the page context for the widest free
@@ -787,7 +1042,7 @@ function layoutParagraph(para, startY, contentWidth, options, pageContext, image
787
1042
  }
788
1043
  else {
789
1044
  const fontSize = getRunFontSizePt(seg.properties) * headingScale;
790
- const fontName = (0, font_metrics_1.mapToStandardFont)(resolveRunFontName(seg.properties));
1045
+ const fontName = (0, font_metrics_1.styledFontVariant)(resolveRunFontName(seg.properties), seg.properties?.bold, seg.properties?.italic);
791
1046
  lineWidth += (0, font_metrics_1.measureTextWidth)(seg.text, fontName, fontSize);
792
1047
  }
793
1048
  }
@@ -818,7 +1073,7 @@ function layoutParagraph(para, startY, contentWidth, options, pageContext, image
818
1073
  }
819
1074
  const fontSize = getRunFontSizePt(seg.properties) * headingScale;
820
1075
  const fontName = resolveRunFontName(seg.properties);
821
- const measuredFont = (0, font_metrics_1.mapToStandardFont)(fontName);
1076
+ const measuredFont = (0, font_metrics_1.styledFontVariant)(fontName, seg.properties?.bold, seg.properties?.italic);
822
1077
  const segWidth = (0, font_metrics_1.measureTextWidth)(seg.text, measuredFont, fontSize);
823
1078
  runs.push({
824
1079
  text: seg.text,
@@ -936,7 +1191,8 @@ function layoutTable(table, startY, contentWidth, sourceIndex, options, imageMap
936
1191
  rect: { x: cellX, y: startY + cursorY, width: cellWidth, height: cellHeight },
937
1192
  row: ri,
938
1193
  col: ci,
939
- content: cellContent
1194
+ content: cellContent,
1195
+ borders: resolveCellBorders(table.properties?.borders, cell.properties?.borders, ri === 0, ri === table.rows.length - 1, startCol === 0, endCol >= colWidths.length)
940
1196
  });
941
1197
  gridCol += span;
942
1198
  }
@@ -955,6 +1211,38 @@ function layoutTable(table, startY, contentWidth, sourceIndex, options, imageMap
955
1211
  sourceIndex
956
1212
  };
957
1213
  }
1214
+ /**
1215
+ * Resolve the four visible borders of a table cell into layout-model form
1216
+ * (`{ width: pt, color: hex }`). A cell's own border wins; otherwise the
1217
+ * table-level border applies — outer edges use `top/left/bottom/right`, inner
1218
+ * edges use `insideH/insideV`. OOXML border `size` is in eighths of a point.
1219
+ */
1220
+ function resolveCellBorders(tableBorders, cellBorders, isTopRow, isBottomRow, isLeftCol, isRightCol) {
1221
+ const edge = (cellEdge, outerEdge, innerEdge, isOuter) => {
1222
+ const b = cellEdge ?? (isOuter ? outerEdge : innerEdge);
1223
+ if (!b || b.style === "none" || b.style === "nil") {
1224
+ return undefined;
1225
+ }
1226
+ // `size` is in eighths of a point; default to a hairline (0.5pt) when
1227
+ // a border is declared without an explicit size.
1228
+ const width = b.size != null ? b.size / 8 : 0.5;
1229
+ const color = !b.color || b.color === "auto" ? "000000" : b.color;
1230
+ return { width: Math.max(0.25, width), color };
1231
+ };
1232
+ const top = edge(cellBorders?.top, tableBorders?.top, tableBorders?.insideH, isTopRow);
1233
+ const bottom = edge(cellBorders?.bottom, tableBorders?.bottom, tableBorders?.insideH, isBottomRow);
1234
+ const left = edge(cellBorders?.left, tableBorders?.left, tableBorders?.insideV, isLeftCol);
1235
+ const right = edge(cellBorders?.right, tableBorders?.right, tableBorders?.insideV, isRightCol);
1236
+ if (!top && !bottom && !left && !right) {
1237
+ return undefined;
1238
+ }
1239
+ return {
1240
+ ...(top ? { top } : {}),
1241
+ ...(bottom ? { bottom } : {}),
1242
+ ...(left ? { left } : {}),
1243
+ ...(right ? { right } : {})
1244
+ };
1245
+ }
958
1246
  /**
959
1247
  * Resolve a table's per-column widths in points.
960
1248
  *
@@ -1004,6 +1292,21 @@ function collectParagraphSegments(para) {
1004
1292
  }
1005
1293
  return segments;
1006
1294
  }
1295
+ /**
1296
+ * Overlay resolved paragraph-style run properties under each segment's own
1297
+ * (inline) properties, so style-defined size/color/font apply when a run does
1298
+ * not override them. Inline run properties always win.
1299
+ */
1300
+ function mergeStyleRunProps(segments, styleRunProps) {
1301
+ if (!styleRunProps) {
1302
+ return segments;
1303
+ }
1304
+ return segments.map(seg => {
1305
+ const own = seg.properties;
1306
+ const merged = own ? { ...styleRunProps, ...own } : styleRunProps;
1307
+ return { ...seg, properties: merged };
1308
+ });
1309
+ }
1007
1310
  /**
1008
1311
  * Emit `ParagraphSegment` tokens for a single run, preserving the
1009
1312
  * relative order of text fragments and inline images. Consecutive
@@ -1059,7 +1362,7 @@ function wrapSegmentsToLinesWithExclusions(segments, leftIndentPt, firstLineInde
1059
1362
  continue;
1060
1363
  }
1061
1364
  const fontSize = getRunFontSizePt(seg.properties) * headingScale;
1062
- const fontName = (0, font_metrics_1.mapToStandardFont)(resolveRunFontName(seg.properties));
1365
+ const fontName = (0, font_metrics_1.styledFontVariant)(resolveRunFontName(seg.properties), seg.properties?.bold, seg.properties?.italic);
1063
1366
  // Split on runs of whitespace, keeping the whitespace tokens so
1064
1367
  // wrapping can decide whether to drop trailing space at line end.
1065
1368
  const tokens = seg.text.split(/(\s+)/);
@@ -1214,14 +1517,18 @@ function wrapSegmentsToLines(segments, availableWidth, firstLineIndent, headingS
1214
1517
  }
1215
1518
  const text = segment.text;
1216
1519
  const fontSize = getRunFontSizePt(segment.properties) * headingScale;
1217
- const fontName = (0, font_metrics_1.mapToStandardFont)(resolveRunFontName(segment.properties));
1520
+ const fontName = (0, font_metrics_1.styledFontVariant)(resolveRunFontName(segment.properties), segment.properties?.bold, segment.properties?.italic);
1218
1521
  const segmentWidth = (0, font_metrics_1.measureTextWidth)(text, fontName, fontSize);
1219
- if (currentLineWidth + segmentWidth <= effectiveWidth || currentLine.length === 0) {
1522
+ if (currentLineWidth + segmentWidth <= effectiveWidth) {
1523
+ // Whole segment fits on the current line — fast path.
1220
1524
  currentLine.push(segment);
1221
1525
  currentLineWidth += segmentWidth;
1222
1526
  }
1223
1527
  else {
1224
- // Word-level splitting
1528
+ // Segment does not fit — split it into words and wrap. The inner
1529
+ // loop's `currentLine.length === 0 && bufferedText.length === 0`
1530
+ // guard guarantees at least one word per line (preventing a dead
1531
+ // loop when even a single word is wider than the line).
1225
1532
  const words = text.split(/(\s+)/);
1226
1533
  let bufferedText = "";
1227
1534
  let bufferedWidth = 0;
@@ -15,6 +15,7 @@ exports.renderPageFromLayout = renderPageFromLayout;
15
15
  const font_metrics_1 = require("../../../utils/font-metrics.js");
16
16
  const encode_1 = require("../../xml/encode.js");
17
17
  const text_utils_1 = require("../core/text-utils");
18
+ const style_resolve_1 = require("../query/style-resolve");
18
19
  const units_1 = require("../units");
19
20
  const layout_1 = require("./layout");
20
21
  const layout_constants_1 = require("./layout-constants");
@@ -194,7 +195,18 @@ function renderParagraph(para, state) {
194
195
  const props = para.properties;
195
196
  const spacing = props?.spacing;
196
197
  const headingLevel = getHeadingLevel(props);
197
- const headingScale = getHeadingFontScale(headingLevel);
198
+ // Resolve the paragraph's effective run properties from the style chain
199
+ // (Heading1 → … → docDefaults). When the style provides a concrete font
200
+ // size we honour it directly; only when no style size is available do we
201
+ // fall back to the heuristic heading scale so headings still look distinct
202
+ // in documents that lack a styles table.
203
+ const styleRunProps = (0, style_resolve_1.resolveStyle)(state.doc, para).runProperties;
204
+ const styleHasSize = styleRunProps.size != null;
205
+ const fallbackScale = getHeadingFontScale(headingLevel);
206
+ const headingScale = styleHasSize ? 1 : fallbackScale;
207
+ // Synthesise bold for headings only in fallback mode (no real style size);
208
+ // otherwise the style's own bold flag governs.
209
+ const synthesizeHeadingBold = headingLevel > 0 && !styleHasSize;
198
210
  // Space before
199
211
  let spaceBefore = 0;
200
212
  if (spacing?.beforeAutoSpacing) {
@@ -236,7 +248,7 @@ function renderParagraph(para, state) {
236
248
  // Apply heading scale to line height
237
249
  lineHeightPt *= headingScale;
238
250
  // Collect runs and render as text spans on lines
239
- const runs = collectParagraphRuns(para);
251
+ const runs = collectParagraphRuns(para, styleRunProps);
240
252
  if (runs.length === 0) {
241
253
  // Empty paragraph — advance by line height
242
254
  state.cursorY += lineHeightPt;
@@ -266,7 +278,7 @@ function renderParagraph(para, state) {
266
278
  for (const segment of line) {
267
279
  const fontSize = getRunFontSizePt(segment.properties) * headingScale;
268
280
  const fontFamily = resolveFontFamily(getRunFontName({ properties: segment.properties, content: [] }), state.fontsMap);
269
- const isBold = segment.properties?.bold || headingLevel > 0;
281
+ const isBold = segment.properties?.bold || synthesizeHeadingBold;
270
282
  const isItalic = segment.properties?.italic;
271
283
  const color = resolveColor(segment.properties?.color);
272
284
  const underline = segment.properties?.underline;
@@ -318,20 +330,32 @@ function renderParagraph(para, state) {
318
330
  state.cursorY += spaceAfter;
319
331
  }
320
332
  /** Collect all text segments from a paragraph's children. */
321
- function collectParagraphRuns(para) {
333
+ function collectParagraphRuns(para, styleRunProps) {
334
+ // Merge the resolved paragraph-style run properties as a fallback under each
335
+ // run's own (inline) properties, so style-defined size/color/font apply when
336
+ // the run does not override them.
337
+ const merge = (own) => {
338
+ if (!styleRunProps) {
339
+ return own;
340
+ }
341
+ if (!own) {
342
+ return styleRunProps;
343
+ }
344
+ return { ...styleRunProps, ...own };
345
+ };
322
346
  const segments = [];
323
347
  for (const child of para.children) {
324
348
  if ((0, text_utils_1.isRun)(child)) {
325
349
  const text = getRunText(child);
326
350
  if (text.length > 0) {
327
- segments.push({ text, properties: child.properties });
351
+ segments.push({ text, properties: merge(child.properties) });
328
352
  }
329
353
  }
330
354
  else if ((0, text_utils_1.isHyperlink)(child)) {
331
355
  for (const run of child.children) {
332
356
  const text = getRunText(run);
333
357
  if (text.length > 0) {
334
- segments.push({ text, properties: run.properties });
358
+ segments.push({ text, properties: merge(run.properties) });
335
359
  }
336
360
  }
337
361
  }
@@ -523,8 +547,11 @@ function renderTable(table, state) {
523
547
  let cellCursorY = rowStartY + cellPadding;
524
548
  for (const content of cell.content) {
525
549
  if (content.type === "paragraph") {
526
- // Simplified: render first run of each paragraph
527
- const runs = collectParagraphRuns(content);
550
+ // Simplified: render first run of each paragraph, with the
551
+ // paragraph style's run properties as a fallback so styled cell
552
+ // text (e.g. a heading paragraph) honours its size/colour/font.
553
+ const cellStyleRunProps = (0, style_resolve_1.resolveStyle)(state.doc, content).runProperties;
554
+ const runs = collectParagraphRuns(content, cellStyleRunProps);
528
555
  if (runs.length > 0) {
529
556
  const allText = runs.map(r => r.text).join("");
530
557
  const firstRun = runs[0];
@@ -13,21 +13,29 @@ exports.setCompatibilityMode = setCompatibilityMode;
13
13
  /**
14
14
  * Get the compatibility mode of a document.
15
15
  *
16
- * Looks at the `compatSetting` named "compatibilityMode" in document settings.
17
- * Returns 15 (Word 2013+) by default if not found.
16
+ * The mode is stored in `settings.compatibilityMode` (the canonical scalar
17
+ * field populated by the reader). For backward compatibility we also honour an
18
+ * explicit `compatibilityMode` entry in `settings.compatSettings` if present,
19
+ * since the writer accepts that advanced-override path.
20
+ *
21
+ * Returns 15 (Word 2013+) by default if nothing is stored.
18
22
  *
19
23
  * @param doc - The document to inspect.
20
24
  * @returns The compatibility mode version number.
21
25
  */
22
26
  function getCompatibilityMode(doc) {
23
- if (!doc.settings?.compatSettings) {
27
+ const settings = doc.settings;
28
+ if (!settings) {
24
29
  return 15;
25
30
  }
26
- const modeSetting = doc.settings.compatSettings.find(s => s.name === "compatibilityMode");
27
- if (!modeSetting?.val) {
31
+ // Prefer an explicit override entry in compatSettings (advanced path) so a
32
+ // hand-authored value wins, then fall back to the canonical scalar field.
33
+ const overrideEntry = settings.compatSettings?.find(s => s.name === "compatibilityMode");
34
+ const raw = overrideEntry?.val ?? settings.compatibilityMode;
35
+ if (raw === undefined) {
28
36
  return 15;
29
37
  }
30
- const n = parseInt(modeSetting.val, 10);
38
+ const n = typeof raw === "number" ? raw : parseInt(raw, 10);
31
39
  if (n === 11 || n === 12 || n === 14 || n === 15) {
32
40
  return n;
33
41
  }
@@ -36,26 +44,26 @@ function getCompatibilityMode(doc) {
36
44
  /**
37
45
  * Set the compatibility mode of a document (mutates settings in place).
38
46
  *
47
+ * Writes the canonical `settings.compatibilityMode` scalar field and removes
48
+ * any stale `compatibilityMode` override entry from `settings.compatSettings`
49
+ * so the two sources never disagree.
50
+ *
39
51
  * @param doc - The document to modify (mutated in place).
40
52
  * @param mode - The target compatibility mode (11=Word 2003, 12=Word 2007, 14=Word 2010, 15=Word 2013+).
41
53
  */
42
54
  function setCompatibilityMode(doc, mode) {
43
55
  const settings = doc.settings ? { ...doc.settings } : {};
44
- const compatSettings = settings.compatSettings
45
- ? [...settings.compatSettings]
46
- : [];
47
- const idx = compatSettings.findIndex(s => s.name === "compatibilityMode");
48
- const entry = {
49
- name: "compatibilityMode",
50
- uri: "http://schemas.microsoft.com/office/word",
51
- val: String(mode)
52
- };
53
- if (idx >= 0) {
54
- compatSettings[idx] = entry;
55
- }
56
- else {
57
- compatSettings.push(entry);
56
+ settings.compatibilityMode = mode;
57
+ // Drop any stale override entry so getCompatibilityMode/writer don't read a
58
+ // conflicting value from the array. The scalar field is now authoritative.
59
+ if (settings.compatSettings) {
60
+ const filtered = settings.compatSettings.filter(s => s.name !== "compatibilityMode");
61
+ if (filtered.length > 0) {
62
+ settings.compatSettings = filtered;
63
+ }
64
+ else {
65
+ delete settings.compatSettings;
66
+ }
58
67
  }
59
- settings.compatSettings = compatSettings;
60
68
  doc.settings = settings;
61
69
  }