@stll/folio-core 0.65.0 → 0.66.1
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/ai-edits/apply.js +31 -9
- package/dist/docx/attributeRemainder.js +1 -1
- package/dist/docx/documentSectionFacts.js +1 -1
- package/dist/docx/ensureParaIds.js +2 -2
- package/dist/docx/importedIdCollisions.js +1 -1
- package/dist/docx/mathToMathml.js +1 -1
- package/dist/docx/numericIdNormalization.js +2 -2
- package/dist/docx/parser.js +1 -1
- package/dist/docx/revisionIdNormalization.js +2 -2
- package/dist/docx/rezip.js +1 -1
- package/dist/docx/sdtProperties.js +1 -2
- package/dist/docx/selectedXmlIdentityScan.d.ts +1 -1
- package/dist/docx/selectedXmlIdentityScan.js +1 -1
- package/dist/docx/selectiveXmlPatch.js +2 -2
- package/dist/docx/server/boundedArchive.d.ts +1 -1
- package/dist/docx/server/boundedArchive.js +1 -1
- package/dist/docx/server/validateDocxConformance.js +1 -1
- package/dist/docx/shapeAlternateContent.js +1 -1
- package/dist/docx/storyBlockReplay.js +1 -1
- package/dist/docx/unzip.js +1 -1
- package/dist/docx/verbatimCapture.js +1 -1
- package/dist/docx/xmlParser.d.ts +5 -36
- package/dist/docx/xmlParser.js +14 -78
- package/dist/docx/xmlSafety.js +5 -5
- package/dist/fonts/bundledFonts.d.ts +54 -0
- package/dist/fonts/bundledFonts.js +45 -0
- package/dist/fonts/fontsourceFaces.d.ts +9 -14
- package/dist/fonts/fontsourceFaces.js +10 -22
- package/dist/layout-painter/renderPage.js +4 -2
- package/dist/markdown/countParagraphList.d.ts +18 -0
- package/dist/markdown/countParagraphList.js +90 -0
- package/dist/markdown/renderParagraph.js +2 -62
- package/dist/markdown/renderRuns.js +4 -1
- package/dist/prosemirror/conversion/fromProseDoc.js +5 -3
- package/dist/prosemirror/conversion/toProseDoc.js +4 -1
- package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.js +4 -1
- package/dist/prosemirror/replacedAnnotations.d.ts +6 -0
- package/dist/prosemirror/replacedAnnotations.js +30 -8
- package/dist/prosemirror/schema/nodes.d.ts +7 -0
- package/dist/prosemirror/textBoxAnchorAttrs.js +9 -1
- package/dist/utils/fontLoader.d.ts +1 -1
- package/dist/utils/fontLoader.js +3 -11
- package/package.json +5 -4
- package/dist/docx/streamingXmlParser.d.ts +0 -60
- package/dist/docx/streamingXmlParser.js +0 -406
- package/dist/docx/xmlNamespaceContext.d.ts +0 -6
- package/dist/docx/xmlNamespaceContext.js +0 -53
- package/dist/docx/xmlResourceLimits.d.ts +0 -107
- package/dist/docx/xmlResourceLimits.js +0 -194
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** Each bundled family and the directory (an `@fontsource` package name) that ships it. */
|
|
2
|
+
const FONTSOURCE_PACKAGES = {
|
|
3
|
+
Arimo: "arimo",
|
|
4
|
+
Caladea: "caladea",
|
|
5
|
+
Carlito: "carlito",
|
|
6
|
+
Cousine: "cousine",
|
|
7
|
+
Lato: "lato",
|
|
8
|
+
"Noto Sans Arabic": "noto-sans-arabic",
|
|
9
|
+
"Source Sans 3": "source-sans-3",
|
|
10
|
+
Tinos: "tinos"
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* What each bundled family stands in for, as prose for the package's notices.
|
|
14
|
+
* The resolver's own mapping lives in `utils/fontLoader.ts` and
|
|
15
|
+
* `utils/fontResolver.ts`.
|
|
16
|
+
*/
|
|
17
|
+
const BUNDLED_FAMILY_SUBSTITUTES = {
|
|
18
|
+
Arimo: "Arial, and the generic sans-serif",
|
|
19
|
+
Caladea: "Cambria",
|
|
20
|
+
Carlito: "Calibri",
|
|
21
|
+
Cousine: "Courier New, and the generic monospace",
|
|
22
|
+
Lato: "Aptos and Aptos Display",
|
|
23
|
+
"Noto Sans Arabic": "Dubai, and Arabic script in every other family",
|
|
24
|
+
"Source Sans 3": "Segoe UI",
|
|
25
|
+
Tinos: "Times New Roman, and the generic serif"
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* The subsets a headless measurer or the PDF writer reads, in resolution
|
|
29
|
+
* priority: a code point two subsets cover resolves to the earlier one.
|
|
30
|
+
* A browser is served every subset the family is cut into.
|
|
31
|
+
*/
|
|
32
|
+
const MEASURED_SUBSETS = [
|
|
33
|
+
"latin",
|
|
34
|
+
"latin-ext",
|
|
35
|
+
"arabic"
|
|
36
|
+
];
|
|
37
|
+
/** Folio paints 400 and 700 only, as `DisplayFontFace` states. */
|
|
38
|
+
const BUNDLED_WEIGHTS = [400, 700];
|
|
39
|
+
/** The styles a family may be cut in; a family without italics ships `normal` only. */
|
|
40
|
+
const BUNDLED_STYLES = ["normal", "italic"];
|
|
41
|
+
/** `files/<package>-<subset>-<weight>-<style>.<format>`: the path of one served binary. */
|
|
42
|
+
const bundledFontFilePath = ({ packageName, subset, weight, style, format }) => `files/${packageName}-${subset}-${String(weight)}-${style}.${format}`;
|
|
43
|
+
/** Each family's directory carries the ranges its subsets were cut with. */
|
|
44
|
+
const UNICODE_RANGE_FILE = "unicode.json";
|
|
45
|
+
export { BUNDLED_FAMILY_SUBSTITUTES, BUNDLED_STYLES, BUNDLED_WEIGHTS, FONTSOURCE_PACKAGES, MEASURED_SUBSETS, UNICODE_RANGE_FILE, bundledFontFilePath };
|
|
@@ -1,20 +1,14 @@
|
|
|
1
1
|
import { HeadlessFontSource } from "./headlessMeasure.js";
|
|
2
|
-
|
|
2
|
+
import { FONTSOURCE_PACKAGES } from "./bundledFonts.js";
|
|
3
|
+
/**
|
|
4
|
+
* Where the face files come from: `null` when the family or file is absent.
|
|
5
|
+
* Each family is a directory laid out like its `@fontsource` package;
|
|
6
|
+
* `@stll/folio-fonts/node` reads the copies that package ships.
|
|
7
|
+
*/
|
|
3
8
|
export type FontsourceFiles = {
|
|
4
|
-
/** Bytes of
|
|
9
|
+
/** Bytes of `<relativePath>` in the family directory `<packageName>`. */
|
|
5
10
|
readonly read: (packageName: string, relativePath: string) => Uint8Array | null;
|
|
6
11
|
};
|
|
7
|
-
/** Each bundled family and the `@fontsource` package that ships it. */
|
|
8
|
-
export declare const FONTSOURCE_PACKAGES: {
|
|
9
|
-
readonly Arimo: "arimo";
|
|
10
|
-
readonly Caladea: "caladea";
|
|
11
|
-
readonly Carlito: "carlito";
|
|
12
|
-
readonly Cousine: "cousine";
|
|
13
|
-
readonly Lato: "lato";
|
|
14
|
-
readonly "Noto Sans Arabic": "noto-sans-arabic";
|
|
15
|
-
readonly "Source Sans 3": "source-sans-3";
|
|
16
|
-
readonly Tinos: "tinos";
|
|
17
|
-
};
|
|
18
12
|
/**
|
|
19
13
|
* A CSS `<string>` token, quoted and escaped. A family name comes from an
|
|
20
14
|
* authored document and is emitted into a `<style>` element, so the
|
|
@@ -41,4 +35,5 @@ export type FontsourceFaces = {
|
|
|
41
35
|
readonly fontFaceCss: (options?: FontsourceFontFaceCssOptions) => string;
|
|
42
36
|
};
|
|
43
37
|
/** The `@fontsource` faces, read through `files` and cached per face. */
|
|
44
|
-
export declare const createFontsourceFaces: (files: FontsourceFiles) => FontsourceFaces;
|
|
38
|
+
export declare const createFontsourceFaces: (files: FontsourceFiles) => FontsourceFaces;
|
|
39
|
+
export { FONTSOURCE_PACKAGES };
|
|
@@ -2,6 +2,7 @@ import { bytesToDataUrl } from "../utils/base64.js";
|
|
|
2
2
|
import { FONT_MAPPING } from "../utils/fontLoader.js";
|
|
3
3
|
import { parseFontFamilyList, resolveFontFamily } from "../utils/fontResolver.js";
|
|
4
4
|
import { decodeText } from "../utils/textEncoding.js";
|
|
5
|
+
import { BUNDLED_WEIGHTS, FONTSOURCE_PACKAGES, MEASURED_SUBSETS, UNICODE_RANGE_FILE, bundledFontFilePath } from "./bundledFonts.js";
|
|
5
6
|
// src/fonts/fontsourceFaces.ts
|
|
6
7
|
/**
|
|
7
8
|
* Faces from the `@fontsource` packages, for a caller that measures and paints
|
|
@@ -23,25 +24,8 @@ import { decodeText } from "../utils/textEncoding.js";
|
|
|
23
24
|
* The subsets served, in resolution priority: a code point two subsets cover
|
|
24
25
|
* resolves to the earlier one on both sides.
|
|
25
26
|
*/
|
|
26
|
-
const SUBSET_PRIORITY =
|
|
27
|
-
|
|
28
|
-
"latin-ext",
|
|
29
|
-
"arabic"
|
|
30
|
-
];
|
|
31
|
-
/** Folio paints 400 and 700 only, as `DisplayFontFace` states. */
|
|
32
|
-
const REGULAR_WEIGHT = 400;
|
|
33
|
-
const BOLD_WEIGHT = 700;
|
|
34
|
-
/** Each bundled family and the `@fontsource` package that ships it. */
|
|
35
|
-
const FONTSOURCE_PACKAGES = {
|
|
36
|
-
Arimo: "arimo",
|
|
37
|
-
Caladea: "caladea",
|
|
38
|
-
Carlito: "carlito",
|
|
39
|
-
Cousine: "cousine",
|
|
40
|
-
Lato: "lato",
|
|
41
|
-
"Noto Sans Arabic": "noto-sans-arabic",
|
|
42
|
-
"Source Sans 3": "source-sans-3",
|
|
43
|
-
Tinos: "tinos"
|
|
44
|
-
};
|
|
27
|
+
const SUBSET_PRIORITY = MEASURED_SUBSETS;
|
|
28
|
+
const [REGULAR_WEIGHT, BOLD_WEIGHT] = BUNDLED_WEIGHTS;
|
|
45
29
|
/** A CSS stack ends in a generic, so a generic must resolve to a bundled family too. */
|
|
46
30
|
const GENERIC_FAMILIES = {
|
|
47
31
|
serif: "Tinos",
|
|
@@ -81,8 +65,6 @@ const resolveBundledFamily = (family) => {
|
|
|
81
65
|
return null;
|
|
82
66
|
};
|
|
83
67
|
const weightOf = (bold) => bold ? BOLD_WEIGHT : REGULAR_WEIGHT;
|
|
84
|
-
/** `@fontsource` ships the ranges it cut each subset with, beside the files. */
|
|
85
|
-
const UNICODE_RANGE_FILE = "unicode.json";
|
|
86
68
|
const RANGE_TOKEN_RE = /^U\+([0-9A-Fa-f]+)(?:-([0-9A-Fa-f]+))?$/u;
|
|
87
69
|
const parseRangeToken = (token) => {
|
|
88
70
|
const match = RANGE_TOKEN_RE.exec(token.trim());
|
|
@@ -172,7 +154,13 @@ const createFontsourceFaces = (files) => {
|
|
|
172
154
|
const cached = faceCache.get(key);
|
|
173
155
|
if (cached !== void 0) return cached;
|
|
174
156
|
const binaries = SUBSET_PRIORITY.flatMap((subset) => {
|
|
175
|
-
const bytes = files.read(packageName,
|
|
157
|
+
const bytes = files.read(packageName, bundledFontFilePath({
|
|
158
|
+
packageName,
|
|
159
|
+
subset,
|
|
160
|
+
weight,
|
|
161
|
+
style,
|
|
162
|
+
format: "woff"
|
|
163
|
+
}));
|
|
176
164
|
return bytes === null ? [] : [{
|
|
177
165
|
subset,
|
|
178
166
|
bytes
|
|
@@ -1124,7 +1124,8 @@ function renderPage(page, context, options = {}) {
|
|
|
1124
1124
|
}
|
|
1125
1125
|
if (shouldClipHeader) {
|
|
1126
1126
|
headerEl.style.maxHeight = `${availableHeaderHeight}px`;
|
|
1127
|
-
headerEl.style.
|
|
1127
|
+
headerEl.style.overflowX = "visible";
|
|
1128
|
+
headerEl.style.overflowY = "clip";
|
|
1128
1129
|
headerEl.style.overflowClipMargin = `${HEADER_FOOTER_RULE_CLIP_MARGIN}px`;
|
|
1129
1130
|
}
|
|
1130
1131
|
pageEl.append(headerEl);
|
|
@@ -1177,7 +1178,8 @@ function renderPage(page, context, options = {}) {
|
|
|
1177
1178
|
}
|
|
1178
1179
|
if (shouldClipFooter) {
|
|
1179
1180
|
footerEl.style.maxHeight = `${availableFooterHeight}px`;
|
|
1180
|
-
footerEl.style.
|
|
1181
|
+
footerEl.style.overflowX = "visible";
|
|
1182
|
+
footerEl.style.overflowY = "clip";
|
|
1181
1183
|
footerEl.style.overflowClipMargin = `${HEADER_FOOTER_RULE_CLIP_MARGIN}px`;
|
|
1182
1184
|
}
|
|
1183
1185
|
pageEl.append(footerEl);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { document_d_exports } from "../types/document.js";
|
|
2
|
+
import { RenderContext } from "./types.js";
|
|
3
|
+
export type CountedParagraphList = {
|
|
4
|
+
/**
|
|
5
|
+
* The list the paragraph shows: its own numbering's, or, while a pending
|
|
6
|
+
* property change removed it, the one that numbering showed.
|
|
7
|
+
*/
|
|
8
|
+
visibleList: document_d_exports.ListRendering | undefined;
|
|
9
|
+
/** The label the counter gave the paragraph; absent when it shows none. */
|
|
10
|
+
label: string | undefined;
|
|
11
|
+
};
|
|
12
|
+
export declare function countParagraphList(ctx: RenderContext, pkg: document_d_exports.DocxPackage | undefined, para: document_d_exports.Paragraph): CountedParagraphList;
|
|
13
|
+
/**
|
|
14
|
+
* Count the paragraphs of a text box Markdown does not write, cells
|
|
15
|
+
* included, in document order. A text box holds no text box, so the walk
|
|
16
|
+
* stops at each paragraph.
|
|
17
|
+
*/
|
|
18
|
+
export declare function countTextBoxLists(ctx: RenderContext, pkg: document_d_exports.DocxPackage | undefined, shape: document_d_exports.Shape): void;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { getCachedNumberingMap } from "../docx/numberingParser.js";
|
|
2
|
+
import { BLOCK_TREE_DESCENT, visitBlockTreeRecords } from "../docx/paragraphTraversal.js";
|
|
3
|
+
import { listLabelAttrsFromRendering } from "../prosemirror/listLabels.js";
|
|
4
|
+
import { effectiveParagraphNumberingReference, paragraphNumberingAttr } from "../prosemirror/numberingAttr.js";
|
|
5
|
+
import { resolveParagraphList } from "../prosemirror/paragraphList.js";
|
|
6
|
+
// src/markdown/countParagraphList.ts
|
|
7
|
+
/**
|
|
8
|
+
* Advance the Markdown walk's list counter past one paragraph, as the page
|
|
9
|
+
* counts it. Every paragraph the page counts, Markdown counts, whatever it
|
|
10
|
+
* renders as and whether or not it writes it: the items after a numbered
|
|
11
|
+
* heading continue from its number, and the paragraphs of a text box count
|
|
12
|
+
* right after the paragraph the box is drawn in (where the page and the
|
|
13
|
+
* content reader count them), although Markdown writes no text box.
|
|
14
|
+
*/
|
|
15
|
+
/** Explicit projection carried by canonical models that are not attached to a package. */
|
|
16
|
+
const standaloneListRendering = (para) => para.listRendering;
|
|
17
|
+
function countParagraphList(ctx, pkg, para) {
|
|
18
|
+
const inherited = ctx.paragraphNumbering(para.formatting?.styleId);
|
|
19
|
+
const inheritedReference = effectiveParagraphNumberingReference({
|
|
20
|
+
numPr: void 0,
|
|
21
|
+
numPrFromStyle: inherited
|
|
22
|
+
});
|
|
23
|
+
const sources = {
|
|
24
|
+
numPr: para.formatting?.numPr,
|
|
25
|
+
numPrFromStyle: para.formatting?.numPrFromStyle
|
|
26
|
+
};
|
|
27
|
+
const numbering = pkg?.numbering ? getCachedNumberingMap(pkg.numbering) : null;
|
|
28
|
+
const resolvedList = resolveParagraphList({
|
|
29
|
+
...sources,
|
|
30
|
+
inheritedReference: inheritedReference ?? null
|
|
31
|
+
}, numbering, standaloneListRendering(para));
|
|
32
|
+
const resolvedRendering = resolvedList.kind === "marker" ? resolvedList.rendering : void 0;
|
|
33
|
+
const counterAttrs = listLabelAttrsFromRendering(resolvedRendering, {
|
|
34
|
+
...sources,
|
|
35
|
+
numPrFromStyle: resolvedList.kind === "none" ? void 0 : inherited ?? sources.numPrFromStyle
|
|
36
|
+
});
|
|
37
|
+
if (sources.numPr === void 0 && sources.numPrFromStyle !== void 0 && inheritedReference === void 0 && para.propertyChanges !== void 0 && para.propertyChanges.length > 0) counterAttrs.numPr = paragraphNumberingAttr({ kind: "none" });
|
|
38
|
+
if (para.pPrMark) counterAttrs.pPrMark = para.pPrMark;
|
|
39
|
+
let originalList;
|
|
40
|
+
if (para.propertyChanges && ctx.opts.trackedChanges !== "clean") counterAttrs._propertyChanges = para.propertyChanges.map(({ previousFormatting, ...change }) => {
|
|
41
|
+
if (!previousFormatting) return change;
|
|
42
|
+
const previousInherited = ctx.paragraphNumbering(previousFormatting.styleId);
|
|
43
|
+
const previousInheritedReference = effectiveParagraphNumberingReference({
|
|
44
|
+
numPr: void 0,
|
|
45
|
+
numPrFromStyle: previousInherited
|
|
46
|
+
});
|
|
47
|
+
const previousSources = {
|
|
48
|
+
numPr: previousFormatting.numPr,
|
|
49
|
+
numPrFromStyle: previousFormatting.numPrFromStyle
|
|
50
|
+
};
|
|
51
|
+
const previousList = resolveParagraphList({
|
|
52
|
+
...previousSources,
|
|
53
|
+
inheritedReference: previousInheritedReference ?? null
|
|
54
|
+
}, numbering);
|
|
55
|
+
const previousRendering = previousList.kind === "marker" ? previousList.rendering : void 0;
|
|
56
|
+
originalList ??= previousRendering;
|
|
57
|
+
const previousAttrs = listLabelAttrsFromRendering(previousRendering, {
|
|
58
|
+
...previousSources,
|
|
59
|
+
numPrFromStyle: previousList.kind === "none" ? void 0 : previousInherited ?? previousSources.numPrFromStyle
|
|
60
|
+
});
|
|
61
|
+
return {
|
|
62
|
+
...change,
|
|
63
|
+
previousFormatting: {
|
|
64
|
+
...previousFormatting,
|
|
65
|
+
...previousAttrs,
|
|
66
|
+
...previousAttrs.numPr === void 0 ? { numPr: null } : { numPr: previousAttrs.numPr },
|
|
67
|
+
...previousAttrs.numPrFromStyle === void 0 ? { numPrFromStyle: null } : { numPrFromStyle: previousAttrs.numPrFromStyle }
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
const countedList = ctx.nextListLabel(counterAttrs, resolvedRendering);
|
|
72
|
+
return {
|
|
73
|
+
visibleList: resolvedRendering ?? (sources.numPr?.kind === "levelOnly" ? void 0 : originalList),
|
|
74
|
+
label: countedList.kind === "marker" ? countedList.label : void 0
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Count the paragraphs of a text box Markdown does not write, cells
|
|
79
|
+
* included, in document order. A text box holds no text box, so the walk
|
|
80
|
+
* stops at each paragraph.
|
|
81
|
+
*/
|
|
82
|
+
function countTextBoxLists(ctx, pkg, shape) {
|
|
83
|
+
if (shape.textBody === void 0) return;
|
|
84
|
+
visitBlockTreeRecords(shape.textBody.content, (record) => {
|
|
85
|
+
if (record.type !== "paragraph") return BLOCK_TREE_DESCENT.descend;
|
|
86
|
+
countParagraphList(ctx, pkg, record);
|
|
87
|
+
return BLOCK_TREE_DESCENT.prune;
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
export { countParagraphList, countTextBoxLists };
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { isQuoteStyle, resolveHeadingLevel } from "../docx/builtInStyles.js";
|
|
2
|
-
import {
|
|
3
|
-
import { listLabelAttrsFromRendering } from "../prosemirror/listLabels.js";
|
|
4
|
-
import { effectiveParagraphNumberingReference, paragraphNumberingAttr } from "../prosemirror/numberingAttr.js";
|
|
5
|
-
import { resolveParagraphList } from "../prosemirror/paragraphList.js";
|
|
2
|
+
import { countParagraphList } from "./countParagraphList.js";
|
|
6
3
|
import { inlineBreaksToHtml } from "./escape.js";
|
|
7
4
|
import { renderParagraphInline } from "./renderRuns.js";
|
|
8
5
|
// src/markdown/renderParagraph.ts
|
|
@@ -21,8 +18,6 @@ import { renderParagraphInline } from "./renderRuns.js";
|
|
|
21
18
|
*/
|
|
22
19
|
/** Markdown has six heading levels; Word has nine. */
|
|
23
20
|
const MAX_MARKDOWN_HEADING_LEVEL = 6;
|
|
24
|
-
/** Explicit projection carried by canonical models that are not attached to a package. */
|
|
25
|
-
const standaloneListRendering = (para) => para.listRendering;
|
|
26
21
|
/**
|
|
27
22
|
* Render a paragraph and return the block text. No surrounding blank line: the
|
|
28
23
|
* caller joins blocks.
|
|
@@ -37,61 +32,7 @@ function renderParagraph(ctx, pkg, para) {
|
|
|
37
32
|
* does not define as prose, as every other reader reads them.
|
|
38
33
|
*/
|
|
39
34
|
function renderParagraphBlock(ctx, pkg, para) {
|
|
40
|
-
const
|
|
41
|
-
const inheritedReference = effectiveParagraphNumberingReference({
|
|
42
|
-
numPr: void 0,
|
|
43
|
-
numPrFromStyle: inherited
|
|
44
|
-
});
|
|
45
|
-
const sources = {
|
|
46
|
-
numPr: para.formatting?.numPr,
|
|
47
|
-
numPrFromStyle: para.formatting?.numPrFromStyle
|
|
48
|
-
};
|
|
49
|
-
const numbering = pkg?.numbering ? getCachedNumberingMap(pkg.numbering) : null;
|
|
50
|
-
const resolvedList = resolveParagraphList({
|
|
51
|
-
...sources,
|
|
52
|
-
inheritedReference: inheritedReference ?? null
|
|
53
|
-
}, numbering, standaloneListRendering(para));
|
|
54
|
-
const resolvedRendering = resolvedList.kind === "marker" ? resolvedList.rendering : void 0;
|
|
55
|
-
const counterAttrs = listLabelAttrsFromRendering(resolvedRendering, {
|
|
56
|
-
...sources,
|
|
57
|
-
numPrFromStyle: resolvedList.kind === "none" ? void 0 : inherited ?? sources.numPrFromStyle
|
|
58
|
-
});
|
|
59
|
-
if (sources.numPr === void 0 && sources.numPrFromStyle !== void 0 && inheritedReference === void 0 && para.propertyChanges !== void 0 && para.propertyChanges.length > 0) counterAttrs.numPr = paragraphNumberingAttr({ kind: "none" });
|
|
60
|
-
if (para.pPrMark) counterAttrs.pPrMark = para.pPrMark;
|
|
61
|
-
let originalList;
|
|
62
|
-
if (para.propertyChanges && ctx.opts.trackedChanges !== "clean") counterAttrs._propertyChanges = para.propertyChanges.map(({ previousFormatting, ...change }) => {
|
|
63
|
-
if (!previousFormatting) return change;
|
|
64
|
-
const previousInherited = ctx.paragraphNumbering(previousFormatting.styleId);
|
|
65
|
-
const previousInheritedReference = effectiveParagraphNumberingReference({
|
|
66
|
-
numPr: void 0,
|
|
67
|
-
numPrFromStyle: previousInherited
|
|
68
|
-
});
|
|
69
|
-
const previousSources = {
|
|
70
|
-
numPr: previousFormatting.numPr,
|
|
71
|
-
numPrFromStyle: previousFormatting.numPrFromStyle
|
|
72
|
-
};
|
|
73
|
-
const previousList = resolveParagraphList({
|
|
74
|
-
...previousSources,
|
|
75
|
-
inheritedReference: previousInheritedReference ?? null
|
|
76
|
-
}, numbering);
|
|
77
|
-
const previousRendering = previousList.kind === "marker" ? previousList.rendering : void 0;
|
|
78
|
-
originalList ??= previousRendering;
|
|
79
|
-
const previousAttrs = listLabelAttrsFromRendering(previousRendering, {
|
|
80
|
-
...previousSources,
|
|
81
|
-
numPrFromStyle: previousList.kind === "none" ? void 0 : previousInherited ?? previousSources.numPrFromStyle
|
|
82
|
-
});
|
|
83
|
-
return {
|
|
84
|
-
...change,
|
|
85
|
-
previousFormatting: {
|
|
86
|
-
...previousFormatting,
|
|
87
|
-
...previousAttrs,
|
|
88
|
-
...previousAttrs.numPr === void 0 ? { numPr: null } : { numPr: previousAttrs.numPr },
|
|
89
|
-
...previousAttrs.numPrFromStyle === void 0 ? { numPrFromStyle: null } : { numPrFromStyle: previousAttrs.numPrFromStyle }
|
|
90
|
-
}
|
|
91
|
-
};
|
|
92
|
-
});
|
|
93
|
-
const countedList = ctx.nextListLabel(counterAttrs, resolvedRendering);
|
|
94
|
-
const label = countedList.kind === "marker" ? countedList.label : void 0;
|
|
35
|
+
const { visibleList, label } = countParagraphList(ctx, pkg, para);
|
|
95
36
|
const inline = renderParagraphInline(ctx, pkg, para.content, para.paraId);
|
|
96
37
|
const styleId = para.formatting?.styleId;
|
|
97
38
|
const headingLevel = markdownHeadingLevel(ctx, para);
|
|
@@ -108,7 +49,6 @@ function renderParagraphBlock(ctx, pkg, para) {
|
|
|
108
49
|
type: "paragraph"
|
|
109
50
|
};
|
|
110
51
|
}
|
|
111
|
-
const visibleList = resolvedRendering ?? (sources.numPr?.kind === "levelOnly" ? void 0 : originalList);
|
|
112
52
|
if (visibleList && label !== void 0) return renderListItem(ctx, visibleList, label, inline);
|
|
113
53
|
ctx.listIndentWidths = [];
|
|
114
54
|
if (isQuoteStyle(styleId, ctx.builtInStyles)) return {
|
|
@@ -2,6 +2,7 @@ import { getHyperlinkRuns } from "../docx/hyperlinkParser.js";
|
|
|
2
2
|
import { RELATIONSHIP_TYPES, resolveRelationshipIdOfType, resolveRelativePath } from "../docx/relsParser.js";
|
|
3
3
|
import { decodeOoxmlSymbolCharacter } from "../utils/ooxmlSymbol.js";
|
|
4
4
|
import { wrapComment, wrapDeletion, wrapInsertion, wrapMoveFrom, wrapMoveTo } from "./annotations.js";
|
|
5
|
+
import { countTextBoxLists } from "./countParagraphList.js";
|
|
5
6
|
import { escapeAltText, escapeInline, escapeLinkUrl } from "./escape.js";
|
|
6
7
|
import { registerImage } from "./images.js";
|
|
7
8
|
import { numberNoteReference, pushWarning } from "./internals.js";
|
|
@@ -251,7 +252,9 @@ function renderRunContent(ctx, pkg, content, paraId) {
|
|
|
251
252
|
pushWarning(ctx, `image rId=${item.image.rId ?? "(absent)"} not resolvable`);
|
|
252
253
|
break;
|
|
253
254
|
}
|
|
254
|
-
case "shape":
|
|
255
|
+
case "shape":
|
|
256
|
+
countTextBoxLists(ctx, pkg, item.shape);
|
|
257
|
+
pushWarning(ctx, "shape not representable in markdown");
|
|
255
258
|
}
|
|
256
259
|
return out;
|
|
257
260
|
}
|
|
@@ -1972,7 +1972,7 @@ function extractParagraphContent(originalParagraph, _documentCounts, emptyHyperl
|
|
|
1972
1972
|
currentTrackedChange = void 0;
|
|
1973
1973
|
return;
|
|
1974
1974
|
}
|
|
1975
|
-
const { anchorId } = expectTextBoxAnchorAttrs(node);
|
|
1975
|
+
const { anchorId, _docxRunFormatting: carried } = expectTextBoxAnchorAttrs(node);
|
|
1976
1976
|
if (textBoxAnchorMarkers.has(anchorId)) {
|
|
1977
1977
|
flushCurrentInline();
|
|
1978
1978
|
currentTrackedChange = void 0;
|
|
@@ -1986,8 +1986,10 @@ function extractParagraphContent(originalParagraph, _documentCounts, emptyHyperl
|
|
|
1986
1986
|
text: ""
|
|
1987
1987
|
}]
|
|
1988
1988
|
};
|
|
1989
|
-
|
|
1990
|
-
|
|
1989
|
+
if (carried === void 0) {
|
|
1990
|
+
const formatting = getAtomRunFormattingFromMarks(node.marks, formattingContext);
|
|
1991
|
+
if (formatting) marker.formatting = formatting;
|
|
1992
|
+
} else if (Object.keys(carried).length > 0) marker.formatting = structuredClone(carried);
|
|
1991
1993
|
restoreRunRecord(marker, node.marks);
|
|
1992
1994
|
textBoxAnchorMarkers.set(anchorId, marker);
|
|
1993
1995
|
anchorRun = marker;
|
|
@@ -2180,7 +2180,10 @@ function convertRunContent({ content, marks, formatting, textBoxAnchors, authore
|
|
|
2180
2180
|
const shp = content.shape;
|
|
2181
2181
|
if (shp.textBody) {
|
|
2182
2182
|
const anchorId = textBoxAnchors?.get(shp);
|
|
2183
|
-
return anchorId ? [schema.node("textBoxAnchor", {
|
|
2183
|
+
return anchorId ? [schema.node("textBoxAnchor", {
|
|
2184
|
+
anchorId,
|
|
2185
|
+
_docxRunFormatting: { ...authoredFormatting }
|
|
2186
|
+
}).mark(marks)] : [];
|
|
2184
2187
|
}
|
|
2185
2188
|
return [withRunBoundaryMarks(withAlternateContent(convertShape(shp, carriedRunFormatting(authoredFormatting)), replayableShapeAlternateContent(content), content.alternateContent), marks)];
|
|
2186
2189
|
}
|
|
@@ -19,7 +19,10 @@ const TextBoxAnchorExtension = createNodeExtension({
|
|
|
19
19
|
marks: "_",
|
|
20
20
|
atom: true,
|
|
21
21
|
selectable: false,
|
|
22
|
-
attrs: {
|
|
22
|
+
attrs: {
|
|
23
|
+
anchorId: {},
|
|
24
|
+
_docxRunFormatting: { default: null }
|
|
25
|
+
},
|
|
23
26
|
parseDOM: [{
|
|
24
27
|
tag: "span[data-docx-textbox-anchor]",
|
|
25
28
|
getAttrs(dom) {
|
|
@@ -48,6 +48,12 @@ export declare const hasReplacedAnnotations: ({ carried, leading, trailing }: Re
|
|
|
48
48
|
* replacement either.
|
|
49
49
|
*/
|
|
50
50
|
export declare const surveyReplacedAnnotations: (doc: Node, from: number, to: number) => ReplacedAnnotations;
|
|
51
|
+
/**
|
|
52
|
+
* {@link surveyReplacedAnnotations} over the span as it reads once accepted:
|
|
53
|
+
* what a replacement of it carries, which a direct edit of the same text
|
|
54
|
+
* carries too.
|
|
55
|
+
*/
|
|
56
|
+
export declare const surveyAcceptedReplacedAnnotations: (doc: Node, from: number, to: number) => ReplacedAnnotations;
|
|
51
57
|
type AnnotatedReplacementOptions = {
|
|
52
58
|
annotations: ReplacedAnnotations;
|
|
53
59
|
/** The replacement's own inline content, already carrying its formatting. */
|
|
@@ -197,19 +197,21 @@ const NOTHING_REPLACED = {
|
|
|
197
197
|
trailing: []
|
|
198
198
|
};
|
|
199
199
|
const hasReplacedAnnotations = ({ carried, leading, trailing }) => carried.length > 0 || leading.length > 0 || trailing.length > 0;
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
200
|
+
const lendsMarks = (node, content) => {
|
|
201
|
+
switch (content) {
|
|
202
|
+
case "asWritten": return true;
|
|
203
|
+
case "asAccepted": return !node.marks.some(({ type }) => type.name === "deletion");
|
|
204
|
+
default: throw new Error(`Unhandled replaced content ${String(content)}`);
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
const surveyAnnotations = (doc, { from, to, content }) => {
|
|
206
208
|
if (to <= from) return NOTHING_REPLACED;
|
|
207
209
|
const carried = [];
|
|
208
210
|
const leading = [];
|
|
209
211
|
const trailing = [];
|
|
210
212
|
doc.nodesBetween(from, to, (node, position) => {
|
|
211
213
|
if (!node.isInline) return true;
|
|
212
|
-
for (const mark of node.marks) if (isCarried(mark) && !alreadyCarried(mark, carried)) carried.push(mark);
|
|
214
|
+
for (const mark of lendsMarks(node, content) ? node.marks : []) if (isCarried(mark) && !alreadyCarried(mark, carried)) carried.push(mark);
|
|
213
215
|
const placement = anchorPlacement(node);
|
|
214
216
|
if (placement === void 0 || position < from || position + node.nodeSize > to) return false;
|
|
215
217
|
(opensRange(node, placement) ? leading : trailing).push(node);
|
|
@@ -222,6 +224,26 @@ const surveyReplacedAnnotations = (doc, from, to) => {
|
|
|
222
224
|
};
|
|
223
225
|
};
|
|
224
226
|
/**
|
|
227
|
+
* The annotations carried by `[from, to)`. An anchor counts only when the
|
|
228
|
+
* range covers it whole: a partially covered atom is not deleted by the
|
|
229
|
+
* replacement either.
|
|
230
|
+
*/
|
|
231
|
+
const surveyReplacedAnnotations = (doc, from, to) => surveyAnnotations(doc, {
|
|
232
|
+
from,
|
|
233
|
+
to,
|
|
234
|
+
content: "asWritten"
|
|
235
|
+
});
|
|
236
|
+
/**
|
|
237
|
+
* {@link surveyReplacedAnnotations} over the span as it reads once accepted:
|
|
238
|
+
* what a replacement of it carries, which a direct edit of the same text
|
|
239
|
+
* carries too.
|
|
240
|
+
*/
|
|
241
|
+
const surveyAcceptedReplacedAnnotations = (doc, from, to) => surveyAnnotations(doc, {
|
|
242
|
+
from,
|
|
243
|
+
to,
|
|
244
|
+
content: "asAccepted"
|
|
245
|
+
});
|
|
246
|
+
/**
|
|
225
247
|
* Whether the span already contributed this mark, or one the mark would evict.
|
|
226
248
|
* Comments do not exclude each other, so every comment is collected; a
|
|
227
249
|
* hyperlink excludes its own type, so the first address in the span wins
|
|
@@ -476,4 +498,4 @@ const inheritedReplacementMarks = (doc, from, to) => {
|
|
|
476
498
|
const $from = doc.resolve(from);
|
|
477
499
|
return (to === from ? $from.marks() : $from.marksAcross(doc.resolve(to))) ?? Mark.none;
|
|
478
500
|
};
|
|
479
|
-
export { NON_INCLUSIVE_MARK_DISPOSITION, addCarriedMarks, annotatedReplacement, commentPoint, commentsAcrossBlockBoundary, commentsForTrackedInsertion, createResolvedCommentPointCollector, hasReplacedAnnotations, inheritedReplacementMarks, joinCommentStretches, nonInclusiveMarkNames, placeTrackedInsertion, resolvedCommentPoints, surveyReplacedAnnotations };
|
|
501
|
+
export { NON_INCLUSIVE_MARK_DISPOSITION, addCarriedMarks, annotatedReplacement, commentPoint, commentsAcrossBlockBoundary, commentsForTrackedInsertion, createResolvedCommentPointCollector, hasReplacedAnnotations, inheritedReplacementMarks, joinCommentStretches, nonInclusiveMarkNames, placeTrackedInsertion, resolvedCommentPoints, surveyAcceptedReplacedAnnotations, surveyReplacedAnnotations };
|
|
@@ -969,6 +969,13 @@ export type TextBoxAttrs = {
|
|
|
969
969
|
/** Internal inline position marker for an extracted text box block. */
|
|
970
970
|
export type TextBoxAnchorAttrs = {
|
|
971
971
|
anchorId: string;
|
|
972
|
+
/**
|
|
973
|
+
* The `w:rPr` of the run the text box came from, possibly empty. The
|
|
974
|
+
* anchor's formatting marks are rendered in the paragraph's style cascade
|
|
975
|
+
* and are not rebased when the paragraph is restyled, so the save writes
|
|
976
|
+
* this instead. Absent on an anchor not read from a package.
|
|
977
|
+
*/
|
|
978
|
+
_docxRunFormatting?: document_d_exports.TextFormatting;
|
|
972
979
|
};
|
|
973
980
|
/**
|
|
974
981
|
* Table node attributes
|
|
@@ -12,13 +12,21 @@ const readTextBoxAnchorAttrs = (node) => {
|
|
|
12
12
|
path: "textBoxAnchor.attrs.anchorId",
|
|
13
13
|
message: "Expected a non-empty string."
|
|
14
14
|
});
|
|
15
|
+
const runFormatting = node.attrs["_docxRunFormatting"];
|
|
16
|
+
if (runFormatting !== null && runFormatting !== void 0 && (typeof runFormatting !== "object" || Array.isArray(runFormatting))) issues.push({
|
|
17
|
+
path: "textBoxAnchor.attrs._docxRunFormatting",
|
|
18
|
+
message: "Expected run formatting or null."
|
|
19
|
+
});
|
|
15
20
|
if (issues.length > 0 || typeof anchorId !== "string") return {
|
|
16
21
|
ok: false,
|
|
17
22
|
issues
|
|
18
23
|
};
|
|
19
24
|
return {
|
|
20
25
|
ok: true,
|
|
21
|
-
value: { anchorId }
|
|
26
|
+
value: runFormatting === null || runFormatting === void 0 ? { anchorId } : {
|
|
27
|
+
anchorId,
|
|
28
|
+
_docxRunFormatting: runFormatting
|
|
29
|
+
}
|
|
22
30
|
};
|
|
23
31
|
};
|
|
24
32
|
const expectTextBoxAnchorAttrs = (node) => {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Font Loader
|
|
3
3
|
*
|
|
4
4
|
* Manages font availability for the DOCX editor. Fonts are bundled via
|
|
5
|
-
*
|
|
5
|
+
* `@stll/folio-fonts` (WOFF2, no external requests). This module
|
|
6
6
|
* tracks which fonts are loaded and provides callbacks for re-rendering
|
|
7
7
|
* when fonts become available.
|
|
8
8
|
*/
|
package/dist/utils/fontLoader.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { FONTSOURCE_PACKAGES } from "../fonts/bundledFonts.js";
|
|
1
2
|
// src/utils/fontLoader.ts
|
|
2
3
|
/**
|
|
3
4
|
* Font Loader
|
|
4
5
|
*
|
|
5
6
|
* Manages font availability for the DOCX editor. Fonts are bundled via
|
|
6
|
-
*
|
|
7
|
+
* `@stll/folio-fonts` (WOFF2, no external requests). This module
|
|
7
8
|
* tracks which fonts are loaded and provides callbacks for re-rendering
|
|
8
9
|
* when fonts become available.
|
|
9
10
|
*/
|
|
@@ -34,16 +35,7 @@ const FONT_MAPPING = {
|
|
|
34
35
|
"Lucida Console": "monospace"
|
|
35
36
|
};
|
|
36
37
|
/** Bundled font families (available without network requests). */
|
|
37
|
-
const BUNDLED_FONTS =
|
|
38
|
-
"Carlito",
|
|
39
|
-
"Caladea",
|
|
40
|
-
"Arimo",
|
|
41
|
-
"Tinos",
|
|
42
|
-
"Cousine",
|
|
43
|
-
"Lato",
|
|
44
|
-
"Source Sans 3",
|
|
45
|
-
"Noto Sans Arabic"
|
|
46
|
-
]);
|
|
38
|
+
const BUNDLED_FONTS = new Set(Object.keys(FONTSOURCE_PACKAGES));
|
|
47
39
|
/**
|
|
48
40
|
* Mark a font as loaded. For bundled fonts this is immediate;
|
|
49
41
|
* for others it checks CSS Font Loading API.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stll/folio-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.66.1",
|
|
4
4
|
"description": "Headless, framework-neutral core of folio: the OOXML (.docx) parser, document model, ProseMirror integration, and page-layout engine. No React.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"document-model",
|
|
@@ -29,7 +29,9 @@
|
|
|
29
29
|
"skills",
|
|
30
30
|
"README.md",
|
|
31
31
|
"LICENSE",
|
|
32
|
-
"NOTICE.md"
|
|
32
|
+
"NOTICE.md",
|
|
33
|
+
"licenses",
|
|
34
|
+
"THIRD_PARTY_NOTICES.md"
|
|
33
35
|
],
|
|
34
36
|
"type": "module",
|
|
35
37
|
"sideEffects": false,
|
|
@@ -171,14 +173,13 @@
|
|
|
171
173
|
"perf": "bun scripts/profile-editor.ts"
|
|
172
174
|
},
|
|
173
175
|
"dependencies": {
|
|
174
|
-
"@stll/docx-core": "^0.
|
|
176
|
+
"@stll/docx-core": "^0.41.0",
|
|
175
177
|
"@stll/docx-utils": "^0.2.0",
|
|
176
178
|
"@stll/template-conditions": "^0.6.2",
|
|
177
179
|
"@stll/utf8-boundaries": "0.2.0",
|
|
178
180
|
"better-result": "3.0.1",
|
|
179
181
|
"csstype": "^3.1.3",
|
|
180
182
|
"dompurify": "^3.4.17",
|
|
181
|
-
"fast-xml-parser": "^5.11.2",
|
|
182
183
|
"hyphen": "1.14.1",
|
|
183
184
|
"utif2": "^4.1.0",
|
|
184
185
|
"valibot": "1.5.0"
|