@stll/folio-core 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-edits/headless.d.ts +52 -12
- package/dist/ai-edits/headless.js +181 -55
- package/dist/ai-edits/index.d.ts +2 -2
- package/dist/ai-edits/index.js +2 -2
- package/dist/controller/collaborationModules.d.ts +7 -0
- package/dist/controller/collaborationModules.js +15 -0
- package/dist/controller/folioEditor.d.ts +1 -1
- package/dist/controller/headerFooterEditorManager.d.ts +41 -0
- package/dist/controller/headerFooterEditorManager.js +182 -0
- package/dist/controller/hiddenEditorManager.d.ts +1 -1
- package/dist/controller/layoutPipeline.js +2 -1
- package/dist/document-operations.d.ts +11 -2
- package/dist/document-operations.js +7 -1
- package/dist/document-stories.d.ts +10 -0
- package/dist/document-stories.js +27 -0
- package/dist/docx/corePropertiesParser.d.ts +8 -0
- package/dist/docx/corePropertiesParser.js +53 -0
- 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/parser.js +4 -1
- package/dist/docx/rezip.d.ts +5 -4
- package/dist/docx/rezip.js +6 -8
- package/dist/layout-bridge/convert/toFlowBlocks.js +1 -0
- package/dist/layout-engine/measure/cache.js +2 -7
- package/dist/layout-engine/measure/effectiveLineBreakPolicy.d.ts +29 -0
- package/dist/layout-engine/measure/effectiveLineBreakPolicy.js +60 -0
- package/dist/layout-engine/measure/measureParagraph.js +32 -36
- package/dist/layout-engine/measure/tableCellFloating.js +39 -33
- package/dist/layout-engine/types.d.ts +5 -3
- package/dist/managers/FindReplaceManager.d.ts +8 -0
- package/dist/managers/FindReplaceManager.js +18 -0
- package/dist/managers/TableSelectionManager.d.ts +6 -0
- package/dist/managers/TableSelectionManager.js +14 -0
- package/dist/prosemirror/attrs/index.js +1 -0
- package/dist/prosemirror/conversion/effectiveTableCellFormatting.d.ts +62 -0
- package/dist/prosemirror/conversion/effectiveTableCellFormatting.js +131 -0
- package/dist/prosemirror/conversion/fromProseDoc.js +1 -0
- package/dist/prosemirror/conversion/toProseDoc.js +40 -68
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +1 -1
- package/dist/prosemirror/extensions/marks/HighlightExtension.js +1 -1
- package/dist/prosemirror/extensions/marks/RunShadingExtension.js +1 -1
- package/dist/prosemirror/extensions/marks/TextColorExtension.js +1 -1
- package/dist/prosemirror/extensions/nodes/ImageExtension.js +1 -0
- package/dist/prosemirror/extensions/nodes/TableExtension.js +1 -1
- package/dist/prosemirror/findReplaceSelection.d.ts +6 -1
- package/dist/prosemirror/findReplaceSelection.js +57 -13
- package/dist/prosemirror/schema/nodes.d.ts +2 -1
- package/dist/redline.d.ts +26 -11
- package/dist/redline.js +95 -62
- package/dist/render-dom/BodySelectionOverlay.d.ts +31 -0
- package/dist/render-dom/BodySelectionOverlay.js +76 -0
- package/dist/render-dom/HeaderFooterSelectionOverlay.d.ts +28 -0
- package/dist/render-dom/HeaderFooterSelectionOverlay.js +121 -0
- package/dist/render-dom/RemoteSelectionOverlay.d.ts +30 -0
- package/dist/render-dom/RemoteSelectionOverlay.js +67 -0
- package/dist/render-dom/RenderedDomContext.d.ts +40 -0
- package/dist/render-dom/RenderedDomContext.js +126 -0
- package/dist/render-dom/resolveSidebarItemPositions.d.ts +33 -0
- package/dist/render-dom/resolveSidebarItemPositions.js +45 -0
- package/dist/server.d.ts +5 -4
- package/dist/server.js +5 -4
- package/dist/utils/findReplace.d.ts +32 -0
- package/dist/utils/findReplace.js +118 -0
- package/dist/utils/headerFooter.js +24 -1
- package/dist/version-comparison.d.ts +65 -11
- package/dist/version-comparison.js +187 -29
- package/package.json +1 -1
|
@@ -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 };
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { CJK_FALLBACK_FONT_FAMILY, isCjkFont } from "../../utils/fontResolver.js";
|
|
2
2
|
import { getFontMetrics, measureRun, measureTextWidth } from "./measureProvider.js";
|
|
3
3
|
import { countCompressibleSpaces } from "./textMeasurementPolicy.js";
|
|
4
|
-
import { buildRunFontStyle, ptToPx } from "./measureHelpers.js";
|
|
4
|
+
import { buildRunFontStyle, ptToPx, twipsToPx } from "./measureHelpers.js";
|
|
5
5
|
import "./lineBreakProvider.js";
|
|
6
6
|
import { calculateTabWidth, pixelsToTwips } from "../../prosemirror/utils/tabCalculator.js";
|
|
7
7
|
import { inlineImageBoundingBox } from "../../utils/rotationBoundingBox.js";
|
|
8
8
|
import { hasCjk } from "../../utils/scriptSegments.js";
|
|
9
9
|
import { measuredLineAdvance } from "../lineFlow.js";
|
|
10
10
|
import { isFloatingImageRun } from "../types.js";
|
|
11
|
+
import { resolveEffectiveLineBreakPolicy } from "./effectiveLineBreakPolicy.js";
|
|
11
12
|
import { clampFloatingWrapMargins } from "./clampFloatingWrapMargins.js";
|
|
12
13
|
import { getFloatingAvailableWidth, getFloatingMargins } from "./floatingZones.js";
|
|
13
14
|
import { getListMarkerInlineWidth } from "./listMarkerWidth.js";
|
|
@@ -24,7 +25,7 @@ const DEFAULT_FONT_FAMILY = "Calibri";
|
|
|
24
25
|
const DEFAULT_LINE_HEIGHT_MULTIPLIER = 1;
|
|
25
26
|
const WIDTH_TOLERANCE = .5;
|
|
26
27
|
const JUSTIFY_SHRINK_TOLERANCE_RATIO = .016;
|
|
27
|
-
const JUSTIFY_SPACE_CONTRACTION_RATIO = .
|
|
28
|
+
const JUSTIFY_SPACE_CONTRACTION_RATIO = .075;
|
|
28
29
|
const JUSTIFY_LIST_MARKER_SPACE_CONTRACTION_RATIO = .195;
|
|
29
30
|
const JUSTIFY_LIST_CONTINUATION_SPACE_CONTRACTION_RATIO = .23;
|
|
30
31
|
const JUSTIFY_INSET_LIST_SHRINK_TOLERANCE_RATIO = .015;
|
|
@@ -51,6 +52,11 @@ function findMaxFittingLength(text, style, maxWidth, forceMin = false, policy) {
|
|
|
51
52
|
}
|
|
52
53
|
return forceMin && best === 0 ? boundaries.at(0) ?? 0 : best;
|
|
53
54
|
}
|
|
55
|
+
function exceedsHyphenationZone(line, zoneTwips) {
|
|
56
|
+
if (line.width <= 0) return true;
|
|
57
|
+
const visibleWidth = Math.max(0, line.width - line.trailingWhitespaceWidth);
|
|
58
|
+
return Math.max(0, line.availableWidth - visibleWidth) > twipsToPx(zoneTwips) + WIDTH_TOLERANCE;
|
|
59
|
+
}
|
|
54
60
|
/**
|
|
55
61
|
* Extract FontStyle from a run that carries RunFormatting (text, tab, or
|
|
56
62
|
* field). All three share the same formatting shape, so they measure the
|
|
@@ -60,31 +66,6 @@ function findMaxFittingLength(text, style, maxWidth, forceMin = false, policy) {
|
|
|
60
66
|
function runToFontStyle(run) {
|
|
61
67
|
return buildRunFontStyle(run, DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE);
|
|
62
68
|
}
|
|
63
|
-
function languageMatches(ruleLanguage, locale) {
|
|
64
|
-
if (!ruleLanguage) return true;
|
|
65
|
-
if (!locale) return false;
|
|
66
|
-
const rule = ruleLanguage.toLowerCase();
|
|
67
|
-
const active = locale.toLowerCase();
|
|
68
|
-
return active === rule || active.startsWith(`${rule}-`) || rule.startsWith(`${active}-`);
|
|
69
|
-
}
|
|
70
|
-
function lineBreakPolicy(block, run) {
|
|
71
|
-
const language = run.language;
|
|
72
|
-
let locale = language?.val;
|
|
73
|
-
if (hasCjk(run.text)) locale = language?.eastAsia ?? language?.val;
|
|
74
|
-
else if (run.rtl) locale = language?.bidi ?? language?.val;
|
|
75
|
-
const rules = block.attrs?.lineBreakRules;
|
|
76
|
-
const before = rules?.noLineBreaksBefore;
|
|
77
|
-
const after = rules?.noLineBreaksAfter;
|
|
78
|
-
return {
|
|
79
|
-
...locale ? { locale } : {},
|
|
80
|
-
...block.attrs?.kinsoku !== void 0 ? { kinsoku: block.attrs.kinsoku } : {},
|
|
81
|
-
...before && languageMatches(before.language, locale) ? { noLineBreaksBefore: before.characters } : {},
|
|
82
|
-
...after && languageMatches(after.language, locale) ? { noLineBreaksAfter: after.characters } : {},
|
|
83
|
-
...rules?.useLegacyEthiopicAmharicRules ? { useLegacyEthiopicAmharicRules: true } : {},
|
|
84
|
-
...block.attrs?.automaticHyphenation?.doNotHyphenateCaps !== void 0 ? { doNotHyphenateCaps: block.attrs.automaticHyphenation.doNotHyphenateCaps } : {},
|
|
85
|
-
...run.allCaps === true ? { renderedAllCaps: true } : {}
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
69
|
/**
|
|
89
70
|
* Line-HEIGHT style for a text run. Word derives a CJK line's height from an
|
|
90
71
|
* East-Asian face, not the run's ascii font: real documents routinely put CJK
|
|
@@ -454,7 +435,10 @@ function computeTrailingGlueWidths(block) {
|
|
|
454
435
|
}
|
|
455
436
|
if (isSpaceOrTab(text.at(0))) continue;
|
|
456
437
|
const style = runToFontStyle(nextRun);
|
|
457
|
-
const firstBreak = findWordBreaks(text,
|
|
438
|
+
const firstBreak = findWordBreaks(text, resolveEffectiveLineBreakPolicy({
|
|
439
|
+
attrs: block.attrs,
|
|
440
|
+
run: nextRun
|
|
441
|
+
}).provider).at(0);
|
|
458
442
|
widths[index] = measureTextWidth(firstBreak === void 0 ? text : trimTrailingSpacesAndTabs(text.slice(0, firstBreak)), style) + (firstBreak === void 0 ? widths[index + 1] ?? 0 : 0);
|
|
459
443
|
}
|
|
460
444
|
return widths;
|
|
@@ -466,7 +450,10 @@ function computeProtectedCrossRunGlueWidths(block) {
|
|
|
466
450
|
if (!run || !isTextRun(run)) continue;
|
|
467
451
|
const trailing = /(\S+)(\s*)$/u.exec(run.text ?? "");
|
|
468
452
|
const token = trailing?.[1];
|
|
469
|
-
const policy =
|
|
453
|
+
const policy = resolveEffectiveLineBreakPolicy({
|
|
454
|
+
attrs: block.attrs,
|
|
455
|
+
run
|
|
456
|
+
}).provider;
|
|
470
457
|
if (!token || token.length !== 1 || !policy.locale?.toLocaleLowerCase().startsWith("cs")) continue;
|
|
471
458
|
let separator = trailing[2] ?? "";
|
|
472
459
|
let glueWidth = separator.length > 0 ? measureTextWidth(separator, runToFontStyle(run)) : 0;
|
|
@@ -512,7 +499,10 @@ function collectCrossRunWord({ block, startRunIndex, startChar }) {
|
|
|
512
499
|
if (runIndex !== startRunIndex && isSpaceOrTab(remainder[0])) break;
|
|
513
500
|
const remainingBudget = 256 - text.length;
|
|
514
501
|
const boundedRemainder = remainder.slice(0, remainingBudget + 1);
|
|
515
|
-
const firstBreak = findWordBreaks(boundedRemainder,
|
|
502
|
+
const firstBreak = findWordBreaks(boundedRemainder, resolveEffectiveLineBreakPolicy({
|
|
503
|
+
attrs: block.attrs,
|
|
504
|
+
run
|
|
505
|
+
}).provider).at(0);
|
|
516
506
|
const segmentText = trimTrailingSpacesAndTabs(firstBreak === void 0 ? boundedRemainder : boundedRemainder.slice(0, firstBreak));
|
|
517
507
|
if (segmentText.length === 0) break;
|
|
518
508
|
if (segmentText.length > remainingBudget) return;
|
|
@@ -534,7 +524,10 @@ function collectCrossRunWord({ block, startRunIndex, startChar }) {
|
|
|
534
524
|
return {
|
|
535
525
|
text,
|
|
536
526
|
width,
|
|
537
|
-
breaks: findHyphenationBreaks(text,
|
|
527
|
+
breaks: findHyphenationBreaks(text, resolveEffectiveLineBreakPolicy({
|
|
528
|
+
attrs: block.attrs,
|
|
529
|
+
run: firstRun
|
|
530
|
+
}).provider),
|
|
538
531
|
segments
|
|
539
532
|
};
|
|
540
533
|
}
|
|
@@ -944,7 +937,11 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
944
937
|
const textRun = run;
|
|
945
938
|
const text = textRun.text;
|
|
946
939
|
const style = runToFontStyle(textRun);
|
|
947
|
-
const
|
|
940
|
+
const effectiveLineBreakPolicy = resolveEffectiveLineBreakPolicy({
|
|
941
|
+
attrs: block.attrs,
|
|
942
|
+
run: textRun
|
|
943
|
+
});
|
|
944
|
+
const breakPolicy = effectiveLineBreakPolicy.provider;
|
|
948
945
|
const lineHeightStyle = cjkLineHeightStyle(textRun, style);
|
|
949
946
|
updateMaxFont(lineHeightStyle);
|
|
950
947
|
if (!text || text.length === 0) {
|
|
@@ -966,13 +963,12 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
966
963
|
const measuredWord = trimTrailingSpacesAndTabs(word);
|
|
967
964
|
const wordWidth = measureTextWidth(measuredWord, style);
|
|
968
965
|
const fullWordWidth = measureTextWidth(word, style);
|
|
969
|
-
const hangingPunctuationWidth = trailingHangingPunctuationWidth(measuredWord, style, breakPolicy,
|
|
966
|
+
const hangingPunctuationWidth = trailingHangingPunctuationWidth(measuredWord, style, breakPolicy, effectiveLineBreakPolicy.hangingPunctuation);
|
|
970
967
|
const isFirstLine = lines.length === 0;
|
|
971
968
|
const regularSpaceWidth = compressibleSpaceWidth(measuredWord, style);
|
|
972
969
|
const widthTolerance = isJustifiedParagraph ? justifyFitTolerance(currentLine, isFirstLine ? firstLineJustifyFitStrategy : continuationJustifyFitStrategy, regularSpaceWidth) : WIDTH_TOLERANCE;
|
|
973
|
-
const automaticHyphenation =
|
|
974
|
-
const
|
|
975
|
-
const mayHyphenateLine = automaticHyphenation?.enabled === true && block.attrs?.suppressAutoHyphens !== true && (consecutiveLineLimit === 0 || consecutiveHyphenatedLines < consecutiveLineLimit);
|
|
970
|
+
const automaticHyphenation = effectiveLineBreakPolicy.automaticHyphenation;
|
|
971
|
+
const mayHyphenateLine = automaticHyphenation.type === "enabled" && (automaticHyphenation.consecutiveLineLimit === 0 || consecutiveHyphenatedLines < automaticHyphenation.consecutiveLineLimit) && exceedsHyphenationZone(currentLine, automaticHyphenation.hyphenationZoneTwips);
|
|
976
972
|
const isRunTail = nextBreak === text.length;
|
|
977
973
|
const crossRunWord = mayHyphenateLine && isRunTail && word.length > 0 && !isBreakChar(word.at(-1)) ? collectCrossRunWord({
|
|
978
974
|
block,
|
|
@@ -3,6 +3,36 @@ import { isFloatingImageRun } from "../types.js";
|
|
|
3
3
|
import { clampFloatingWrapMargins } from "./clampFloatingWrapMargins.js";
|
|
4
4
|
import { createTableCellFlowState, placeTableCellBlock } from "./tableCellFlow.js";
|
|
5
5
|
//#region src/layout-engine/measure/tableCellFloating.ts
|
|
6
|
+
const resolveCellScopedPosition = ({ run, paragraphY, contentWidth }) => {
|
|
7
|
+
const position = run.position;
|
|
8
|
+
let side = "left";
|
|
9
|
+
let x = 0;
|
|
10
|
+
if (position?.horizontal) {
|
|
11
|
+
const horizontal = position.horizontal;
|
|
12
|
+
if (horizontal.align === "right") {
|
|
13
|
+
side = "right";
|
|
14
|
+
x = contentWidth - run.width;
|
|
15
|
+
} else if (horizontal.align === "center") x = (contentWidth - run.width) / 2;
|
|
16
|
+
else if (horizontal.posOffset !== void 0) {
|
|
17
|
+
x = emuToPixels(horizontal.posOffset);
|
|
18
|
+
side = x > contentWidth / 2 ? "right" : "left";
|
|
19
|
+
}
|
|
20
|
+
} else if (run.cssFloat === "right") {
|
|
21
|
+
side = "right";
|
|
22
|
+
x = contentWidth - run.width;
|
|
23
|
+
}
|
|
24
|
+
let y = paragraphY;
|
|
25
|
+
if (position?.vertical) {
|
|
26
|
+
const vertical = position.vertical;
|
|
27
|
+
if (vertical.posOffset !== void 0) y = paragraphY + emuToPixels(vertical.posOffset);
|
|
28
|
+
else if (vertical.align === "top") y = 0;
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
side,
|
|
32
|
+
x,
|
|
33
|
+
y
|
|
34
|
+
};
|
|
35
|
+
};
|
|
6
36
|
function getTableCellContentWidth(cell, cellMeasure) {
|
|
7
37
|
const padLeft = cell?.padding?.left ?? 7;
|
|
8
38
|
const padRight = cell?.padding?.right ?? 7;
|
|
@@ -19,40 +49,16 @@ function getTableCellFloatingImages(cell, cellMeasure, contentWidth, resolvePosi
|
|
|
19
49
|
if (block.kind !== "paragraph") continue;
|
|
20
50
|
for (const run of block.runs) {
|
|
21
51
|
if (run.kind !== "image" || !isFloatingImageRun(run)) continue;
|
|
22
|
-
const position = run.position;
|
|
23
52
|
const distTop = run.distTop ?? 0;
|
|
24
53
|
const distBottom = run.distBottom ?? 0;
|
|
25
54
|
const distLeft = run.distLeft ?? 12;
|
|
26
55
|
const distRight = run.distRight ?? 12;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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") {
|
|
45
|
-
side = "right";
|
|
46
|
-
x = contentWidth - run.width;
|
|
47
|
-
}
|
|
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));
|
|
55
|
-
}
|
|
56
|
+
const verticalOriginY = run.position?.vertical?.relativeTo === "paragraph" ? placement.top : placement.contentTop;
|
|
57
|
+
const resolved = run.layoutInCell === false && resolvePosition ? resolvePosition(run, verticalOriginY) : resolveCellScopedPosition({
|
|
58
|
+
run,
|
|
59
|
+
paragraphY: verticalOriginY,
|
|
60
|
+
contentWidth
|
|
61
|
+
});
|
|
56
62
|
let wrapText = "bothSides";
|
|
57
63
|
if (run.cssFloat === "left") wrapText = "right";
|
|
58
64
|
else if (run.cssFloat === "right") wrapText = "left";
|
|
@@ -67,9 +73,9 @@ function getTableCellFloatingImages(cell, cellMeasure, contentWidth, resolvePosi
|
|
|
67
73
|
...run.cropRight != null ? { cropRight: run.cropRight } : {},
|
|
68
74
|
...run.cropBottom != null ? { cropBottom: run.cropBottom } : {},
|
|
69
75
|
...run.cropLeft != null ? { cropLeft: run.cropLeft } : {},
|
|
70
|
-
x,
|
|
71
|
-
y,
|
|
72
|
-
side,
|
|
76
|
+
x: resolved.x,
|
|
77
|
+
y: resolved.y,
|
|
78
|
+
side: resolved.side,
|
|
73
79
|
distTop,
|
|
74
80
|
distBottom,
|
|
75
81
|
distLeft,
|
|
@@ -162,7 +162,8 @@ type ImageRun = {
|
|
|
162
162
|
* eigenpal #424 (opacity render pipeline).
|
|
163
163
|
*/
|
|
164
164
|
opacity?: number; /** Position for floating/anchored images */
|
|
165
|
-
position?: ImageRunPosition; /**
|
|
165
|
+
position?: ImageRunPosition; /** Whether a table-cell anchor uses the cell as its positioning scope. Undefined defaults true. */
|
|
166
|
+
layoutInCell?: boolean; /** Wrap type from DOCX (inline, square, tight, through, topAndBottom, etc.) */
|
|
166
167
|
wrapType?: ImageWrap["type"]; /** Display mode for CSS rendering */
|
|
167
168
|
displayMode?: "inline" | "block" | "float"; /** CSS float direction */
|
|
168
169
|
cssFloat?: "left" | "right" | "none"; /** Wrap distances in pixels */
|
|
@@ -293,13 +294,14 @@ type ListNumPr = {
|
|
|
293
294
|
*/
|
|
294
295
|
type ParagraphAttrs = {
|
|
295
296
|
alignment?: "left" | "center" | "right" | "justify"; /** East Asian first/last-character restrictions (`w:kinsoku`). */
|
|
296
|
-
kinsoku?: boolean; /** Hanging punctuation (`w:overflowPunct`)
|
|
297
|
+
kinsoku?: boolean; /** Hanging punctuation (`w:overflowPunct`); defaults to enabled when omitted. */
|
|
297
298
|
overflowPunctuation?: boolean; /** Exempt this paragraph from document automatic hyphenation. */
|
|
298
299
|
suppressAutoHyphens?: boolean; /** Document-wide automatic hyphenation controls retained for line layout. */
|
|
299
300
|
automaticHyphenation?: {
|
|
300
301
|
enabled: true;
|
|
301
302
|
doNotHyphenateCaps?: boolean;
|
|
302
|
-
consecutiveLineLimit?: number;
|
|
303
|
+
consecutiveLineLimit?: number; /** Maximum tolerated line-end whitespace before hyphenation, in twips. */
|
|
304
|
+
hyphenationZoneTwips?: number;
|
|
303
305
|
}; /** Document-wide custom line-edge characters and compatibility behavior. */
|
|
304
306
|
lineBreakRules?: {
|
|
305
307
|
noLineBreaksBefore?: {
|
|
@@ -37,6 +37,14 @@ declare class FindReplaceManager<TMatch extends FindMatchPosition> {
|
|
|
37
37
|
match: TMatch;
|
|
38
38
|
index: number;
|
|
39
39
|
} | null;
|
|
40
|
+
/**
|
|
41
|
+
* Move the cursor to an explicit match index. Returns the selected match, or
|
|
42
|
+
* null when there is no active result or the index is out of bounds.
|
|
43
|
+
*/
|
|
44
|
+
goTo(index: number): {
|
|
45
|
+
match: TMatch;
|
|
46
|
+
index: number;
|
|
47
|
+
} | null;
|
|
40
48
|
/**
|
|
41
49
|
* Replace the current match's text. Returns the new document, or null when
|
|
42
50
|
* there is no current match or the replace fails.
|
|
@@ -61,6 +61,24 @@ var FindReplaceManager = class {
|
|
|
61
61
|
} : null;
|
|
62
62
|
}
|
|
63
63
|
/**
|
|
64
|
+
* Move the cursor to an explicit match index. Returns the selected match, or
|
|
65
|
+
* null when there is no active result or the index is out of bounds.
|
|
66
|
+
*/
|
|
67
|
+
goTo(index) {
|
|
68
|
+
const result = this.result;
|
|
69
|
+
if (!result || index < 0 || index >= result.matches.length) return null;
|
|
70
|
+
const match = result.matches.at(index);
|
|
71
|
+
if (!match) return null;
|
|
72
|
+
this.result = {
|
|
73
|
+
...result,
|
|
74
|
+
currentIndex: index
|
|
75
|
+
};
|
|
76
|
+
return {
|
|
77
|
+
match,
|
|
78
|
+
index
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
64
82
|
* Replace the current match's text. Returns the new document, or null when
|
|
65
83
|
* there is no current match or the replace fails.
|
|
66
84
|
*/
|
|
@@ -52,6 +52,12 @@ type TableActionResult = {
|
|
|
52
52
|
};
|
|
53
53
|
declare class TableSelectionManager extends Subscribable<TableSelectionState> {
|
|
54
54
|
constructor();
|
|
55
|
+
/**
|
|
56
|
+
* Track cell coordinates before a document model is available. This supports
|
|
57
|
+
* adapter pointer state without fabricating a table context; callers that
|
|
58
|
+
* need structural operations should use {@link selectCell} with a document.
|
|
59
|
+
*/
|
|
60
|
+
selectCellCoordinates(coords: CellCoordinates): void;
|
|
55
61
|
/**
|
|
56
62
|
* Select a cell within `doc`. Resolves the table at `coords.tableIndex` and
|
|
57
63
|
* derives the full {@link TableContext}. Returns the context, or `null` (and
|
|
@@ -107,6 +107,20 @@ var TableSelectionManager = class extends Subscribable {
|
|
|
107
107
|
super(EMPTY_SELECTION);
|
|
108
108
|
}
|
|
109
109
|
/**
|
|
110
|
+
* Track cell coordinates before a document model is available. This supports
|
|
111
|
+
* adapter pointer state without fabricating a table context; callers that
|
|
112
|
+
* need structural operations should use {@link selectCell} with a document.
|
|
113
|
+
*/
|
|
114
|
+
selectCellCoordinates(coords) {
|
|
115
|
+
this.setSnapshot({
|
|
116
|
+
context: null,
|
|
117
|
+
table: null,
|
|
118
|
+
tableIndex: coords.tableIndex,
|
|
119
|
+
rowIndex: coords.rowIndex,
|
|
120
|
+
columnIndex: coords.columnIndex
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
110
124
|
* Select a cell within `doc`. Resolves the table at `coords.tableIndex` and
|
|
111
125
|
* derives the full {@link TableContext}. Returns the context, or `null` (and
|
|
112
126
|
* clears the selection) when that table no longer exists.
|
|
@@ -306,6 +306,7 @@ const readImageAttrs = (node) => {
|
|
|
306
306
|
optionalNumber(attrs, "distLeft", "image.attrs.distLeft", issues);
|
|
307
307
|
optionalNumber(attrs, "distRight", "image.attrs.distRight", issues);
|
|
308
308
|
optionalImagePosition(attrs, "position", "image.attrs.position", issues);
|
|
309
|
+
optionalBoolean(attrs, "layoutInCell", "image.attrs.layoutInCell", issues);
|
|
309
310
|
optionalNumber(attrs, "borderWidth", "image.attrs.borderWidth", issues);
|
|
310
311
|
optionalString(attrs, "borderColor", "image.attrs.borderColor", issues);
|
|
311
312
|
optionalString(attrs, "borderStyle", "image.attrs.borderStyle", issues);
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { document_d_exports } from "../../types/document.js";
|
|
2
|
+
|
|
3
|
+
//#region src/prosemirror/conversion/effectiveTableCellFormatting.d.ts
|
|
4
|
+
type TableCellMarginsAttrs = {
|
|
5
|
+
top?: number;
|
|
6
|
+
bottom?: number;
|
|
7
|
+
left?: number;
|
|
8
|
+
right?: number;
|
|
9
|
+
};
|
|
10
|
+
type EffectiveTableCellWidth = {
|
|
11
|
+
type: "none";
|
|
12
|
+
} | {
|
|
13
|
+
type: "value";
|
|
14
|
+
source: "direct" | "grid";
|
|
15
|
+
value: number;
|
|
16
|
+
widthType: document_d_exports.TableWidthType;
|
|
17
|
+
};
|
|
18
|
+
type EffectiveTableCellBackground = {
|
|
19
|
+
type: "none";
|
|
20
|
+
source: "none" | "direct" | "style";
|
|
21
|
+
} | {
|
|
22
|
+
type: "color";
|
|
23
|
+
source: "direct" | "style";
|
|
24
|
+
rgb: string;
|
|
25
|
+
};
|
|
26
|
+
type EffectiveTableCellFormatting = Readonly<{
|
|
27
|
+
width: EffectiveTableCellWidth;
|
|
28
|
+
background: EffectiveTableCellBackground;
|
|
29
|
+
borders: document_d_exports.TableCellBorders | undefined;
|
|
30
|
+
margins: TableCellMarginsAttrs | undefined;
|
|
31
|
+
}>;
|
|
32
|
+
type TableCellPosition = {
|
|
33
|
+
isFirstRow?: boolean;
|
|
34
|
+
isLastRow?: boolean;
|
|
35
|
+
isFirstColumn?: boolean;
|
|
36
|
+
isLastColumn?: boolean;
|
|
37
|
+
};
|
|
38
|
+
type ResolveEffectiveTableCellFormattingOptions = {
|
|
39
|
+
directFormatting: document_d_exports.TableCellFormatting | undefined;
|
|
40
|
+
styleFormatting: document_d_exports.TableCellFormatting | undefined;
|
|
41
|
+
tableBorders: document_d_exports.TableBorders | undefined;
|
|
42
|
+
position: TableCellPosition;
|
|
43
|
+
gridWidthPercent: number | undefined;
|
|
44
|
+
defaultMargins: TableCellMarginsAttrs | undefined;
|
|
45
|
+
theme: document_d_exports.Theme | null | undefined;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Resolve the display-facing table-cell cascade without mutating authored
|
|
49
|
+
* OOXML formatting. Source discriminators keep inherited values distinct from
|
|
50
|
+
* direct values that the editor may later override.
|
|
51
|
+
*/
|
|
52
|
+
declare const resolveEffectiveTableCellFormatting: ({
|
|
53
|
+
directFormatting,
|
|
54
|
+
styleFormatting,
|
|
55
|
+
tableBorders,
|
|
56
|
+
position,
|
|
57
|
+
gridWidthPercent,
|
|
58
|
+
defaultMargins,
|
|
59
|
+
theme
|
|
60
|
+
}: ResolveEffectiveTableCellFormattingOptions) => EffectiveTableCellFormatting;
|
|
61
|
+
//#endregion
|
|
62
|
+
export { TableCellMarginsAttrs, TableCellPosition, resolveEffectiveTableCellFormatting };
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { resolveColor } from "../../utils/colorResolver.js";
|
|
2
|
+
import { resolveShadingFill } from "../../utils/formatToStyle.js";
|
|
3
|
+
//#region src/prosemirror/conversion/effectiveTableCellFormatting.ts
|
|
4
|
+
const TABLE_BORDER_SIDES = [
|
|
5
|
+
"top",
|
|
6
|
+
"bottom",
|
|
7
|
+
"left",
|
|
8
|
+
"right",
|
|
9
|
+
"insideH",
|
|
10
|
+
"insideV",
|
|
11
|
+
"topLeftToBottomRight",
|
|
12
|
+
"topRightToBottomLeft"
|
|
13
|
+
];
|
|
14
|
+
/**
|
|
15
|
+
* Resolve the display-facing table-cell cascade without mutating authored
|
|
16
|
+
* OOXML formatting. Source discriminators keep inherited values distinct from
|
|
17
|
+
* direct values that the editor may later override.
|
|
18
|
+
*/
|
|
19
|
+
const resolveEffectiveTableCellFormatting = ({ directFormatting, styleFormatting, tableBorders, position, gridWidthPercent, defaultMargins, theme }) => {
|
|
20
|
+
return {
|
|
21
|
+
width: resolveEffectiveWidth(directFormatting, gridWidthPercent),
|
|
22
|
+
background: resolveEffectiveBackground({
|
|
23
|
+
directFormatting,
|
|
24
|
+
styleFormatting,
|
|
25
|
+
theme
|
|
26
|
+
}),
|
|
27
|
+
borders: resolveEffectiveBorders({
|
|
28
|
+
directFormatting,
|
|
29
|
+
styleFormatting,
|
|
30
|
+
tableBorders,
|
|
31
|
+
position,
|
|
32
|
+
theme
|
|
33
|
+
}),
|
|
34
|
+
margins: resolveEffectiveMargins({
|
|
35
|
+
directFormatting,
|
|
36
|
+
styleFormatting,
|
|
37
|
+
defaultMargins
|
|
38
|
+
})
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
const resolveEffectiveWidth = (directFormatting, gridWidthPercent) => {
|
|
42
|
+
const directWidth = directFormatting?.width;
|
|
43
|
+
if (directWidth) return {
|
|
44
|
+
type: "value",
|
|
45
|
+
source: "direct",
|
|
46
|
+
value: directWidth.value,
|
|
47
|
+
widthType: directWidth.type
|
|
48
|
+
};
|
|
49
|
+
if (gridWidthPercent !== void 0) return {
|
|
50
|
+
type: "value",
|
|
51
|
+
source: "grid",
|
|
52
|
+
value: gridWidthPercent,
|
|
53
|
+
widthType: "pct"
|
|
54
|
+
};
|
|
55
|
+
return { type: "none" };
|
|
56
|
+
};
|
|
57
|
+
const resolveEffectiveBackground = ({ directFormatting, styleFormatting, theme }) => {
|
|
58
|
+
const directShading = directFormatting?.shading;
|
|
59
|
+
if (directShading !== void 0) return resolveBackgroundColor({
|
|
60
|
+
shading: directShading,
|
|
61
|
+
source: "direct",
|
|
62
|
+
theme
|
|
63
|
+
});
|
|
64
|
+
const styleShading = styleFormatting?.shading;
|
|
65
|
+
if (styleShading !== void 0) return resolveBackgroundColor({
|
|
66
|
+
shading: styleShading,
|
|
67
|
+
source: "style",
|
|
68
|
+
theme
|
|
69
|
+
});
|
|
70
|
+
return {
|
|
71
|
+
type: "none",
|
|
72
|
+
source: "none"
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
const resolveBackgroundColor = ({ shading, source, theme }) => {
|
|
76
|
+
const rgb = resolveShadingFill(shading, theme).replace(/^#/u, "");
|
|
77
|
+
if (!rgb) return {
|
|
78
|
+
type: "none",
|
|
79
|
+
source
|
|
80
|
+
};
|
|
81
|
+
return {
|
|
82
|
+
type: "color",
|
|
83
|
+
source,
|
|
84
|
+
rgb
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
const resolveEffectiveBorders = ({ directFormatting, styleFormatting, tableBorders, position, theme }) => {
|
|
88
|
+
const baseBorders = tableBorders ? {
|
|
89
|
+
top: position.isFirstRow ? tableBorders.top : tableBorders.insideH,
|
|
90
|
+
bottom: position.isLastRow ? tableBorders.bottom : tableBorders.insideH,
|
|
91
|
+
left: position.isFirstColumn ? tableBorders.left : tableBorders.insideV,
|
|
92
|
+
right: position.isLastColumn ? tableBorders.right : tableBorders.insideV
|
|
93
|
+
} : void 0;
|
|
94
|
+
const styleBorders = styleFormatting?.borders;
|
|
95
|
+
const directBorders = directFormatting?.borders;
|
|
96
|
+
const borders = baseBorders || styleBorders || directBorders ? {
|
|
97
|
+
...baseBorders,
|
|
98
|
+
...styleBorders,
|
|
99
|
+
...directBorders
|
|
100
|
+
} : void 0;
|
|
101
|
+
return resolveThemedBorderColors(borders, theme);
|
|
102
|
+
};
|
|
103
|
+
const resolveEffectiveMargins = ({ directFormatting, styleFormatting, defaultMargins }) => {
|
|
104
|
+
if (directFormatting?.margins) return cellMarginsToAttrs(directFormatting.margins);
|
|
105
|
+
if (styleFormatting?.margins) return cellMarginsToAttrs(styleFormatting.margins);
|
|
106
|
+
return defaultMargins;
|
|
107
|
+
};
|
|
108
|
+
const cellMarginsToAttrs = (margins) => {
|
|
109
|
+
const result = {};
|
|
110
|
+
if (margins.top?.value !== void 0) result.top = margins.top.value;
|
|
111
|
+
if (margins.bottom?.value !== void 0) result.bottom = margins.bottom.value;
|
|
112
|
+
if (margins.left?.value !== void 0) result.left = margins.left.value;
|
|
113
|
+
if (margins.right?.value !== void 0) result.right = margins.right.value;
|
|
114
|
+
return result;
|
|
115
|
+
};
|
|
116
|
+
const resolveThemedBorderColors = (borders, theme) => {
|
|
117
|
+
if (!borders || !theme?.colorScheme) return borders;
|
|
118
|
+
let resolved;
|
|
119
|
+
for (const side of TABLE_BORDER_SIDES) {
|
|
120
|
+
const border = borders[side];
|
|
121
|
+
if (!border?.color?.themeColor || border.color.auto) continue;
|
|
122
|
+
resolved ??= { ...borders };
|
|
123
|
+
resolved[side] = {
|
|
124
|
+
...border,
|
|
125
|
+
color: { rgb: resolveColor(border.color, theme).replace(/^#/u, "") }
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
return resolved ?? borders;
|
|
129
|
+
};
|
|
130
|
+
//#endregion
|
|
131
|
+
export { resolveEffectiveTableCellFormatting };
|
|
@@ -911,6 +911,7 @@ function createImageRun(node) {
|
|
|
911
911
|
if (attrs.opacity != null) image.opacity = attrs.opacity;
|
|
912
912
|
const imagePosition = imagePositionFromAttrs(attrs.position);
|
|
913
913
|
if (imagePosition) image.position = imagePosition;
|
|
914
|
+
if (attrs.layoutInCell !== void 0) image.layoutInCell = attrs.layoutInCell;
|
|
914
915
|
if (attrs.borderWidth && attrs.borderWidth > 0) {
|
|
915
916
|
const outline = {
|
|
916
917
|
width: pixelsToEmu(attrs.borderWidth),
|