@beyondwork/docx-react-component 1.0.28 → 1.0.30
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/package.json +26 -37
- package/src/api/public-types.ts +531 -0
- package/src/api/session-state.ts +2 -0
- package/src/core/commands/index.ts +201 -79
- package/src/core/commands/table-structure-commands.ts +138 -5
- package/src/core/state/text-transaction.ts +370 -3
- package/src/index.ts +41 -0
- package/src/io/docx-session.ts +318 -25
- package/src/io/export/serialize-footnotes.ts +41 -46
- package/src/io/export/serialize-headers-footers.ts +36 -40
- package/src/io/export/serialize-main-document.ts +55 -89
- package/src/io/export/serialize-numbering.ts +104 -4
- package/src/io/export/serialize-runtime-revisions.ts +196 -2
- package/src/io/export/split-story-blocks-for-runtime-revisions.ts +252 -0
- package/src/io/export/table-properties-xml.ts +318 -0
- package/src/io/normalize/normalize-text.ts +34 -3
- package/src/io/ooxml/parse-comments.ts +6 -0
- package/src/io/ooxml/parse-footnotes.ts +69 -13
- package/src/io/ooxml/parse-headers-footers.ts +54 -11
- package/src/io/ooxml/parse-main-document.ts +112 -42
- package/src/io/ooxml/parse-numbering.ts +341 -26
- package/src/io/ooxml/parse-revisions.ts +118 -4
- package/src/io/ooxml/parse-styles.ts +176 -0
- package/src/io/ooxml/parse-tables.ts +34 -25
- package/src/io/ooxml/revision-boundaries.ts +127 -3
- package/src/io/ooxml/workflow-payload.ts +544 -0
- package/src/model/canonical-document.ts +91 -1
- package/src/model/snapshot.ts +112 -1
- package/src/preservation/store.ts +73 -3
- package/src/review/store/comment-store.ts +19 -1
- package/src/review/store/revision-actions.ts +29 -0
- package/src/review/store/revision-store.ts +12 -1
- package/src/review/store/revision-types.ts +11 -0
- package/src/runtime/context-analytics.ts +824 -0
- package/src/runtime/document-locations.ts +521 -0
- package/src/runtime/document-navigation.ts +14 -1
- package/src/runtime/document-outline.ts +440 -0
- package/src/runtime/document-runtime.ts +941 -45
- package/src/runtime/event-refresh-hints.ts +137 -0
- package/src/runtime/numbering-prefix.ts +67 -39
- package/src/runtime/page-layout-estimation.ts +100 -7
- package/src/runtime/resolved-numbering-geometry.ts +293 -0
- package/src/runtime/session-capabilities.ts +2 -2
- package/src/runtime/suggestions-snapshot.ts +137 -0
- package/src/runtime/surface-projection.ts +223 -27
- package/src/runtime/table-style-resolver.ts +409 -0
- package/src/runtime/view-state.ts +17 -1
- package/src/runtime/workflow-markup.ts +54 -14
- package/src/ui/WordReviewEditor.tsx +1269 -87
- package/src/ui/editor-command-bag.ts +7 -0
- package/src/ui/editor-runtime-boundary.ts +111 -10
- package/src/ui/editor-shell-view.tsx +17 -15
- package/src/ui/editor-surface-controller.tsx +5 -0
- package/src/ui/headless/selection-tool-context.ts +19 -0
- package/src/ui/headless/selection-tool-resolver.ts +752 -0
- package/src/ui/headless/selection-tool-types.ts +129 -0
- package/src/ui/headless/selection-toolbar-model.ts +10 -33
- package/src/ui/runtime-shortcut-dispatch.ts +365 -0
- package/src/ui-tailwind/chrome/chrome-preset-model.ts +107 -0
- package/src/ui-tailwind/chrome/chrome-preset-toolbar.tsx +15 -0
- package/src/ui-tailwind/chrome/review-queue-bar.tsx +97 -0
- package/src/ui-tailwind/chrome/tw-context-analytics-summary.tsx +122 -0
- package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +1 -9
- package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +1 -5
- package/src/ui-tailwind/chrome/tw-page-ruler.tsx +8 -29
- package/src/ui-tailwind/chrome/tw-selection-tool-blocked.tsx +23 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-comment.tsx +35 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-formatting.tsx +37 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +298 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +116 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-suggestion.tsx +29 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-workflow.tsx +27 -0
- package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +3 -3
- package/src/ui-tailwind/chrome/tw-suggestion-card.tsx +3 -3
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +86 -14
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +57 -52
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +36 -52
- package/src/ui-tailwind/editor-surface/pm-schema.ts +56 -5
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +87 -24
- package/src/ui-tailwind/editor-surface/surface-build-keys.ts +4 -0
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +135 -32
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +74 -7
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +17 -17
- package/src/ui-tailwind/review/tw-review-rail.tsx +19 -17
- package/src/ui-tailwind/review/tw-revision-sidebar.tsx +10 -10
- package/src/ui-tailwind/status/tw-status-bar.tsx +10 -6
- package/src/ui-tailwind/theme/editor-theme.css +58 -40
- package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +4 -4
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +250 -181
- package/src/ui-tailwind/tw-review-workspace.tsx +323 -280
- package/src/validation/compatibility-engine.ts +246 -2
- package/src/validation/docx-comment-proof.ts +24 -11
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import type { SnapshotRefreshHints, WordReviewEditorEvent } from "../api/public-types";
|
|
2
|
+
|
|
3
|
+
export function describeEventImpact(
|
|
4
|
+
event: WordReviewEditorEvent,
|
|
5
|
+
): SnapshotRefreshHints {
|
|
6
|
+
switch (event.type) {
|
|
7
|
+
case "selection_changed":
|
|
8
|
+
return {
|
|
9
|
+
invalidate: ["selection", "locations", "contextAnalytics"],
|
|
10
|
+
staleTargets: ["selection_only"],
|
|
11
|
+
changeKinds: ["selection"],
|
|
12
|
+
};
|
|
13
|
+
case "story_changed":
|
|
14
|
+
return {
|
|
15
|
+
invalidate: ["selection", "render", "locations", "outline", "sections", "contextAnalytics"],
|
|
16
|
+
staleTargets: ["anchors"],
|
|
17
|
+
changeKinds: ["selection", "structure"],
|
|
18
|
+
};
|
|
19
|
+
case "workflow_overlay_changed":
|
|
20
|
+
case "workflow_active_work_item_changed":
|
|
21
|
+
return {
|
|
22
|
+
invalidate: [
|
|
23
|
+
"workflowScope",
|
|
24
|
+
"workflowMarkup",
|
|
25
|
+
"locations",
|
|
26
|
+
"reviewWork",
|
|
27
|
+
"chunks",
|
|
28
|
+
"contextAnalytics",
|
|
29
|
+
],
|
|
30
|
+
staleTargets: ["anchors"],
|
|
31
|
+
changeKinds: ["workflow"],
|
|
32
|
+
};
|
|
33
|
+
case "workflow_metadata_changed":
|
|
34
|
+
return {
|
|
35
|
+
invalidate: ["workflowMarkup", "render", "locations", "reviewWork", "chunks"],
|
|
36
|
+
staleTargets: ["anchors"],
|
|
37
|
+
changeKinds: ["workflow"],
|
|
38
|
+
};
|
|
39
|
+
case "change_authored":
|
|
40
|
+
case "change_accepted":
|
|
41
|
+
case "change_rejected":
|
|
42
|
+
case "suggestion_authored":
|
|
43
|
+
case "suggestion_updated":
|
|
44
|
+
case "comment_added":
|
|
45
|
+
case "comment_resolved":
|
|
46
|
+
return {
|
|
47
|
+
invalidate: [
|
|
48
|
+
"render",
|
|
49
|
+
"comments",
|
|
50
|
+
"trackedChanges",
|
|
51
|
+
"navigation",
|
|
52
|
+
"outline",
|
|
53
|
+
"toc",
|
|
54
|
+
"sections",
|
|
55
|
+
"locations",
|
|
56
|
+
"reviewWork",
|
|
57
|
+
"chunks",
|
|
58
|
+
"textStream",
|
|
59
|
+
"contextAnalytics",
|
|
60
|
+
],
|
|
61
|
+
staleTargets: ["search_results", "anchors"],
|
|
62
|
+
changeKinds: ["content", "review", "structure"],
|
|
63
|
+
};
|
|
64
|
+
case "warning_added":
|
|
65
|
+
case "warning_cleared":
|
|
66
|
+
case "error":
|
|
67
|
+
return {
|
|
68
|
+
invalidate: ["render", "contextAnalytics"],
|
|
69
|
+
staleTargets: ["none"],
|
|
70
|
+
changeKinds: ["content"],
|
|
71
|
+
};
|
|
72
|
+
case "snapshot_saved":
|
|
73
|
+
return {
|
|
74
|
+
invalidate: [],
|
|
75
|
+
staleTargets: ["none"],
|
|
76
|
+
changeKinds: ["checkpoint"],
|
|
77
|
+
checkpointType: "snapshot",
|
|
78
|
+
};
|
|
79
|
+
case "session_saved":
|
|
80
|
+
return {
|
|
81
|
+
invalidate: [],
|
|
82
|
+
staleTargets: ["none"],
|
|
83
|
+
changeKinds: ["checkpoint"],
|
|
84
|
+
checkpointType: "session",
|
|
85
|
+
};
|
|
86
|
+
case "export_completed":
|
|
87
|
+
return {
|
|
88
|
+
invalidate: [],
|
|
89
|
+
staleTargets: ["none"],
|
|
90
|
+
changeKinds: ["checkpoint"],
|
|
91
|
+
checkpointType: "export",
|
|
92
|
+
};
|
|
93
|
+
case "command_blocked":
|
|
94
|
+
return {
|
|
95
|
+
invalidate: ["workflowScope", "locations", "contextAnalytics"],
|
|
96
|
+
staleTargets: ["anchors"],
|
|
97
|
+
changeKinds: ["workflow"],
|
|
98
|
+
};
|
|
99
|
+
case "ready":
|
|
100
|
+
return {
|
|
101
|
+
invalidate: [
|
|
102
|
+
"render",
|
|
103
|
+
"navigation",
|
|
104
|
+
"outline",
|
|
105
|
+
"toc",
|
|
106
|
+
"sections",
|
|
107
|
+
"locations",
|
|
108
|
+
"reviewWork",
|
|
109
|
+
"chunks",
|
|
110
|
+
"textStream",
|
|
111
|
+
"contextAnalytics",
|
|
112
|
+
],
|
|
113
|
+
staleTargets: ["none"],
|
|
114
|
+
changeKinds: ["structure"],
|
|
115
|
+
};
|
|
116
|
+
case "context_analytics_changed":
|
|
117
|
+
return {
|
|
118
|
+
invalidate: ["contextAnalytics"],
|
|
119
|
+
staleTargets: ["none"],
|
|
120
|
+
changeKinds: ["selection", "content", "review", "workflow"],
|
|
121
|
+
};
|
|
122
|
+
case "dirty_changed":
|
|
123
|
+
case "autosave_state":
|
|
124
|
+
case "host_annotation_overlay_changed":
|
|
125
|
+
return {
|
|
126
|
+
invalidate: ["render"],
|
|
127
|
+
staleTargets: ["none"],
|
|
128
|
+
changeKinds: ["checkpoint"],
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
invalidate: ["render"],
|
|
134
|
+
staleTargets: ["none"],
|
|
135
|
+
changeKinds: ["content"],
|
|
136
|
+
};
|
|
137
|
+
}
|
|
@@ -3,6 +3,11 @@ import type {
|
|
|
3
3
|
NumberingLevelDefinition,
|
|
4
4
|
ParagraphNode,
|
|
5
5
|
} from "../model/canonical-document.ts";
|
|
6
|
+
import {
|
|
7
|
+
DEFAULT_NUMBERING_START_AT,
|
|
8
|
+
resolveNumberingDefinitionSet,
|
|
9
|
+
type ResolvedNumberingGeometry,
|
|
10
|
+
} from "./resolved-numbering-geometry.ts";
|
|
6
11
|
|
|
7
12
|
interface NumberingSequenceState {
|
|
8
13
|
counters: Array<number | undefined>;
|
|
@@ -10,56 +15,83 @@ interface NumberingSequenceState {
|
|
|
10
15
|
}
|
|
11
16
|
|
|
12
17
|
export interface NumberingPrefixResult {
|
|
13
|
-
text: string;
|
|
18
|
+
text: string | null;
|
|
19
|
+
level: number;
|
|
20
|
+
format: string;
|
|
21
|
+
startAt: number;
|
|
14
22
|
suffix?: "tab" | "space" | "nothing";
|
|
23
|
+
paragraphStyleId?: string;
|
|
24
|
+
isLegalNumbering?: boolean;
|
|
25
|
+
geometry: ResolvedNumberingGeometry;
|
|
15
26
|
}
|
|
16
27
|
|
|
17
28
|
export interface NumberingPrefixResolver {
|
|
18
29
|
resolve(numbering: ParagraphNode["numbering"] | undefined): string | null;
|
|
19
|
-
resolveDetailed(
|
|
30
|
+
resolveDetailed(
|
|
31
|
+
numbering: ParagraphNode["numbering"] | undefined,
|
|
32
|
+
paragraph?: ParagraphNode,
|
|
33
|
+
): NumberingPrefixResult | null;
|
|
34
|
+
resolveParagraph(paragraph: ParagraphNode): NumberingPrefixResult | null;
|
|
20
35
|
}
|
|
21
36
|
|
|
22
|
-
const DEFAULT_START_AT = 1;
|
|
23
|
-
|
|
24
37
|
export function createNumberingPrefixResolver(
|
|
25
38
|
catalog: NumberingCatalog,
|
|
26
39
|
): NumberingPrefixResolver {
|
|
27
40
|
const sequenceStates = new Map<string, NumberingSequenceState>();
|
|
28
41
|
|
|
29
|
-
function resolveInternal(
|
|
30
|
-
|
|
42
|
+
function resolveInternal(
|
|
43
|
+
numbering: ParagraphNode["numbering"] | undefined,
|
|
44
|
+
paragraph?: ParagraphNode,
|
|
45
|
+
): NumberingPrefixResult | null {
|
|
46
|
+
const resolved = resolveNumberingDefinitionSet(
|
|
47
|
+
catalog,
|
|
48
|
+
paragraph?.numbering ?? numbering,
|
|
49
|
+
paragraph,
|
|
50
|
+
);
|
|
51
|
+
if (!resolved) {
|
|
31
52
|
return null;
|
|
32
53
|
}
|
|
33
54
|
|
|
34
|
-
const
|
|
35
|
-
if (!
|
|
55
|
+
const resolvedNumbering = paragraph?.numbering ?? numbering;
|
|
56
|
+
if (!resolvedNumbering) {
|
|
36
57
|
return null;
|
|
37
58
|
}
|
|
38
59
|
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
60
|
+
const sequenceState = getSequenceState(sequenceStates, resolvedNumbering.numberingInstanceId);
|
|
61
|
+
advanceSequence(sequenceState, resolved.effectiveLevel.level, resolved.effectiveLevels);
|
|
43
62
|
|
|
44
|
-
|
|
45
|
-
|
|
63
|
+
// When isLegalNumbering is true, use decimal format for all referenced levels
|
|
64
|
+
const effectiveLevelDefs = resolved.effectiveLevel.isLegalNumbering
|
|
65
|
+
? new Map(
|
|
66
|
+
Array.from(resolved.effectiveLevels.entries()).map(([level, definition]) => [
|
|
67
|
+
level,
|
|
68
|
+
{ ...definition, format: "decimal" },
|
|
69
|
+
]),
|
|
70
|
+
)
|
|
71
|
+
: resolved.effectiveLevels;
|
|
72
|
+
|
|
73
|
+
const text = renderLevelText(
|
|
74
|
+
resolved.effectiveLevel.text,
|
|
75
|
+
sequenceState.counters,
|
|
76
|
+
effectiveLevelDefs,
|
|
46
77
|
);
|
|
47
|
-
|
|
48
|
-
if (!levelDefinition || levelDefinition.format === "none") {
|
|
78
|
+
if (resolved.effectiveLevel.format !== "none" && text === null) {
|
|
49
79
|
return null;
|
|
50
80
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
:
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
81
|
+
const visibleText = resolved.effectiveLevel.format === "none" ? null : text;
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
text: visibleText,
|
|
85
|
+
level: resolved.effectiveLevel.level,
|
|
86
|
+
format: resolved.effectiveLevel.format,
|
|
87
|
+
startAt: resolved.effectiveLevel.startAt ?? DEFAULT_NUMBERING_START_AT,
|
|
88
|
+
...(resolved.effectiveLevel.suffix ? { suffix: resolved.effectiveLevel.suffix } : {}),
|
|
89
|
+
...(resolved.effectiveLevel.paragraphStyleId
|
|
90
|
+
? { paragraphStyleId: resolved.effectiveLevel.paragraphStyleId }
|
|
91
|
+
: {}),
|
|
92
|
+
...(resolved.effectiveLevel.isLegalNumbering ? { isLegalNumbering: true } : {}),
|
|
93
|
+
geometry: resolved.geometry,
|
|
94
|
+
};
|
|
63
95
|
}
|
|
64
96
|
|
|
65
97
|
return {
|
|
@@ -67,8 +99,11 @@ export function createNumberingPrefixResolver(
|
|
|
67
99
|
const result = resolveInternal(numbering);
|
|
68
100
|
return result?.text ?? null;
|
|
69
101
|
},
|
|
70
|
-
resolveDetailed(numbering) {
|
|
71
|
-
return resolveInternal(numbering);
|
|
102
|
+
resolveDetailed(numbering, paragraph) {
|
|
103
|
+
return resolveInternal(numbering, paragraph);
|
|
104
|
+
},
|
|
105
|
+
resolveParagraph(paragraph) {
|
|
106
|
+
return resolveInternal(paragraph.numbering, paragraph);
|
|
72
107
|
},
|
|
73
108
|
};
|
|
74
109
|
}
|
|
@@ -94,13 +129,12 @@ function advanceSequence(
|
|
|
94
129
|
state: NumberingSequenceState,
|
|
95
130
|
currentLevel: number,
|
|
96
131
|
levelDefinitions: Map<number, NumberingLevelDefinition>,
|
|
97
|
-
overrides: NumberingCatalog["instances"][string]["overrides"],
|
|
98
132
|
): void {
|
|
99
133
|
if (state.lastLevel !== null && currentLevel <= state.lastLevel) {
|
|
100
134
|
state.counters.length = currentLevel + 1;
|
|
101
135
|
}
|
|
102
136
|
|
|
103
|
-
const startAt = getLevelStartAt(currentLevel, levelDefinitions
|
|
137
|
+
const startAt = getLevelStartAt(currentLevel, levelDefinitions);
|
|
104
138
|
const currentValue = state.counters[currentLevel];
|
|
105
139
|
state.counters[currentLevel] =
|
|
106
140
|
currentValue === undefined ? startAt : currentValue + 1;
|
|
@@ -110,14 +144,8 @@ function advanceSequence(
|
|
|
110
144
|
function getLevelStartAt(
|
|
111
145
|
level: number,
|
|
112
146
|
levelDefinitions: Map<number, NumberingLevelDefinition>,
|
|
113
|
-
overrides: NumberingCatalog["instances"][string]["overrides"],
|
|
114
147
|
): number {
|
|
115
|
-
|
|
116
|
-
if (override?.startAt !== undefined) {
|
|
117
|
-
return override.startAt;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return levelDefinitions.get(level)?.startAt ?? DEFAULT_START_AT;
|
|
148
|
+
return levelDefinitions.get(level)?.startAt ?? DEFAULT_NUMBERING_START_AT;
|
|
121
149
|
}
|
|
122
150
|
|
|
123
151
|
function renderLevelText(
|
|
@@ -38,10 +38,11 @@ export function estimateParagraphHeight(
|
|
|
38
38
|
block: Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>,
|
|
39
39
|
columnWidth: number,
|
|
40
40
|
): number {
|
|
41
|
+
const spacing = resolveParagraphSpacing(block);
|
|
41
42
|
const lineHeight = estimateParagraphLineHeight(block);
|
|
42
43
|
const lineCount = estimateParagraphLineCount(block, columnWidth);
|
|
43
|
-
const spacingBefore =
|
|
44
|
-
const spacingAfter =
|
|
44
|
+
const spacingBefore = spacing?.before ?? 0;
|
|
45
|
+
const spacingAfter = spacing?.after ?? 0;
|
|
45
46
|
return Math.max(
|
|
46
47
|
MIN_BLOCK_HEIGHT_TWIPS,
|
|
47
48
|
lineHeight * lineCount + spacingBefore + spacingAfter,
|
|
@@ -51,7 +52,7 @@ export function estimateParagraphHeight(
|
|
|
51
52
|
export function estimateParagraphLineHeight(
|
|
52
53
|
block: Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>,
|
|
53
54
|
): number {
|
|
54
|
-
const explicitLine = block
|
|
55
|
+
const explicitLine = resolveParagraphSpacing(block)?.line;
|
|
55
56
|
if (typeof explicitLine === "number" && explicitLine > 0) {
|
|
56
57
|
return explicitLine;
|
|
57
58
|
}
|
|
@@ -75,36 +76,55 @@ export function estimateParagraphLineCount(
|
|
|
75
76
|
columnWidth: number,
|
|
76
77
|
): number {
|
|
77
78
|
const averageCharWidth = estimateAverageCharWidth(block);
|
|
78
|
-
const
|
|
79
|
+
const lineMetrics = resolveParagraphLineMetrics(block, columnWidth, averageCharWidth);
|
|
79
80
|
let lineCount = 1;
|
|
80
|
-
let currentLineChars =
|
|
81
|
+
let currentLineChars = lineMetrics.initialLineChars;
|
|
82
|
+
let currentLineCapacity = lineMetrics.firstLineCapacity;
|
|
81
83
|
|
|
82
84
|
for (const segment of block.segments) {
|
|
83
85
|
switch (segment.kind) {
|
|
84
86
|
case "text":
|
|
85
87
|
currentLineChars += Array.from(segment.text).length;
|
|
86
|
-
while (currentLineChars >
|
|
88
|
+
while (currentLineChars > currentLineCapacity) {
|
|
87
89
|
lineCount += 1;
|
|
88
|
-
currentLineChars -=
|
|
90
|
+
currentLineChars -= currentLineCapacity;
|
|
91
|
+
currentLineCapacity = lineMetrics.subsequentLineCapacity;
|
|
89
92
|
}
|
|
90
93
|
break;
|
|
91
94
|
case "tab":
|
|
92
95
|
currentLineChars += 4;
|
|
96
|
+
while (currentLineChars > currentLineCapacity) {
|
|
97
|
+
lineCount += 1;
|
|
98
|
+
currentLineChars -= currentLineCapacity;
|
|
99
|
+
currentLineCapacity = lineMetrics.subsequentLineCapacity;
|
|
100
|
+
}
|
|
93
101
|
break;
|
|
94
102
|
case "hard_break":
|
|
95
103
|
lineCount += 1;
|
|
96
104
|
currentLineChars = 0;
|
|
105
|
+
currentLineCapacity = lineMetrics.subsequentLineCapacity;
|
|
97
106
|
break;
|
|
98
107
|
case "image":
|
|
99
108
|
lineCount += Math.max(1, Math.round(segment.display === "floating" ? 2 : 1));
|
|
100
109
|
currentLineChars = 0;
|
|
110
|
+
currentLineCapacity = lineMetrics.subsequentLineCapacity;
|
|
101
111
|
break;
|
|
102
112
|
case "note_ref":
|
|
103
113
|
currentLineChars += 1;
|
|
114
|
+
while (currentLineChars > currentLineCapacity) {
|
|
115
|
+
lineCount += 1;
|
|
116
|
+
currentLineChars -= currentLineCapacity;
|
|
117
|
+
currentLineCapacity = lineMetrics.subsequentLineCapacity;
|
|
118
|
+
}
|
|
104
119
|
break;
|
|
105
120
|
case "opaque_inline":
|
|
106
121
|
if (segment.presentation !== "quiet-marker") {
|
|
107
122
|
currentLineChars += segment.label.length > 0 ? 1 : 0;
|
|
123
|
+
while (currentLineChars > currentLineCapacity) {
|
|
124
|
+
lineCount += 1;
|
|
125
|
+
currentLineChars -= currentLineCapacity;
|
|
126
|
+
currentLineCapacity = lineMetrics.subsequentLineCapacity;
|
|
127
|
+
}
|
|
108
128
|
}
|
|
109
129
|
break;
|
|
110
130
|
}
|
|
@@ -198,6 +218,79 @@ function estimateAverageCharWidth(
|
|
|
198
218
|
return Math.max(96, Math.round(fontSizePoints * 12));
|
|
199
219
|
}
|
|
200
220
|
|
|
221
|
+
function resolveParagraphSpacing(
|
|
222
|
+
block: Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>,
|
|
223
|
+
): Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>["spacing"] | undefined {
|
|
224
|
+
return block.resolvedNumbering?.geometry.spacing ?? block.spacing;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function resolveParagraphLineMetrics(
|
|
228
|
+
block: Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>,
|
|
229
|
+
columnWidth: number,
|
|
230
|
+
averageCharWidth: number,
|
|
231
|
+
): {
|
|
232
|
+
initialLineChars: number;
|
|
233
|
+
firstLineCapacity: number;
|
|
234
|
+
subsequentLineCapacity: number;
|
|
235
|
+
} {
|
|
236
|
+
const textColumn =
|
|
237
|
+
block.resolvedNumbering?.geometry.textColumn ??
|
|
238
|
+
deriveParagraphTextColumn(block.indentation);
|
|
239
|
+
const hasResolvedTextColumn = Boolean(textColumn);
|
|
240
|
+
const baseLineWidth = resolveTextColumnWidth(columnWidth, textColumn);
|
|
241
|
+
const firstLineWidth =
|
|
242
|
+
textColumn?.firstLine && textColumn.firstLine > 0
|
|
243
|
+
? Math.max(averageCharWidth, baseLineWidth - textColumn.firstLine)
|
|
244
|
+
: baseLineWidth;
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
initialLineChars: hasResolvedTextColumn ? 0 : estimatedPrefixLength(block),
|
|
248
|
+
firstLineCapacity: resolveCharsPerLine(firstLineWidth, averageCharWidth),
|
|
249
|
+
subsequentLineCapacity: resolveCharsPerLine(baseLineWidth, averageCharWidth),
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function deriveParagraphTextColumn(
|
|
254
|
+
indentation: Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>["indentation"],
|
|
255
|
+
): { start: number; right?: number; firstLine?: number; hanging?: number } | undefined {
|
|
256
|
+
if (!indentation) {
|
|
257
|
+
return undefined;
|
|
258
|
+
}
|
|
259
|
+
const hanging =
|
|
260
|
+
indentation.hanging ??
|
|
261
|
+
(typeof indentation.firstLine === "number" && indentation.firstLine < 0
|
|
262
|
+
? Math.abs(indentation.firstLine)
|
|
263
|
+
: undefined);
|
|
264
|
+
const hasGeometry =
|
|
265
|
+
indentation.left !== undefined ||
|
|
266
|
+
indentation.right !== undefined ||
|
|
267
|
+
indentation.firstLine !== undefined ||
|
|
268
|
+
hanging !== undefined;
|
|
269
|
+
if (!hasGeometry) {
|
|
270
|
+
return undefined;
|
|
271
|
+
}
|
|
272
|
+
return {
|
|
273
|
+
start: indentation.left ?? 0,
|
|
274
|
+
...(indentation.right !== undefined ? { right: indentation.right } : {}),
|
|
275
|
+
...(indentation.firstLine !== undefined ? { firstLine: indentation.firstLine } : {}),
|
|
276
|
+
...(hanging !== undefined ? { hanging } : {}),
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function resolveTextColumnWidth(
|
|
281
|
+
columnWidth: number,
|
|
282
|
+
textColumn: { start: number; right?: number; firstLine?: number; hanging?: number } | undefined,
|
|
283
|
+
): number {
|
|
284
|
+
if (!textColumn) {
|
|
285
|
+
return columnWidth;
|
|
286
|
+
}
|
|
287
|
+
return Math.max(1, columnWidth - textColumn.start - (textColumn.right ?? 0));
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function resolveCharsPerLine(width: number, averageCharWidth: number): number {
|
|
291
|
+
return Math.max(1, Math.floor(width / averageCharWidth));
|
|
292
|
+
}
|
|
293
|
+
|
|
201
294
|
function estimatedPrefixLength(
|
|
202
295
|
block: Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>,
|
|
203
296
|
): number {
|