@stll/folio-core 0.8.0 → 0.10.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 +73 -6
- package/dist/ai-edits/headless.js +299 -60
- 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 +13 -1
- package/dist/document-operations.d.ts +20 -4
- package/dist/document-operations.js +36 -15
- package/dist/document-stories.d.ts +10 -0
- package/dist/document-stories.js +27 -0
- 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/corePropertiesParser.d.ts +8 -0
- package/dist/docx/corePropertiesParser.js +53 -0
- 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/index.d.ts +2 -1
- package/dist/docx/index.js +2 -1
- package/dist/docx/metadataPrivacy.d.ts +40 -0
- package/dist/docx/metadataPrivacy.js +131 -0
- 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 +16 -10
- package/dist/docx/rezip.d.ts +5 -4
- package/dist/docx/rezip.js +76 -13
- 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 +52 -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 +6 -3
- package/dist/layout-engine/measure/effectiveLineBreakPolicy.d.ts +29 -0
- package/dist/layout-engine/measure/effectiveLineBreakPolicy.js +60 -0
- 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 +332 -73
- 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 +51 -37
- 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 +43 -8
- 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 +22 -2
- package/dist/prosemirror/commands/propertyChangeScope.js +1 -1
- package/dist/prosemirror/conversion/effectiveTableCellFormatting.d.ts +62 -0
- package/dist/prosemirror/conversion/effectiveTableCellFormatting.js +131 -0
- package/dist/prosemirror/conversion/fromProseDoc.js +34 -18
- package/dist/prosemirror/conversion/toProseDoc.js +81 -77
- 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 +3 -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 +15 -12
- 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.d.ts +26 -11
- package/dist/redline.js +107 -64
- package/dist/server.d.ts +9 -5
- package/dist/server.js +8 -4
- 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/dist/version-comparison.d.ts +65 -11
- package/dist/version-comparison.js +187 -29
- package/package.json +5 -1
- package/dist/docx/capabilities.d.ts +0 -41
- package/dist/docx/capabilities.js +0 -322
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { getLineBreakProviderGeneration } from "./lineBreakProvider.js";
|
|
2
|
+
import { lineBreakPolicyCacheParts } from "./effectiveLineBreakPolicy.js";
|
|
1
3
|
//#region src/layout-engine/measure/cache.ts
|
|
2
4
|
/**
|
|
3
5
|
* Current max size for text width cache
|
|
@@ -152,10 +154,10 @@ const paragraphMeasureCache = /* @__PURE__ */ new Map();
|
|
|
152
154
|
* Used as cache key to identify identical content
|
|
153
155
|
*/
|
|
154
156
|
function hashParagraphBlock(block) {
|
|
155
|
-
const parts = [];
|
|
156
|
-
for (const run of block.runs) if (run.kind === "text") parts.push(`t:${run.text}|${run.fontFamily}|${run.eastAsiaFontFamily}|${run.fontSize}|${run.bold}|${run.italic}|${run.allCaps}|${run.smallCaps}|${run.horizontalScale}|${run.letterSpacing}`);
|
|
157
|
+
const parts = [`lbp:${getLineBreakProviderGeneration()}`];
|
|
158
|
+
for (const run of block.runs) if (run.kind === "text") parts.push(`t:${run.text}|${run.fontFamily}|${run.eastAsiaFontFamily}|${run.fontSize}|${run.bold}|${run.italic}|${run.allCaps}|${run.smallCaps}|${run.horizontalScale}|${run.letterSpacing}|${run.language?.val}|${run.language?.eastAsia}|${run.language?.bidi}`);
|
|
157
159
|
else if (run.kind === "tab") parts.push(`tab:${run.width}`);
|
|
158
|
-
else if (run.kind === "image") parts.push(`img:${run.width}x${run.height}`);
|
|
160
|
+
else if (run.kind === "image") parts.push(`img:${run.width}x${run.height}:${run.exactLineHeight === true ? "exact" : "text"}`);
|
|
159
161
|
else if (run.kind === "lineBreak") parts.push("br");
|
|
160
162
|
const attrs = block.attrs;
|
|
161
163
|
if (attrs) {
|
|
@@ -167,6 +169,7 @@ function hashParagraphBlock(block) {
|
|
|
167
169
|
if (attrs.defaultFontFamily != null) parts.push(`dff:${attrs.defaultFontFamily}`);
|
|
168
170
|
if (attrs.suppressEmptyParagraphHeight) parts.push("sup");
|
|
169
171
|
if (attrs.reserveEmptyOutlineHeight) parts.push("outline-empty-reserve");
|
|
172
|
+
parts.push(...lineBreakPolicyCacheParts(attrs));
|
|
170
173
|
const borders = attrs.borders;
|
|
171
174
|
if (borders) {
|
|
172
175
|
const signature = (border) => border ? `${border.width ?? ""},${border.style ?? ""},${border.color ?? ""}` : "";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ParagraphAttrs, TextRun } from "../types.js";
|
|
2
|
+
import { LineBreakPolicy } from "./lineBreakProvider.js";
|
|
3
|
+
|
|
4
|
+
//#region src/layout-engine/measure/effectiveLineBreakPolicy.d.ts
|
|
5
|
+
type EffectiveAutomaticHyphenation = Readonly<{
|
|
6
|
+
type: "disabled";
|
|
7
|
+
}> | Readonly<{
|
|
8
|
+
type: "enabled";
|
|
9
|
+
consecutiveLineLimit: number;
|
|
10
|
+
hyphenationZoneTwips: number;
|
|
11
|
+
}>;
|
|
12
|
+
type EffectiveLineBreakPolicy = Readonly<{
|
|
13
|
+
/** Exact provider inputs; omitted OOXML values stay omitted for custom providers. */provider: Readonly<LineBreakPolicy>; /** `w:overflowPunct` defaults to enabled when omitted. */
|
|
14
|
+
hangingPunctuation: boolean; /** Effective document and paragraph automatic-hyphenation state. */
|
|
15
|
+
automaticHyphenation: EffectiveAutomaticHyphenation;
|
|
16
|
+
}>;
|
|
17
|
+
type ResolveEffectiveLineBreakPolicyOptions = {
|
|
18
|
+
attrs: ParagraphAttrs | undefined;
|
|
19
|
+
run: TextRun;
|
|
20
|
+
};
|
|
21
|
+
/** Resolve all authored line-breaking inputs used while measuring one text run. */
|
|
22
|
+
declare const resolveEffectiveLineBreakPolicy: ({
|
|
23
|
+
attrs,
|
|
24
|
+
run
|
|
25
|
+
}: ResolveEffectiveLineBreakPolicyOptions) => EffectiveLineBreakPolicy;
|
|
26
|
+
/** Cache fragments for every paragraph attribute consumed by the resolver. */
|
|
27
|
+
declare const lineBreakPolicyCacheParts: (attrs: ParagraphAttrs) => readonly string[];
|
|
28
|
+
//#endregion
|
|
29
|
+
export { lineBreakPolicyCacheParts, resolveEffectiveLineBreakPolicy };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { hasCjk } from "../../utils/scriptSegments.js";
|
|
2
|
+
//#region src/layout-engine/measure/effectiveLineBreakPolicy.ts
|
|
3
|
+
const DEFAULT_HYPHENATION_ZONE_TWIPS = 360;
|
|
4
|
+
/** Resolve all authored line-breaking inputs used while measuring one text run. */
|
|
5
|
+
const resolveEffectiveLineBreakPolicy = ({ attrs, run }) => {
|
|
6
|
+
const locale = resolveRunLocale(run);
|
|
7
|
+
const rules = attrs?.lineBreakRules;
|
|
8
|
+
const before = rules?.noLineBreaksBefore;
|
|
9
|
+
const after = rules?.noLineBreaksAfter;
|
|
10
|
+
const automaticHyphenation = attrs?.automaticHyphenation;
|
|
11
|
+
return {
|
|
12
|
+
provider: {
|
|
13
|
+
...locale ? { locale } : {},
|
|
14
|
+
...attrs?.kinsoku !== void 0 ? { kinsoku: attrs.kinsoku } : {},
|
|
15
|
+
...before && languageMatches(before.language, locale) ? { noLineBreaksBefore: before.characters } : {},
|
|
16
|
+
...after && languageMatches(after.language, locale) ? { noLineBreaksAfter: after.characters } : {},
|
|
17
|
+
...rules?.useLegacyEthiopicAmharicRules ? { useLegacyEthiopicAmharicRules: true } : {},
|
|
18
|
+
...automaticHyphenation?.doNotHyphenateCaps !== void 0 ? { doNotHyphenateCaps: automaticHyphenation.doNotHyphenateCaps } : {},
|
|
19
|
+
...run.allCaps === true ? { renderedAllCaps: true } : {}
|
|
20
|
+
},
|
|
21
|
+
hangingPunctuation: attrs?.overflowPunctuation !== false,
|
|
22
|
+
automaticHyphenation: resolveAutomaticHyphenation(attrs)
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
/** Cache fragments for every paragraph attribute consumed by the resolver. */
|
|
26
|
+
const lineBreakPolicyCacheParts = (attrs) => {
|
|
27
|
+
const parts = [];
|
|
28
|
+
if (attrs.kinsoku !== void 0) parts.push(`kinsoku:${attrs.kinsoku}`);
|
|
29
|
+
if (attrs.overflowPunctuation !== void 0) parts.push(`overflow-punct:${attrs.overflowPunctuation}`);
|
|
30
|
+
if (attrs.suppressAutoHyphens !== void 0) parts.push(`suppress-auto-hyphens:${attrs.suppressAutoHyphens}`);
|
|
31
|
+
const automaticHyphenation = attrs.automaticHyphenation;
|
|
32
|
+
if (automaticHyphenation) parts.push(`auto-hyphens:${automaticHyphenation.doNotHyphenateCaps}|${automaticHyphenation.consecutiveLineLimit}|${automaticHyphenation.hyphenationZoneTwips}`);
|
|
33
|
+
const rules = attrs.lineBreakRules;
|
|
34
|
+
if (rules) parts.push(`line-break-rules:${rules.noLineBreaksBefore?.language}|${rules.noLineBreaksBefore?.characters}|${rules.noLineBreaksAfter?.language}|${rules.noLineBreaksAfter?.characters}|${rules.useLegacyEthiopicAmharicRules}`);
|
|
35
|
+
return parts;
|
|
36
|
+
};
|
|
37
|
+
const resolveAutomaticHyphenation = (attrs) => {
|
|
38
|
+
const automaticHyphenation = attrs?.automaticHyphenation;
|
|
39
|
+
if (automaticHyphenation?.enabled !== true || attrs?.suppressAutoHyphens === true) return { type: "disabled" };
|
|
40
|
+
return {
|
|
41
|
+
type: "enabled",
|
|
42
|
+
consecutiveLineLimit: automaticHyphenation.consecutiveLineLimit ?? 0,
|
|
43
|
+
hyphenationZoneTwips: automaticHyphenation.hyphenationZoneTwips ?? DEFAULT_HYPHENATION_ZONE_TWIPS
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
const resolveRunLocale = (run) => {
|
|
47
|
+
const language = run.language;
|
|
48
|
+
if (hasCjk(run.text)) return language?.eastAsia ?? language?.val;
|
|
49
|
+
if (run.rtl) return language?.bidi ?? language?.val;
|
|
50
|
+
return language?.val;
|
|
51
|
+
};
|
|
52
|
+
const languageMatches = (ruleLanguage, locale) => {
|
|
53
|
+
if (!ruleLanguage) return true;
|
|
54
|
+
if (!locale) return false;
|
|
55
|
+
const rule = ruleLanguage.toLowerCase();
|
|
56
|
+
const active = locale.toLowerCase();
|
|
57
|
+
return active === rule || active.startsWith(`${rule}-`) || rule.startsWith(`${active}-`);
|
|
58
|
+
};
|
|
59
|
+
//#endregion
|
|
60
|
+
export { lineBreakPolicyCacheParts, resolveEffectiveLineBreakPolicy };
|
|
@@ -44,6 +44,7 @@ function isFontFingerprintMatch(context, expectedWidth) {
|
|
|
44
44
|
function measureEntry(entry) {
|
|
45
45
|
const context = getCtx();
|
|
46
46
|
context.font = entry.font;
|
|
47
|
+
context.fontKerning = entry.fontKerning;
|
|
47
48
|
if (!isFontFingerprintMatch(context, entry.fontFingerprintWidth)) return null;
|
|
48
49
|
let width = context.measureText(entry.text).width;
|
|
49
50
|
const codePoints = countCodePoints(entry.text);
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { FontMetrics, FontStyle, RunMeasurement, TextMeasurement } from "./measureTypes.js";
|
|
2
2
|
import { MeasureProvider, getFontMetrics, getMeasureProvider, measureRun, measureText, measureTextWidth, resetMeasureProvider, setMeasureProvider } from "./measureProvider.js";
|
|
3
3
|
import { buildFontString, findCharacterAtX, getXForCharacter, halfPtToPx, ptToPx, pxToHalfPt, pxToPt, pxToTwips, twipsToPx } from "./measureHelpers.js";
|
|
4
|
+
import { LineBreakPolicy, LineBreakProvider, defaultLineBreakProvider, getLineBreakProvider, getLineBreakProviderGeneration, resetLineBreakProvider, setLineBreakProvider } from "./lineBreakProvider.js";
|
|
4
5
|
import { FloatingExclusionRect, FloatingImageZone, FloatingLineMargins, FloatingLineSegmentZone, WrapTextDirection, getFloatingAvailableWidth, getFloatingMargins, rectsToFloatingZones } from "./floatingZones.js";
|
|
5
6
|
import { clampFloatingWrapMargins } from "./clampFloatingWrapMargins.js";
|
|
6
7
|
import { MeasureParagraphOptions, getRunCharWidths, measureParagraph, measureParagraphs } from "./measureParagraph.js";
|
|
7
8
|
import { clearAllCaches, clearFontMetricsCache, clearParagraphMeasureCache, clearTextWidthCache, getCachedFontMetrics, getCachedParagraphMeasure, getCachedTextWidth, getFontCacheSize, getParagraphCacheSize, getTextCacheSize, getTotalCacheSize, hashParagraphBlock, setCachedFontMetrics, setCachedParagraphMeasure, setCachedTextWidth, setFontCacheSize, setParagraphCacheSize, setTextCacheSize } from "./cache.js";
|
|
8
9
|
import { FolioMeasurementFeatureFlags, isWorkerFontMetricsEnabled, setFolioMeasurementFlags } from "./featureFlags.js";
|
|
9
|
-
export { type FloatingExclusionRect, type FloatingImageZone, type FloatingLineMargins, type FloatingLineSegmentZone, type FolioMeasurementFeatureFlags, type FontMetrics, type FontStyle, type MeasureParagraphOptions, type MeasureProvider, type RunMeasurement, type TextMeasurement, type WrapTextDirection, buildFontString, clampFloatingWrapMargins, clearAllCaches, clearFontMetricsCache, clearParagraphMeasureCache, clearTextWidthCache, findCharacterAtX, getCachedFontMetrics, getCachedParagraphMeasure, getCachedTextWidth, getFloatingAvailableWidth, getFloatingMargins, getFontCacheSize, getFontMetrics, getMeasureProvider, getParagraphCacheSize, getRunCharWidths, getTextCacheSize, getTotalCacheSize, getXForCharacter, halfPtToPx, hashParagraphBlock, isWorkerFontMetricsEnabled, measureParagraph, measureParagraphs, measureRun, measureText, measureTextWidth, ptToPx, pxToHalfPt, pxToPt, pxToTwips, rectsToFloatingZones, resetMeasureProvider, setCachedFontMetrics, setCachedParagraphMeasure, setCachedTextWidth, setFolioMeasurementFlags, setFontCacheSize, setMeasureProvider, setParagraphCacheSize, setTextCacheSize, twipsToPx };
|
|
10
|
+
export { type FloatingExclusionRect, type FloatingImageZone, type FloatingLineMargins, type FloatingLineSegmentZone, type FolioMeasurementFeatureFlags, type FontMetrics, type FontStyle, type LineBreakPolicy, type LineBreakProvider, type MeasureParagraphOptions, type MeasureProvider, type RunMeasurement, type TextMeasurement, type WrapTextDirection, buildFontString, clampFloatingWrapMargins, clearAllCaches, clearFontMetricsCache, clearParagraphMeasureCache, clearTextWidthCache, defaultLineBreakProvider, findCharacterAtX, getCachedFontMetrics, getCachedParagraphMeasure, getCachedTextWidth, getFloatingAvailableWidth, getFloatingMargins, getFontCacheSize, getFontMetrics, getLineBreakProvider, getLineBreakProviderGeneration, getMeasureProvider, getParagraphCacheSize, getRunCharWidths, getTextCacheSize, getTotalCacheSize, getXForCharacter, halfPtToPx, hashParagraphBlock, isWorkerFontMetricsEnabled, measureParagraph, measureParagraphs, measureRun, measureText, measureTextWidth, ptToPx, pxToHalfPt, pxToPt, pxToTwips, rectsToFloatingZones, resetLineBreakProvider, resetMeasureProvider, setCachedFontMetrics, setCachedParagraphMeasure, setCachedTextWidth, setFolioMeasurementFlags, setFontCacheSize, setLineBreakProvider, setMeasureProvider, setParagraphCacheSize, setTextCacheSize, twipsToPx };
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { getFontMetrics, getMeasureProvider, measureRun, measureText, measureTextWidth, resetMeasureProvider, setMeasureProvider } from "./measureProvider.js";
|
|
2
2
|
import { buildFontString, findCharacterAtX, getXForCharacter, halfPtToPx, ptToPx, pxToHalfPt, pxToPt, pxToTwips, twipsToPx } from "./measureHelpers.js";
|
|
3
|
+
import { defaultLineBreakProvider, getLineBreakProvider, getLineBreakProviderGeneration, resetLineBreakProvider, setLineBreakProvider } from "./lineBreakProvider.js";
|
|
3
4
|
import { clampFloatingWrapMargins } from "./clampFloatingWrapMargins.js";
|
|
4
5
|
import { getFloatingAvailableWidth, getFloatingMargins, rectsToFloatingZones } from "./floatingZones.js";
|
|
5
6
|
import { getRunCharWidths, measureParagraph, measureParagraphs } from "./measureParagraph.js";
|
|
6
7
|
import { clearAllCaches, clearFontMetricsCache, clearParagraphMeasureCache, clearTextWidthCache, getCachedFontMetrics, getCachedParagraphMeasure, getCachedTextWidth, getFontCacheSize, getParagraphCacheSize, getTextCacheSize, getTotalCacheSize, hashParagraphBlock, setCachedFontMetrics, setCachedParagraphMeasure, setCachedTextWidth, setFontCacheSize, setParagraphCacheSize, setTextCacheSize } from "./cache.js";
|
|
7
8
|
import { isWorkerFontMetricsEnabled, setFolioMeasurementFlags } from "./featureFlags.js";
|
|
8
|
-
export { buildFontString, clampFloatingWrapMargins, clearAllCaches, clearFontMetricsCache, clearParagraphMeasureCache, clearTextWidthCache, findCharacterAtX, getCachedFontMetrics, getCachedParagraphMeasure, getCachedTextWidth, getFloatingAvailableWidth, getFloatingMargins, getFontCacheSize, getFontMetrics, getMeasureProvider, getParagraphCacheSize, getRunCharWidths, getTextCacheSize, getTotalCacheSize, getXForCharacter, halfPtToPx, hashParagraphBlock, isWorkerFontMetricsEnabled, measureParagraph, measureParagraphs, measureRun, measureText, measureTextWidth, ptToPx, pxToHalfPt, pxToPt, pxToTwips, rectsToFloatingZones, resetMeasureProvider, setCachedFontMetrics, setCachedParagraphMeasure, setCachedTextWidth, setFolioMeasurementFlags, setFontCacheSize, setMeasureProvider, setParagraphCacheSize, setTextCacheSize, twipsToPx };
|
|
9
|
+
export { buildFontString, clampFloatingWrapMargins, clearAllCaches, clearFontMetricsCache, clearParagraphMeasureCache, clearTextWidthCache, defaultLineBreakProvider, findCharacterAtX, getCachedFontMetrics, getCachedParagraphMeasure, getCachedTextWidth, getFloatingAvailableWidth, getFloatingMargins, getFontCacheSize, getFontMetrics, getLineBreakProvider, getLineBreakProviderGeneration, getMeasureProvider, getParagraphCacheSize, getRunCharWidths, getTextCacheSize, getTotalCacheSize, getXForCharacter, halfPtToPx, hashParagraphBlock, isWorkerFontMetricsEnabled, measureParagraph, measureParagraphs, measureRun, measureText, measureTextWidth, ptToPx, pxToHalfPt, pxToPt, pxToTwips, rectsToFloatingZones, resetLineBreakProvider, resetMeasureProvider, setCachedFontMetrics, setCachedParagraphMeasure, setCachedTextWidth, setFolioMeasurementFlags, setFontCacheSize, setLineBreakProvider, setMeasureProvider, setParagraphCacheSize, setTextCacheSize, twipsToPx };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
//#region src/layout-engine/measure/lineBreakProvider.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Replaceable line-break policy for the layout engine.
|
|
4
|
+
*
|
|
5
|
+
* Providers return UTF-16 offsets so their output composes directly with the
|
|
6
|
+
* run and ProseMirror positions used by the rest of the layout pipeline. The
|
|
7
|
+
* default provider uses the platform's Unicode segmenter, then applies the
|
|
8
|
+
* OOXML line-edge restrictions that are available in the flow model.
|
|
9
|
+
*/
|
|
10
|
+
type LineBreakPolicy = {
|
|
11
|
+
/** BCP-47 language tag resolved from `w:lang` for this run. */locale?: string; /** Apply East Asian first/last-character restrictions (`w:kinsoku`). */
|
|
12
|
+
kinsoku?: boolean; /** Document replacement list: characters that may not begin a line. */
|
|
13
|
+
noLineBreaksBefore?: string; /** Document replacement list: characters that may not end a line. */
|
|
14
|
+
noLineBreaksAfter?: string; /** Use the legacy Ethiopic/Amharic compatibility behavior. */
|
|
15
|
+
useLegacyEthiopicAmharicRules?: boolean; /** Keep words made entirely of capital letters unhyphenated. */
|
|
16
|
+
doNotHyphenateCaps?: boolean; /** The run renders with the DOCX all-caps transform. */
|
|
17
|
+
renderedAllCaps?: boolean;
|
|
18
|
+
};
|
|
19
|
+
type LineBreakProvider = {
|
|
20
|
+
/** Legal soft-wrap offsets, in ascending UTF-16 order. */findBreaks: (text: string, policy?: LineBreakPolicy) => number[]; /** Grapheme-safe emergency-wrap offsets, in ascending UTF-16 order. */
|
|
21
|
+
findGraphemeBreaks: (text: string, policy?: LineBreakPolicy) => number[]; /** Dictionary-based discretionary hyphen offsets, in ascending UTF-16 order. */
|
|
22
|
+
findHyphenationBreaks?: (text: string, policy?: LineBreakPolicy) => number[]; /** Whether a trailing grapheme may overhang the line edge. */
|
|
23
|
+
isHangingPunctuation?: (text: string, policy?: LineBreakPolicy) => boolean;
|
|
24
|
+
};
|
|
25
|
+
declare const MAX_HYPHENATION_WORD_LENGTH = 256;
|
|
26
|
+
declare const defaultLineBreakProvider: LineBreakProvider;
|
|
27
|
+
declare const getLineBreakProvider: () => LineBreakProvider;
|
|
28
|
+
/** Changes whenever the active provider changes, so layout caches cannot go stale. */
|
|
29
|
+
declare const getLineBreakProviderGeneration: () => number;
|
|
30
|
+
declare const setLineBreakProvider: (provider: LineBreakProvider) => void;
|
|
31
|
+
declare const resetLineBreakProvider: () => void;
|
|
32
|
+
//#endregion
|
|
33
|
+
export { LineBreakPolicy, LineBreakProvider, MAX_HYPHENATION_WORD_LENGTH, defaultLineBreakProvider, getLineBreakProvider, getLineBreakProviderGeneration, resetLineBreakProvider, setLineBreakProvider };
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import czechHyphenation from "hyphen/cs/index.js";
|
|
2
|
+
import britishEnglishHyphenation from "hyphen/en-gb/index.js";
|
|
3
|
+
import americanEnglishHyphenation from "hyphen/en-us/index.js";
|
|
4
|
+
import slovakHyphenation from "hyphen/sk/index.js";
|
|
5
|
+
//#region src/layout-engine/measure/lineBreakProvider.ts
|
|
6
|
+
/**
|
|
7
|
+
* Replaceable line-break policy for the layout engine.
|
|
8
|
+
*
|
|
9
|
+
* Providers return UTF-16 offsets so their output composes directly with the
|
|
10
|
+
* run and ProseMirror positions used by the rest of the layout pipeline. The
|
|
11
|
+
* default provider uses the platform's Unicode segmenter, then applies the
|
|
12
|
+
* OOXML line-edge restrictions that are available in the flow model.
|
|
13
|
+
*/
|
|
14
|
+
const { hyphenateSync: hyphenateCzech } = czechHyphenation;
|
|
15
|
+
const { hyphenateSync: hyphenateBritishEnglish } = britishEnglishHyphenation;
|
|
16
|
+
const { hyphenateSync: hyphenateAmericanEnglish } = americanEnglishHyphenation;
|
|
17
|
+
const { hyphenateSync: hyphenateSlovak } = slovakHyphenation;
|
|
18
|
+
const SEGMENTER_CACHE_LIMIT = 16;
|
|
19
|
+
const DEFAULT_SEGMENTER_KEY = "";
|
|
20
|
+
const SOFT_HYPHEN = "";
|
|
21
|
+
const MAX_HYPHENATION_WORD_LENGTH = 256;
|
|
22
|
+
const wordSegmenters = /* @__PURE__ */ new Map();
|
|
23
|
+
const graphemeSegmenters = /* @__PURE__ */ new Map();
|
|
24
|
+
const segmenterLocale = (locale) => {
|
|
25
|
+
const language = locale?.trim().replaceAll("_", "-").split("-").at(0);
|
|
26
|
+
return language && /^[a-z]{2,3}$/iu.test(language) ? language.toLowerCase() : void 0;
|
|
27
|
+
};
|
|
28
|
+
const DICTIONARY_BREAK_SCRIPT = /[\p{Script=Thai}\p{Script=Lao}\p{Script=Khmer}\p{Script=Myanmar}]/u;
|
|
29
|
+
const CJK_SCRIPT = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/u;
|
|
30
|
+
const BREAK_AFTER_CHARACTER = /* @__PURE__ */ new Set([
|
|
31
|
+
"-",
|
|
32
|
+
"֊",
|
|
33
|
+
"־",
|
|
34
|
+
"᐀",
|
|
35
|
+
"᠆",
|
|
36
|
+
"‐",
|
|
37
|
+
"⸗",
|
|
38
|
+
"⹀",
|
|
39
|
+
"゠",
|
|
40
|
+
"﹣",
|
|
41
|
+
"-",
|
|
42
|
+
"",
|
|
43
|
+
""
|
|
44
|
+
]);
|
|
45
|
+
const CZECH_ONE_LETTER_PREPOSITIONS = /* @__PURE__ */ new Set([
|
|
46
|
+
"k",
|
|
47
|
+
"o",
|
|
48
|
+
"s",
|
|
49
|
+
"u",
|
|
50
|
+
"v",
|
|
51
|
+
"z"
|
|
52
|
+
]);
|
|
53
|
+
const DEFAULT_PROHIBITED_LINE_START = /* @__PURE__ */ new Set([
|
|
54
|
+
"!",
|
|
55
|
+
"%",
|
|
56
|
+
")",
|
|
57
|
+
",",
|
|
58
|
+
".",
|
|
59
|
+
":",
|
|
60
|
+
";",
|
|
61
|
+
"?",
|
|
62
|
+
"]",
|
|
63
|
+
"}",
|
|
64
|
+
"、",
|
|
65
|
+
"。",
|
|
66
|
+
"〉",
|
|
67
|
+
"》",
|
|
68
|
+
"」",
|
|
69
|
+
"』",
|
|
70
|
+
"】",
|
|
71
|
+
"〕",
|
|
72
|
+
")",
|
|
73
|
+
"]",
|
|
74
|
+
"}",
|
|
75
|
+
",",
|
|
76
|
+
".",
|
|
77
|
+
":",
|
|
78
|
+
";",
|
|
79
|
+
"!",
|
|
80
|
+
"?",
|
|
81
|
+
"…",
|
|
82
|
+
"’",
|
|
83
|
+
"”"
|
|
84
|
+
]);
|
|
85
|
+
const DEFAULT_PROHIBITED_LINE_END = /* @__PURE__ */ new Set([
|
|
86
|
+
"$",
|
|
87
|
+
"(",
|
|
88
|
+
"[",
|
|
89
|
+
"{",
|
|
90
|
+
"£",
|
|
91
|
+
"¥",
|
|
92
|
+
"〈",
|
|
93
|
+
"《",
|
|
94
|
+
"「",
|
|
95
|
+
"『",
|
|
96
|
+
"【",
|
|
97
|
+
"〔",
|
|
98
|
+
"(",
|
|
99
|
+
"[",
|
|
100
|
+
"{",
|
|
101
|
+
"‘",
|
|
102
|
+
"“"
|
|
103
|
+
]);
|
|
104
|
+
const getSegmenter = (cache, granularity, locale) => {
|
|
105
|
+
const normalizedLocale = segmenterLocale(locale);
|
|
106
|
+
const key = normalizedLocale ?? DEFAULT_SEGMENTER_KEY;
|
|
107
|
+
const cached = cache.get(key);
|
|
108
|
+
if (cached) return cached;
|
|
109
|
+
const segmenter = new Intl.Segmenter(normalizedLocale, { granularity });
|
|
110
|
+
if (cache.size >= SEGMENTER_CACHE_LIMIT) {
|
|
111
|
+
const oldestKey = cache.keys().next().value;
|
|
112
|
+
if (typeof oldestKey === "string") cache.delete(oldestKey);
|
|
113
|
+
}
|
|
114
|
+
cache.set(key, segmenter);
|
|
115
|
+
return segmenter;
|
|
116
|
+
};
|
|
117
|
+
const firstCodePoint = (text, index) => {
|
|
118
|
+
if (index >= text.length) return;
|
|
119
|
+
return String.fromCodePoint(text.codePointAt(index) ?? 0);
|
|
120
|
+
};
|
|
121
|
+
const previousCodePoint = (text, index) => {
|
|
122
|
+
if (index <= 0) return;
|
|
123
|
+
const trailing = text.charCodeAt(index - 1);
|
|
124
|
+
if (trailing >= 56320 && trailing <= 57343 && index >= 2) return text.slice(index - 2, index);
|
|
125
|
+
return text[index - 1];
|
|
126
|
+
};
|
|
127
|
+
const isProhibitedLineStart = (character, policy) => character !== void 0 && policy?.kinsoku !== false && (policy?.noLineBreaksBefore !== void 0 ? policy.noLineBreaksBefore.includes(character) : DEFAULT_PROHIBITED_LINE_START.has(character));
|
|
128
|
+
const isProhibitedLineEnd = (character, policy) => character !== void 0 && policy?.kinsoku !== false && (policy?.noLineBreaksAfter !== void 0 ? policy.noLineBreaksAfter.includes(character) : DEFAULT_PROHIBITED_LINE_END.has(character));
|
|
129
|
+
const isCjkLineBreakParticipant = (character, policy) => character !== void 0 && (CJK_SCRIPT.test(character) || DEFAULT_PROHIBITED_LINE_START.has(character) || DEFAULT_PROHIBITED_LINE_END.has(character) || policy?.noLineBreaksBefore?.includes(character) === true || policy?.noLineBreaksAfter?.includes(character) === true);
|
|
130
|
+
const isLegacyEthiopicBreakCharacter = (character, policy) => {
|
|
131
|
+
if (!policy?.useLegacyEthiopicAmharicRules || character === void 0) return false;
|
|
132
|
+
const codePoint = character.codePointAt(0);
|
|
133
|
+
return codePoint !== void 0 && codePoint >= 4961 && codePoint <= 4968;
|
|
134
|
+
};
|
|
135
|
+
const czechProtectedBreaks = (text, policy) => {
|
|
136
|
+
const breaks = /* @__PURE__ */ new Set();
|
|
137
|
+
if (segmenterLocale(policy?.locale) !== "cs") return breaks;
|
|
138
|
+
let tokenStart = 0;
|
|
139
|
+
let index = 0;
|
|
140
|
+
while (index < text.length) {
|
|
141
|
+
const character = firstCodePoint(text, index);
|
|
142
|
+
if (character === void 0) break;
|
|
143
|
+
if (!/\s/u.test(character)) {
|
|
144
|
+
index += character.length;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const token = text.slice(tokenStart, index);
|
|
148
|
+
const protectsNextWord = token.length === 1 && CZECH_ONE_LETTER_PREPOSITIONS.has(token.toLocaleLowerCase("cs"));
|
|
149
|
+
while (index < text.length) {
|
|
150
|
+
const whitespace = firstCodePoint(text, index);
|
|
151
|
+
if (whitespace === void 0 || !/\s/u.test(whitespace)) break;
|
|
152
|
+
index += whitespace.length;
|
|
153
|
+
if (protectsNextWord) breaks.add(index);
|
|
154
|
+
}
|
|
155
|
+
tokenStart = index;
|
|
156
|
+
}
|
|
157
|
+
return breaks;
|
|
158
|
+
};
|
|
159
|
+
const allowsBreak = (text, index, policy) => {
|
|
160
|
+
const previous = previousCodePoint(text, index);
|
|
161
|
+
const next = firstCodePoint(text, index);
|
|
162
|
+
return !isProhibitedLineEnd(previous, policy) && !isProhibitedLineStart(next, policy);
|
|
163
|
+
};
|
|
164
|
+
const pushBreak = (breaks, text, index, policy) => {
|
|
165
|
+
if (index <= 0 || index > text.length || breaks.at(-1) === index) return;
|
|
166
|
+
if (allowsBreak(text, index, policy)) breaks.push(index);
|
|
167
|
+
};
|
|
168
|
+
const findUnicodeGraphemeBreaks = (text, policy) => {
|
|
169
|
+
const breaks = [];
|
|
170
|
+
for (const segment of getSegmenter(graphemeSegmenters, "grapheme", policy?.locale).segment(text)) {
|
|
171
|
+
const end = segment.index + segment.segment.length;
|
|
172
|
+
breaks.push(end);
|
|
173
|
+
}
|
|
174
|
+
return breaks;
|
|
175
|
+
};
|
|
176
|
+
const findUnicodeBreaks = (text, policy) => {
|
|
177
|
+
if (text.length === 0) return [];
|
|
178
|
+
const breaks = [];
|
|
179
|
+
const graphemeBreaks = findUnicodeGraphemeBreaks(text, policy);
|
|
180
|
+
for (const index of graphemeBreaks) {
|
|
181
|
+
const previous = previousCodePoint(text, index);
|
|
182
|
+
if (previous !== void 0 && (/\s/u.test(previous) || BREAK_AFTER_CHARACTER.has(previous) || isLegacyEthiopicBreakCharacter(previous, policy))) pushBreak(breaks, text, index, policy);
|
|
183
|
+
}
|
|
184
|
+
const wordSegments = [...getSegmenter(wordSegmenters, "word", policy?.locale).segment(text)];
|
|
185
|
+
for (const [segmentIndex, segment] of wordSegments.entries()) {
|
|
186
|
+
const end = segment.index + segment.segment.length;
|
|
187
|
+
const nextSegment = wordSegments[segmentIndex + 1];
|
|
188
|
+
if (segment.isWordLike && nextSegment?.isWordLike === true && DICTIONARY_BREAK_SCRIPT.test(segment.segment + nextSegment.segment)) pushBreak(breaks, text, end, policy);
|
|
189
|
+
}
|
|
190
|
+
if (CJK_SCRIPT.test(text)) for (const index of graphemeBreaks) {
|
|
191
|
+
const previous = previousCodePoint(text, index);
|
|
192
|
+
if (isCjkLineBreakParticipant(previous, policy)) pushBreak(breaks, text, index, policy);
|
|
193
|
+
}
|
|
194
|
+
const protectedBreaks = czechProtectedBreaks(text, policy);
|
|
195
|
+
return [...new Set(breaks)].filter((index) => !protectedBreaks.has(index)).sort((left, right) => left - right);
|
|
196
|
+
};
|
|
197
|
+
const hyphenatorFor = (locale) => {
|
|
198
|
+
const normalized = locale?.trim().replaceAll("_", "-").toLowerCase();
|
|
199
|
+
if (!normalized) return;
|
|
200
|
+
if (normalized === "en-gb" || normalized.startsWith("en-gb-")) return hyphenateBritishEnglish;
|
|
201
|
+
if (normalized === "en-us" || normalized.startsWith("en-us-")) return hyphenateAmericanEnglish;
|
|
202
|
+
if (normalized === "cs" || normalized.startsWith("cs-")) return hyphenateCzech;
|
|
203
|
+
if (normalized === "sk" || normalized.startsWith("sk-")) return hyphenateSlovak;
|
|
204
|
+
};
|
|
205
|
+
const isAllCapsWord = (text, locale) => {
|
|
206
|
+
const letters = [...text].filter((character) => /\p{Letter}/u.test(character)).join("");
|
|
207
|
+
if (letters.length === 0) return false;
|
|
208
|
+
const casingLocale = segmenterLocale(locale);
|
|
209
|
+
return letters === letters.toLocaleUpperCase(casingLocale) && letters !== letters.toLocaleLowerCase(casingLocale);
|
|
210
|
+
};
|
|
211
|
+
const findPatternHyphenationBreaks = (text, policy) => {
|
|
212
|
+
if (text.length > 256 || text.includes(SOFT_HYPHEN)) return [];
|
|
213
|
+
const hyphenate = hyphenatorFor(policy?.locale);
|
|
214
|
+
if (!hyphenate) return [];
|
|
215
|
+
if (policy?.doNotHyphenateCaps && (policy.renderedAllCaps === true || isAllCapsWord(text, policy.locale))) return [];
|
|
216
|
+
const hyphenated = hyphenate(text);
|
|
217
|
+
const breaks = [];
|
|
218
|
+
let sourceOffset = 0;
|
|
219
|
+
for (const character of hyphenated) {
|
|
220
|
+
if (character === SOFT_HYPHEN) {
|
|
221
|
+
breaks.push(sourceOffset);
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
sourceOffset += character.length;
|
|
225
|
+
}
|
|
226
|
+
return sourceOffset === text.length ? breaks : [];
|
|
227
|
+
};
|
|
228
|
+
const isDefaultHangingPunctuation = (text, policy) => {
|
|
229
|
+
const characters = [...text];
|
|
230
|
+
return characters.length > 0 && characters.every((character) => isProhibitedLineStart(character, policy));
|
|
231
|
+
};
|
|
232
|
+
const defaultLineBreakProvider = {
|
|
233
|
+
findBreaks: findUnicodeBreaks,
|
|
234
|
+
findGraphemeBreaks: findUnicodeGraphemeBreaks,
|
|
235
|
+
findHyphenationBreaks: findPatternHyphenationBreaks,
|
|
236
|
+
isHangingPunctuation: isDefaultHangingPunctuation
|
|
237
|
+
};
|
|
238
|
+
let activeLineBreakProvider = defaultLineBreakProvider;
|
|
239
|
+
let lineBreakProviderGeneration = 0;
|
|
240
|
+
const getLineBreakProvider = () => activeLineBreakProvider;
|
|
241
|
+
/** Changes whenever the active provider changes, so layout caches cannot go stale. */
|
|
242
|
+
const getLineBreakProviderGeneration = () => lineBreakProviderGeneration;
|
|
243
|
+
const setLineBreakProvider = (provider) => {
|
|
244
|
+
activeLineBreakProvider = provider;
|
|
245
|
+
lineBreakProviderGeneration += 1;
|
|
246
|
+
};
|
|
247
|
+
const resetLineBreakProvider = () => {
|
|
248
|
+
activeLineBreakProvider = defaultLineBreakProvider;
|
|
249
|
+
lineBreakProviderGeneration += 1;
|
|
250
|
+
};
|
|
251
|
+
//#endregion
|
|
252
|
+
export { MAX_HYPHENATION_WORD_LENGTH, defaultLineBreakProvider, getLineBreakProvider, getLineBreakProviderGeneration, resetLineBreakProvider, setLineBreakProvider };
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
+
import { LineBreakPolicy } from "./lineBreakProvider.js";
|
|
2
|
+
|
|
1
3
|
//#region src/layout-engine/measure/lineBreaks.d.ts
|
|
2
4
|
/**
|
|
3
5
|
* Indices in `text` where a line may end. Latin text breaks after
|
|
4
6
|
* whitespace or hyphen; CJK text can break after every ideograph so a
|
|
5
7
|
* partial line is filled instead of pushing the whole run to the next line.
|
|
6
8
|
*/
|
|
7
|
-
declare
|
|
9
|
+
declare const findWordBreaks: (text: string, policy?: LineBreakPolicy) => number[];
|
|
10
|
+
declare const findGraphemeBreaks: (text: string, policy?: LineBreakPolicy) => number[];
|
|
11
|
+
declare const findHyphenationBreaks: (text: string, policy?: LineBreakPolicy) => number[];
|
|
12
|
+
declare const isHangingPunctuation: (text: string, policy?: LineBreakPolicy) => boolean;
|
|
8
13
|
declare function isBreakChar(char: string | undefined): boolean;
|
|
9
14
|
//#endregion
|
|
10
|
-
export { findWordBreaks, isBreakChar };
|
|
15
|
+
export { findGraphemeBreaks, findHyphenationBreaks, findWordBreaks, isBreakChar, isHangingPunctuation };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { defaultLineBreakProvider, getLineBreakProvider } from "./lineBreakProvider.js";
|
|
1
2
|
import { isCjkCodePoint } from "../../utils/scriptSegments.js";
|
|
2
3
|
//#region src/layout-engine/measure/lineBreaks.ts
|
|
3
4
|
/**
|
|
@@ -5,26 +6,18 @@ import { isCjkCodePoint } from "../../utils/scriptSegments.js";
|
|
|
5
6
|
* whitespace or hyphen; CJK text can break after every ideograph so a
|
|
6
7
|
* partial line is filled instead of pushing the whole run to the next line.
|
|
7
8
|
*/
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const char = text[index];
|
|
15
|
-
if (char === " " || char === "-" || char === " ") breaks.push(index + charLength);
|
|
16
|
-
else if (isCjkCodePoint(codePoint)) breaks.push(index + charLength);
|
|
17
|
-
index += charLength;
|
|
18
|
-
}
|
|
19
|
-
return breaks;
|
|
20
|
-
}
|
|
9
|
+
const findWordBreaks = (text, policy) => getLineBreakProvider().findBreaks(text, policy);
|
|
10
|
+
const findGraphemeBreaks = (text, policy) => getLineBreakProvider().findGraphemeBreaks(text, policy);
|
|
11
|
+
const findHyphenationBreaks = (text, policy) => getLineBreakProvider().findHyphenationBreaks?.(text, policy) ?? [];
|
|
12
|
+
const isHangingPunctuation = (text, policy) => {
|
|
13
|
+
return (getLineBreakProvider().isHangingPunctuation ?? defaultLineBreakProvider.isHangingPunctuation)?.(text, policy) ?? false;
|
|
14
|
+
};
|
|
21
15
|
function isBreakChar(char) {
|
|
22
16
|
if (char === void 0) return false;
|
|
23
|
-
if (char === "
|
|
24
|
-
const codePoint = char.codePointAt(0);
|
|
25
|
-
if (codePoint === void 0) return false;
|
|
17
|
+
if (/\s/u.test(char) || char === "-" || char === "" || char === "") return true;
|
|
26
18
|
if (char >= "\udc00" && char <= "\udfff") return true;
|
|
27
|
-
|
|
19
|
+
const codePoint = char.codePointAt(0);
|
|
20
|
+
return codePoint !== void 0 && isCjkCodePoint(codePoint);
|
|
28
21
|
}
|
|
29
22
|
//#endregion
|
|
30
|
-
export { findWordBreaks, isBreakChar };
|
|
23
|
+
export { findGraphemeBreaks, findHyphenationBreaks, findWordBreaks, isBreakChar, isHangingPunctuation };
|
|
@@ -16,6 +16,7 @@ declare const DEFAULT_TAB_STOP_TWIPS = 720;
|
|
|
16
16
|
declare function resolveListMarkerFont(block: ParagraphBlock): {
|
|
17
17
|
fontFamily: string;
|
|
18
18
|
fontSize: number;
|
|
19
|
+
bold?: boolean;
|
|
19
20
|
};
|
|
20
21
|
/**
|
|
21
22
|
* Compute the marker's inline-block width in pixels, or 0 if the paragraph
|
|
@@ -19,9 +19,13 @@ const TWIPS_TO_PX = 96 / 1440;
|
|
|
19
19
|
function resolveListMarkerFont(block) {
|
|
20
20
|
const attrs = block.attrs;
|
|
21
21
|
const firstTextRun = block.runs.find((r) => r.kind === "text");
|
|
22
|
+
const fontFamily = attrs?.listMarkerFontFamily ?? firstTextRun?.fontFamily ?? attrs?.defaultFontFamily ?? DEFAULT_FONT_FAMILY;
|
|
23
|
+
const fontSize = attrs?.listMarkerFontSize ?? firstTextRun?.fontSize ?? attrs?.defaultFontSize ?? DEFAULT_FONT_SIZE;
|
|
24
|
+
const bold = attrs?.listMarkerBold ?? firstTextRun?.bold;
|
|
22
25
|
return {
|
|
23
|
-
fontFamily
|
|
24
|
-
fontSize
|
|
26
|
+
fontFamily,
|
|
27
|
+
fontSize,
|
|
28
|
+
...bold !== void 0 ? { bold } : {}
|
|
25
29
|
};
|
|
26
30
|
}
|
|
27
31
|
/**
|
|
@@ -45,10 +49,11 @@ function resolveListMarkerFont(block) {
|
|
|
45
49
|
function getListMarkerInlineWidth(block) {
|
|
46
50
|
const attrs = block.attrs;
|
|
47
51
|
if (!attrs?.listMarker || attrs.listMarkerHidden) return 0;
|
|
48
|
-
const { fontFamily, fontSize } = resolveListMarkerFont(block);
|
|
52
|
+
const { fontFamily, fontSize, bold } = resolveListMarkerFont(block);
|
|
49
53
|
const style = {
|
|
50
54
|
fontFamily,
|
|
51
|
-
fontSize
|
|
55
|
+
fontSize,
|
|
56
|
+
...bold !== void 0 ? { bold } : {}
|
|
52
57
|
};
|
|
53
58
|
const naturalWidth = measureTextWidth(attrs.listMarker, style);
|
|
54
59
|
const markerEndOffset = getMarkerEndOffset(naturalWidth, attrs.listMarkerAlignment);
|
|
@@ -77,10 +82,11 @@ function getListMarkerInlineWidth(block) {
|
|
|
77
82
|
function getListMarkerVisualOffset(block) {
|
|
78
83
|
const attrs = block.attrs;
|
|
79
84
|
if (!attrs?.listMarker || attrs.listMarkerHidden) return 0;
|
|
80
|
-
const { fontFamily, fontSize } = resolveListMarkerFont(block);
|
|
85
|
+
const { fontFamily, fontSize, bold } = resolveListMarkerFont(block);
|
|
81
86
|
const naturalWidth = measureTextWidth(attrs.listMarker, {
|
|
82
87
|
fontFamily,
|
|
83
|
-
fontSize
|
|
88
|
+
fontSize,
|
|
89
|
+
...bold !== void 0 ? { bold } : {}
|
|
84
90
|
});
|
|
85
91
|
if (attrs.listMarkerAlignment === "right") return -naturalWidth;
|
|
86
92
|
if (attrs.listMarkerAlignment === "center") return -naturalWidth / 2;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { FlowBlock, Measure, TableBlock, TableMeasure, TextBoxBlock, TextBoxMeasure } from "../types.js";
|
|
2
2
|
import { FloatingImageZone } from "./floatingZones.js";
|
|
3
3
|
//#region src/layout-engine/measure/measureBlocks.d.ts
|
|
4
|
-
declare function measureTableCellBlockVisualHeight(block: FlowBlock, blockMeasure: Measure): number;
|
|
5
4
|
declare function measureTableBlock(tableBlock: TableBlock, contentWidth: number, fieldValues?: ReadonlyMap<number, string>): TableMeasure;
|
|
6
5
|
type BandPageGeometry = {
|
|
7
6
|
pageWidth?: number | number[];
|
|
@@ -25,4 +24,4 @@ declare function measureTextBoxBlock(tb: TextBoxBlock, fieldValues?: ReadonlyMap
|
|
|
25
24
|
declare function measureBlocks(blocks: FlowBlock[], contentWidth: number | number[], marginTop?: number | number[], pageGeometry?: BandPageGeometry, fieldValues?: ReadonlyMap<number, string>): Measure[];
|
|
26
25
|
declare function measureSingleBlockWithoutFloatingZones(block: FlowBlock, blockWidth: number, blockIndex: number): Measure;
|
|
27
26
|
//#endregion
|
|
28
|
-
export { measureBlock, measureBlocks, measureSingleBlockWithoutFloatingZones, measureTableBlock,
|
|
27
|
+
export { measureBlock, measureBlocks, measureSingleBlockWithoutFloatingZones, measureTableBlock, measureTextBoxBlock };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { DEFAULT_TEXTBOX_MARGINS, floatingTextBoxReservesBand, floatingTextBoxWrapsText,
|
|
1
|
+
import { DEFAULT_TEXTBOX_MARGINS, floatingTextBoxReservesBand, floatingTextBoxWrapsText, tableColumnsArePinned } from "../types.js";
|
|
2
2
|
import { findClearLineY, measureParagraph } from "./measureParagraph.js";
|
|
3
3
|
import { getCachedParagraphMeasure, setCachedParagraphMeasure } from "./cache.js";
|
|
4
|
+
import { createTableCellFlowState, finishTableCellFlow, placeTableCellBlock } from "./tableCellFlow.js";
|
|
4
5
|
import { layoutTextBoxParagraphs } from "./textBoxParagraphLayout.js";
|
|
5
6
|
import { getTextBoxGroupId } from "../textBoxGroup.js";
|
|
6
7
|
import { isParagraphFrameTextBox } from "../paragraphFrame.js";
|
|
@@ -52,39 +53,6 @@ function resolveTableWidthPx(width, widthType, contentWidth) {
|
|
|
52
53
|
if (widthType === "pct") return contentWidth * width / 5e3;
|
|
53
54
|
if (widthType === "dxa" || !widthType || widthType === "auto") return Math.round(width / 20 * 1.333);
|
|
54
55
|
}
|
|
55
|
-
function isInlineFlowImageRun(run) {
|
|
56
|
-
if (run.displayMode === "float") return false;
|
|
57
|
-
if (run.wrapType === "square" || run.wrapType === "tight" || run.wrapType === "through" || run.wrapType === "behind" || run.wrapType === "inFront") return false;
|
|
58
|
-
if (run.displayMode === "block" || run.wrapType === "topAndBottom") return false;
|
|
59
|
-
return true;
|
|
60
|
-
}
|
|
61
|
-
function measureTableCellBlockVisualHeight(block, blockMeasure) {
|
|
62
|
-
if (block.kind === "textBox" && isFloatingTextBoxBlock(block)) return 0;
|
|
63
|
-
if (block.kind !== "paragraph" || blockMeasure.kind !== "paragraph") {
|
|
64
|
-
if ("totalHeight" in blockMeasure) return blockMeasure.totalHeight;
|
|
65
|
-
if ("height" in blockMeasure) return blockMeasure.height;
|
|
66
|
-
return 0;
|
|
67
|
-
}
|
|
68
|
-
const paragraphBlock = block;
|
|
69
|
-
const paragraphMeasure = blockMeasure;
|
|
70
|
-
const nonEmptyRuns = paragraphBlock.runs.filter((run) => run.kind !== "text" || run.text.replace(/\u00a0/gu, " ").trim().length > 0);
|
|
71
|
-
if (paragraphMeasure.lines.length !== 1 || nonEmptyRuns.length === 0) return paragraphMeasure.totalHeight;
|
|
72
|
-
const inlineImageRuns = [];
|
|
73
|
-
for (const run of nonEmptyRuns) {
|
|
74
|
-
if (run.kind !== "image" || !isInlineFlowImageRun(run)) return paragraphMeasure.totalHeight;
|
|
75
|
-
inlineImageRuns.push(run);
|
|
76
|
-
}
|
|
77
|
-
let maxImageHeight = 0;
|
|
78
|
-
for (const run of inlineImageRuns) maxImageHeight = Math.max(maxImageHeight, run.height);
|
|
79
|
-
if (inlineImageRuns.length === 1) {
|
|
80
|
-
const spacingBefore = paragraphBlock.attrs?.spacing?.before ?? 0;
|
|
81
|
-
const spacingAfter = paragraphBlock.attrs?.spacing?.after ?? 0;
|
|
82
|
-
return spacingBefore + maxImageHeight + spacingAfter;
|
|
83
|
-
}
|
|
84
|
-
const spacingBefore = paragraphBlock.attrs?.spacing?.before ?? 0;
|
|
85
|
-
const spacingAfter = paragraphBlock.attrs?.spacing?.after ?? 0;
|
|
86
|
-
return spacingBefore + maxImageHeight + spacingAfter;
|
|
87
|
-
}
|
|
88
56
|
function measureTableBlock(tableBlock, contentWidth, fieldValues) {
|
|
89
57
|
const DEFAULT_CELL_PADDING_X = 7;
|
|
90
58
|
const DEFAULT_CELL_PADDING_Y = 0;
|
|
@@ -147,12 +115,14 @@ function measureTableBlock(tableBlock, contentWidth, fieldValues) {
|
|
|
147
115
|
const cell = row.cells[cellIdx];
|
|
148
116
|
const sourceCell = sourceRowCells?.[cellIdx];
|
|
149
117
|
cell.height = 0;
|
|
118
|
+
const flowState = createTableCellFlowState();
|
|
150
119
|
for (let blockIdx = 0; blockIdx < cell.blocks.length; blockIdx++) {
|
|
151
120
|
const sourceBlock = sourceCell?.blocks[blockIdx];
|
|
152
121
|
const blockMeasure = cell.blocks[blockIdx];
|
|
153
122
|
if (!sourceBlock || !blockMeasure) continue;
|
|
154
|
-
|
|
123
|
+
placeTableCellBlock(flowState, sourceBlock, blockMeasure);
|
|
155
124
|
}
|
|
125
|
+
cell.height = finishTableCellFlow(flowState);
|
|
156
126
|
const padTop = sourceCell?.padding?.top ?? DEFAULT_CELL_PADDING_Y;
|
|
157
127
|
const padBottom = sourceCell?.padding?.bottom ?? DEFAULT_CELL_PADDING_Y;
|
|
158
128
|
cell.height += padTop + padBottom;
|
|
@@ -576,4 +546,4 @@ function measureSingleBlockWithoutFloatingZones(block, blockWidth, blockIndex) {
|
|
|
576
546
|
return measureBlock(block, blockWidth);
|
|
577
547
|
}
|
|
578
548
|
//#endregion
|
|
579
|
-
export { measureBlock, measureBlocks, measureSingleBlockWithoutFloatingZones, measureTableBlock,
|
|
549
|
+
export { measureBlock, measureBlocks, measureSingleBlockWithoutFloatingZones, measureTableBlock, measureTextBoxBlock };
|