@stll/folio-core 0.25.3 → 0.25.4
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.
|
@@ -54,10 +54,7 @@ declare function setFontCacheSize(size: number): void;
|
|
|
54
54
|
* Get current font metrics cache size
|
|
55
55
|
*/
|
|
56
56
|
declare function getFontCacheSize(): number;
|
|
57
|
-
/**
|
|
58
|
-
* Generate a simple hash for a paragraph block
|
|
59
|
-
* Used as cache key to identify identical content
|
|
60
|
-
*/
|
|
57
|
+
/** Serialize the complete paragraph measurement contract into a cache key. */
|
|
61
58
|
declare function hashParagraphBlock(block: ParagraphBlock): string;
|
|
62
59
|
/**
|
|
63
60
|
* Get cached paragraph measurement or return undefined
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { lineBreakPolicyCacheParts } from "./effectiveLineBreakPolicy.js";
|
|
2
1
|
import { getLineBreakProviderGeneration } from "./lineBreakProvider.js";
|
|
3
2
|
import { clearFontResolvedCache } from "./measureHelpers.js";
|
|
4
3
|
//#region src/layout-engine/measure/cache.ts
|
|
@@ -164,40 +163,38 @@ let paragraphCacheMaxSize = 5e3;
|
|
|
164
163
|
* Key format: block content hash
|
|
165
164
|
*/
|
|
166
165
|
const paragraphMeasureCache = /* @__PURE__ */ new Map();
|
|
167
|
-
/**
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const marker = attrs.listMarkerFormatting;
|
|
191
|
-
parts.push(`marker:${attrs.listMarker}|${attrs.listMarkerHidden}|${attrs.listMarkerAlignment}|${attrs.listMarkerSuffix}|${marker?.fontFamily}|${marker?.eastAsiaFontFamily}|${marker?.complexScriptFontFamily}|${marker?.fontSize}|${marker?.complexScriptFontSize}|${marker?.bold}|${marker?.complexScriptBold}|${marker?.italic}|${marker?.complexScriptItalic}|${marker?.rtl}|${marker?.forceComplexScript}`);
|
|
192
|
-
}
|
|
193
|
-
parts.push(...lineBreakPolicyCacheParts(attrs));
|
|
194
|
-
const borders = attrs.borders;
|
|
195
|
-
if (borders) {
|
|
196
|
-
const signature = (border) => border ? `${border.width ?? ""},${border.style ?? ""},${border.color ?? ""}` : "";
|
|
197
|
-
parts.push(`bdr:${signature(borders.top)}|${signature(borders.bottom)}|${signature(borders.left)}|${signature(borders.right)}`);
|
|
198
|
-
}
|
|
166
|
+
/** Keep image payloads bounded while classifying every field as measured or intentionally ignored. */
|
|
167
|
+
const imageMeasureCacheInput = (run) => ({
|
|
168
|
+
kind: run.kind,
|
|
169
|
+
width: run.width,
|
|
170
|
+
height: run.height,
|
|
171
|
+
transform: run.transform,
|
|
172
|
+
wrapType: run.wrapType,
|
|
173
|
+
displayMode: run.displayMode,
|
|
174
|
+
distTop: run.distTop,
|
|
175
|
+
distBottom: run.distBottom,
|
|
176
|
+
exactLineHeight: run.exactLineHeight,
|
|
177
|
+
positioned: run.position !== void 0
|
|
178
|
+
});
|
|
179
|
+
const runMeasureCacheInput = (run) => {
|
|
180
|
+
switch (run.kind) {
|
|
181
|
+
case "text":
|
|
182
|
+
case "tab":
|
|
183
|
+
case "lineBreak":
|
|
184
|
+
case "renderedPageBreak":
|
|
185
|
+
case "field":
|
|
186
|
+
case "math": return run;
|
|
187
|
+
case "image": return imageMeasureCacheInput(run);
|
|
188
|
+
default: return run;
|
|
199
189
|
}
|
|
200
|
-
|
|
190
|
+
};
|
|
191
|
+
/** Serialize the complete paragraph measurement contract into a cache key. */
|
|
192
|
+
function hashParagraphBlock(block) {
|
|
193
|
+
return JSON.stringify({
|
|
194
|
+
lineBreakProviderGeneration: getLineBreakProviderGeneration(),
|
|
195
|
+
attrs: block.attrs,
|
|
196
|
+
runs: block.runs.map(runMeasureCacheInput)
|
|
197
|
+
});
|
|
201
198
|
}
|
|
202
199
|
/**
|
|
203
200
|
* Evict oldest entries if paragraph cache exceeds max size
|
|
@@ -1454,9 +1454,10 @@ function renderParagraphFragment(fragment, block, measure, context, options = {}
|
|
|
1454
1454
|
});
|
|
1455
1455
|
const isFlexLine = lineEl.dataset["flexLine"] === "true";
|
|
1456
1456
|
const lineMarginLeft = Math.min(indentLeft, 0) + lineLeftOffset;
|
|
1457
|
+
const lineMarginRight = Math.min(indentRight, 0) + lineRightOffset;
|
|
1457
1458
|
if (lineMarginLeft !== 0 || lineRightOffset > 0 || indentLeft < 0 || indentRight < 0) {
|
|
1458
1459
|
lineEl.style.marginLeft = `${lineMarginLeft}px`;
|
|
1459
|
-
if (
|
|
1460
|
+
if (lineMarginRight !== 0) lineEl.style.marginRight = `${lineMarginRight}px`;
|
|
1460
1461
|
const constrainedWidth = lineAvailableWidth - lineLeftOffset - lineRightOffset;
|
|
1461
1462
|
if (constrainedWidth > 0) lineEl.style.width = `${constrainedWidth}px`;
|
|
1462
1463
|
}
|
|
@@ -1476,27 +1477,33 @@ function renderParagraphFragment(fragment, block, measure, context, options = {}
|
|
|
1476
1477
|
if (isFirstLine && block.attrs?.listMarker && !block.attrs.listMarkerHidden) {
|
|
1477
1478
|
const hanging = indent?.hanging ?? 0;
|
|
1478
1479
|
const firstLine = indent?.firstLine ?? 0;
|
|
1479
|
-
const
|
|
1480
|
-
|
|
1480
|
+
const markerPhysicalStart = isRtl ? "right" : "left";
|
|
1481
|
+
const markerIndent = markerPhysicalStart === "right" ? indentRight : indentLeft;
|
|
1482
|
+
const markerStart = hanging > 0 ? markerIndent - hanging : markerIndent + firstLine;
|
|
1483
|
+
const logicalMarkerVisualOffset = getListMarkerVisualOffset(block);
|
|
1484
|
+
if (markerPhysicalStart === "right") lineEl.style.paddingRight = `${Math.max(0, markerStart)}px`;
|
|
1485
|
+
else lineEl.style.paddingLeft = `${Math.max(0, markerStart)}px`;
|
|
1481
1486
|
lineEl.style.textIndent = "0";
|
|
1482
|
-
const marker = renderListMarker(
|
|
1483
|
-
|
|
1484
|
-
|
|
1487
|
+
const marker = renderListMarker({
|
|
1488
|
+
marker: block.attrs.listMarker,
|
|
1489
|
+
inlineWidth: getListMarkerInlineWidth(block),
|
|
1490
|
+
visualOffset: markerPhysicalStart === "right" ? -logicalMarkerVisualOffset : logicalMarkerVisualOffset,
|
|
1491
|
+
doc,
|
|
1492
|
+
formatting: resolveListMarkerFont(block),
|
|
1493
|
+
...block.attrs.listMarkerRevision === void 0 ? {} : { revision: block.attrs.listMarkerRevision },
|
|
1494
|
+
...block.attrs.listMarkerSecondSlotOffsetTwips === void 0 ? {} : { secondSlotOffsetTwips: block.attrs.listMarkerSecondSlotOffsetTwips },
|
|
1495
|
+
physicalStart: markerPhysicalStart
|
|
1496
|
+
});
|
|
1497
|
+
const markerMarginLeft = markerStart - (markerPhysicalStart === "right" ? Math.min(indentRight, 0) : Math.min(indentLeft, 0));
|
|
1498
|
+
if (markerMarginLeft < 0) if (markerPhysicalStart === "right") marker.style.marginRight = `${markerMarginLeft}px`;
|
|
1499
|
+
else marker.style.marginLeft = `${markerMarginLeft}px`;
|
|
1485
1500
|
lineEl.prepend(marker);
|
|
1486
1501
|
}
|
|
1487
1502
|
fragmentEl.append(lineEl);
|
|
1488
1503
|
}
|
|
1489
1504
|
return fragmentEl;
|
|
1490
1505
|
}
|
|
1491
|
-
|
|
1492
|
-
* Render a list marker element as an inline-block at the start of the first
|
|
1493
|
-
* body line. `inlineWidth` (from `getListMarkerInlineWidth`) sizes the marker
|
|
1494
|
-
* so the body text aligns at the next tab stop per ECMA-376 §17.9.25 —
|
|
1495
|
-
* this honours `w:suff` (`tab` / `space` / `nothing`) and the document's
|
|
1496
|
-
* tab grid. Long markers like "1.1.1." therefore grow to the next stop
|
|
1497
|
-
* instead of butting against the body text.
|
|
1498
|
-
*/
|
|
1499
|
-
function renderListMarker(marker, inlineWidth, visualOffset, doc, formatting, revision, secondSlotOffsetTwips) {
|
|
1506
|
+
function renderListMarker({ marker, inlineWidth, visualOffset, doc, formatting, revision, secondSlotOffsetTwips, physicalStart }) {
|
|
1500
1507
|
const span = doc.createElement("span");
|
|
1501
1508
|
span.className = "layout-list-marker";
|
|
1502
1509
|
span.style.display = "inline-block";
|
|
@@ -1505,8 +1512,8 @@ function renderListMarker(marker, inlineWidth, visualOffset, doc, formatting, re
|
|
|
1505
1512
|
if (formatting.bold !== void 0) span.style.fontWeight = formatting.bold ? "700" : "normal";
|
|
1506
1513
|
if (formatting.italic !== void 0) span.style.fontStyle = formatting.italic ? "italic" : "normal";
|
|
1507
1514
|
if (formatting.rtl !== void 0) span.dir = formatting.rtl ? "rtl" : "ltr";
|
|
1508
|
-
span.style.textAlign =
|
|
1509
|
-
span.style.textAlignLast =
|
|
1515
|
+
span.style.textAlign = physicalStart;
|
|
1516
|
+
span.style.textAlignLast = physicalStart;
|
|
1510
1517
|
span.style.boxSizing = "border-box";
|
|
1511
1518
|
span.style.width = `${inlineWidth}px`;
|
|
1512
1519
|
if (visualOffset !== 0) span.style.transform = `translateX(${visualOffset}px)`;
|
|
@@ -1540,7 +1547,8 @@ function renderListMarker(marker, inlineWidth, visualOffset, doc, formatting, re
|
|
|
1540
1547
|
secondSlot.style.display = "inline-block";
|
|
1541
1548
|
span.style.position = "relative";
|
|
1542
1549
|
secondSlot.style.position = "absolute";
|
|
1543
|
-
secondSlot.style.
|
|
1550
|
+
if (physicalStart === "right") secondSlot.style.right = `${secondSlotOffsetTwips / 15}px`;
|
|
1551
|
+
else secondSlot.style.left = `${secondSlotOffsetTwips / 15}px`;
|
|
1544
1552
|
secondSlot.style.top = "0";
|
|
1545
1553
|
span.append(firstSlot, secondSlot);
|
|
1546
1554
|
return span;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stll/folio-core",
|
|
3
|
-
"version": "0.25.
|
|
3
|
+
"version": "0.25.4",
|
|
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",
|