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