@stll/folio-core 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -0
- package/dist/ai-edits/apply.js +18 -4
- package/dist/ai-edits/headless.d.ts +31 -4
- package/dist/ai-edits/headless.js +148 -35
- package/dist/ai-edits/index.d.ts +3 -3
- package/dist/ai-edits/index.js +2 -1
- package/dist/ai-edits/snapshot.js +40 -3
- package/dist/ai-edits/types.d.ts +2 -1
- package/dist/compat/eigenpal.d.ts +6 -4
- package/dist/compat/eigenpal.js +5 -3
- package/dist/controller/latestRequestGate.d.ts +13 -0
- package/dist/controller/latestRequestGate.js +16 -0
- package/dist/controller/layoutPipeline.js +12 -1
- package/dist/document-operations.d.ts +10 -3
- package/dist/document-operations.js +29 -14
- package/dist/docx/blockContentParser.js +2 -2
- package/dist/docx/commentParser.js +1 -1
- package/dist/docx/compatibility.d.ts +3 -7
- package/dist/docx/compatibility.js +0 -11
- package/dist/docx/encryption/agileDecryption.js +1 -1
- package/dist/docx/encryption/encryptionInfo.js +1 -1
- package/dist/docx/encryption/openEncryptedDocx.js +1 -1
- package/dist/docx/fontTableParser.d.ts +6 -0
- package/dist/docx/fontTableParser.js +72 -0
- package/dist/docx/groupDrawingParser.js +7 -2
- package/dist/docx/headerFooterParser.js +1 -1
- package/dist/docx/normalizeBaseDirection.js +1 -1
- package/dist/docx/numberingParser.js +1 -0
- package/dist/docx/paragraphParser.js +12 -1
- package/dist/docx/parser.d.ts +1 -1
- package/dist/docx/parser.js +12 -9
- package/dist/docx/rezip.js +70 -5
- package/dist/docx/runConsolidator.js +4 -0
- package/dist/docx/runParser.js +19 -1
- package/dist/docx/selectiveSave.js +3 -3
- package/dist/docx/serializer/fontTableSerializer.d.ts +6 -0
- package/dist/docx/serializer/fontTableSerializer.js +26 -0
- package/dist/docx/serializer/headerFooterSerializer.js +1 -1
- package/dist/docx/serializer/paragraphSerializer.js +4 -0
- package/dist/docx/serializer/runSerializer.js +7 -0
- package/dist/docx/serializer/settingsSerializer.d.ts +6 -0
- package/dist/docx/serializer/settingsSerializer.js +16 -0
- package/dist/docx/serializer/stylesSerializer.d.ts +6 -0
- package/dist/docx/serializer/stylesSerializer.js +56 -0
- package/dist/docx/serializer/tableSerializer.js +25 -26
- package/dist/docx/serializer/themeSerializer.d.ts +6 -0
- package/dist/docx/serializer/themeSerializer.js +36 -0
- package/dist/docx/settingsParser.js +46 -0
- package/dist/docx/styleParser.js +26 -1
- package/dist/docx/tableParser.js +11 -1
- package/dist/docx/vmlImageParser.js +1 -0
- package/dist/i18n/messages/catalogs.gen.d.ts +5916 -5916
- package/dist/i18n/messages/catalogs.gen.js +5910 -5910
- package/dist/index.d.ts +6 -4
- package/dist/index.js +5 -3
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +3 -0
- package/dist/layout-bridge/convert/footnoteLayout.js +3 -1
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +3 -0
- package/dist/layout-bridge/convert/headerFooterLayout.js +6 -1
- package/dist/layout-bridge/convert/toFlowBlocks.d.ts +14 -2
- package/dist/layout-bridge/convert/toFlowBlocks.js +51 -14
- package/dist/layout-bridge/engine/measuring/index.d.ts +2 -1
- package/dist/layout-bridge/engine/measuring/index.js +2 -1
- package/dist/layout-engine/index.js +36 -28
- package/dist/layout-engine/measure/cache.js +11 -3
- package/dist/layout-engine/measure/font-metrics.worker.js +1 -0
- package/dist/layout-engine/measure/index.d.ts +2 -1
- package/dist/layout-engine/measure/index.js +2 -1
- package/dist/layout-engine/measure/lineBreakProvider.d.ts +33 -0
- package/dist/layout-engine/measure/lineBreakProvider.js +252 -0
- package/dist/layout-engine/measure/lineBreaks.d.ts +7 -2
- package/dist/layout-engine/measure/lineBreaks.js +11 -18
- package/dist/layout-engine/measure/listMarkerWidth.d.ts +1 -0
- package/dist/layout-engine/measure/listMarkerWidth.js +12 -6
- package/dist/layout-engine/measure/measureBlocks.d.ts +1 -2
- package/dist/layout-engine/measure/measureBlocks.js +6 -36
- package/dist/layout-engine/measure/measureContainer.js +32 -9
- package/dist/layout-engine/measure/measureHelpers.js +5 -2
- package/dist/layout-engine/measure/measureParagraph.js +335 -72
- package/dist/layout-engine/measure/measureTypes.d.ts +2 -1
- package/dist/layout-engine/measure/measureWorker.d.ts +2 -2
- package/dist/layout-engine/measure/measureWorker.js +8 -19
- package/dist/layout-engine/measure/measureWorkerProtocol.d.ts +3 -14
- package/dist/layout-engine/measure/tableCellFloating.d.ts +10 -3
- package/dist/layout-engine/measure/tableCellFloating.js +37 -29
- package/dist/layout-engine/measure/tableCellFlow.d.ts +27 -0
- package/dist/layout-engine/measure/tableCellFlow.js +73 -0
- package/dist/layout-engine/measure/tableCellGrid.js +1 -1
- package/dist/layout-engine/measure/textMeasurementPolicy.d.ts +18 -0
- package/dist/layout-engine/measure/textMeasurementPolicy.js +24 -0
- package/dist/layout-engine/renderedBreakReconciliation.d.ts +6 -1
- package/dist/layout-engine/renderedBreakReconciliation.js +34 -11
- package/dist/layout-engine/tableRowBreak.d.ts +5 -2
- package/dist/layout-engine/tableRowBreak.js +62 -29
- package/dist/layout-engine/types.d.ts +40 -7
- package/dist/layout-painter/anchoredImagePosition.d.ts +23 -0
- package/dist/layout-painter/anchoredImagePosition.js +95 -0
- package/dist/layout-painter/borderStroke.d.ts +23 -0
- package/dist/layout-painter/borderStroke.js +39 -0
- package/dist/layout-painter/renderImage.d.ts +5 -4
- package/dist/layout-painter/renderImage.js +18 -4
- package/dist/layout-painter/renderPage.d.ts +2 -27
- package/dist/layout-painter/renderPage.js +13 -99
- package/dist/layout-painter/renderParagraph.js +41 -23
- package/dist/layout-painter/renderTable.d.ts +2 -0
- package/dist/layout-painter/renderTable.js +223 -43
- package/dist/managers/DocumentLoaderManager.d.ts +1 -1
- package/dist/managers/DocumentLoaderManager.js +2 -2
- package/dist/prosemirror/attrs/index.d.ts +4 -2
- package/dist/prosemirror/attrs/index.js +21 -2
- package/dist/prosemirror/commands/propertyChangeScope.js +1 -1
- package/dist/prosemirror/conversion/fromProseDoc.js +33 -18
- package/dist/prosemirror/conversion/toProseDoc.js +50 -18
- package/dist/prosemirror/extensions/StarterKit.js +2 -0
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +7 -2
- package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +1 -1
- package/dist/prosemirror/extensions/features/ListExtension.js +1 -0
- package/dist/prosemirror/extensions/marks/FootnoteRefExtension.js +1 -0
- package/dist/prosemirror/extensions/marks/HighlightExtension.js +1 -1
- package/dist/prosemirror/extensions/marks/LanguageExtension.d.ts +7 -0
- package/dist/prosemirror/extensions/marks/LanguageExtension.js +30 -0
- package/dist/prosemirror/extensions/marks/RunShadingExtension.js +1 -1
- package/dist/prosemirror/extensions/marks/TextColorExtension.js +1 -1
- package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +1 -1
- package/dist/prosemirror/extensions/marks/markUtils.js +12 -0
- package/dist/prosemirror/extensions/nodes/ImageExtension.js +2 -1
- package/dist/prosemirror/extensions/nodes/TableExtension.js +4 -2
- package/dist/prosemirror/schema/index.d.ts +2 -2
- package/dist/prosemirror/schema/marks.d.ts +6 -1
- package/dist/prosemirror/schema/nodes.d.ts +13 -11
- package/dist/prosemirror/styles/resolvedStyleAttrs.js +1 -0
- package/dist/prosemirror/utils/tabCalculator.d.ts +3 -1
- package/dist/prosemirror/utils/tabCalculator.js +12 -10
- package/dist/prosemirror/validation.js +4 -1
- package/dist/redline.js +13 -3
- package/dist/server.d.ts +6 -3
- package/dist/server.js +5 -2
- package/dist/style-sets/extract.d.ts +35 -0
- package/dist/style-sets/extract.js +123 -0
- package/dist/style-sets/stellaStyle.d.ts +13 -0
- package/dist/style-sets/stellaStyle.js +516 -0
- package/dist/style-sets/types.d.ts +31 -0
- package/dist/style-sets/types.js +5 -0
- package/dist/utils/createDocument.d.ts +12 -2
- package/dist/utils/createDocument.js +42 -31
- package/dist/utils/fontResolver.js +6 -6
- package/dist/utils/formatToStyle.js +1 -1
- package/dist/utils/paragraphFormattingMerge.d.ts +14 -3
- package/dist/utils/paragraphFormattingMerge.js +14 -4
- package/package.json +5 -1
- package/dist/docx/capabilities.d.ts +0 -41
- package/dist/docx/capabilities.js +0 -322
|
@@ -142,16 +142,10 @@ function flush(current) {
|
|
|
142
142
|
const entries = [];
|
|
143
143
|
for (const entry of current.pending.values()) {
|
|
144
144
|
if (entries.length >= MAX_BATCH_SIZE) break;
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
font: entry.font,
|
|
150
|
-
fontCacheKey: entry.fontCacheKey,
|
|
151
|
-
fontFingerprintWidth: entry.fontFingerprintWidth,
|
|
152
|
-
letterSpacing: entry.letterSpacing,
|
|
153
|
-
horizontalScale: entry.horizontalScale
|
|
154
|
-
});
|
|
145
|
+
const { cacheGeneration: entryGeneration, ...request } = entry;
|
|
146
|
+
current.pending.delete(makeKey(request.text, request.fontCacheKey, request.letterSpacing));
|
|
147
|
+
if (entryGeneration !== cacheGeneration) continue;
|
|
148
|
+
entries.push(request);
|
|
155
149
|
}
|
|
156
150
|
if (entries.length === 0) {
|
|
157
151
|
if (current.pending.size > 0) scheduleFlush(current);
|
|
@@ -196,20 +190,15 @@ function handleResponse(current, message) {
|
|
|
196
190
|
* `OffscreenCanvas`/`Worker`, or when the proxy has been disabled by
|
|
197
191
|
* a previous error.
|
|
198
192
|
*/
|
|
199
|
-
function prefetchMeasurement(
|
|
193
|
+
function prefetchMeasurement(request) {
|
|
200
194
|
if (!isWorkerFontMetricsEnabled()) return;
|
|
201
|
-
if (!text) return;
|
|
195
|
+
if (!request.text) return;
|
|
202
196
|
const current = ensureProxy();
|
|
203
197
|
if (current === null) return;
|
|
204
|
-
const key = makeKey(text, fontCacheKey, letterSpacing);
|
|
198
|
+
const key = makeKey(request.text, request.fontCacheKey, request.letterSpacing);
|
|
205
199
|
if (current.pending.has(key)) return;
|
|
206
200
|
current.pending.set(key, {
|
|
207
|
-
|
|
208
|
-
font,
|
|
209
|
-
fontCacheKey,
|
|
210
|
-
fontFingerprintWidth,
|
|
211
|
-
letterSpacing,
|
|
212
|
-
horizontalScale,
|
|
201
|
+
...request,
|
|
213
202
|
cacheGeneration: getTextWidthCacheGeneration()
|
|
214
203
|
});
|
|
215
204
|
scheduleFlush(current);
|
|
@@ -1,18 +1,6 @@
|
|
|
1
|
+
import { FontKerningMode } from "./textMeasurementPolicy.js";
|
|
2
|
+
|
|
1
3
|
//#region src/layout-engine/measure/measureWorkerProtocol.d.ts
|
|
2
|
-
/**
|
|
3
|
-
* Wire format shared between the main-thread proxy
|
|
4
|
-
* (`measureWorker.ts`) and the worker entry
|
|
5
|
-
* (`font-metrics.worker.ts`).
|
|
6
|
-
*
|
|
7
|
-
* The protocol is intentionally minimal: a batched request carrying an
|
|
8
|
-
* array of measurement inputs, and a matching response with widths in
|
|
9
|
-
* the same order. The worker is a pure function — no state, no cache.
|
|
10
|
-
* The main thread owns the LRU cache and is the sole authority on what
|
|
11
|
-
* is worth measuring.
|
|
12
|
-
*
|
|
13
|
-
* Keep this file tiny and dependency-free so it can be imported from
|
|
14
|
-
* both contexts.
|
|
15
|
-
*/
|
|
16
4
|
/**
|
|
17
5
|
* Single measurement request inside a batch. Mirrors the inputs that
|
|
18
6
|
* `measureTextWidth` already takes on the main thread.
|
|
@@ -40,6 +28,7 @@ type MeasureRequestEntry = {
|
|
|
40
28
|
fontFingerprintWidth: number;
|
|
41
29
|
letterSpacing: number;
|
|
42
30
|
horizontalScale: number;
|
|
31
|
+
fontKerning: FontKerningMode;
|
|
43
32
|
};
|
|
44
33
|
declare const WORKER_FONT_FINGERPRINT_TEXT = "HAMBURGEFONTS ivwqy 0123456789";
|
|
45
34
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TableCell, TableCellMeasure } from "../types.js";
|
|
1
|
+
import { ImageRun, TableCell, TableCellMeasure } from "../types.js";
|
|
2
2
|
import { FloatingImageZone } from "./floatingZones.js";
|
|
3
3
|
|
|
4
4
|
//#region src/layout-engine/measure/tableCellFloating.d.ts
|
|
@@ -8,6 +8,11 @@ type TableCellFloatingImage = {
|
|
|
8
8
|
height: number;
|
|
9
9
|
alt?: string;
|
|
10
10
|
transform?: string;
|
|
11
|
+
opacity?: number;
|
|
12
|
+
cropTop?: number;
|
|
13
|
+
cropRight?: number;
|
|
14
|
+
cropBottom?: number;
|
|
15
|
+
cropLeft?: number;
|
|
11
16
|
x: number;
|
|
12
17
|
y: number;
|
|
13
18
|
side: "left" | "right";
|
|
@@ -19,8 +24,10 @@ type TableCellFloatingImage = {
|
|
|
19
24
|
pmStart?: number;
|
|
20
25
|
pmEnd?: number;
|
|
21
26
|
};
|
|
27
|
+
type TableCellFloatingPosition = Pick<TableCellFloatingImage, "x" | "y" | "side">;
|
|
28
|
+
type ResolveTableCellFloatingPosition = (run: ImageRun, paragraphY: number) => TableCellFloatingPosition;
|
|
22
29
|
declare function getTableCellContentWidth(cell: TableCell | undefined, cellMeasure: TableCellMeasure): number;
|
|
23
|
-
declare function getTableCellFloatingImages(cell: TableCell, cellMeasure: TableCellMeasure, contentWidth: number): TableCellFloatingImage[];
|
|
30
|
+
declare function getTableCellFloatingImages(cell: TableCell, cellMeasure: TableCellMeasure, contentWidth: number, resolvePosition?: ResolveTableCellFloatingPosition): TableCellFloatingImage[];
|
|
24
31
|
declare function buildTableCellFloatingZones(floatingImages: TableCellFloatingImage[], contentWidth: number): FloatingImageZone[];
|
|
25
32
|
//#endregion
|
|
26
|
-
export { TableCellFloatingImage, buildTableCellFloatingZones, getTableCellContentWidth, getTableCellFloatingImages };
|
|
33
|
+
export { ResolveTableCellFloatingPosition, TableCellFloatingImage, buildTableCellFloatingZones, getTableCellContentWidth, getTableCellFloatingImages };
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import { emuToPixels } from "../../utils/units.js";
|
|
2
2
|
import { isFloatingImageRun } from "../types.js";
|
|
3
3
|
import { clampFloatingWrapMargins } from "./clampFloatingWrapMargins.js";
|
|
4
|
+
import { createTableCellFlowState, placeTableCellBlock } from "./tableCellFlow.js";
|
|
4
5
|
//#region src/layout-engine/measure/tableCellFloating.ts
|
|
5
6
|
function getTableCellContentWidth(cell, cellMeasure) {
|
|
6
7
|
const padLeft = cell?.padding?.left ?? 7;
|
|
7
8
|
const padRight = cell?.padding?.right ?? 7;
|
|
8
9
|
return Math.max(0, cellMeasure.width - padLeft - padRight);
|
|
9
10
|
}
|
|
10
|
-
function getTableCellFloatingImages(cell, cellMeasure, contentWidth) {
|
|
11
|
+
function getTableCellFloatingImages(cell, cellMeasure, contentWidth, resolvePosition) {
|
|
11
12
|
const result = [];
|
|
12
|
-
|
|
13
|
+
const flowState = createTableCellFlowState();
|
|
13
14
|
for (let blockIndex = 0; blockIndex < cell.blocks.length; blockIndex++) {
|
|
14
15
|
const block = cell.blocks[blockIndex];
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
16
|
+
const blockMeasure = cellMeasure.blocks[blockIndex];
|
|
17
|
+
if (!block || !blockMeasure) continue;
|
|
18
|
+
const placement = placeTableCellBlock(flowState, block, blockMeasure);
|
|
19
|
+
if (block.kind !== "paragraph") continue;
|
|
20
20
|
for (const run of block.runs) {
|
|
21
21
|
if (run.kind !== "image" || !isFloatingImageRun(run)) continue;
|
|
22
22
|
const position = run.position;
|
|
@@ -24,30 +24,35 @@ function getTableCellFloatingImages(cell, cellMeasure, contentWidth) {
|
|
|
24
24
|
const distBottom = run.distBottom ?? 0;
|
|
25
25
|
const distLeft = run.distLeft ?? 12;
|
|
26
26
|
const distRight = run.distRight ?? 12;
|
|
27
|
-
let side
|
|
28
|
-
let x
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
let side;
|
|
28
|
+
let x;
|
|
29
|
+
let y;
|
|
30
|
+
if (resolvePosition) ({side, x, y} = resolvePosition(run, placement.contentTop));
|
|
31
|
+
else {
|
|
32
|
+
side = "left";
|
|
33
|
+
x = 0;
|
|
34
|
+
if (position?.horizontal) {
|
|
35
|
+
const horizontal = position.horizontal;
|
|
36
|
+
if (horizontal.align === "right") {
|
|
37
|
+
side = "right";
|
|
38
|
+
x = contentWidth - run.width;
|
|
39
|
+
} else if (horizontal.align === "center") x = (contentWidth - run.width) / 2;
|
|
40
|
+
else if (horizontal.posOffset !== void 0) {
|
|
41
|
+
x = emuToPixels(horizontal.posOffset);
|
|
42
|
+
side = x > contentWidth / 2 ? "right" : "left";
|
|
43
|
+
}
|
|
44
|
+
} else if (run.cssFloat === "right") {
|
|
32
45
|
side = "right";
|
|
33
46
|
x = contentWidth - run.width;
|
|
34
|
-
} else if (horizontal.align === "left") x = 0;
|
|
35
|
-
else if (horizontal.align === "center") x = (contentWidth - run.width) / 2;
|
|
36
|
-
else if (horizontal.posOffset !== void 0) {
|
|
37
|
-
x = emuToPixels(horizontal.posOffset);
|
|
38
|
-
side = x > contentWidth / 2 ? "right" : "left";
|
|
39
47
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (vertical.posOffset !== void 0) y = paragraphY + emuToPixels(vertical.posOffset);
|
|
48
|
-
else if (vertical.align === "top") y = 0;
|
|
48
|
+
y = placement.contentTop;
|
|
49
|
+
if (position?.vertical) {
|
|
50
|
+
const vertical = position.vertical;
|
|
51
|
+
if (vertical.posOffset !== void 0) y = placement.contentTop + emuToPixels(vertical.posOffset);
|
|
52
|
+
else if (vertical.align === "top") y = 0;
|
|
53
|
+
}
|
|
54
|
+
x = Math.max(0, Math.min(x, contentWidth - run.width));
|
|
49
55
|
}
|
|
50
|
-
x = Math.max(0, Math.min(x, contentWidth - run.width));
|
|
51
56
|
let wrapText = "bothSides";
|
|
52
57
|
if (run.cssFloat === "left") wrapText = "right";
|
|
53
58
|
else if (run.cssFloat === "right") wrapText = "left";
|
|
@@ -57,6 +62,11 @@ function getTableCellFloatingImages(cell, cellMeasure, contentWidth) {
|
|
|
57
62
|
height: run.height,
|
|
58
63
|
...run.alt !== void 0 ? { alt: run.alt } : {},
|
|
59
64
|
...run.transform !== void 0 ? { transform: run.transform } : {},
|
|
65
|
+
...run.opacity != null ? { opacity: run.opacity } : {},
|
|
66
|
+
...run.cropTop != null ? { cropTop: run.cropTop } : {},
|
|
67
|
+
...run.cropRight != null ? { cropRight: run.cropRight } : {},
|
|
68
|
+
...run.cropBottom != null ? { cropBottom: run.cropBottom } : {},
|
|
69
|
+
...run.cropLeft != null ? { cropLeft: run.cropLeft } : {},
|
|
60
70
|
x,
|
|
61
71
|
y,
|
|
62
72
|
side,
|
|
@@ -69,8 +79,6 @@ function getTableCellFloatingImages(cell, cellMeasure, contentWidth) {
|
|
|
69
79
|
...run.pmEnd !== void 0 ? { pmEnd: run.pmEnd } : {}
|
|
70
80
|
});
|
|
71
81
|
}
|
|
72
|
-
const blockMeasure = cellMeasure.blocks[blockIndex];
|
|
73
|
-
if (blockMeasure?.kind === "paragraph") paragraphY += blockMeasure.totalHeight;
|
|
74
82
|
}
|
|
75
83
|
return result;
|
|
76
84
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { FlowBlock, Measure } from "../types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/layout-engine/measure/tableCellFlow.d.ts
|
|
4
|
+
type TableCellBlockPlacement = {
|
|
5
|
+
top: number;
|
|
6
|
+
contentTop: number;
|
|
7
|
+
contentHeight: number;
|
|
8
|
+
leadingSpacing: number;
|
|
9
|
+
};
|
|
10
|
+
type TableCellFlowState = {
|
|
11
|
+
height: number;
|
|
12
|
+
previousParagraphWasEmpty: boolean;
|
|
13
|
+
trailingSpacing: number;
|
|
14
|
+
};
|
|
15
|
+
declare const createTableCellFlowState: () => TableCellFlowState;
|
|
16
|
+
declare const finishTableCellFlow: (state: TableCellFlowState) => number;
|
|
17
|
+
declare const getTableCellBlockContentHeight: (block: FlowBlock, measure: Measure) => number;
|
|
18
|
+
/**
|
|
19
|
+
* Place one block in an unpaginated table-cell story.
|
|
20
|
+
*
|
|
21
|
+
* Adjacent paragraph spacing collapses to the larger side. Consecutive
|
|
22
|
+
* authored empty paragraphs remain independent vertical spacers, including
|
|
23
|
+
* at the end of the cell.
|
|
24
|
+
*/
|
|
25
|
+
declare const placeTableCellBlock: (state: TableCellFlowState, block: FlowBlock, measure: Measure) => TableCellBlockPlacement;
|
|
26
|
+
//#endregion
|
|
27
|
+
export { TableCellBlockPlacement, TableCellFlowState, createTableCellFlowState, finishTableCellFlow, getTableCellBlockContentHeight, placeTableCellBlock };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { measuredLineRangeHeight } from "../lineFlow.js";
|
|
2
|
+
import { isFloatingTextBoxBlock } from "../types.js";
|
|
3
|
+
import { getParagraphSpacingAfter, getParagraphSpacingBefore, isEmptyParagraph } from "../paragraphSpacing.js";
|
|
4
|
+
//#region src/layout-engine/measure/tableCellFlow.ts
|
|
5
|
+
const createTableCellFlowState = () => ({
|
|
6
|
+
height: 0,
|
|
7
|
+
previousParagraphWasEmpty: false,
|
|
8
|
+
trailingSpacing: 0
|
|
9
|
+
});
|
|
10
|
+
const finishTableCellFlow = (state) => state.height + state.trailingSpacing;
|
|
11
|
+
const isSuppressedParagraphMeasure = (measure) => measure.totalHeight === 0 && measure.lines.every(({ lineHeight }) => lineHeight === 0);
|
|
12
|
+
const paragraphSpacing = (block, measure) => {
|
|
13
|
+
if (isSuppressedParagraphMeasure(measure)) return {
|
|
14
|
+
before: 0,
|
|
15
|
+
after: 0
|
|
16
|
+
};
|
|
17
|
+
return {
|
|
18
|
+
before: getParagraphSpacingBefore(block),
|
|
19
|
+
after: getParagraphSpacingAfter(block)
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
const getTableCellBlockContentHeight = (block, measure) => {
|
|
23
|
+
if (block.kind === "textBox" && isFloatingTextBoxBlock(block)) return 0;
|
|
24
|
+
if (block.kind === "paragraph" && measure.kind === "paragraph") return measuredLineRangeHeight(measure.lines, 0, measure.lines.length);
|
|
25
|
+
if ("totalHeight" in measure) return measure.totalHeight;
|
|
26
|
+
if ("height" in measure) return measure.height;
|
|
27
|
+
return 0;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Place one block in an unpaginated table-cell story.
|
|
31
|
+
*
|
|
32
|
+
* Adjacent paragraph spacing collapses to the larger side. Consecutive
|
|
33
|
+
* authored empty paragraphs remain independent vertical spacers, including
|
|
34
|
+
* at the end of the cell.
|
|
35
|
+
*/
|
|
36
|
+
const placeTableCellBlock = (state, block, measure) => {
|
|
37
|
+
const top = state.height;
|
|
38
|
+
const contentHeight = getTableCellBlockContentHeight(block, measure);
|
|
39
|
+
if (block.kind === "paragraph" && measure.kind === "paragraph" && isSuppressedParagraphMeasure(measure)) return {
|
|
40
|
+
top,
|
|
41
|
+
contentTop: top,
|
|
42
|
+
contentHeight: 0,
|
|
43
|
+
leadingSpacing: 0
|
|
44
|
+
};
|
|
45
|
+
if (block.kind !== "paragraph" || measure.kind !== "paragraph") {
|
|
46
|
+
const leadingSpacing = state.trailingSpacing;
|
|
47
|
+
const contentTop = top + leadingSpacing;
|
|
48
|
+
state.height = contentTop + contentHeight;
|
|
49
|
+
state.previousParagraphWasEmpty = false;
|
|
50
|
+
state.trailingSpacing = 0;
|
|
51
|
+
return {
|
|
52
|
+
top,
|
|
53
|
+
contentTop,
|
|
54
|
+
contentHeight,
|
|
55
|
+
leadingSpacing
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const spacing = paragraphSpacing(block, measure);
|
|
59
|
+
const empty = isEmptyParagraph(block);
|
|
60
|
+
const leadingSpacing = empty && state.previousParagraphWasEmpty ? spacing.before + state.trailingSpacing : Math.max(spacing.before, state.trailingSpacing);
|
|
61
|
+
const contentTop = top + leadingSpacing;
|
|
62
|
+
state.height = contentTop + contentHeight;
|
|
63
|
+
state.previousParagraphWasEmpty = empty;
|
|
64
|
+
state.trailingSpacing = spacing.after;
|
|
65
|
+
return {
|
|
66
|
+
top,
|
|
67
|
+
contentTop,
|
|
68
|
+
contentHeight,
|
|
69
|
+
leadingSpacing
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
//#endregion
|
|
73
|
+
export { createTableCellFlowState, finishTableCellFlow, getTableCellBlockContentHeight, placeTableCellBlock };
|
|
@@ -36,7 +36,7 @@ const getSourceCellColumn = (grid, cell) => grid.sourceColumnsByCell.get(cell);
|
|
|
36
36
|
const getTableCellVerticalBorderHeight = (grid, cell, rowIndex) => {
|
|
37
37
|
const sourceColumn = cell ? getSourceCellColumn(grid, cell) : void 0;
|
|
38
38
|
const aboveBottom = (sourceColumn === void 0 ? void 0 : getSourceCellAt(grid, rowIndex - 1, sourceColumn))?.borders?.bottom;
|
|
39
|
-
const aboveOwnsEdge = aboveBottom !== void 0 && aboveBottom.
|
|
39
|
+
const aboveOwnsEdge = aboveBottom !== void 0 && aboveBottom.style !== "none" && aboveBottom.style !== "nil";
|
|
40
40
|
return (rowIndex === 0 || !aboveOwnsEdge ? cell?.borders?.top?.width ?? 0 : 0) + (cell?.borders?.bottom?.width ?? 0);
|
|
41
41
|
};
|
|
42
42
|
const firstAvailableColumn = (occupiedColumns, startingColumn) => {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { RunFormatting } from "../types.js";
|
|
2
|
+
import { FontStyle } from "./measureTypes.js";
|
|
3
|
+
|
|
4
|
+
//#region src/layout-engine/measure/textMeasurementPolicy.d.ts
|
|
5
|
+
declare const FONT_KERNING_MODE: {
|
|
6
|
+
readonly enabled: "normal";
|
|
7
|
+
readonly disabled: "none";
|
|
8
|
+
};
|
|
9
|
+
type FontKerningMode = (typeof FONT_KERNING_MODE)[keyof typeof FONT_KERNING_MODE];
|
|
10
|
+
type RunKerningInput = Pick<RunFormatting, "fontSize" | "kerningMinPt">;
|
|
11
|
+
/** Resolve an authored kerning threshold against the effective run size. */
|
|
12
|
+
declare function getRunFontKerningMode(run: RunKerningInput, fallbackFontSize: number): FontKerningMode;
|
|
13
|
+
/** Convert normalized measurement style into the canvas/CSS kerning mode. */
|
|
14
|
+
declare function getFontKerningMode(style: Pick<FontStyle, "kerning">): FontKerningMode;
|
|
15
|
+
/** Count ordinary ASCII spaces that justification may compress. */
|
|
16
|
+
declare function countCompressibleSpaces(text: string): number;
|
|
17
|
+
//#endregion
|
|
18
|
+
export { FONT_KERNING_MODE, FontKerningMode, countCompressibleSpaces, getFontKerningMode, getRunFontKerningMode };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//#region src/layout-engine/measure/textMeasurementPolicy.ts
|
|
2
|
+
const FONT_KERNING_MODE = {
|
|
3
|
+
enabled: "normal",
|
|
4
|
+
disabled: "none"
|
|
5
|
+
};
|
|
6
|
+
/** Resolve an authored kerning threshold against the effective run size. */
|
|
7
|
+
function getRunFontKerningMode(run, fallbackFontSize) {
|
|
8
|
+
const threshold = run.kerningMinPt;
|
|
9
|
+
const fontSize = run.fontSize ?? fallbackFontSize;
|
|
10
|
+
if (threshold === void 0 || fontSize < threshold) return FONT_KERNING_MODE.disabled;
|
|
11
|
+
return FONT_KERNING_MODE.enabled;
|
|
12
|
+
}
|
|
13
|
+
/** Convert normalized measurement style into the canvas/CSS kerning mode. */
|
|
14
|
+
function getFontKerningMode(style) {
|
|
15
|
+
return style.kerning ? FONT_KERNING_MODE.enabled : FONT_KERNING_MODE.disabled;
|
|
16
|
+
}
|
|
17
|
+
/** Count ordinary ASCII spaces that justification may compress. */
|
|
18
|
+
function countCompressibleSpaces(text) {
|
|
19
|
+
let count = 0;
|
|
20
|
+
for (const char of text) if (char === " ") count += 1;
|
|
21
|
+
return count;
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
export { FONT_KERNING_MODE, countCompressibleSpaces, getFontKerningMode, getRunFontKerningMode };
|
|
@@ -6,6 +6,10 @@ type RenderedBreakState = {
|
|
|
6
6
|
} | {
|
|
7
7
|
type: "pageAdvance";
|
|
8
8
|
reason: "ordinary" | "reflowBoundary";
|
|
9
|
+
} | {
|
|
10
|
+
type: "pageAdvance";
|
|
11
|
+
reason: "authoredBoundary";
|
|
12
|
+
boundary: "hardBreak" | "sectionBreak";
|
|
9
13
|
};
|
|
10
14
|
declare const INITIAL_RENDERED_BREAK_STATE: RenderedBreakState;
|
|
11
15
|
type ReconcileBeforeBlockOptions = {
|
|
@@ -19,6 +23,7 @@ type ReconcileBeforeBlockOptions = {
|
|
|
19
23
|
};
|
|
20
24
|
type BreakDecision = {
|
|
21
25
|
forcePageBreak: boolean;
|
|
26
|
+
suppressSpaceBefore: boolean;
|
|
22
27
|
state: RenderedBreakState;
|
|
23
28
|
};
|
|
24
29
|
/**
|
|
@@ -46,7 +51,7 @@ type ReconcileAfterBlockOptions = {
|
|
|
46
51
|
pageNumberAfter: number;
|
|
47
52
|
previousPage: Page | undefined;
|
|
48
53
|
};
|
|
49
|
-
/** Record page movement
|
|
54
|
+
/** Record page movement and carry authored boundaries across pagination-empty paragraphs. */
|
|
50
55
|
declare const reconcileAfterBlock: ({
|
|
51
56
|
state,
|
|
52
57
|
block,
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { isEmptyParagraph } from "./paragraphSpacing.js";
|
|
2
1
|
import { continuesNumberedSequence, continuesTabbedParagraphSequence } from "./paragraphSequence.js";
|
|
3
2
|
//#region src/layout-engine/renderedBreakReconciliation.ts
|
|
4
3
|
const INITIAL_RENDERED_BREAK_STATE = { type: "noPageAdvance" };
|
|
@@ -13,15 +12,24 @@ const INITIAL_RENDERED_BREAK_STATE = { type: "noPageAdvance" };
|
|
|
13
12
|
const reconcileBreakBeforeBlock = ({ state, block, previousBlock, page, blocksById, hasExplicitPageBreak, renderedBreakNeedsSnap }) => {
|
|
14
13
|
if (hasExplicitPageBreak) return {
|
|
15
14
|
forcePageBreak: true,
|
|
15
|
+
suppressSpaceBefore: false,
|
|
16
16
|
state: INITIAL_RENDERED_BREAK_STATE
|
|
17
17
|
};
|
|
18
18
|
if (block.kind !== "paragraph" || block.attrs?.renderedPageBreakBefore !== true) return {
|
|
19
19
|
forcePageBreak: false,
|
|
20
|
+
suppressSpaceBefore: false,
|
|
20
21
|
state
|
|
21
22
|
};
|
|
22
|
-
const markerAlreadySatisfied = pageStartsWithPreviousParagraphContinuation(page, previousBlock) || state.type === "pageAdvance" && state.reason === "reflowBoundary" || state.type === "pageAdvance" && isPaginationEmptyParagraph(block) || state.type === "pageAdvance" && (continuesNumberedSequence(previousBlock, block) || continuesTabbedParagraphSequence(previousBlock, block));
|
|
23
|
+
const markerAlreadySatisfied = pageStartsWithPreviousParagraphContinuation(page, previousBlock) || state.type === "pageAdvance" && state.reason === "authoredBoundary" || state.type === "pageAdvance" && state.reason === "reflowBoundary" || state.type === "pageAdvance" && isPaginationEmptyParagraph(block) || state.type === "pageAdvance" && (continuesNumberedSequence(previousBlock, block) || continuesTabbedParagraphSequence(previousBlock, block));
|
|
24
|
+
const forcePageBreak = (renderedBreakNeedsSnap || block.attrs?.keepNext === true) && !markerAlreadySatisfied && pageHasVisibleBodyContent(page, blocksById);
|
|
25
|
+
const followsAuthoredPageBreak = previousBlock?.kind === "pageBreak";
|
|
26
|
+
const followsSectionBreak = previousBlock?.kind === "sectionBreak";
|
|
27
|
+
const followsPageBreakingSection = followsSectionBreak && previousBlock.type !== "continuous";
|
|
28
|
+
const followsCarriedSectionBoundary = state.type === "pageAdvance" && state.reason === "authoredBoundary" && state.boundary === "sectionBreak";
|
|
29
|
+
const preserveSectionLeadingSpacing = (followsSectionBreak || followsCarriedSectionBoundary) && block.attrs?.spacingExplicit?.before === true;
|
|
23
30
|
return {
|
|
24
|
-
forcePageBreak
|
|
31
|
+
forcePageBreak,
|
|
32
|
+
suppressSpaceBefore: (forcePageBreak || markerAlreadySatisfied || followsAuthoredPageBreak || followsPageBreakingSection) && !preserveSectionLeadingSpacing,
|
|
25
33
|
state: INITIAL_RENDERED_BREAK_STATE
|
|
26
34
|
};
|
|
27
35
|
};
|
|
@@ -29,19 +37,38 @@ const recordReflowBoundary = (state, crossedPageBoundary) => crossedPageBoundary
|
|
|
29
37
|
type: "pageAdvance",
|
|
30
38
|
reason: "reflowBoundary"
|
|
31
39
|
} : state;
|
|
32
|
-
/** Record page movement
|
|
40
|
+
/** Record page movement and carry authored boundaries across pagination-empty paragraphs. */
|
|
33
41
|
const reconcileAfterBlock = ({ state, block, pageNumberBefore, pageNumberAfter, previousPage }) => {
|
|
34
|
-
if (isStructuralBreak(block))
|
|
35
|
-
|
|
42
|
+
if (isStructuralBreak(block)) {
|
|
43
|
+
if (pageNumberAfter <= pageNumberBefore) {
|
|
44
|
+
if (state.type === "pageAdvance" && state.reason === "authoredBoundary") return block.kind === "sectionBreak" ? {
|
|
45
|
+
...state,
|
|
46
|
+
boundary: "sectionBreak"
|
|
47
|
+
} : state;
|
|
48
|
+
return INITIAL_RENDERED_BREAK_STATE;
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
type: "pageAdvance",
|
|
52
|
+
reason: "authoredBoundary",
|
|
53
|
+
boundary: block.kind === "sectionBreak" ? "sectionBreak" : "hardBreak"
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
if (block.kind === "paragraph" && isPaginationEmptyParagraph(block)) return state;
|
|
57
|
+
if (pageNumberAfter <= pageNumberBefore) return state.type === "pageAdvance" && state.reason === "authoredBoundary" ? INITIAL_RENDERED_BREAK_STATE : state;
|
|
36
58
|
return {
|
|
37
59
|
type: "pageAdvance",
|
|
38
60
|
reason: previousPage?.fragments.some(({ blockId }) => blockId === block.id) ? "reflowBoundary" : "ordinary"
|
|
39
61
|
};
|
|
40
62
|
};
|
|
41
|
-
const isPaginationEmptyParagraph = (block) => block.runs.every((run) => {
|
|
63
|
+
const isPaginationEmptyParagraph = (block) => !hasVisibleEmptyParagraphDecoration(block) && block.runs.every((run) => {
|
|
42
64
|
if (run.kind === "text") return run.text.trim().length === 0;
|
|
43
65
|
return run.kind === "tab" || run.kind === "lineBreak";
|
|
44
66
|
});
|
|
67
|
+
const hasVisibleEmptyParagraphDecoration = (block) => {
|
|
68
|
+
if (block.attrs?.listMarker !== void 0 && block.attrs.listMarker.length > 0 && block.attrs.listMarkerHidden !== true) return true;
|
|
69
|
+
if (block.attrs?.shading) return true;
|
|
70
|
+
return Object.values(block.attrs?.borders ?? {}).some((border) => border !== void 0 && border.style !== "none" && border.style !== "nil" && border.width !== 0);
|
|
71
|
+
};
|
|
45
72
|
const pageHasVisibleBodyContent = (page, blocksById) => {
|
|
46
73
|
for (const fragment of page.fragments) {
|
|
47
74
|
if (fragment.kind !== "paragraph") return true;
|
|
@@ -55,9 +82,5 @@ const pageStartsWithPreviousParagraphContinuation = (page, previousBlock) => {
|
|
|
55
82
|
return page.fragments.some((fragment) => fragment.kind === "paragraph" && String(fragment.blockId) === String(previousBlock.id) && fragment.continuesFromPrev === true);
|
|
56
83
|
};
|
|
57
84
|
const isStructuralBreak = (block) => block.kind === "pageBreak" || block.kind === "columnBreak" || block.kind === "sectionBreak";
|
|
58
|
-
const isVisibleBodyBlock = (block) => {
|
|
59
|
-
if (block.kind === "paragraph") return !isEmptyParagraph(block);
|
|
60
|
-
return !isStructuralBreak(block);
|
|
61
|
-
};
|
|
62
85
|
//#endregion
|
|
63
86
|
export { INITIAL_RENDERED_BREAK_STATE, reconcileAfterBlock, reconcileBreakBeforeBlock, recordReflowBoundary };
|
|
@@ -9,10 +9,13 @@ type TableRowBreakInfo = {
|
|
|
9
9
|
* at which a break is clean. Always includes the row's full height as the
|
|
10
10
|
* final boundary.
|
|
11
11
|
*/
|
|
12
|
-
breakOffsets: number[][];
|
|
12
|
+
breakOffsets: number[][]; /** Suppressible leading paragraph whitespace after each matching break offset. */
|
|
13
|
+
continuationSkips: number[][];
|
|
13
14
|
};
|
|
14
15
|
/** Build break geometry for a table from its block + measure. */
|
|
15
16
|
declare function buildTableRowBreakInfo(block: TableBlock, measure: TableMeasure): TableRowBreakInfo;
|
|
17
|
+
/** Leading empty-paragraph whitespace to consume when a row resumes after `offset`. */
|
|
18
|
+
declare function getRowContinuationSkip(info: TableRowBreakInfo, rowIndex: number, offset: number): number;
|
|
16
19
|
/**
|
|
17
20
|
* Given a row and how much of it has already been placed (`fromOffset`), return
|
|
18
21
|
* how many more px can be placed ending on a whole line, without exceeding
|
|
@@ -20,4 +23,4 @@ declare function buildTableRowBreakInfo(block: TableBlock, measure: TableMeasure
|
|
|
20
23
|
*/
|
|
21
24
|
declare function snapRowBreak(info: TableRowBreakInfo, rowIndex: number, fromOffset: number, maxSlice: number): number;
|
|
22
25
|
//#endregion
|
|
23
|
-
export { TableRowBreakInfo, buildTableRowBreakInfo, snapRowBreak };
|
|
26
|
+
export { TableRowBreakInfo, buildTableRowBreakInfo, getRowContinuationSkip, snapRowBreak };
|