@stll/folio-core 0.48.0 → 0.49.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/ai-edits/apply.js +565 -121
- package/dist/ai-edits/minimal-replacement.d.ts +64 -0
- package/dist/ai-edits/minimal-replacement.js +282 -0
- package/dist/controller/layoutPipeline.js +11 -1
- package/dist/display-list/build/paragraphPrimitives.js +63 -7
- package/dist/display-list/build/textBoxPrimitives.js +1 -1
- package/dist/display-list/build/unsupported.d.ts +2 -2
- package/dist/display-list/build/unsupported.js +0 -0
- package/dist/display-list/dom/renderDisplayListToDom.js +0 -1
- package/dist/docx/blockContentParser.js +2 -2
- package/dist/docx/bulletMarkers.d.ts +18 -2
- package/dist/docx/bulletMarkers.js +27 -2
- package/dist/docx/drawingGroupChildren.d.ts +60 -0
- package/dist/docx/drawingGroupChildren.js +176 -0
- package/dist/docx/drawingUtils.js +3 -0
- package/dist/docx/groupDrawingParser.d.ts +7 -1
- package/dist/docx/groupDrawingParser.js +123 -32
- package/dist/docx/packageParts.d.ts +45 -1
- package/dist/docx/packageParts.js +93 -2
- package/dist/docx/paraIdAttribute.d.ts +3 -2
- package/dist/docx/paragraphParser.js +91 -7
- package/dist/docx/paragraphTextBoxEnrichment.js +126 -4
- package/dist/docx/runParser.js +10 -0
- package/dist/docx/selectiveSaveFlags.d.ts +10 -3
- package/dist/docx/selectiveSaveFlags.js +1 -1
- package/dist/docx/selectiveXmlPatch.d.ts +56 -34
- package/dist/docx/selectiveXmlPatch.js +222 -115
- package/dist/docx/serializer/groupTextBoxWriteBack.d.ts +12 -0
- package/dist/docx/serializer/groupTextBoxWriteBack.js +101 -0
- package/dist/docx/serializer/paragraphSerializer.js +5 -4
- package/dist/docx/serializer/runSerializer.d.ts +5 -1
- package/dist/docx/serializer/runSerializer.js +4 -3
- package/dist/docx/shapeAlternateContent.d.ts +25 -0
- package/dist/docx/shapeAlternateContent.js +52 -0
- package/dist/docx/textBoxParser.d.ts +10 -2
- package/dist/docx/textBoxParser.js +9 -3
- package/dist/internal/headlessRevisionResolution.js +50 -13
- package/dist/layout-bridge/convert/toFlowBlocks.js +26 -4
- package/dist/layout-engine/index.d.ts +2 -2
- package/dist/layout-engine/index.js +33 -4
- package/dist/layout-engine/keep-together.d.ts +5 -2
- package/dist/layout-engine/keep-together.js +5 -1
- package/dist/layout-engine/measure/advanceComposition.js +49 -3
- package/dist/layout-engine/measure/measureBlocks.js +5 -3
- package/dist/layout-engine/measure/measureContainer.js +46 -3
- package/dist/layout-engine/measure/measureHelpers.js +13 -3
- package/dist/layout-engine/measure/measureParagraph.js +44 -81
- package/dist/layout-engine/measure/smallCapsCasing.d.ts +56 -0
- package/dist/layout-engine/measure/smallCapsCasing.js +88 -0
- package/dist/layout-engine/paginator.d.ts +2 -0
- package/dist/layout-engine/paginator.js +5 -1
- package/dist/layout-engine/paragraphSpacing.d.ts +4 -2
- package/dist/layout-engine/paragraphSpacing.js +2 -1
- package/dist/layout-engine/tableRowBreak.d.ts +8 -1
- package/dist/layout-engine/tableRowBreak.js +22 -1
- package/dist/layout-engine/types.d.ts +21 -2
- package/dist/layout-painter/renderParagraph.js +41 -4
- package/dist/layout-painter/renderTextBox.d.ts +12 -2
- package/dist/layout-painter/renderTextBox.js +26 -2
- package/dist/prosemirror/alternateContentAttrs.d.ts +9 -0
- package/dist/prosemirror/alternateContentAttrs.js +35 -0
- package/dist/prosemirror/attrs/index.js +90 -0
- package/dist/prosemirror/commands/comments.js +51 -41
- package/dist/prosemirror/contentControlRevisions.d.ts +43 -0
- package/dist/prosemirror/contentControlRevisions.js +84 -0
- package/dist/prosemirror/conversion/fromProseDoc.js +46 -22
- package/dist/prosemirror/conversion/toProseDoc.js +42 -19
- package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.d.ts +7 -7
- package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +7 -7
- package/dist/prosemirror/extensions/nodes/FieldExtension.js +6 -3
- package/dist/prosemirror/extensions/nodes/SdtExtension.js +9 -2
- package/dist/prosemirror/extensions/nodes/ShapeExtension.js +1 -0
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +3 -0
- package/dist/prosemirror/listMarker.js +2 -2
- package/dist/prosemirror/paragraphDirection.d.ts +29 -2
- package/dist/prosemirror/paragraphDirection.js +21 -2
- package/dist/prosemirror/plugins/suggestionMode.js +37 -8
- package/dist/prosemirror/rejoinRunCarriers.d.ts +27 -0
- package/dist/prosemirror/rejoinRunCarriers.js +66 -0
- package/dist/prosemirror/runIdentityAcrossRevisions.d.ts +10 -0
- package/dist/prosemirror/runIdentityAcrossRevisions.js +90 -0
- package/dist/prosemirror/schema/nodes.d.ts +37 -5
- package/dist/types/content.d.ts +2 -2
- package/package.json +2 -2
- package/dist/layout-engine/justifiedLineFit.d.ts +0 -7
- package/dist/layout-engine/justifiedLineFit.js +0 -6
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Shape, ShapeAlternateContent, ShapeContent } from "../types/content.js";
|
|
2
|
+
import { XmlElement } from "./xmlParser.js";
|
|
3
|
+
//#region src/docx/shapeAlternateContent.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Capture the `mc:AlternateContent` a shape was read from, when the shape's
|
|
6
|
+
* drawing is the sole child of the branch it came from.
|
|
7
|
+
*/
|
|
8
|
+
declare const captureShapeAlternateContent: ({ shape, alternateContent, branch, drawing }: {
|
|
9
|
+
shape: Shape;
|
|
10
|
+
alternateContent: XmlElement;
|
|
11
|
+
branch: XmlElement;
|
|
12
|
+
drawing: XmlElement;
|
|
13
|
+
}) => ShapeAlternateContent | undefined;
|
|
14
|
+
/** Re-fingerprint a capture for a shape rebuilt from an unedited projection. */
|
|
15
|
+
declare const refingerprintShapeAlternateContent: (shape: Shape, verbatimXml: string) => ShapeAlternateContent;
|
|
16
|
+
/**
|
|
17
|
+
* The captured `mc:AlternateContent` to write in place of the shape's drawing,
|
|
18
|
+
* or undefined when the shape must be regenerated.
|
|
19
|
+
*
|
|
20
|
+
* Replay requires the modeled shape to still match the capture. An edited shape
|
|
21
|
+
* is regenerated as a bare `w:drawing` without the stale Fallback.
|
|
22
|
+
*/
|
|
23
|
+
declare const replayableShapeAlternateContent: (content: ShapeContent) => string | undefined;
|
|
24
|
+
//#endregion
|
|
25
|
+
export { captureShapeAlternateContent, refingerprintShapeAlternateContent, replayableShapeAlternateContent };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { canonicalJson } from "../utils/canonicalJson.js";
|
|
2
|
+
import { captureVerbatimXml } from "./verbatimCapture.js";
|
|
3
|
+
import { getChildElements, getLocalName } from "./xmlParser.js";
|
|
4
|
+
//#region src/docx/shapeAlternateContent.ts
|
|
5
|
+
/**
|
|
6
|
+
* Every modeled field, text body included: the Fallback repeats the shape's
|
|
7
|
+
* geometry and its text, so an edit to either makes the captured element stale.
|
|
8
|
+
*/
|
|
9
|
+
const shapeFingerprint = (shape) => canonicalJson(shape);
|
|
10
|
+
/**
|
|
11
|
+
* Whether `element` is the only child element of its `mc:Choice` or
|
|
12
|
+
* `mc:Fallback` branch.
|
|
13
|
+
*
|
|
14
|
+
* A branch holding more than the one drawing cannot be written back by the
|
|
15
|
+
* drawing's shape alone: the siblings are parsed into their own run content,
|
|
16
|
+
* and replaying the whole element would write them twice.
|
|
17
|
+
*/
|
|
18
|
+
const isSoleBranchChild = (branch, element) => {
|
|
19
|
+
const children = getChildElements(branch);
|
|
20
|
+
return children.length === 1 && children[0] === element;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Capture the `mc:AlternateContent` a shape was read from, when the shape's
|
|
24
|
+
* drawing is the sole child of the branch it came from.
|
|
25
|
+
*/
|
|
26
|
+
const captureShapeAlternateContent = ({ shape, alternateContent, branch, drawing }) => {
|
|
27
|
+
if (getLocalName(alternateContent.name ?? "") !== "AlternateContent") return;
|
|
28
|
+
if (!isSoleBranchChild(branch, drawing)) return;
|
|
29
|
+
return {
|
|
30
|
+
verbatimXml: captureVerbatimXml(alternateContent),
|
|
31
|
+
verbatimFingerprint: shapeFingerprint(shape)
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
/** Re-fingerprint a capture for a shape rebuilt from an unedited projection. */
|
|
35
|
+
const refingerprintShapeAlternateContent = (shape, verbatimXml) => ({
|
|
36
|
+
verbatimXml,
|
|
37
|
+
verbatimFingerprint: shapeFingerprint(shape)
|
|
38
|
+
});
|
|
39
|
+
/**
|
|
40
|
+
* The captured `mc:AlternateContent` to write in place of the shape's drawing,
|
|
41
|
+
* or undefined when the shape must be regenerated.
|
|
42
|
+
*
|
|
43
|
+
* Replay requires the modeled shape to still match the capture. An edited shape
|
|
44
|
+
* is regenerated as a bare `w:drawing` without the stale Fallback.
|
|
45
|
+
*/
|
|
46
|
+
const replayableShapeAlternateContent = (content) => {
|
|
47
|
+
const captured = content.alternateContent;
|
|
48
|
+
if (captured === void 0) return;
|
|
49
|
+
return captured.verbatimFingerprint === shapeFingerprint(content.shape) ? captured.verbatimXml : void 0;
|
|
50
|
+
};
|
|
51
|
+
//#endregion
|
|
52
|
+
export { captureShapeAlternateContent, refingerprintShapeAlternateContent, replayableShapeAlternateContent };
|
|
@@ -119,8 +119,16 @@ declare function resolveTextBoxOutlineColor(textBox: document_d_exports.TextBox)
|
|
|
119
119
|
* Get outline width in pixels
|
|
120
120
|
*/
|
|
121
121
|
declare function getTextBoxOutlineWidthPx(textBox: document_d_exports.TextBox): number;
|
|
122
|
+
/** A text-box `w:drawing`, with the `mc:AlternateContent` branch it sits in, if any. */
|
|
123
|
+
type TextBoxDrawing = {
|
|
124
|
+
drawing: XmlElement;
|
|
125
|
+
alternateContent?: {
|
|
126
|
+
element: XmlElement;
|
|
127
|
+
branch: XmlElement;
|
|
128
|
+
};
|
|
129
|
+
};
|
|
122
130
|
type TextBoxRunScan = {
|
|
123
|
-
textBoxDrawings:
|
|
131
|
+
textBoxDrawings: TextBoxDrawing[];
|
|
124
132
|
vmlTextBoxes: XmlElement[];
|
|
125
133
|
hasNonTextBoxContent: boolean;
|
|
126
134
|
};
|
|
@@ -143,4 +151,4 @@ type ScanRunForTextBoxDrawingsOptions = {
|
|
|
143
151
|
};
|
|
144
152
|
declare const scanRunForTextBoxDrawings: ({ xmlRun, claimedByRunParser }: ScanRunForTextBoxDrawingsOptions) => TextBoxRunScan;
|
|
145
153
|
//#endregion
|
|
146
|
-
export { ParagraphParserFn, ScanRunForTextBoxDrawingsOptions, TableParserFn, TextBoxRunScan, extractTextBoxContentElements, getTextBoxContentElement, getTextBoxDimensionsPx, getTextBoxHeightPx, getTextBoxMarginsPx, getTextBoxOutlineWidthPx, getTextBoxText, getTextBoxWidthPx, hasTextBoxContent, hasTextBoxFill, hasTextBoxOutline, isFloatingTextBox, isShapeTextBox, isTextBoxDrawing, parseTextBox, parseTextBoxContent, parseTextBoxFromShape, resolveTextBoxFillColor, resolveTextBoxOutlineColor, scanRunForTextBoxDrawings };
|
|
154
|
+
export { ParagraphParserFn, ScanRunForTextBoxDrawingsOptions, TableParserFn, TextBoxDrawing, TextBoxRunScan, extractTextBoxContentElements, getTextBoxContentElement, getTextBoxDimensionsPx, getTextBoxHeightPx, getTextBoxMarginsPx, getTextBoxOutlineWidthPx, getTextBoxText, getTextBoxWidthPx, hasTextBoxContent, hasTextBoxFill, hasTextBoxOutline, isFloatingTextBox, isShapeTextBox, isTextBoxDrawing, parseTextBox, parseTextBoxContent, parseTextBoxFromShape, resolveTextBoxFillColor, resolveTextBoxOutlineColor, scanRunForTextBoxDrawings };
|
|
@@ -328,9 +328,12 @@ const scanRunForTextBoxDrawings = ({ xmlRun, claimedByRunParser }) => {
|
|
|
328
328
|
const textBoxDrawings = [];
|
|
329
329
|
const vmlTextBoxes = [];
|
|
330
330
|
let hasNonTextBoxContent = false;
|
|
331
|
-
const visitDrawing = (drawingEl) => {
|
|
331
|
+
const visitDrawing = (drawingEl, alternateContent) => {
|
|
332
332
|
if (isTextBoxDrawing(drawingEl)) {
|
|
333
|
-
textBoxDrawings.push(
|
|
333
|
+
textBoxDrawings.push({
|
|
334
|
+
drawing: drawingEl,
|
|
335
|
+
...alternateContent === void 0 ? {} : { alternateContent }
|
|
336
|
+
});
|
|
334
337
|
return;
|
|
335
338
|
}
|
|
336
339
|
hasNonTextBoxContent = true;
|
|
@@ -355,7 +358,10 @@ const scanRunForTextBoxDrawings = ({ xmlRun, claimedByRunParser }) => {
|
|
|
355
358
|
if (!branch) return false;
|
|
356
359
|
let found = false;
|
|
357
360
|
for (const innerEl of getChildElements(branch)) if (getLocalName(innerEl.name ?? "") === "drawing") {
|
|
358
|
-
visitDrawing(innerEl
|
|
361
|
+
visitDrawing(innerEl, {
|
|
362
|
+
element: el,
|
|
363
|
+
branch
|
|
364
|
+
});
|
|
359
365
|
found = true;
|
|
360
366
|
}
|
|
361
367
|
return found;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { recreateProseNodeWithParagraphPropertySource } from "../docx/paragraphPropertySource.js";
|
|
2
2
|
import { expectRunPropertyChangeMarkAttrs } from "../prosemirror/attrs/index.js";
|
|
3
|
+
import { resolutionRemovesControl, withoutResolvedEnclosures } from "../prosemirror/contentControlRevisions.js";
|
|
3
4
|
import "../prosemirror/extensions/nodes/SdtExtension.js";
|
|
5
|
+
import { continuedRunMarks } from "../prosemirror/rejoinRunCarriers.js";
|
|
4
6
|
import { RUN_FORMATTING_MARK_NAMES } from "../prosemirror/runFormattingMarkNames.js";
|
|
5
7
|
import { reconstructRejectedRunFormattingMarks } from "../prosemirror/runPropertyChangeResolution.js";
|
|
6
8
|
import { paragraphRunStyleContext } from "../prosemirror/runStyleFormatting.js";
|
|
@@ -75,8 +77,26 @@ const resolveInlineNode = ({ node, context, paragraphScope }) => {
|
|
|
75
77
|
if (context.keepType) marks = marks.filter((mark) => mark.type !== context.keepType);
|
|
76
78
|
return marksEqual(marks, node.marks) ? node : node.mark(marks);
|
|
77
79
|
};
|
|
80
|
+
/**
|
|
81
|
+
* The pieces a revision split off its run, one run again: the headless half of
|
|
82
|
+
* `rejoinRunsAt`, over the children of one inline container.
|
|
83
|
+
*/
|
|
84
|
+
const rejoinResolvedRuns = ({ children, resolvedBoundaries, paragraphScope, context }) => {
|
|
85
|
+
for (const index of [...resolvedBoundaries].toSorted((left, right) => left - right)) {
|
|
86
|
+
const left = children[index - 1];
|
|
87
|
+
const right = children[index];
|
|
88
|
+
if (!left || !right) continue;
|
|
89
|
+
const marks = continuedRunMarks({
|
|
90
|
+
left,
|
|
91
|
+
right,
|
|
92
|
+
context: () => resolveParagraphRunStyleScope(paragraphScope, context.styleResolver),
|
|
93
|
+
styleResolver: context.styleResolver
|
|
94
|
+
});
|
|
95
|
+
if (marks !== null) children[index] = right.mark(marks);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
78
98
|
const resolveInlineContent = ({ node, position, context, inheritedParagraphScope }) => {
|
|
79
|
-
const paragraphScope = node.type.name === "paragraph"
|
|
99
|
+
const paragraphScope = node.type.name === "paragraph" ? { paragraph: node } : inheritedParagraphScope;
|
|
80
100
|
const replacementRangeStart = context.replacementRanges.length;
|
|
81
101
|
let resolvedNode = node;
|
|
82
102
|
if (node.isInline) {
|
|
@@ -96,7 +116,21 @@ const resolveInlineContent = ({ node, position, context, inheritedParagraphScope
|
|
|
96
116
|
resolvedNode = resolved;
|
|
97
117
|
if (resolvedNode.isLeaf) return resolvedNode;
|
|
98
118
|
}
|
|
119
|
+
if (resolutionRemovesControl({
|
|
120
|
+
control: resolvedNode,
|
|
121
|
+
removeType: context.removeType,
|
|
122
|
+
resolves: () => true
|
|
123
|
+
})) {
|
|
124
|
+
context.replacementRanges.push({
|
|
125
|
+
from: position,
|
|
126
|
+
to: position + node.nodeSize,
|
|
127
|
+
newSize: 0
|
|
128
|
+
});
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
99
131
|
const children = [];
|
|
132
|
+
/** Indices into `children` where resolved content began or ended. */
|
|
133
|
+
const resolvedBoundaries = /* @__PURE__ */ new Set();
|
|
100
134
|
const contentStart = resolvedNode.type.name === "doc" ? 0 : position + 1;
|
|
101
135
|
let contentChanged = false;
|
|
102
136
|
resolvedNode.forEach((child, offset) => {
|
|
@@ -106,20 +140,23 @@ const resolveInlineContent = ({ node, position, context, inheritedParagraphScope
|
|
|
106
140
|
context,
|
|
107
141
|
...paragraphScope !== void 0 ? { inheritedParagraphScope: paragraphScope } : {}
|
|
108
142
|
});
|
|
143
|
+
if (resolved !== child) {
|
|
144
|
+
contentChanged = true;
|
|
145
|
+
resolvedBoundaries.add(children.length);
|
|
146
|
+
}
|
|
109
147
|
if (resolved) {
|
|
110
148
|
children.push(resolved);
|
|
111
|
-
|
|
112
|
-
}
|
|
149
|
+
if (resolved !== child) resolvedBoundaries.add(children.length);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
const resolvedAttrs = resolvedNode.type.name === "sdt" ? withoutResolvedEnclosures(resolvedNode, () => true) ?? resolvedNode.attrs : resolvedNode.attrs;
|
|
153
|
+
if (!contentChanged && resolvedAttrs === resolvedNode.attrs) return resolvedNode;
|
|
154
|
+
if (contentChanged && resolvedNode.inlineContent) rejoinResolvedRuns({
|
|
155
|
+
children,
|
|
156
|
+
resolvedBoundaries,
|
|
157
|
+
paragraphScope,
|
|
158
|
+
context
|
|
113
159
|
});
|
|
114
|
-
if (!contentChanged) return resolvedNode;
|
|
115
|
-
if (children.length === 0 && node.childCount > 0 && resolvedNode.type.name === "sdt") {
|
|
116
|
-
context.replacementRanges.splice(replacementRangeStart, context.replacementRanges.length - replacementRangeStart, {
|
|
117
|
-
from: position,
|
|
118
|
-
to: position + node.nodeSize,
|
|
119
|
-
newSize: 0
|
|
120
|
-
});
|
|
121
|
-
return null;
|
|
122
|
-
}
|
|
123
160
|
let resolvedContent = Fragment.fromArray(children);
|
|
124
161
|
if (!resolvedNode.type.validContent(resolvedContent)) {
|
|
125
162
|
const fitted = resolvedNode.type.createAndFill(resolvedNode.attrs, resolvedContent, resolvedNode.marks);
|
|
@@ -143,7 +180,7 @@ const resolveInlineContent = ({ node, position, context, inheritedParagraphScope
|
|
|
143
180
|
from: position,
|
|
144
181
|
to: position + resolvedNode.nodeSize
|
|
145
182
|
});
|
|
146
|
-
return rebuildNode(node,
|
|
183
|
+
return rebuildNode(node, resolvedAttrs, resolvedContent, resolvedNode.marks);
|
|
147
184
|
};
|
|
148
185
|
const coalesceReplacementRanges = (replacementRanges) => {
|
|
149
186
|
const coalesced = [];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { convertBulletToUnicode } from "../../docx/bulletMarkers.js";
|
|
1
|
+
import { bulletMarkerFontName, convertBulletToUnicode } from "../../docx/bulletMarkers.js";
|
|
2
2
|
import { resolveDocumentGridLinePitch } from "../../docx/documentGrid.js";
|
|
3
3
|
import { getFontAlternate } from "../../fonts/fontAlternates.js";
|
|
4
4
|
import { buildPageBreakRunDescendantIndex } from "../../internal/pageBreakRunDescendantIndex.js";
|
|
@@ -1053,7 +1053,7 @@ function resolveDeletedListMarker(previousListAttrs, listCounterState) {
|
|
|
1053
1053
|
const marker = computeListMarker(previousListAttrs, listCounterState);
|
|
1054
1054
|
if (marker) return marker;
|
|
1055
1055
|
}
|
|
1056
|
-
if (previousListAttrs.listMarker) return previousListAttrs.listIsBullet ? convertBulletToUnicode(previousListAttrs.listMarker) : previousListAttrs.listMarker;
|
|
1056
|
+
if (previousListAttrs.listMarker) return previousListAttrs.listIsBullet ? convertBulletToUnicode(previousListAttrs.listMarker, bulletMarkerFontName(previousListAttrs.listMarkerFormatting)) : previousListAttrs.listMarker;
|
|
1057
1057
|
if (previousListAttrs.listIsBullet) return "•";
|
|
1058
1058
|
return null;
|
|
1059
1059
|
}
|
|
@@ -1219,7 +1219,7 @@ function convertParagraphAttrs(pmAttrs, { theme, fontAlternates, listCounterStre
|
|
|
1219
1219
|
}
|
|
1220
1220
|
const resolvedMarker = applyMarkerAllCaps(advanceVisibleListMarker(pmAttrs, listCounterStreams).marker, pmAttrs.listMarkerAllCaps);
|
|
1221
1221
|
if (resolvedMarker !== null) attrs.listMarker = resolvedMarker;
|
|
1222
|
-
else if (pmAttrs.listMarker) attrs.listMarker = pmAttrs.listIsBullet ? convertBulletToUnicode(pmAttrs.listMarker) : pmAttrs.listMarker;
|
|
1222
|
+
else if (pmAttrs.listMarker) attrs.listMarker = pmAttrs.listIsBullet ? convertBulletToUnicode(pmAttrs.listMarker, bulletMarkerFontName(pmAttrs.listMarkerFormatting)) : pmAttrs.listMarker;
|
|
1223
1223
|
if (pmAttrs.listIsBullet !== void 0) attrs.listIsBullet = pmAttrs.listIsBullet;
|
|
1224
1224
|
if (pmAttrs.listMarkerHidden) attrs.listMarkerHidden = true;
|
|
1225
1225
|
if (attrs.listMarker !== void 0 || pmAttrs.listMarkerFormatting) {
|
|
@@ -1243,7 +1243,7 @@ function convertParagraphAttrs(pmAttrs, { theme, fontAlternates, listCounterStre
|
|
|
1243
1243
|
const dtf = pmAttrs.defaultTextFormatting;
|
|
1244
1244
|
if (dtf) {
|
|
1245
1245
|
if (dtf.fontSize !== void 0) attrs.defaultFontSize = dtf.fontSize / 2;
|
|
1246
|
-
if (attrs.listMarker !== void 0 && !attrs.listMarkerHidden && dtf.fontSizeCs !== void 0 && (dtf.fontSize === void 0 || dtf.fontSizeCs > dtf.fontSize)) attrs.listParagraphMarkFontSize = dtf.fontSizeCs / 2;
|
|
1246
|
+
if (attrs.listMarker !== void 0 && !attrs.listMarkerHidden && (attrs.bidi === true || dtf.rtl === true || dtf.cs === true) && dtf.fontSizeCs !== void 0 && (dtf.fontSize === void 0 || dtf.fontSizeCs > dtf.fontSize)) attrs.listParagraphMarkFontSize = dtf.fontSizeCs / 2;
|
|
1247
1247
|
if (dtf.fontFamily) {
|
|
1248
1248
|
const resolvedFamily = resolveWesternThemeFont(dtf.fontFamily, theme);
|
|
1249
1249
|
if (resolvedFamily) {
|
|
@@ -1847,6 +1847,24 @@ function inlineEffectExtentPx(wrapType, extent) {
|
|
|
1847
1847
|
};
|
|
1848
1848
|
return resolved.top === 0 && resolved.bottom === 0 && resolved.left === 0 && resolved.right === 0 ? void 0 : resolved;
|
|
1849
1849
|
}
|
|
1850
|
+
const GRADIENT_POSITION_SCALE = 1e5;
|
|
1851
|
+
/**
|
|
1852
|
+
* A linear `a:gradFill` with its stop colors resolved through the theme. A path
|
|
1853
|
+
* gradient (`a:path`) is not painted: nothing here places its focus.
|
|
1854
|
+
*/
|
|
1855
|
+
function resolveLinearGradientFill(fill, theme) {
|
|
1856
|
+
const gradient = fill?.gradient;
|
|
1857
|
+
if (gradient === void 0 || gradient.type !== "linear" || gradient.stops.length === 0) return;
|
|
1858
|
+
const stops = gradient.stops.map((stop) => ({
|
|
1859
|
+
offset: Math.min(1, Math.max(0, stop.position / GRADIENT_POSITION_SCALE)),
|
|
1860
|
+
color: resolveColor(stop.color, theme)
|
|
1861
|
+
})).toSorted((left, right) => left.offset - right.offset);
|
|
1862
|
+
return {
|
|
1863
|
+
angle: gradient.angle ?? 0,
|
|
1864
|
+
scaled: gradient.scaled ?? false,
|
|
1865
|
+
stops
|
|
1866
|
+
};
|
|
1867
|
+
}
|
|
1850
1868
|
/** Convert a textBox PM node to a TextBoxBlock. */
|
|
1851
1869
|
function convertTextBoxNode(node, startPos, opts) {
|
|
1852
1870
|
const attrs = expectTextBoxAttrs(node);
|
|
@@ -1879,6 +1897,10 @@ function convertTextBoxNode(node, startPos, opts) {
|
|
|
1879
1897
|
if (attrs.autoFit !== void 0) textBox.autoFit = attrs.autoFit;
|
|
1880
1898
|
if (attrs.textWrap !== void 0) textBox.textWrap = attrs.textWrap;
|
|
1881
1899
|
if (attrs.fillColor !== void 0) textBox.fillColor = attrs.fillColor;
|
|
1900
|
+
else {
|
|
1901
|
+
const fillGradient = resolveLinearGradientFill(attrs.gradientFill, opts.theme);
|
|
1902
|
+
if (fillGradient !== void 0) textBox.fillGradient = fillGradient;
|
|
1903
|
+
}
|
|
1882
1904
|
if (attrs.outlineWidth !== void 0) textBox.outlineWidth = attrs.outlineWidth;
|
|
1883
1905
|
if (attrs.outlineColor !== void 0) textBox.outlineColor = attrs.outlineColor;
|
|
1884
1906
|
if (attrs.outlineStyle !== void 0) textBox.outlineStyle = attrs.outlineStyle;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BlockId, BorderStyle, CellBorderSpec, CellBorders, ColumnBreakBlock, ColumnBreakMeasure, ColumnLayout, DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, DocumentPosition, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, FieldRun, FloatingTablePosition, FlowBlock, FootnoteContent, Fragment, FragmentBase, HeaderFooterContent, HeaderFooterContentHeights, HeaderFooterLayout, HitTestResult, HyperlinkInfo, ImageBlock, ImageFragment, ImageMeasure, ImageRun, ImageRunPosition, Layout, LayoutOptions, LineBreakRun, ListMarkerFormatting, MathRun, Measure, MeasuredLine, NOTE_SEPARATOR_RULE_THICKNESS, NOTE_SEPARATOR_WIDTH_FRACTION, NoteAreaLayout, NoteSeparatorKind, NoteSeparatorRule, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, RenderedPageBreakRun, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, SectionPageNumbering, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxMeasure, TextRun, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, isListNumPr, isTextWrappingFloatingImageRun, resolveTableCellPadding, resolveTableWidthPx, stripFlowBlockPmAnchors, tableColumnsArePinned } from "./types.js";
|
|
1
|
+
import { BlockId, BorderStyle, CellBorderSpec, CellBorders, ColumnBreakBlock, ColumnBreakMeasure, ColumnLayout, DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, DocumentPosition, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, FieldRun, FloatingTablePosition, FlowBlock, FootnoteContent, Fragment, FragmentBase, HeaderFooterContent, HeaderFooterContentHeights, HeaderFooterLayout, HitTestResult, HyperlinkInfo, ImageBlock, ImageFragment, ImageMeasure, ImageRun, ImageRunPosition, Layout, LayoutOptions, LineBreakRun, ListMarkerFormatting, MathRun, Measure, MeasuredLine, NOTE_SEPARATOR_RULE_THICKNESS, NOTE_SEPARATOR_WIDTH_FRACTION, NoteAreaLayout, NoteSeparatorKind, NoteSeparatorRule, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, RenderedPageBreakRun, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, SectionPageNumbering, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxGradientFill, TextBoxMeasure, TextRun, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, isListNumPr, isTextWrappingFloatingImageRun, resolveTableCellPadding, resolveTableWidthPx, stripFlowBlockPmAnchors, tableColumnsArePinned } from "./types.js";
|
|
2
2
|
import { PageState, Paginator, PaginatorOptions, createPaginator } from "./paginator.js";
|
|
3
3
|
import { KeepNextChain, calculateChainHeight, computeKeepNextChains, getMidChainIndices, hasKeepLines, hasPageBreakBefore } from "./keep-together.js";
|
|
4
4
|
import { resolveSectionHeaderFooterRefs } from "./headerFooterRefs.js";
|
|
@@ -42,4 +42,4 @@ declare function layoutDocument(blocks: FlowBlock[], measures: Measure[], option
|
|
|
42
42
|
*/
|
|
43
43
|
declare function getHeaderRowsHeight(measure: TableMeasure, headerRowCount: number): number;
|
|
44
44
|
//#endregion
|
|
45
|
-
export { BlockId, BorderStyle, type BreakDecision, CellBorderSpec, CellBorders, ColumnBreakBlock, ColumnBreakMeasure, ColumnLayout, DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, DocumentPosition, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, FieldRun, FloatingTablePosition, FlowBlock, FootnoteContent, Fragment, FragmentBase, HeaderFooterContent, HeaderFooterContentHeights, HeaderFooterLayout, HitTestResult, HyperlinkInfo, ImageBlock, ImageFragment, ImageMeasure, ImageRun, ImageRunPosition, type KeepNextChain, Layout, LayoutOptions, LineBreakRun, ListMarkerFormatting, MathRun, Measure, MeasuredLine, NOTE_SEPARATOR_RULE_THICKNESS, NOTE_SEPARATOR_WIDTH_FRACTION, NoteAreaLayout, NoteSeparatorKind, NoteSeparatorRule, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, type PageState, type Paginator, type PaginatorOptions, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, RenderedPageBreakRun, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, SectionLayoutConfig, SectionPageNumbering, type SectionState, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxMeasure, TextRun, applyContextualSpacing, applyPendingToActive, assertExhaustiveFlowBlock, calculateChainHeight, collectSectionConfigs, computeKeepNextChains, createInitialSectionState, createPaginator, findPageIndexContainingPmPos, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getEffectiveColumns, getEffectiveMargins, getEffectivePageSize, getHeaderRowsHeight, getMidChainIndices, getTableRowLeadingWidth, hasKeepLines, hasPageBreakBefore, isFloatingImageRun, isFloatingTextBoxBlock, isListNumPr, isTextWrappingFloatingImageRun, layoutDocument, resolveSectionHeaderFooterRefs, resolveTableCellPadding, resolveTableWidthPx, scheduleSectionBreak, stripFlowBlockPmAnchors, tableColumnsArePinned };
|
|
45
|
+
export { BlockId, BorderStyle, type BreakDecision, CellBorderSpec, CellBorders, ColumnBreakBlock, ColumnBreakMeasure, ColumnLayout, DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, DocumentPosition, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, FieldRun, FloatingTablePosition, FlowBlock, FootnoteContent, Fragment, FragmentBase, HeaderFooterContent, HeaderFooterContentHeights, HeaderFooterLayout, HitTestResult, HyperlinkInfo, ImageBlock, ImageFragment, ImageMeasure, ImageRun, ImageRunPosition, type KeepNextChain, Layout, LayoutOptions, LineBreakRun, ListMarkerFormatting, MathRun, Measure, MeasuredLine, NOTE_SEPARATOR_RULE_THICKNESS, NOTE_SEPARATOR_WIDTH_FRACTION, NoteAreaLayout, NoteSeparatorKind, NoteSeparatorRule, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, type PageState, type Paginator, type PaginatorOptions, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, RenderedPageBreakRun, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, SectionLayoutConfig, SectionPageNumbering, type SectionState, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxGradientFill, TextBoxMeasure, TextRun, applyContextualSpacing, applyPendingToActive, assertExhaustiveFlowBlock, calculateChainHeight, collectSectionConfigs, computeKeepNextChains, createInitialSectionState, createPaginator, findPageIndexContainingPmPos, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getEffectiveColumns, getEffectiveMargins, getEffectivePageSize, getHeaderRowsHeight, getMidChainIndices, getTableRowLeadingWidth, hasKeepLines, hasPageBreakBefore, isFloatingImageRun, isFloatingTextBoxBlock, isListNumPr, isTextWrappingFloatingImageRun, layoutDocument, resolveSectionHeaderFooterRefs, resolveTableCellPadding, resolveTableWidthPx, scheduleSectionBreak, stripFlowBlockPmAnchors, tableColumnsArePinned };
|
|
@@ -13,7 +13,7 @@ import { assertExhaustiveFlowBlock, findPageIndexContainingPmPos } from "./pmPag
|
|
|
13
13
|
import { INITIAL_RENDERED_BREAK_STATE, PAGE_ADVANCE, reconcileAfterBlock, reconcileBreakBeforeBlock, recordReflowBoundary } from "./renderedBreakReconciliation.js";
|
|
14
14
|
import { applyPendingToActive, createInitialSectionState, getEffectiveColumns, getEffectiveMargins, getEffectivePageSize, normalizeSectionBreakType, physicalColumnRegionIsShared, scheduleSectionBreak } from "./section-breaks.js";
|
|
15
15
|
import { applySectionVerticalAlignment } from "./sectionVerticalAlignment.js";
|
|
16
|
-
import { buildTableRowBreakInfo, getRowContinuationSkip, snapRowBreak } from "./tableRowBreak.js";
|
|
16
|
+
import { buildTableRowBreakInfo, getRowContinuationSkip, snapRowBreak, tableKeepNextOpeningHeight } from "./tableRowBreak.js";
|
|
17
17
|
import { bandFragmentX, bandTopContentY, isPageFrameRelativeAnchor } from "./textBoxFlow.js";
|
|
18
18
|
import { DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, NOTE_SEPARATOR_RULE_THICKNESS, NOTE_SEPARATOR_WIDTH_FRACTION, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, isListNumPr, isTextWrappingFloatingImageRun, resolveTableCellPadding, resolveTableWidthPx, stripFlowBlockPmAnchors, tableColumnsArePinned } from "./types.js";
|
|
19
19
|
import { panic } from "better-result";
|
|
@@ -194,6 +194,7 @@ function layoutDocumentPass(blocks, measures, options) {
|
|
|
194
194
|
...options.mirrorMargins === true ? { mirrorMargins: true } : {},
|
|
195
195
|
...options.firstPageMargins !== void 0 ? { firstPageMargins: options.firstPageMargins } : {},
|
|
196
196
|
...options.sectionEvenPageMargins !== void 0 ? { sectionEvenPageMargins: options.sectionEvenPageMargins } : {},
|
|
197
|
+
...options.sectionFirstPageMargins !== void 0 ? { sectionFirstPageMargins: options.sectionFirstPageMargins } : {},
|
|
197
198
|
columns: initialConfig.columns ?? DEFAULT_COLUMNS,
|
|
198
199
|
pageNumbering: initialConfig.pageNumbering,
|
|
199
200
|
...options.footnoteReservedHeights !== void 0 ? { footnoteReservedHeights: options.footnoteReservedHeights } : {},
|
|
@@ -234,7 +235,7 @@ function layoutDocumentPass(blocks, measures, options) {
|
|
|
234
235
|
renderedBreakState = breakDecision.state;
|
|
235
236
|
const chain = keepNextChains.get(i);
|
|
236
237
|
if (chain && !midChainIndices.has(i)) {
|
|
237
|
-
const chainHeight = calculateChainHeight(chain, blocks, measures, paginator.getCurrentState().trailingSpacing);
|
|
238
|
+
const chainHeight = calculateChainHeight(chain, blocks, measures, paginator.getCurrentState().trailingSpacing, tableKeepNextOpeningHeight);
|
|
238
239
|
const pageBeforeChainLayout = paginator.getCurrentState().page.number;
|
|
239
240
|
paginator.ensureFits(chainHeight);
|
|
240
241
|
if (paginator.getCurrentState().page.number > pageBeforeChainLayout) renderedBreakState = recordReflowBoundary(renderedBreakState, true);
|
|
@@ -1070,6 +1071,32 @@ function startSectionInPlace(paginator, nextSectionConfig, nextSectionIndex) {
|
|
|
1070
1071
|
paginator.retargetCurrentBlankPage();
|
|
1071
1072
|
}
|
|
1072
1073
|
/**
|
|
1074
|
+
* Whether a section opening on `target` would give two consecutive sheets page
|
|
1075
|
+
* numbers that are both odd or both even under `w:evenAndOddHeaders` (§17.10.1). Odd and
|
|
1076
|
+
* even page numbers alternate between right- and left-hand sheets, so a
|
|
1077
|
+
* `w:pgNumType w:start` restart that repeats whether the previous sheet's number is odd or even
|
|
1078
|
+
* needs a blank sheet between them.
|
|
1079
|
+
*/
|
|
1080
|
+
function sectionStartRepeatsOddEven(paginator, target) {
|
|
1081
|
+
if (target.page.headerFooterRefs?.evenAndOddHeaders !== true) return false;
|
|
1082
|
+
const previous = paginator.states.at(-2)?.page;
|
|
1083
|
+
if (previous === void 0 || previous.number !== target.page.number - 1) return false;
|
|
1084
|
+
return previous.logicalNumber % 2 === target.page.logicalNumber % 2;
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
1087
|
+
* Keep the blank sheet the section break opened as a filler with no page
|
|
1088
|
+
* furniture, and open the section again on the following sheet so its first
|
|
1089
|
+
* page keeps the restarted page number.
|
|
1090
|
+
*/
|
|
1091
|
+
function insertOddEvenFillerPage(paginator, filler, sectionIndex, sectionConfig) {
|
|
1092
|
+
filler.page.headerFooterRefs = {};
|
|
1093
|
+
paginator.startSection({
|
|
1094
|
+
sectionIndex,
|
|
1095
|
+
pageNumbering: sectionConfig.pageNumbering
|
|
1096
|
+
});
|
|
1097
|
+
paginator.forcePageBreak();
|
|
1098
|
+
}
|
|
1099
|
+
/**
|
|
1073
1100
|
* Handle a section break block.
|
|
1074
1101
|
* @param block - The section break block (current section's properties)
|
|
1075
1102
|
* @param paginator - The paginator instance
|
|
@@ -1078,14 +1105,16 @@ function startSectionInPlace(paginator, nextSectionConfig, nextSectionIndex) {
|
|
|
1078
1105
|
*/
|
|
1079
1106
|
function handleSectionBreak(_block, paginator, nextSectionConfig, nextSectionType, nextSectionIndex) {
|
|
1080
1107
|
switch (nextSectionType) {
|
|
1081
|
-
case "nextPage":
|
|
1108
|
+
case "nextPage": {
|
|
1082
1109
|
paginator.updatePageLayout(nextSectionConfig.pageSize, nextSectionConfig.margins);
|
|
1083
1110
|
if (nextSectionIndex !== void 0) paginator.startSection({
|
|
1084
1111
|
sectionIndex: nextSectionIndex,
|
|
1085
1112
|
pageNumbering: nextSectionConfig.pageNumbering
|
|
1086
1113
|
});
|
|
1087
|
-
paginator.forcePageBreak({ coalesceBlankPage: true });
|
|
1114
|
+
const target = paginator.forcePageBreak({ coalesceBlankPage: true });
|
|
1115
|
+
if (nextSectionIndex !== void 0 && sectionStartRepeatsOddEven(paginator, target)) insertOddEvenFillerPage(paginator, target, nextSectionIndex, nextSectionConfig);
|
|
1088
1116
|
break;
|
|
1117
|
+
}
|
|
1089
1118
|
case "evenPage":
|
|
1090
1119
|
if (paginator.forcePageBreak({ coalesceBlankPage: true }).page.number % 2 !== 0) paginator.forcePageBreak();
|
|
1091
1120
|
paginator.updatePageLayout(nextSectionConfig.pageSize, nextSectionConfig.margins);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FlowBlock, Measure } from "./types.js";
|
|
1
|
+
import { FlowBlock, Measure, TableBlock, TableMeasure } from "./types.js";
|
|
2
2
|
//#region src/layout-engine/keep-together.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* A chain of paragraphs that Word keeps with following content. This includes
|
|
@@ -36,12 +36,15 @@ declare function computeKeepNextChains(blocks: FlowBlock[]): Map<number, KeepNex
|
|
|
36
36
|
* reservation continues through it; otherwise the chain stops after the
|
|
37
37
|
* opening, because a later split cannot separate it from its predecessor.
|
|
38
38
|
*
|
|
39
|
+
* A table anchor contributes `tableOpeningHeight`: the part of the table
|
|
40
|
+
* that must start on the same page as the paragraph before it.
|
|
41
|
+
*
|
|
39
42
|
* A paragraph measure's `totalHeight` already includes its own spacing before
|
|
40
43
|
* and after. The chain accounts for spacing separately (collapsing each gap to
|
|
41
44
|
* the larger side, as the paginator does), so members contribute only their
|
|
42
45
|
* line advances; otherwise every gap would be reserved twice.
|
|
43
46
|
*/
|
|
44
|
-
declare function calculateChainHeight(chain: KeepNextChain, blocks: FlowBlock[], measures: Measure[], incomingSpacing?: number): number;
|
|
47
|
+
declare function calculateChainHeight(chain: KeepNextChain, blocks: FlowBlock[], measures: Measure[], incomingSpacing?: number, tableOpeningHeight?: (block: TableBlock, measure: TableMeasure) => number): number;
|
|
45
48
|
/**
|
|
46
49
|
* Get the set of indices that are mid-chain (not chain starters).
|
|
47
50
|
* These should skip the keepNext check since their chain starter already decided.
|
|
@@ -102,12 +102,15 @@ function minimumOpeningLineCount(block, measure) {
|
|
|
102
102
|
* reservation continues through it; otherwise the chain stops after the
|
|
103
103
|
* opening, because a later split cannot separate it from its predecessor.
|
|
104
104
|
*
|
|
105
|
+
* A table anchor contributes `tableOpeningHeight`: the part of the table
|
|
106
|
+
* that must start on the same page as the paragraph before it.
|
|
107
|
+
*
|
|
105
108
|
* A paragraph measure's `totalHeight` already includes its own spacing before
|
|
106
109
|
* and after. The chain accounts for spacing separately (collapsing each gap to
|
|
107
110
|
* the larger side, as the paginator does), so members contribute only their
|
|
108
111
|
* line advances; otherwise every gap would be reserved twice.
|
|
109
112
|
*/
|
|
110
|
-
function calculateChainHeight(chain, blocks, measures, incomingSpacing = 0) {
|
|
113
|
+
function calculateChainHeight(chain, blocks, measures, incomingSpacing = 0, tableOpeningHeight) {
|
|
111
114
|
const firstMemberIndex = chain.memberIndices.at(0);
|
|
112
115
|
if (firstMemberIndex === void 0) return 0;
|
|
113
116
|
const firstBlock = blocks[firstMemberIndex];
|
|
@@ -125,6 +128,7 @@ function calculateChainHeight(chain, blocks, measures, incomingSpacing = 0) {
|
|
|
125
128
|
if (successorIndex === void 0) continue;
|
|
126
129
|
const successorBlock = blocks[successorIndex];
|
|
127
130
|
const successorMeasure = measures[successorIndex];
|
|
131
|
+
if (tableOpeningHeight !== void 0 && successorBlock?.kind === "table" && successorMeasure?.kind === "table" && successorBlock.floating === void 0) return totalHeight + trailingSpacing + tableOpeningHeight(successorBlock, successorMeasure);
|
|
128
132
|
if (successorBlock?.kind !== "paragraph" || successorMeasure?.kind !== "paragraph") return totalHeight;
|
|
129
133
|
totalHeight += collapseParagraphSpacing({
|
|
130
134
|
before: successorBlock.attrs?.spacing?.before ?? 0,
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { getHorizontalScaleFactor } from "../../utils/horizontalScale.js";
|
|
2
2
|
import { SCRIPT_CLASS, hasComplexScript, hasEastAsiaSlotText, scriptClassOf, segmentByScript } from "../../utils/scriptSegments.js";
|
|
3
3
|
import { applyComplexScriptFormatting, hasComplexScriptFormatting } from "./complexScriptFormatting.js";
|
|
4
|
+
import "./measureHelpers.js";
|
|
5
|
+
import { SMALL_CAPS_SCALE, smallCapsMask, smallCapsSegments } from "./smallCapsCasing.js";
|
|
4
6
|
//#region src/layout-engine/measure/advanceComposition.ts
|
|
5
7
|
/**
|
|
6
8
|
* The arithmetic that turns raw glyph advances into measured widths.
|
|
@@ -50,6 +52,38 @@ const glyphAdvanceStyle = (style) => {
|
|
|
50
52
|
};
|
|
51
53
|
const needsPerScriptFonts = (style, text) => style.eastAsiaFontFamily !== void 0 && hasEastAsiaSlotText(text, style.eastAsiaHint) || hasComplexScriptFormatting(style) && hasComplexScript(text);
|
|
52
54
|
/**
|
|
55
|
+
* Width of a `w:smallCaps` string: its lowercase letters (and the whitespace
|
|
56
|
+
* that follows them — see smallCapsCasing.ts) draw at `SMALL_CAPS_SCALE` of
|
|
57
|
+
* the style's size, everything else at full size. A small segment is measured
|
|
58
|
+
* at that literal, smaller size — not the full-size advance scaled by
|
|
59
|
+
* arithmetic — mirroring {@link canvasMeasureTextWidth}'s
|
|
60
|
+
* `measureSmallCapsWidth` so the two backends agree; each segment still
|
|
61
|
+
* resolves its own per-script font, the same as the plain (non-small-caps)
|
|
62
|
+
* path below.
|
|
63
|
+
*/
|
|
64
|
+
const smallCapsGlyphWidth = (text, style, advanceOf) => {
|
|
65
|
+
const segmentStyle = { ...style };
|
|
66
|
+
delete segmentStyle.fontVariant;
|
|
67
|
+
delete segmentStyle.letterSpacing;
|
|
68
|
+
delete segmentStyle.horizontalScale;
|
|
69
|
+
const smallSegmentStyle = {
|
|
70
|
+
...segmentStyle,
|
|
71
|
+
fontSize: (segmentStyle.fontSize ?? 11) * SMALL_CAPS_SCALE
|
|
72
|
+
};
|
|
73
|
+
let total = 0;
|
|
74
|
+
for (const segment of smallCapsSegments(text)) {
|
|
75
|
+
const activeStyle = segment.small ? smallSegmentStyle : segmentStyle;
|
|
76
|
+
total += needsPerScriptFonts(activeStyle, segment.text) ? segmentByScript(segment.text, activeStyle.eastAsiaHint).reduce((sum, scriptSegment) => sum + advanceOf(scriptSegment.text, glyphAdvanceStyle(scriptStyle(activeStyle, scriptSegment.script))), 0) : advanceOf(segment.text, activeStyle);
|
|
77
|
+
}
|
|
78
|
+
return total;
|
|
79
|
+
};
|
|
80
|
+
/** The un-spaced, un-scaled glyph width `composeTextWidth` spaces and scales. */
|
|
81
|
+
const glyphWidthOf = ({ source, style, transformed, letterSpacing, advanceOf }) => {
|
|
82
|
+
if (!source.forceComplexScript && style.fontVariant === "small-caps") return smallCapsGlyphWidth(transformed, style, advanceOf);
|
|
83
|
+
if (!source.forceComplexScript && !letterSpacing && needsPerScriptFonts(style, transformed)) return segmentByScript(transformed, style.eastAsiaHint).reduce((sum, segment) => sum + advanceOf(segment.text, glyphAdvanceStyle(scriptStyle(style, segment.script))), 0);
|
|
84
|
+
return advanceOf(transformed, style);
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
53
87
|
* Width of a string: the advance width used for line breaking, never the
|
|
54
88
|
* painted (ink) extent, which includes overhang a break decision must ignore.
|
|
55
89
|
*/
|
|
@@ -59,7 +93,13 @@ const composeTextWidth = ({ text, style: source, advanceOf }) => {
|
|
|
59
93
|
const transformed = applyMeasurementTextTransform(text, style);
|
|
60
94
|
const letterSpacing = style.letterSpacing ?? 0;
|
|
61
95
|
const horizontalScale = getHorizontalScaleFactor(style.horizontalScale);
|
|
62
|
-
const glyphWidth =
|
|
96
|
+
const glyphWidth = glyphWidthOf({
|
|
97
|
+
source,
|
|
98
|
+
style,
|
|
99
|
+
transformed,
|
|
100
|
+
letterSpacing,
|
|
101
|
+
advanceOf
|
|
102
|
+
});
|
|
63
103
|
const codePoints = countCodePoints(transformed);
|
|
64
104
|
return (letterSpacing && codePoints > 1 ? glyphWidth + letterSpacing * (codePoints - 1) : glyphWidth) * horizontalScale;
|
|
65
105
|
};
|
|
@@ -92,10 +132,16 @@ const composeRunMeasurement = ({ text, style: source, metrics, advanceOf, advanc
|
|
|
92
132
|
perScript
|
|
93
133
|
});
|
|
94
134
|
let codePointIndex = 0;
|
|
135
|
+
const smallCapsAt = style.fontVariant === "small-caps" ? smallCapsMask(text) : void 0;
|
|
95
136
|
for (const char of text) {
|
|
96
137
|
const cp = char.codePointAt(0);
|
|
97
|
-
const
|
|
98
|
-
|
|
138
|
+
const isSmallCap = smallCapsAt?.[codePointIndex] ?? false;
|
|
139
|
+
const scriptedStyle = perScript ? scriptStyle(style, scriptClassOf(cp, style.eastAsiaHint)) : style;
|
|
140
|
+
const charStyle = isSmallCap ? {
|
|
141
|
+
...scriptedStyle,
|
|
142
|
+
fontSize: (scriptedStyle.fontSize ?? 11) * SMALL_CAPS_SCALE
|
|
143
|
+
} : scriptedStyle;
|
|
144
|
+
let charWidth = shaped.get(codePointIndex) ?? advanceOf(isSmallCap ? char.toLocaleUpperCase() : applyMeasurementTextTransform(char, style), charStyle);
|
|
99
145
|
if (letterSpacing && offset + char.length < text.length) charWidth += letterSpacing;
|
|
100
146
|
charWidth *= scale;
|
|
101
147
|
charWidths.push(charWidth);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { hasPageBreakBefore } from "../keep-together.js";
|
|
2
2
|
import { recordMeasureBlock, recordMeasureBlockError } from "../layoutInstrumentation.js";
|
|
3
3
|
import { isParagraphFrameTextBox } from "../paragraphFrame.js";
|
|
4
|
+
import { resolveTableSpacing } from "../paragraphSpacing.js";
|
|
4
5
|
import { sectionStartAdvanceOf } from "../section-breaks.js";
|
|
5
6
|
import { bandFragmentX, bandTopContentY, isPageFrameRelativeAnchor } from "../textBoxFlow.js";
|
|
6
7
|
import { getTextBoxGroupId } from "../textBoxGroup.js";
|
|
@@ -97,6 +98,7 @@ function measureTableBlock(tableBlock, contentWidth, fieldValues) {
|
|
|
97
98
|
}
|
|
98
99
|
}
|
|
99
100
|
const cellGrid = buildTableCellGrid(tableBlock.rows, columnWidths.length);
|
|
101
|
+
const spacingRows = resolveTableSpacing(tableBlock).rows;
|
|
100
102
|
const cellSpacing = tableBlock.cellSpacing;
|
|
101
103
|
const bordersSeparated = (cellSpacing ?? 0) > 0;
|
|
102
104
|
const rowCount = tableBlock.rows.length;
|
|
@@ -104,7 +106,7 @@ function measureTableBlock(tableBlock, contentWidth, fieldValues) {
|
|
|
104
106
|
const rows = tableBlock.rows.map((row, rowIdx) => {
|
|
105
107
|
let columnIndex = getFirstAvailableColumn(cellGrid, rowIdx, row.gridBefore ?? 0);
|
|
106
108
|
return {
|
|
107
|
-
cells: row.cells.map((cell) => {
|
|
109
|
+
cells: row.cells.map((cell, cellIdx) => {
|
|
108
110
|
const colSpan = cell.colSpan ?? 1;
|
|
109
111
|
let cellWidth = 0;
|
|
110
112
|
for (let c = 0; c < colSpan && columnIndex + c < columnWidths.length; c++) cellWidth += columnWidths[columnIndex + c] ?? 0;
|
|
@@ -121,7 +123,7 @@ function measureTableBlock(tableBlock, contentWidth, fieldValues) {
|
|
|
121
123
|
width: cellWidth,
|
|
122
124
|
height: 0
|
|
123
125
|
};
|
|
124
|
-
if (!keepSingleLine) wrapTableCellParagraphsAroundFloats(cell, cellMeasure, cellContentWidth, fieldValues);
|
|
126
|
+
if (!keepSingleLine) wrapTableCellParagraphsAroundFloats(spacingRows[rowIdx]?.cells[cellIdx] ?? cell, cellMeasure, cellContentWidth, fieldValues);
|
|
125
127
|
if (cell.colSpan !== void 0) cellMeasure.colSpan = cell.colSpan;
|
|
126
128
|
if (cell.rowSpan !== void 0) cellMeasure.rowSpan = cell.rowSpan;
|
|
127
129
|
return cellMeasure;
|
|
@@ -141,7 +143,7 @@ function measureTableBlock(tableBlock, contentWidth, fieldValues) {
|
|
|
141
143
|
}));
|
|
142
144
|
continue;
|
|
143
145
|
}
|
|
144
|
-
const sourceRowCells =
|
|
146
|
+
const sourceRowCells = spacingRows[rowIdx]?.cells;
|
|
145
147
|
let maxCellHeightWithBorders = 0;
|
|
146
148
|
let maxCellInsets = 0;
|
|
147
149
|
for (let cellIdx = 0; cellIdx < row.cells.length; cellIdx++) {
|