@stll/folio-core 0.23.0 → 0.24.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/dist/controller/fontReadiness.js +12 -7
- package/dist/controller/layoutPipeline.js +7 -3
- package/dist/docx/blockContentParser.js +2 -47
- package/dist/docx/fieldParser.d.ts +1 -1
- package/dist/docx/fieldParser.js +12 -4
- package/dist/docx/numberingParser.d.ts +3 -11
- package/dist/docx/numberingParser.js +26 -158
- package/dist/docx/ooxmlCounterFormatter.d.ts +8 -0
- package/dist/docx/ooxmlCounterFormatter.js +134 -0
- package/dist/docx/paragraphParser.js +3 -4
- package/dist/docx/serializer/tableSerializer.js +1 -1
- package/dist/docx/tableParser.js +2 -1
- package/dist/fields/bookmarkPages.js +1 -1
- package/dist/fields/evaluateField.js +1 -1
- package/dist/fields/fieldContext.d.ts +2 -0
- package/dist/fields/resolveFieldValues.js +5 -2
- package/dist/layout-bridge/convert/headerFooterLayout.js +7 -1
- package/dist/layout-bridge/convert/toFlowBlocks.js +27 -64
- package/dist/layout-engine/index.d.ts +3 -2
- package/dist/layout-engine/index.js +40 -16
- package/dist/layout-engine/measure/cache.js +5 -1
- package/dist/layout-engine/measure/complexScriptFormatting.d.ts +18 -0
- package/dist/layout-engine/measure/complexScriptFormatting.js +11 -0
- package/dist/layout-engine/measure/listMarkerWidth.d.ts +3 -1
- package/dist/layout-engine/measure/listMarkerWidth.js +25 -21
- package/dist/layout-engine/measure/measureContainer.js +31 -16
- package/dist/layout-engine/measure/measureHelpers.js +4 -0
- package/dist/layout-engine/measure/measureParagraph.js +13 -3
- package/dist/layout-engine/measure/measureTypes.d.ts +4 -0
- package/dist/layout-engine/paginator.d.ts +5 -3
- package/dist/layout-engine/paginator.js +34 -13
- package/dist/layout-engine/types.d.ts +34 -7
- package/dist/layout-painter/index.js +2 -1
- package/dist/layout-painter/renderPage.js +9 -3
- package/dist/layout-painter/renderParagraph.js +50 -42
- package/dist/layout-painter/renderUtils.d.ts +2 -0
- package/dist/paged-layout/sectionGeometry.d.ts +3 -2
- package/dist/paged-layout/sectionGeometry.js +17 -1
- package/dist/prosemirror/attrs/index.js +7 -6
- package/dist/prosemirror/conversion/fromProseDoc.js +5 -19
- package/dist/prosemirror/conversion/toProseDoc.js +5 -4
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +1 -3
- package/dist/prosemirror/extensions/features/ListExtension.js +1 -3
- package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.d.ts +3 -2
- package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.js +31 -17
- package/dist/prosemirror/extensions/nodes/TableExtension.js +1 -0
- package/dist/prosemirror/schema/marks.d.ts +8 -0
- package/dist/prosemirror/schema/nodes.d.ts +5 -7
- package/dist/prosemirror/styles/resolvedStyleAttrs.js +1 -3
- package/package.json +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { expectFontFamilyMarkAttrs } from "../prosemirror/attrs/index.js";
|
|
1
|
+
import { expectFontFamilyMarkAttrs, expectParagraphAttrs } from "../prosemirror/attrs/index.js";
|
|
2
2
|
import { parseFontFamilyList, resolveFontFamily } from "../utils/fontResolver.js";
|
|
3
3
|
//#region src/controller/fontReadiness.ts
|
|
4
4
|
function getDocumentFontSet() {
|
|
@@ -62,13 +62,15 @@ function collectInitialLayoutFontFaces(documentModel, pmDoc) {
|
|
|
62
62
|
return Array.from(faces.values());
|
|
63
63
|
}
|
|
64
64
|
function addTextFormattingFontFaces(faces, formatting) {
|
|
65
|
-
|
|
65
|
+
const standardDescriptor = layoutDescriptorFromFormatting(formatting);
|
|
66
|
+
const complexScriptDescriptor = layoutDescriptorFromEmphasis(formatting?.boldCs ?? formatting?.bold, formatting?.italicCs ?? formatting?.italic);
|
|
67
|
+
addLayoutFontFamilyFace(faces, formatting?.fontFamily, standardDescriptor, complexScriptDescriptor);
|
|
66
68
|
}
|
|
67
69
|
function collectProseMirrorFontFaces(faces, node, inheritedTextFormatting) {
|
|
68
70
|
const paragraphDefaults = readParagraphDefaultTextFormatting(node);
|
|
69
71
|
const textFormatting = paragraphDefaults ?? inheritedTextFormatting;
|
|
70
72
|
if (paragraphDefaults) addTextFormattingFontFaces(faces, paragraphDefaults);
|
|
71
|
-
if (node.
|
|
73
|
+
if (node.type.name === "paragraph") addTextFormattingFontFaces(faces, expectParagraphAttrs(node).listMarkerFormatting);
|
|
72
74
|
if (node.isText) {
|
|
73
75
|
const descriptor = layoutDescriptorFromFormattingAndMarks(textFormatting, node.marks);
|
|
74
76
|
addLayoutFontFamilyFace(faces, readFontFamilyMarkAttrs(node.marks) ?? textFormatting?.fontFamily ?? DEFAULT_LAYOUT_FONT_FAMILY, descriptor);
|
|
@@ -86,9 +88,12 @@ function readFontFamilyMarkAttrs(marks) {
|
|
|
86
88
|
for (const mark of marks) if (mark.type.name === "fontFamily") return expectFontFamilyMarkAttrs(mark);
|
|
87
89
|
}
|
|
88
90
|
function layoutDescriptorFromFormatting(formatting) {
|
|
91
|
+
return layoutDescriptorFromEmphasis(formatting?.bold, formatting?.italic);
|
|
92
|
+
}
|
|
93
|
+
function layoutDescriptorFromEmphasis(bold, italic) {
|
|
89
94
|
return {
|
|
90
|
-
style:
|
|
91
|
-
weight:
|
|
95
|
+
style: italic ? "italic" : "normal",
|
|
96
|
+
weight: bold ? 700 : 400
|
|
92
97
|
};
|
|
93
98
|
}
|
|
94
99
|
function layoutDescriptorFromFormattingAndMarks(formatting, marks) {
|
|
@@ -103,7 +108,7 @@ function layoutDescriptorFromFormattingAndMarks(formatting, marks) {
|
|
|
103
108
|
weight: bold ? 700 : 400
|
|
104
109
|
};
|
|
105
110
|
}
|
|
106
|
-
function addLayoutFontFamilyFace(faces, value, descriptor) {
|
|
111
|
+
function addLayoutFontFamilyFace(faces, value, descriptor, complexScriptDescriptor = descriptor) {
|
|
107
112
|
if (typeof value === "string") {
|
|
108
113
|
addLayoutFontFamilyNameFace(faces, value, descriptor);
|
|
109
114
|
return;
|
|
@@ -112,7 +117,7 @@ function addLayoutFontFamilyFace(faces, value, descriptor) {
|
|
|
112
117
|
const fontFamily = value;
|
|
113
118
|
addLayoutFontFamilyFace(faces, fontFamily.ascii, descriptor);
|
|
114
119
|
addLayoutFontFamilyFace(faces, fontFamily.hAnsi, descriptor);
|
|
115
|
-
addLayoutFontFamilyFace(faces, fontFamily.cs,
|
|
120
|
+
addLayoutFontFamilyFace(faces, fontFamily.cs, complexScriptDescriptor);
|
|
116
121
|
addLayoutFontFamilyFace(faces, fontFamily.eastAsia, descriptor);
|
|
117
122
|
}
|
|
118
123
|
function addLayoutFontFamilyNameFace(faces, family, descriptor) {
|
|
@@ -18,7 +18,7 @@ import "../layout-engine/types.js";
|
|
|
18
18
|
import { renderPages } from "../layout-painter/renderPage.js";
|
|
19
19
|
import { tryBuildIncrementalMeasures } from "../paged-layout/incrementalMeasure.js";
|
|
20
20
|
import { computePerBlockMeasureInputs } from "../paged-layout/sectionBlockWidths.js";
|
|
21
|
-
import { getMargins, getPageSize, twipsToPixels } from "../paged-layout/sectionGeometry.js";
|
|
21
|
+
import { getMargins, getPageNumbering, getPageSize, twipsToPixels } from "../paged-layout/sectionGeometry.js";
|
|
22
22
|
import { templatePreviewValuesKey } from "../prosemirror/plugins/templatePreviewValues.js";
|
|
23
23
|
import { getDocumentWatermark } from "../watermark/index.js";
|
|
24
24
|
//#region src/controller/layoutPipeline.ts
|
|
@@ -195,7 +195,8 @@ function runLayoutPipeline(deps, state, options = {}) {
|
|
|
195
195
|
phaseStartedAt = performance.now();
|
|
196
196
|
const bodyLayoutConfig = {
|
|
197
197
|
pageSize,
|
|
198
|
-
margins: initialBodyMargins
|
|
198
|
+
margins: initialBodyMargins,
|
|
199
|
+
pageNumbering: getPageNumbering(sectionProperties)
|
|
199
200
|
};
|
|
200
201
|
if (columns !== void 0) bodyLayoutConfig.columns = columns;
|
|
201
202
|
const finalSectionProperties = document?.package.document.sections?.at(-1)?.properties;
|
|
@@ -213,7 +214,8 @@ function runLayoutPipeline(deps, state, options = {}) {
|
|
|
213
214
|
authoredMargins: getMargins(finalSectionProperties),
|
|
214
215
|
preparedHeader: finalHeaderForLayout,
|
|
215
216
|
preparedFooter: finalFooterForLayout
|
|
216
|
-
})
|
|
217
|
+
}),
|
|
218
|
+
pageNumbering: getPageNumbering(finalSectionProperties)
|
|
217
219
|
} : bodyLayoutConfig;
|
|
218
220
|
const finalColumns = getColumns(finalSectionProperties);
|
|
219
221
|
if (finalColumns !== void 0) finalLayoutConfig.columns = finalColumns;
|
|
@@ -258,6 +260,7 @@ function runLayoutPipeline(deps, state, options = {}) {
|
|
|
258
260
|
const nextLayoutOpts = {
|
|
259
261
|
pageSize,
|
|
260
262
|
margins: initialBodyMargins,
|
|
263
|
+
pageNumbering: bodyLayoutConfig.pageNumbering,
|
|
261
264
|
pageGap,
|
|
262
265
|
mirrorMargins
|
|
263
266
|
};
|
|
@@ -273,6 +276,7 @@ function runLayoutPipeline(deps, state, options = {}) {
|
|
|
273
276
|
count: 1,
|
|
274
277
|
gap: 0
|
|
275
278
|
};
|
|
279
|
+
nextLayoutOpts.finalPageNumbering = finalLayoutConfig.pageNumbering;
|
|
276
280
|
}
|
|
277
281
|
if (columns !== void 0) nextLayoutOpts.columns = columns;
|
|
278
282
|
if (bodyBreakType !== void 0) nextLayoutOpts.bodyBreakType = bodyBreakType;
|
|
@@ -1,58 +1,13 @@
|
|
|
1
1
|
import { parseBookmarkEnd, parseBookmarkStart } from "./bookmarkParser.js";
|
|
2
2
|
import { appendBookmarkMarkerToLastParagraphInBlocks, prependBookmarkMarkersToFirstParagraphInBlocks } from "./bookmarkPlacement.js";
|
|
3
3
|
import { convertBulletToUnicode } from "./bulletMarkers.js";
|
|
4
|
-
import {
|
|
4
|
+
import { formatOoxmlCounter } from "./ooxmlCounterFormatter.js";
|
|
5
5
|
import { parseParagraph } from "./paragraphParser.js";
|
|
6
6
|
import { enrichParagraphTextBoxes } from "./paragraphTextBoxEnrichment.js";
|
|
7
7
|
import { parseSdtProperties } from "./sdtProperties.js";
|
|
8
8
|
import { parseTable } from "./tableParser.js";
|
|
9
9
|
import { elementToXml, findChild, getChildElements, getLocalName, mergeXmlnsDeclarations } from "./xmlParser.js";
|
|
10
10
|
//#region src/docx/blockContentParser.ts
|
|
11
|
-
const toRoman = (numParam) => {
|
|
12
|
-
let num = numParam;
|
|
13
|
-
const romanNumerals = [
|
|
14
|
-
[1e3, "M"],
|
|
15
|
-
[900, "CM"],
|
|
16
|
-
[500, "D"],
|
|
17
|
-
[400, "CD"],
|
|
18
|
-
[100, "C"],
|
|
19
|
-
[90, "XC"],
|
|
20
|
-
[50, "L"],
|
|
21
|
-
[40, "XL"],
|
|
22
|
-
[10, "X"],
|
|
23
|
-
[9, "IX"],
|
|
24
|
-
[5, "V"],
|
|
25
|
-
[4, "IV"],
|
|
26
|
-
[1, "I"]
|
|
27
|
-
];
|
|
28
|
-
let result = "";
|
|
29
|
-
for (const [value, symbol] of romanNumerals) while (num >= value) {
|
|
30
|
-
result += symbol;
|
|
31
|
-
num -= value;
|
|
32
|
-
}
|
|
33
|
-
return result;
|
|
34
|
-
};
|
|
35
|
-
const toRepeatedLetter = (value, baseCodePoint) => {
|
|
36
|
-
if (value <= 0) return "0";
|
|
37
|
-
const zeroBased = value - 1;
|
|
38
|
-
return String.fromCodePoint(baseCodePoint + zeroBased % 26).repeat(Math.floor(zeroBased / 26) + 1);
|
|
39
|
-
};
|
|
40
|
-
const formatNumber = (value, numFmt) => {
|
|
41
|
-
switch (numFmt) {
|
|
42
|
-
case "decimal": return String(value);
|
|
43
|
-
case "decimalZero": return padDecimal(value, 2);
|
|
44
|
-
case "decimalZero3": return padDecimal(value, 3);
|
|
45
|
-
case "decimalZero4": return padDecimal(value, 4);
|
|
46
|
-
case "decimalZero5": return padDecimal(value, 5);
|
|
47
|
-
case "lowerLetter": return toRepeatedLetter(value, 97);
|
|
48
|
-
case "upperLetter": return toRepeatedLetter(value, 65);
|
|
49
|
-
case "lowerRoman": return toRoman(value).toLowerCase();
|
|
50
|
-
case "upperRoman": return toRoman(value);
|
|
51
|
-
case "bullet": return "•";
|
|
52
|
-
case "none": return "";
|
|
53
|
-
default: return String(value);
|
|
54
|
-
}
|
|
55
|
-
};
|
|
56
11
|
const computeListMarker = (paragraph, { numbering, listCounters, abstractCounters, restartedNumIds, previousList, siblingNumIdsByAbstractNumId }) => {
|
|
57
12
|
const listRendering = paragraph.listRendering;
|
|
58
13
|
if (!listRendering || !numbering) {
|
|
@@ -137,7 +92,7 @@ const computeListMarker = (paragraph, { numbering, listCounters, abstractCounter
|
|
|
137
92
|
if (computedMarker.includes(placeholder)) {
|
|
138
93
|
const value = counters[lvl] ?? 0;
|
|
139
94
|
const levelInfo = numbering.getLevel(numId, lvl);
|
|
140
|
-
const formatted =
|
|
95
|
+
const formatted = formatOoxmlCounter(value, useLegalNumbering ? "decimal" : levelInfo?.numFmt || "decimal");
|
|
141
96
|
computedMarker = computedMarker.replaceAll(placeholder, formatted);
|
|
142
97
|
}
|
|
143
98
|
}
|
|
@@ -188,7 +188,7 @@ declare function isTocField(field: document_d_exports.Field): boolean;
|
|
|
188
188
|
* @param instruction - Parsed instruction for format switches
|
|
189
189
|
* @returns Formatted page number string
|
|
190
190
|
*/
|
|
191
|
-
declare function computePageNumber(pageNumber: number, instruction?: ParsedFieldInstruction): string;
|
|
191
|
+
declare function computePageNumber(pageNumber: number, instruction?: ParsedFieldInstruction, sectionFormat?: string): string;
|
|
192
192
|
/**
|
|
193
193
|
* Format a date according to a format string
|
|
194
194
|
*
|
package/dist/docx/fieldParser.js
CHANGED
|
@@ -345,16 +345,24 @@ function isTocField(field) {
|
|
|
345
345
|
* @param instruction - Parsed instruction for format switches
|
|
346
346
|
* @returns Formatted page number string
|
|
347
347
|
*/
|
|
348
|
-
function computePageNumber(pageNumber, instruction) {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
if (!format) return String(pageNumber);
|
|
348
|
+
function computePageNumber(pageNumber, instruction, sectionFormat) {
|
|
349
|
+
const format = instruction ? getFormatSwitch(instruction) : void 0;
|
|
350
|
+
if (!format || format.toUpperCase() === "MERGEFORMAT") return formatSectionPageNumber(pageNumber, sectionFormat);
|
|
352
351
|
switch (format.toUpperCase()) {
|
|
353
352
|
case "ROMAN": return toRoman(pageNumber);
|
|
354
353
|
case "ALPHABETIC": return toLetter(pageNumber);
|
|
355
354
|
default: return String(pageNumber);
|
|
356
355
|
}
|
|
357
356
|
}
|
|
357
|
+
function formatSectionPageNumber(pageNumber, format) {
|
|
358
|
+
switch (format) {
|
|
359
|
+
case "upperRoman": return toRoman(pageNumber);
|
|
360
|
+
case "lowerRoman": return toRoman(pageNumber).toLowerCase();
|
|
361
|
+
case "upperLetter": return toLetter(pageNumber);
|
|
362
|
+
case "lowerLetter": return toLetter(pageNumber).toLowerCase();
|
|
363
|
+
default: return String(pageNumber);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
358
366
|
/**
|
|
359
367
|
* Convert number to uppercase Roman numerals
|
|
360
368
|
*/
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { document_d_exports } from "../types/document.js";
|
|
2
|
+
import { formatOoxmlCounter, padDecimal } from "./ooxmlCounterFormatter.js";
|
|
2
3
|
//#region src/docx/numberingParser.d.ts
|
|
3
4
|
/**
|
|
4
5
|
* Map of rId to numbering definitions
|
|
@@ -23,6 +24,7 @@ type NumberingMap = {
|
|
|
23
24
|
* @returns NumberingMap with definitions and helper functions
|
|
24
25
|
*/
|
|
25
26
|
declare function parseNumbering(numberingXml: string | null): NumberingMap;
|
|
27
|
+
declare const markerFormattingFromLevel: (formatting: document_d_exports.TextFormatting | undefined) => document_d_exports.ListMarkerFormatting | undefined;
|
|
26
28
|
declare function getCachedNumberingMap(definitions: document_d_exports.NumberingDefinitions): NumberingMap;
|
|
27
29
|
/**
|
|
28
30
|
* Create a NumberingMap with helper functions
|
|
@@ -46,16 +48,6 @@ declare function computeListRendering(numPr: {
|
|
|
46
48
|
}, numbering: NumberingMap): (document_d_exports.ListRendering & {
|
|
47
49
|
levelStarts: number[];
|
|
48
50
|
}) | null;
|
|
49
|
-
/**
|
|
50
|
-
* Format a number according to the specified format
|
|
51
|
-
*
|
|
52
|
-
* @param num - The number to format
|
|
53
|
-
* @param format - The number format
|
|
54
|
-
* @returns Formatted string
|
|
55
|
-
*/
|
|
56
|
-
declare function formatNumber(num: number, format: document_d_exports.NumberFormat): string;
|
|
57
|
-
/** Zero-pad a counter to `width` digits ("decimalZero" family, §17.18.59). */
|
|
58
|
-
declare function padDecimal(num: number, width: number): string;
|
|
59
51
|
/**
|
|
60
52
|
* Compare two `numPr` references by value — `numId` plus `ilvl` (a missing
|
|
61
53
|
* `ilvl` is level 0). Used for the style-sourced-numbering provenance check;
|
|
@@ -89,4 +81,4 @@ declare function getBulletCharacter(level: document_d_exports.ListLevel): string
|
|
|
89
81
|
*/
|
|
90
82
|
declare function isBulletLevel(level: document_d_exports.ListLevel): boolean;
|
|
91
83
|
//#endregion
|
|
92
|
-
export { NumberingMap, computeListRendering, createNumberingMap, formatNumber, getBulletCharacter, getCachedNumberingMap, isBulletLevel, numPrEqual, padDecimal, parseNumbering, renderListMarker };
|
|
84
|
+
export { NumberingMap, computeListRendering, createNumberingMap, formatOoxmlCounter as formatNumber, getBulletCharacter, getCachedNumberingMap, isBulletLevel, markerFormattingFromLevel, numPrEqual, padDecimal, parseNumbering, renderListMarker };
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { formatOoxmlCounter, padDecimal } from "./ooxmlCounterFormatter.js";
|
|
2
|
+
import { LevelSuffixSchema, narrowEnum } from "./parserEnums.js";
|
|
3
|
+
import { parseRunProperties } from "./runParser.js";
|
|
2
4
|
import { findChild, findChildren, getAttribute, parseBooleanElement, parseNumericAttribute, parseXmlDocument } from "./xmlParser.js";
|
|
3
5
|
//#region src/docx/numberingParser.ts
|
|
4
6
|
const NUMBER_FORMAT_MAP = {
|
|
@@ -314,7 +316,10 @@ function parseListLevel(element) {
|
|
|
314
316
|
};
|
|
315
317
|
}
|
|
316
318
|
if (pPrEl) level.pPr = parseLevelParagraphProps(pPrEl);
|
|
317
|
-
if (rPrEl)
|
|
319
|
+
if (rPrEl) {
|
|
320
|
+
const runProperties = parseRunProperties(rPrEl, null);
|
|
321
|
+
if (runProperties) level.rPr = runProperties;
|
|
322
|
+
}
|
|
318
323
|
return level;
|
|
319
324
|
}
|
|
320
325
|
/**
|
|
@@ -427,79 +432,21 @@ function parseTabLeader(val) {
|
|
|
427
432
|
default: return;
|
|
428
433
|
}
|
|
429
434
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
case "w:rFonts":
|
|
446
|
-
rFontsEl ??= child;
|
|
447
|
-
break;
|
|
448
|
-
case "w:sz":
|
|
449
|
-
szEl ??= child;
|
|
450
|
-
break;
|
|
451
|
-
case "w:color":
|
|
452
|
-
colorEl ??= child;
|
|
453
|
-
break;
|
|
454
|
-
case "w:b":
|
|
455
|
-
bEl ??= child;
|
|
456
|
-
break;
|
|
457
|
-
case "w:i":
|
|
458
|
-
iEl ??= child;
|
|
459
|
-
break;
|
|
460
|
-
case "w:vanish":
|
|
461
|
-
vanishEl ??= child;
|
|
462
|
-
break;
|
|
463
|
-
default: break;
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
if (rFontsEl) {
|
|
467
|
-
const ascii = getAttribute(rFontsEl, "w", "ascii");
|
|
468
|
-
const hAnsi = getAttribute(rFontsEl, "w", "hAnsi");
|
|
469
|
-
const eastAsia = getAttribute(rFontsEl, "w", "eastAsia");
|
|
470
|
-
const cs = getAttribute(rFontsEl, "w", "cs");
|
|
471
|
-
const hint = narrowEnum(getAttribute(rFontsEl, "w", "hint"), FontHintSchema);
|
|
472
|
-
formatting.fontFamily = {
|
|
473
|
-
...ascii != null ? { ascii } : {},
|
|
474
|
-
...hAnsi != null ? { hAnsi } : {},
|
|
475
|
-
...eastAsia != null ? { eastAsia } : {},
|
|
476
|
-
...cs != null ? { cs } : {},
|
|
477
|
-
...hint !== void 0 ? { hint } : {}
|
|
478
|
-
};
|
|
479
|
-
}
|
|
480
|
-
if (szEl) {
|
|
481
|
-
const size = parseNumericAttribute(szEl, "w", "val");
|
|
482
|
-
if (size !== void 0) formatting.fontSize = size;
|
|
483
|
-
}
|
|
484
|
-
if (colorEl) {
|
|
485
|
-
const val = getAttribute(colorEl, "w", "val");
|
|
486
|
-
const validatedThemeColor = narrowEnum(getAttribute(colorEl, "w", "themeColor"), ThemeColorSlotSchema);
|
|
487
|
-
if (val === "auto") formatting.color = { auto: true };
|
|
488
|
-
else if (validatedThemeColor) {
|
|
489
|
-
const themeTint = getAttribute(colorEl, "w", "themeTint");
|
|
490
|
-
const themeShade = getAttribute(colorEl, "w", "themeShade");
|
|
491
|
-
formatting.color = {
|
|
492
|
-
themeColor: validatedThemeColor,
|
|
493
|
-
...themeTint != null ? { themeTint } : {},
|
|
494
|
-
...themeShade != null ? { themeShade } : {}
|
|
495
|
-
};
|
|
496
|
-
} else if (val) formatting.color = { rgb: val };
|
|
497
|
-
}
|
|
498
|
-
if (bEl) formatting.bold = parseBooleanElement(bEl);
|
|
499
|
-
if (iEl) formatting.italic = parseBooleanElement(iEl);
|
|
500
|
-
if (vanishEl) formatting.hidden = parseBooleanElement(vanishEl);
|
|
501
|
-
return formatting;
|
|
502
|
-
}
|
|
435
|
+
const markerFormattingFromLevel = (formatting) => {
|
|
436
|
+
if (!formatting) return;
|
|
437
|
+
const markerFormatting = {
|
|
438
|
+
...formatting.fontFamily !== void 0 ? { fontFamily: formatting.fontFamily } : {},
|
|
439
|
+
...formatting.fontSize !== void 0 ? { fontSize: formatting.fontSize } : {},
|
|
440
|
+
...formatting.fontSizeCs !== void 0 ? { fontSizeCs: formatting.fontSizeCs } : {},
|
|
441
|
+
...formatting.bold !== void 0 ? { bold: formatting.bold } : {},
|
|
442
|
+
...formatting.boldCs !== void 0 ? { boldCs: formatting.boldCs } : {},
|
|
443
|
+
...formatting.italic !== void 0 ? { italic: formatting.italic } : {},
|
|
444
|
+
...formatting.italicCs !== void 0 ? { italicCs: formatting.italicCs } : {},
|
|
445
|
+
...formatting.rtl !== void 0 ? { rtl: formatting.rtl } : {},
|
|
446
|
+
...formatting.cs !== void 0 ? { cs: formatting.cs } : {}
|
|
447
|
+
};
|
|
448
|
+
return Object.keys(markerFormatting).length > 0 ? markerFormatting : void 0;
|
|
449
|
+
};
|
|
503
450
|
/**
|
|
504
451
|
* Per-definitions cache for `createNumberingMap`. Style application rebuilds
|
|
505
452
|
* the lookup map on every picker click otherwise; the definitions object is
|
|
@@ -602,10 +549,8 @@ function computeListRendering(numPr, numbering) {
|
|
|
602
549
|
};
|
|
603
550
|
if (level.isLgl) rendering.isLegal = true;
|
|
604
551
|
if (level.rPr?.hidden) rendering.markerHidden = true;
|
|
605
|
-
const
|
|
606
|
-
if (
|
|
607
|
-
if (level.rPr?.fontSize) rendering.markerFontSize = level.rPr.fontSize / 2;
|
|
608
|
-
if (level.rPr?.bold !== void 0) rendering.markerBold = level.rPr.bold;
|
|
552
|
+
const markerFormatting = markerFormattingFromLevel(level.rPr);
|
|
553
|
+
if (markerFormatting) rendering.markerFormatting = markerFormatting;
|
|
609
554
|
if (level.rPr?.allCaps) rendering.markerAllCaps = true;
|
|
610
555
|
if (level.lvlJc) rendering.markerAlignment = level.lvlJc;
|
|
611
556
|
if (level.suffix) rendering.markerSuffix = level.suffix;
|
|
@@ -614,37 +559,6 @@ function computeListRendering(numPr, numbering) {
|
|
|
614
559
|
return rendering;
|
|
615
560
|
}
|
|
616
561
|
/**
|
|
617
|
-
* Format a number according to the specified format
|
|
618
|
-
*
|
|
619
|
-
* @param num - The number to format
|
|
620
|
-
* @param format - The number format
|
|
621
|
-
* @returns Formatted string
|
|
622
|
-
*/
|
|
623
|
-
function formatNumber(num, format) {
|
|
624
|
-
switch (format) {
|
|
625
|
-
case "decimal": return num.toString();
|
|
626
|
-
case "decimalZero": return padDecimal(num, 2);
|
|
627
|
-
case "decimalZero3": return padDecimal(num, 3);
|
|
628
|
-
case "decimalZero4": return padDecimal(num, 4);
|
|
629
|
-
case "decimalZero5": return padDecimal(num, 5);
|
|
630
|
-
case "upperRoman": return toRoman(num).toUpperCase();
|
|
631
|
-
case "lowerRoman": return toRoman(num).toLowerCase();
|
|
632
|
-
case "upperLetter": return toLetter(num).toUpperCase();
|
|
633
|
-
case "lowerLetter": return toLetter(num).toLowerCase();
|
|
634
|
-
case "ordinal": return toOrdinal(num);
|
|
635
|
-
case "bullet": return "•";
|
|
636
|
-
case "none": return "";
|
|
637
|
-
case "decimalEnclosedParen": return `(${num})`;
|
|
638
|
-
case "numberInDash": return `-${num}-`;
|
|
639
|
-
default: return num.toString();
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
/** Zero-pad a counter to `width` digits ("decimalZero" family, §17.18.59). */
|
|
643
|
-
function padDecimal(num, width) {
|
|
644
|
-
if (num < 0) return num.toString();
|
|
645
|
-
return num.toString().padStart(width, "0");
|
|
646
|
-
}
|
|
647
|
-
/**
|
|
648
562
|
* Compare two `numPr` references by value — `numId` plus `ilvl` (a missing
|
|
649
563
|
* `ilvl` is level 0). Used for the style-sourced-numbering provenance check;
|
|
650
564
|
* avoids JSON.stringify equality, which is sensitive to key order.
|
|
@@ -654,52 +568,6 @@ function numPrEqual(a, b) {
|
|
|
654
568
|
return a.numId === b.numId && (a.ilvl ?? 0) === (b.ilvl ?? 0);
|
|
655
569
|
}
|
|
656
570
|
/**
|
|
657
|
-
* Convert number to Roman numerals
|
|
658
|
-
*/
|
|
659
|
-
function toRoman(num) {
|
|
660
|
-
if (num <= 0 || num > 3999) return num.toString();
|
|
661
|
-
const romanNumerals = [
|
|
662
|
-
[1e3, "m"],
|
|
663
|
-
[900, "cm"],
|
|
664
|
-
[500, "d"],
|
|
665
|
-
[400, "cd"],
|
|
666
|
-
[100, "c"],
|
|
667
|
-
[90, "xc"],
|
|
668
|
-
[50, "l"],
|
|
669
|
-
[40, "xl"],
|
|
670
|
-
[10, "x"],
|
|
671
|
-
[9, "ix"],
|
|
672
|
-
[5, "v"],
|
|
673
|
-
[4, "iv"],
|
|
674
|
-
[1, "i"]
|
|
675
|
-
];
|
|
676
|
-
let result = "";
|
|
677
|
-
let remaining = num;
|
|
678
|
-
for (const [value, numeral] of romanNumerals) while (remaining >= value) {
|
|
679
|
-
result += numeral;
|
|
680
|
-
remaining -= value;
|
|
681
|
-
}
|
|
682
|
-
return result;
|
|
683
|
-
}
|
|
684
|
-
function toLetter(num) {
|
|
685
|
-
if (num <= 0) return "";
|
|
686
|
-
const zeroBased = num - 1;
|
|
687
|
-
return String.fromCodePoint(97 + zeroBased % 26).repeat(Math.floor(zeroBased / 26) + 1);
|
|
688
|
-
}
|
|
689
|
-
/**
|
|
690
|
-
* Convert number to ordinal (1st, 2nd, 3rd, ...)
|
|
691
|
-
*/
|
|
692
|
-
function toOrdinal(num) {
|
|
693
|
-
const suffix = [
|
|
694
|
-
"th",
|
|
695
|
-
"st",
|
|
696
|
-
"nd",
|
|
697
|
-
"rd"
|
|
698
|
-
];
|
|
699
|
-
const v = num % 100;
|
|
700
|
-
return num + (suffix[(v - 20) % 10] ?? suffix[v] ?? suffix[0] ?? "th");
|
|
701
|
-
}
|
|
702
|
-
/**
|
|
703
571
|
* Render list marker text by replacing placeholders with formatted numbers
|
|
704
572
|
*
|
|
705
573
|
* @param lvlText - The level text pattern (e.g., "%1.", "%1.%2")
|
|
@@ -713,7 +581,7 @@ function renderListMarker(lvlText, counters, formats) {
|
|
|
713
581
|
const placeholder = `%${i}`;
|
|
714
582
|
if (result.includes(placeholder)) {
|
|
715
583
|
const counterIndex = i - 1;
|
|
716
|
-
const formatted =
|
|
584
|
+
const formatted = formatOoxmlCounter(counters[counterIndex] ?? 1, formats[counterIndex] ?? "decimal");
|
|
717
585
|
result = result.replaceAll(placeholder, formatted);
|
|
718
586
|
}
|
|
719
587
|
}
|
|
@@ -742,4 +610,4 @@ function isBulletLevel(level) {
|
|
|
742
610
|
return level.numFmt === "bullet" || level.numFmt === "none";
|
|
743
611
|
}
|
|
744
612
|
//#endregion
|
|
745
|
-
export { computeListRendering, createNumberingMap, formatNumber, getBulletCharacter, getCachedNumberingMap, isBulletLevel, numPrEqual, padDecimal, parseNumbering, renderListMarker };
|
|
613
|
+
export { computeListRendering, createNumberingMap, formatOoxmlCounter as formatNumber, getBulletCharacter, getCachedNumberingMap, isBulletLevel, markerFormattingFromLevel, numPrEqual, padDecimal, parseNumbering, renderListMarker };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { document_d_exports } from "../types/document.js";
|
|
2
|
+
//#region src/docx/ooxmlCounterFormatter.d.ts
|
|
3
|
+
/** Zero-pad a counter to `width` digits (the `decimalZero` family). */
|
|
4
|
+
declare const padDecimal: (value: number, width: number) => string;
|
|
5
|
+
/** Format one OOXML numbering counter using Microsoft Word display semantics. */
|
|
6
|
+
declare const formatOoxmlCounter: (value: number, format: document_d_exports.NumberFormat | undefined) => string;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { formatOoxmlCounter, padDecimal };
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
//#region src/docx/ooxmlCounterFormatter.ts
|
|
2
|
+
const ROMAN_PAIRS = [
|
|
3
|
+
[1e3, "M"],
|
|
4
|
+
[900, "CM"],
|
|
5
|
+
[500, "D"],
|
|
6
|
+
[400, "CD"],
|
|
7
|
+
[100, "C"],
|
|
8
|
+
[90, "XC"],
|
|
9
|
+
[50, "L"],
|
|
10
|
+
[40, "XL"],
|
|
11
|
+
[10, "X"],
|
|
12
|
+
[9, "IX"],
|
|
13
|
+
[5, "V"],
|
|
14
|
+
[4, "IV"],
|
|
15
|
+
[1, "I"]
|
|
16
|
+
];
|
|
17
|
+
const LATIN_LOWER_SEQUENCE = [..."abcdefghijklmnopqrstuvwxyz"];
|
|
18
|
+
const ARABIC_ALPHA_SEQUENCE = [..."أبتثجحخدذرزسشصضطظعغفقكلمنهوي"];
|
|
19
|
+
const ARABIC_ABJAD_SEQUENCE = [..."أبجدهوزحطيكلمنسعفصقرشتثخذضظغ"];
|
|
20
|
+
const ZERO_WIDTH_NON_JOINER = "";
|
|
21
|
+
const MAX_REPEATED_COUNTER_LENGTH = 1024;
|
|
22
|
+
const toRoman = (value) => {
|
|
23
|
+
if (value <= 0 || value > 3999) return String(value);
|
|
24
|
+
let remaining = value;
|
|
25
|
+
let result = "";
|
|
26
|
+
for (const [number, numeral] of ROMAN_PAIRS) while (remaining >= number) {
|
|
27
|
+
result += numeral;
|
|
28
|
+
remaining -= number;
|
|
29
|
+
}
|
|
30
|
+
return result;
|
|
31
|
+
};
|
|
32
|
+
const toRepeatedSequence = (value, sequence) => {
|
|
33
|
+
if (!Number.isSafeInteger(value) || value <= 0) return "";
|
|
34
|
+
const zeroBased = value - 1;
|
|
35
|
+
const character = sequence.at(zeroBased % sequence.length);
|
|
36
|
+
const repetitions = Math.floor(zeroBased / sequence.length) + 1;
|
|
37
|
+
if (repetitions > MAX_REPEATED_COUNTER_LENGTH) return;
|
|
38
|
+
return character?.repeat(repetitions) ?? "";
|
|
39
|
+
};
|
|
40
|
+
const toOrdinal = (value) => {
|
|
41
|
+
const suffixes = [
|
|
42
|
+
"th",
|
|
43
|
+
"st",
|
|
44
|
+
"nd",
|
|
45
|
+
"rd"
|
|
46
|
+
];
|
|
47
|
+
const remainder = value % 100;
|
|
48
|
+
return `${value}${suffixes[(remainder - 20) % 10] ?? suffixes[remainder] ?? "th"}`;
|
|
49
|
+
};
|
|
50
|
+
/** Zero-pad a counter to `width` digits (the `decimalZero` family). */
|
|
51
|
+
const padDecimal = (value, width) => {
|
|
52
|
+
if (value < 0) return String(value);
|
|
53
|
+
return String(value).padStart(width, "0");
|
|
54
|
+
};
|
|
55
|
+
/** Format one OOXML numbering counter using Microsoft Word display semantics. */
|
|
56
|
+
const formatOoxmlCounter = (value, format) => {
|
|
57
|
+
if (!Number.isFinite(value)) return "";
|
|
58
|
+
switch (format ?? "decimal") {
|
|
59
|
+
case "decimal":
|
|
60
|
+
case "cardinalText":
|
|
61
|
+
case "ordinalText":
|
|
62
|
+
case "hex":
|
|
63
|
+
case "chicago":
|
|
64
|
+
case "ideographDigital":
|
|
65
|
+
case "japaneseCounting":
|
|
66
|
+
case "aiueo":
|
|
67
|
+
case "iroha":
|
|
68
|
+
case "decimalFullWidth":
|
|
69
|
+
case "decimalHalfWidth":
|
|
70
|
+
case "japaneseLegal":
|
|
71
|
+
case "japaneseDigitalTenThousand":
|
|
72
|
+
case "decimalEnclosedCircle":
|
|
73
|
+
case "decimalFullWidth2":
|
|
74
|
+
case "aiueoFullWidth":
|
|
75
|
+
case "irohaFullWidth":
|
|
76
|
+
case "ganada":
|
|
77
|
+
case "chosung":
|
|
78
|
+
case "decimalEnclosedFullstop":
|
|
79
|
+
case "decimalEnclosedCircleChinese":
|
|
80
|
+
case "ideographEnclosedCircle":
|
|
81
|
+
case "ideographTraditional":
|
|
82
|
+
case "ideographZodiac":
|
|
83
|
+
case "ideographZodiacTraditional":
|
|
84
|
+
case "taiwaneseCounting":
|
|
85
|
+
case "ideographLegalTraditional":
|
|
86
|
+
case "taiwaneseCountingThousand":
|
|
87
|
+
case "taiwaneseDigital":
|
|
88
|
+
case "chineseCounting":
|
|
89
|
+
case "chineseLegalSimplified":
|
|
90
|
+
case "chineseCountingThousand":
|
|
91
|
+
case "koreanDigital":
|
|
92
|
+
case "koreanCounting":
|
|
93
|
+
case "koreanLegal":
|
|
94
|
+
case "koreanDigital2":
|
|
95
|
+
case "vietnameseCounting":
|
|
96
|
+
case "russianLower":
|
|
97
|
+
case "russianUpper":
|
|
98
|
+
case "hebrew1":
|
|
99
|
+
case "hebrew2":
|
|
100
|
+
case "hindiVowels":
|
|
101
|
+
case "hindiConsonants":
|
|
102
|
+
case "hindiNumbers":
|
|
103
|
+
case "hindiCounting":
|
|
104
|
+
case "thaiLetters":
|
|
105
|
+
case "thaiNumbers":
|
|
106
|
+
case "thaiCounting": return String(value);
|
|
107
|
+
case "decimalZero": return padDecimal(value, 2);
|
|
108
|
+
case "decimalZero3": return padDecimal(value, 3);
|
|
109
|
+
case "decimalZero4": return padDecimal(value, 4);
|
|
110
|
+
case "decimalZero5": return padDecimal(value, 5);
|
|
111
|
+
case "upperRoman": return toRoman(value);
|
|
112
|
+
case "lowerRoman": return toRoman(value).toLowerCase();
|
|
113
|
+
case "upperLetter": return (toRepeatedSequence(value, LATIN_LOWER_SEQUENCE) ?? String(value)).toUpperCase();
|
|
114
|
+
case "lowerLetter": return toRepeatedSequence(value, LATIN_LOWER_SEQUENCE) ?? String(value);
|
|
115
|
+
case "ordinal": return toOrdinal(value);
|
|
116
|
+
case "bullet": return "•";
|
|
117
|
+
case "none": return "";
|
|
118
|
+
case "decimalEnclosedParen": return `(${value})`;
|
|
119
|
+
case "numberInDash": return `-${value}-`;
|
|
120
|
+
case "arabicAlpha": {
|
|
121
|
+
const counter = toRepeatedSequence(value, ARABIC_ALPHA_SEQUENCE);
|
|
122
|
+
if (counter === void 0) return String(value);
|
|
123
|
+
return counter ? `${counter}${ZERO_WIDTH_NON_JOINER}` : "";
|
|
124
|
+
}
|
|
125
|
+
case "arabicAbjad": {
|
|
126
|
+
const counter = toRepeatedSequence(value, ARABIC_ABJAD_SEQUENCE);
|
|
127
|
+
if (counter === void 0) return String(value);
|
|
128
|
+
return counter ? `${ZERO_WIDTH_NON_JOINER}${counter}` : "";
|
|
129
|
+
}
|
|
130
|
+
default: return String(value);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
//#endregion
|
|
134
|
+
export { formatOoxmlCounter, padDecimal };
|
|
@@ -2,6 +2,7 @@ import { isValidHexId } from "../utils/hexId.js";
|
|
|
2
2
|
import { parseBookmarkEnd as parseBookmarkEnd$1, parseBookmarkStart as parseBookmarkStart$1 } from "./bookmarkParser.js";
|
|
3
3
|
import { parseFieldType } from "./fieldParser.js";
|
|
4
4
|
import { parseHyperlink as parseHyperlink$1 } from "./hyperlinkParser.js";
|
|
5
|
+
import { markerFormattingFromLevel } from "./numberingParser.js";
|
|
5
6
|
import { BorderStyleSchema, FrameWrapSchema, FrameXAlignSchema, FrameYAlignSchema, LineSpacingRuleSchema, ParagraphAlignmentSchema, ShadingPatternSchema, TabLeaderSchema, TabStopAlignmentSchema, ThemeColorSlotSchema, narrowEnum } from "./parserEnums.js";
|
|
6
7
|
import { consolidateParagraphContent } from "./runConsolidator.js";
|
|
7
8
|
import { parseRun, parseRunProperties } from "./runParser.js";
|
|
@@ -1016,10 +1017,8 @@ function parseParagraph(node, styles, theme, numbering, rels = null, media = nul
|
|
|
1016
1017
|
if (level.isLgl) listRendering.isLegal = true;
|
|
1017
1018
|
listRendering.numFmt = level.isLgl ? "decimal" : level.numFmt;
|
|
1018
1019
|
if (level.rPr?.hidden) listRendering.markerHidden = true;
|
|
1019
|
-
const
|
|
1020
|
-
if (
|
|
1021
|
-
if (level.rPr?.fontSize) listRendering.markerFontSize = level.rPr.fontSize / 2;
|
|
1022
|
-
if (level.rPr?.bold !== void 0) listRendering.markerBold = level.rPr.bold;
|
|
1020
|
+
const markerFormatting = markerFormattingFromLevel(level.rPr);
|
|
1021
|
+
if (markerFormatting) listRendering.markerFormatting = markerFormatting;
|
|
1023
1022
|
if (level.rPr?.allCaps) listRendering.markerAllCaps = true;
|
|
1024
1023
|
if (level.lvlJc) listRendering.markerAlignment = level.lvlJc;
|
|
1025
1024
|
if (level.suffix) listRendering.markerSuffix = level.suffix;
|
|
@@ -134,7 +134,7 @@ function serializeTableFormatting(formatting, propertyChanges) {
|
|
|
134
134
|
if (formatting.styleId) parts.push(`<w:tblStyle w:val="${escapeXml(formatting.styleId)}"/>`);
|
|
135
135
|
const floatingXml = serializeFloatingTableProperties(formatting.floating);
|
|
136
136
|
if (floatingXml) parts.push(floatingXml);
|
|
137
|
-
if (formatting.bidi) parts.push("<w:bidiVisual/>");
|
|
137
|
+
if (formatting.bidi !== void 0) parts.push(formatting.bidi ? "<w:bidiVisual/>" : "<w:bidiVisual w:val=\"0\"/>");
|
|
138
138
|
const widthXml = serializeMeasurement(formatting.width, "tblW");
|
|
139
139
|
if (widthXml) parts.push(widthXml);
|
|
140
140
|
if (formatting.justification) parts.push(`<w:jc w:val="${formatting.justification}"/>`);
|
package/dist/docx/tableParser.js
CHANGED
|
@@ -284,7 +284,8 @@ function parseTableProperties(tblPrElement) {
|
|
|
284
284
|
}
|
|
285
285
|
const floating = parseFloatingTableProperties(findChild(tblPrElement, "w", "tblpPr"));
|
|
286
286
|
if (floating) formatting.floating = floating;
|
|
287
|
-
|
|
287
|
+
const bidiVisual = findChild(tblPrElement, "w", "bidiVisual");
|
|
288
|
+
if (bidiVisual) formatting.bidi = parseBooleanElement(bidiVisual);
|
|
288
289
|
if (Object.keys(formatting).length === 0) return;
|
|
289
290
|
return formatting;
|
|
290
291
|
}
|