@beyondwork/docx-react-component 1.0.57 → 1.0.59
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 +1 -1
- package/package.json +2 -1
- package/src/api/awareness-identity-types.ts +4 -2
- package/src/api/comment-negotiation-types.ts +4 -1
- package/src/api/external-custody-types.ts +16 -0
- package/src/api/internal/build-ref-projections.ts +108 -0
- package/src/api/package-version.ts +1 -1
- package/src/api/participants-types.ts +11 -1
- package/src/api/public-types.ts +1149 -8
- package/src/api/scope-metadata-resolver-types.ts +6 -0
- package/src/compare/diff-engine.ts +3 -0
- package/src/core/commands/formatting-commands.ts +1 -0
- package/src/core/commands/index.ts +225 -16
- package/src/core/commands/legacy-form-field-commands.ts +181 -0
- package/src/core/commands/table-structure-commands.ts +149 -31
- package/src/core/selection/mapping.ts +20 -0
- package/src/core/state/editor-state.ts +2 -1
- package/src/index.ts +28 -0
- package/src/io/docx-session.ts +22 -3
- package/src/io/export/export-session.ts +11 -7
- package/src/io/export/ooxml-namespaces.ts +47 -0
- package/src/io/export/reattach-preserved-parts.ts +4 -16
- package/src/io/export/serialize-comments.ts +3 -131
- package/src/io/export/serialize-ffdata.ts +89 -0
- package/src/io/export/serialize-headers-footers.ts +5 -0
- package/src/io/export/serialize-main-document.ts +224 -34
- package/src/io/export/serialize-numbering.ts +22 -2
- package/src/io/export/serialize-revisions.ts +99 -0
- package/src/io/export/serialize-tables.ts +9 -0
- package/src/io/export/split-review-boundaries.ts +1 -0
- package/src/io/export/table-properties-xml.ts +14 -0
- package/src/io/load-scheduler.ts +70 -28
- package/src/io/normalize/normalize-text.ts +13 -0
- package/src/io/ooxml/_mini-xml.ts +198 -0
- package/src/io/ooxml/canonicalize-payload.ts +1 -4
- package/src/io/ooxml/chart/chart-style-table.ts +4 -3
- package/src/io/ooxml/chart/parse-chart-space.ts +2 -4
- package/src/io/ooxml/chart/parse-series.ts +2 -1
- package/src/io/ooxml/chart/resolve-color.ts +2 -2
- package/src/io/ooxml/chart/types.ts +6 -434
- package/src/io/ooxml/comment-presentation-payload.ts +6 -5
- package/src/io/ooxml/highlight-colors.ts +8 -5
- package/src/io/ooxml/parse-anchor.ts +68 -53
- package/src/io/ooxml/parse-comments.ts +14 -142
- package/src/io/ooxml/parse-complex-content.ts +3 -106
- package/src/io/ooxml/parse-drawing.ts +100 -195
- package/src/io/ooxml/parse-ffdata.ts +93 -0
- package/src/io/ooxml/parse-fields.ts +7 -146
- package/src/io/ooxml/parse-fill.ts +88 -8
- package/src/io/ooxml/parse-font-table.ts +5 -105
- package/src/io/ooxml/parse-footnotes.ts +28 -152
- package/src/io/ooxml/parse-headers-footers.ts +106 -212
- package/src/io/ooxml/parse-inline-media.ts +3 -200
- package/src/io/ooxml/parse-main-document.ts +180 -217
- package/src/io/ooxml/parse-numbering.ts +154 -335
- package/src/io/ooxml/parse-object.ts +147 -0
- package/src/io/ooxml/parse-ole-relationship.ts +82 -0
- package/src/io/ooxml/parse-paragraph-formatting.ts +7 -10
- package/src/io/ooxml/parse-picture-sdt.ts +85 -0
- package/src/io/ooxml/parse-picture.ts +120 -39
- package/src/io/ooxml/parse-revisions.ts +285 -51
- package/src/io/ooxml/parse-settings.ts +6 -99
- package/src/io/ooxml/parse-shapes.ts +25 -140
- package/src/io/ooxml/parse-styles.ts +3 -218
- package/src/io/ooxml/parse-tables.ts +76 -256
- package/src/io/ooxml/parse-theme.ts +1 -4
- package/src/io/ooxml/property-grab-bag.ts +5 -47
- package/src/io/ooxml/xml-element-serialize.ts +32 -0
- package/src/io/ooxml/xml-parser.ts +183 -0
- package/src/legal/bookmarks.ts +1 -1
- package/src/legal/cross-references.ts +1 -1
- package/src/legal/defined-terms.ts +1 -1
- package/src/legal/{_document-root.ts → document-root.ts} +8 -0
- package/src/legal/signature-blocks.ts +1 -1
- package/src/model/canonical-document.ts +165 -6
- package/src/model/chart-types.ts +439 -0
- package/src/model/snapshot.ts +3 -1
- package/src/review/store/comment-remapping.ts +24 -11
- package/src/review/store/revision-actions.ts +482 -2
- package/src/review/store/revision-store.ts +15 -0
- package/src/review/store/revision-types.ts +76 -0
- package/src/runtime/collab/remote-cursor-awareness.ts +24 -0
- package/src/runtime/collab/runtime-collab-sync.ts +33 -0
- package/src/runtime/diagnostics/build-diagnostic.ts +151 -0
- package/src/runtime/diagnostics/code-metadata-table.ts +221 -0
- package/src/runtime/document-runtime.ts +544 -35
- package/src/runtime/document-search.ts +176 -0
- package/src/runtime/edit-ops/index.ts +18 -2
- package/src/runtime/footnote-resolver.ts +130 -0
- package/src/runtime/layout/layout-engine-instance.ts +31 -4
- package/src/runtime/layout/layout-engine-version.ts +37 -1
- package/src/runtime/layout/page-graph.ts +14 -1
- package/src/runtime/layout/resolved-formatting-state.ts +21 -0
- package/src/runtime/numbering-prefix.ts +17 -0
- package/src/runtime/query-scopes.ts +183 -0
- package/src/runtime/resolved-numbering-geometry.ts +37 -6
- package/src/runtime/revision-runtime.ts +27 -1
- package/src/runtime/scope-resolver.ts +60 -0
- package/src/runtime/selection/post-edit-validator.ts +60 -6
- package/src/runtime/structure-ops/index.ts +20 -4
- package/src/runtime/surface-projection.ts +293 -18
- package/src/runtime/table-schema.ts +6 -0
- package/src/runtime/theme-color-resolver.ts +2 -2
- package/src/runtime/units.ts +9 -0
- package/src/runtime/workflow-rail-segments.ts +4 -0
- package/src/ui/WordReviewEditor.tsx +258 -44
- package/src/ui/editor-runtime-boundary.ts +13 -0
- package/src/ui/editor-shell-view.tsx +4 -1
- package/src/ui/headless/chrome-registry.ts +53 -0
- package/src/ui/headless/selection-tool-resolver.ts +11 -1
- package/src/ui-tailwind/chrome/chrome-preset-model.ts +13 -0
- package/src/ui-tailwind/chrome/tw-command-palette-mount.tsx +96 -0
- package/src/ui-tailwind/chrome/tw-context-menu.tsx +2 -1
- package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +5 -4
- package/src/ui-tailwind/chrome/tw-mode-dock.tsx +6 -2
- package/src/ui-tailwind/chrome/use-container-breakpoint.ts +111 -0
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +23 -9
- package/src/ui-tailwind/chrome-overlay/tw-object-selection-overlay.tsx +158 -0
- package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +6 -7
- package/src/ui-tailwind/editor-surface/pm-schema.ts +105 -17
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +13 -0
- package/src/ui-tailwind/editor-surface/shape-renderer.ts +76 -14
- package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +18 -1
- package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +2 -0
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +18 -2
- package/src/ui-tailwind/index.ts +9 -0
- package/src/ui-tailwind/page-chrome-model.ts +77 -5
- package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +56 -1
- package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +2 -0
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +116 -113
- package/src/ui-tailwind/review/tw-review-rail-footer.tsx +2 -2
- package/src/ui-tailwind/theme/tokens.ts +14 -0
- package/src/ui-tailwind/toolbar/tw-shell-header.tsx +5 -0
- package/src/ui-tailwind/tw-review-workspace.tsx +52 -87
- package/src/validation/diagnostics.ts +1 -0
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
import {
|
|
9
9
|
isSupportedShapeGeometry,
|
|
10
10
|
renderShapeSvg,
|
|
11
|
+
type GradientFill,
|
|
11
12
|
type ShapeFill,
|
|
12
13
|
type ShapeLine,
|
|
13
14
|
} from "./shape-renderer.ts";
|
|
@@ -83,6 +84,16 @@ function safeHexColor(raw: string | null | undefined): string | null {
|
|
|
83
84
|
return HEX_COLOR_RE.test(raw) ? `#${raw}` : null;
|
|
84
85
|
}
|
|
85
86
|
|
|
87
|
+
/** Strict CSS hex validator for inline style sinks. Accepts 3/4/6/8-digit hex with optional leading #. */
|
|
88
|
+
function safeFilterHexColor(raw: string | null | undefined): string | null {
|
|
89
|
+
if (!raw || raw === "auto") return null;
|
|
90
|
+
const trimmed = raw.trim();
|
|
91
|
+
if (!/^#?(?:[0-9A-Fa-f]{3}|[0-9A-Fa-f]{4}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/.test(trimmed)) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
return `#${trimmed.replace(/^#/, "").toUpperCase()}`;
|
|
95
|
+
}
|
|
96
|
+
|
|
86
97
|
/** Validate a CSS color value (may already include #). Returns the value or null. */
|
|
87
98
|
function safeCssColor(raw: string | null | undefined): string | null {
|
|
88
99
|
if (!raw) return null;
|
|
@@ -124,6 +135,20 @@ function resolveMarkerJustificationCss(raw: string | null): string {
|
|
|
124
135
|
}
|
|
125
136
|
}
|
|
126
137
|
|
|
138
|
+
function resolveMarkerAlignCss(raw: string | null): string {
|
|
139
|
+
switch (raw) {
|
|
140
|
+
case "left":
|
|
141
|
+
return "left";
|
|
142
|
+
case "center":
|
|
143
|
+
return "center";
|
|
144
|
+
case "right":
|
|
145
|
+
case "both":
|
|
146
|
+
case "distribute":
|
|
147
|
+
default:
|
|
148
|
+
return "right";
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
127
152
|
/**
|
|
128
153
|
* ProseMirror schema for the supported live surface slice.
|
|
129
154
|
*
|
|
@@ -147,8 +172,10 @@ export const editorSchema = new Schema({
|
|
|
147
172
|
numberingPrefix: { default: null },
|
|
148
173
|
numberingSuffix: { default: null },
|
|
149
174
|
numberingMarkerWidth: { default: null },
|
|
175
|
+
numberingMarkerStart: { default: null },
|
|
150
176
|
numberingMarkerJustification: { default: null },
|
|
151
177
|
numberingMarkerRunProperties: { default: null },
|
|
178
|
+
numberingPicBulletSrc: { default: null },
|
|
152
179
|
alignment: { default: null },
|
|
153
180
|
spacingBefore: { default: null },
|
|
154
181
|
spacingAfter: { default: null },
|
|
@@ -217,7 +244,7 @@ export const editorSchema = new Schema({
|
|
|
217
244
|
else if (lineSpacing && lineRule === "exact") styles.push(`line-height: ${lineSpacing / 20}pt`);
|
|
218
245
|
else if (lineSpacing && lineRule === "atLeast") styles.push(`min-height: ${lineSpacing / 20}pt`);
|
|
219
246
|
const indentLeft = node.attrs.indentLeft as number | null;
|
|
220
|
-
if (indentLeft) styles.push(`padding-left: ${indentLeft / 20}pt`);
|
|
247
|
+
if (indentLeft !== null) styles.push(`padding-left: ${indentLeft / 20}pt`);
|
|
221
248
|
const indentRight = node.attrs.indentRight as number | null;
|
|
222
249
|
if (indentRight) styles.push(`padding-right: ${indentRight / 20}pt`);
|
|
223
250
|
const indentFirstLine = node.attrs.indentFirstLine as number | null;
|
|
@@ -271,7 +298,9 @@ export const editorSchema = new Schema({
|
|
|
271
298
|
const numberingLevel = node.attrs.numberingLevel as number | null;
|
|
272
299
|
const numberingSuffix = node.attrs.numberingSuffix as "tab" | "space" | "nothing" | null;
|
|
273
300
|
const numberingMarkerWidth = node.attrs.numberingMarkerWidth as number | null;
|
|
301
|
+
const numberingMarkerStart = node.attrs.numberingMarkerStart as number | null;
|
|
274
302
|
const numberingMarkerJustification = node.attrs.numberingMarkerJustification as string | null;
|
|
303
|
+
const numberingPicBulletSrc = node.attrs.numberingPicBulletSrc as string | null;
|
|
275
304
|
const children: Array<string | number | readonly unknown[]> = [];
|
|
276
305
|
if (pageBreak) {
|
|
277
306
|
children.push([
|
|
@@ -285,10 +314,10 @@ export const editorSchema = new Schema({
|
|
|
285
314
|
"Page break",
|
|
286
315
|
]);
|
|
287
316
|
}
|
|
288
|
-
if (numberingPrefix) {
|
|
317
|
+
if (numberingPrefix || numberingPicBulletSrc) {
|
|
289
318
|
const hasResolvedMarkerWidth =
|
|
290
319
|
typeof numberingMarkerWidth === "number" && numberingMarkerWidth > 0;
|
|
291
|
-
const fallbackMinWidth = Math.min(Math.max(numberingPrefix
|
|
320
|
+
const fallbackMinWidth = Math.min(Math.max((numberingPrefix?.length ?? 1) + 1, 4), 14);
|
|
292
321
|
const fallbackMarginRight =
|
|
293
322
|
numberingSuffix === "nothing"
|
|
294
323
|
? "0.25rem"
|
|
@@ -315,7 +344,7 @@ export const editorSchema = new Schema({
|
|
|
315
344
|
|
|
316
345
|
const prefixStyles = [
|
|
317
346
|
`font-variant-numeric: tabular-nums`,
|
|
318
|
-
`
|
|
347
|
+
`text-align: ${resolveMarkerAlignCss(numberingMarkerJustification)}`,
|
|
319
348
|
];
|
|
320
349
|
|
|
321
350
|
if (markerRunProperties) {
|
|
@@ -344,9 +373,11 @@ export const editorSchema = new Schema({
|
|
|
344
373
|
`width: ${markerWidthPt}pt`,
|
|
345
374
|
`min-width: ${markerWidthPt}pt`,
|
|
346
375
|
`flex-basis: ${markerWidthPt}pt`,
|
|
376
|
+
`margin-left: -${markerWidthPt}pt`,
|
|
347
377
|
`margin-right: 0`,
|
|
348
378
|
`overflow: visible`,
|
|
349
379
|
);
|
|
380
|
+
void numberingMarkerStart; // consumed via paragraph padding-left geometry
|
|
350
381
|
} else {
|
|
351
382
|
prefixStyles.push(
|
|
352
383
|
`min-width: ${fallbackMinWidth}ch`,
|
|
@@ -359,14 +390,16 @@ export const editorSchema = new Schema({
|
|
|
359
390
|
{
|
|
360
391
|
class: baseClasses.join(" "),
|
|
361
392
|
contenteditable: "false",
|
|
362
|
-
"data-numbering-prefix": numberingPrefix,
|
|
393
|
+
"data-numbering-prefix": numberingPicBulletSrc ? "" : (numberingPrefix ?? ""),
|
|
363
394
|
...(typeof numberingLevel === "number"
|
|
364
395
|
? { "data-numbering-level": String(numberingLevel) }
|
|
365
396
|
: {}),
|
|
366
397
|
...(numberingSuffix ? { "data-numbering-suffix": numberingSuffix } : {}),
|
|
367
398
|
style: prefixStyles.join("; "),
|
|
368
399
|
},
|
|
369
|
-
|
|
400
|
+
numberingPicBulletSrc
|
|
401
|
+
? (["img", { src: numberingPicBulletSrc, alt: "", "aria-hidden": "true", style: "max-width:100%;max-height:100%;object-fit:contain;display:block;" }] as readonly unknown[])
|
|
402
|
+
: (numberingPrefix ?? ""),
|
|
370
403
|
]);
|
|
371
404
|
}
|
|
372
405
|
children.push([
|
|
@@ -465,6 +498,12 @@ export const editorSchema = new Schema({
|
|
|
465
498
|
wrapMode: { default: null },
|
|
466
499
|
distMargins: { default: null },
|
|
467
500
|
positionH: { default: null },
|
|
501
|
+
// Lane 6d N9.b — polygon clip for tight/through wrap.
|
|
502
|
+
wrapPolygon: { default: null },
|
|
503
|
+
// Lane 6d N11.b — CSS filter effects (soft-edge, outer shadow, glow).
|
|
504
|
+
softEdgeRadius: { default: null },
|
|
505
|
+
outerShadow: { default: null },
|
|
506
|
+
glow: { default: null },
|
|
468
507
|
},
|
|
469
508
|
toDOM(node) {
|
|
470
509
|
const isMissing = node.attrs.state === "missing";
|
|
@@ -496,14 +535,67 @@ export const editorSchema = new Schema({
|
|
|
496
535
|
`inset(${(srcRect.top / SRCRECT_UNITS_PER_PERCENT).toFixed(3)}% ${(srcRect.right / SRCRECT_UNITS_PER_PERCENT).toFixed(3)}% ${(srcRect.bottom / SRCRECT_UNITS_PER_PERCENT).toFixed(3)}% ${(srcRect.left / SRCRECT_UNITS_PER_PERCENT).toFixed(3)}%)`,
|
|
497
536
|
);
|
|
498
537
|
}
|
|
538
|
+
// N11.b filter effects → CSS filter on the img element.
|
|
539
|
+
const softEdgeRadius = node.attrs.softEdgeRadius as number | null;
|
|
540
|
+
const outerShadow = node.attrs.outerShadow as {
|
|
541
|
+
blurRad: number; dist: number; dir: number; color: string;
|
|
542
|
+
colorType: "srgbClr" | "schemeClr";
|
|
543
|
+
} | null;
|
|
544
|
+
const glow = node.attrs.glow as {
|
|
545
|
+
radius: number; color: string;
|
|
546
|
+
colorType: "srgbClr" | "schemeClr";
|
|
547
|
+
} | null;
|
|
548
|
+
const filterParts: string[] = [];
|
|
549
|
+
if (softEdgeRadius) {
|
|
550
|
+
filterParts.push(`blur(${(softEdgeRadius / EMU_PER_PX).toFixed(2)}px)`);
|
|
551
|
+
}
|
|
552
|
+
// Defense in depth: even though parse-picture.ts validates
|
|
553
|
+
// srgbClr@val against a strict hex allowlist, re-validate here at
|
|
554
|
+
// the CSS sink so a future parser refactor or a bypass that lands
|
|
555
|
+
// attacker-controlled text in node.attrs cannot escape
|
|
556
|
+
// `drop-shadow(#…)` into arbitrary CSS (e.g. `FF0000) url(…)/*`).
|
|
557
|
+
// safeFilterHexColor returns `#RRGGBB` on valid hex input and
|
|
558
|
+
// empty string otherwise, so schemeClr tokens (e.g. "accent1")
|
|
559
|
+
// naturally skip this branch until a theme resolver runs.
|
|
560
|
+
const safeFilterHexColor = (raw: unknown): string => {
|
|
561
|
+
return typeof raw === "string" &&
|
|
562
|
+
/^[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$/.test(raw)
|
|
563
|
+
? `#${raw.toUpperCase()}`
|
|
564
|
+
: "";
|
|
565
|
+
};
|
|
566
|
+
if (glow) {
|
|
567
|
+
const glowColor = safeFilterHexColor(glow.color);
|
|
568
|
+
if (glowColor) {
|
|
569
|
+
filterParts.push(`drop-shadow(0 0 ${(glow.radius / EMU_PER_PX).toFixed(2)}px ${glowColor})`);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
if (outerShadow) {
|
|
573
|
+
const shadowColor = safeFilterHexColor(outerShadow.color);
|
|
574
|
+
if (shadowColor) {
|
|
575
|
+
const blurPx = (outerShadow.blurRad / EMU_PER_PX).toFixed(2);
|
|
576
|
+
const distPx = outerShadow.dist / EMU_PER_PX;
|
|
577
|
+
const dirRad = (outerShadow.dir / ROTATION_UNITS_PER_DEGREE) * (Math.PI / 180);
|
|
578
|
+
const dx = (distPx * Math.cos(dirRad)).toFixed(2);
|
|
579
|
+
const dy = (distPx * Math.sin(dirRad)).toFixed(2);
|
|
580
|
+
filterParts.push(`drop-shadow(${dx}px ${dy}px ${blurPx}px ${shadowColor})`);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
499
583
|
// N9 float-wrap → CSS float + shape-outside on the wrapper span.
|
|
500
584
|
const wrapMode = node.attrs.wrapMode as string | null;
|
|
501
585
|
const positionH = node.attrs.positionH as { align?: string } | null;
|
|
502
586
|
const distMargins = node.attrs.distMargins as
|
|
503
587
|
| { top?: number; bottom?: number; left?: number; right?: number }
|
|
504
588
|
| null;
|
|
589
|
+
const wrapPolygon = node.attrs.wrapPolygon as Array<{ x: number; y: number }> | null;
|
|
505
590
|
const wrapperStyleParts: string[] = [];
|
|
506
|
-
if (isFloating && wrapMode === "
|
|
591
|
+
if (isFloating && (wrapMode === "tight" || wrapMode === "through") && wrapPolygon?.length) {
|
|
592
|
+
// N9.b — polygon clip: OOXML wrapPolygon coords are in 21600ths-of-image units.
|
|
593
|
+
const floatSide = positionH?.align === "right" ? "right" : "left";
|
|
594
|
+
const pts = wrapPolygon
|
|
595
|
+
.map((p) => `${(p.x / 21600 * 100).toFixed(2)}% ${(p.y / 21600 * 100).toFixed(2)}%`)
|
|
596
|
+
.join(", ");
|
|
597
|
+
wrapperStyleParts.push(`float:${floatSide}`, `shape-outside:polygon(${pts})`);
|
|
598
|
+
} else if (isFloating && wrapMode === "square") {
|
|
507
599
|
const floatSide = positionH?.align === "right" ? "right" : "left";
|
|
508
600
|
wrapperStyleParts.push(
|
|
509
601
|
`float:${floatSide}`,
|
|
@@ -527,6 +619,7 @@ export const editorSchema = new Schema({
|
|
|
527
619
|
heightPx ? `height:${heightPx}px` : "",
|
|
528
620
|
transformParts.length > 0 ? `transform:${transformParts.join(" ")}` : "",
|
|
529
621
|
clipParts.length > 0 ? `clip-path:${clipParts[0]}` : "",
|
|
622
|
+
filterParts.length > 0 ? `filter:${filterParts.join(" ")}` : "",
|
|
530
623
|
].filter(Boolean).join(";");
|
|
531
624
|
const wrapperStyle = wrapperStyleParts.join(";");
|
|
532
625
|
const wrapperAttrs: Record<string, string> = {
|
|
@@ -872,14 +965,7 @@ export const editorSchema = new Schema({
|
|
|
872
965
|
const geometry = node.attrs.geometry as string | null;
|
|
873
966
|
const fill = node.attrs.fill as
|
|
874
967
|
| ShapeFill
|
|
875
|
-
|
|
|
876
|
-
kind: "gradient";
|
|
877
|
-
stops: Array<{ pos: number; color: string; colorType: "srgbClr" | "schemeClr" }>;
|
|
878
|
-
direction:
|
|
879
|
-
| { kind: "linear"; angle: number; scaled?: boolean }
|
|
880
|
-
| { kind: "path"; path: "circle" | "rect" | "shape" };
|
|
881
|
-
rotWithShape?: boolean;
|
|
882
|
-
}
|
|
968
|
+
| GradientFill
|
|
883
969
|
| {
|
|
884
970
|
kind: "pattern";
|
|
885
971
|
preset: string;
|
|
@@ -891,11 +977,13 @@ export const editorSchema = new Schema({
|
|
|
891
977
|
const heightEmu = node.attrs.heightEmu as number | null;
|
|
892
978
|
const widthPx = widthEmu ? Math.max(8, Math.round(widthEmu / EMU_PER_PX)) : null;
|
|
893
979
|
const heightPx = heightEmu ? Math.max(8, Math.round(heightEmu / EMU_PER_PX)) : null;
|
|
980
|
+
// N10.b — gradient fills pass through to renderShapeSvg (SVG defs path).
|
|
981
|
+
// Pattern fills remain unsupported → chip fallback.
|
|
894
982
|
const svgFill =
|
|
895
983
|
fill === undefined || fill === null
|
|
896
984
|
? undefined
|
|
897
|
-
: fill.kind === "solid" || fill.kind === "none"
|
|
898
|
-
? fill
|
|
985
|
+
: fill.kind === "solid" || fill.kind === "none" || fill.kind === "gradient"
|
|
986
|
+
? (fill as ShapeFill | GradientFill)
|
|
899
987
|
: undefined;
|
|
900
988
|
// N10 — try SVG render path for supported geometries with extent.
|
|
901
989
|
if (
|
|
@@ -374,8 +374,14 @@ function buildParagraph(
|
|
|
374
374
|
paragraphLayout.indentation.firstLine < 0
|
|
375
375
|
? Math.abs(paragraphLayout.indentation.firstLine)
|
|
376
376
|
: null),
|
|
377
|
+
numberingMarkerStart: paragraphLayout.markerLane?.start ?? null,
|
|
377
378
|
numberingMarkerJustification: paragraphLayout.markerJustification ?? null,
|
|
378
379
|
numberingMarkerRunProperties: block.resolvedNumbering?.markerRunProperties ?? null,
|
|
380
|
+
numberingPicBulletSrc: (() => {
|
|
381
|
+
const mediaId = block.resolvedNumbering?.picBulletMediaId;
|
|
382
|
+
if (!mediaId) return null;
|
|
383
|
+
return mediaPreviews[mediaId]?.src ?? null;
|
|
384
|
+
})(),
|
|
379
385
|
shadingFill: block.shading?.fill ?? cascade?.shading?.fill ?? null,
|
|
380
386
|
borderTop: (block.borders as Record<string, unknown>)?.top ?? cascadeBorders?.top ?? null,
|
|
381
387
|
borderBottom: (block.borders as Record<string, unknown>)?.bottom ?? cascadeBorders?.bottom ?? null,
|
|
@@ -464,6 +470,12 @@ function buildInlineContent(
|
|
|
464
470
|
wrapMode: segment.anchor?.wrapMode ?? null,
|
|
465
471
|
distMargins: segment.anchor?.distMargins ?? null,
|
|
466
472
|
positionH: segment.anchor?.positionH ?? null,
|
|
473
|
+
// Lane 6d N9.b — polygon clip.
|
|
474
|
+
wrapPolygon: segment.anchor?.wrapPolygon ?? null,
|
|
475
|
+
// Lane 6d N11.b — filter effects.
|
|
476
|
+
softEdgeRadius: segment.pictureEffects?.softEdgeRadius ?? null,
|
|
477
|
+
outerShadow: segment.pictureEffects?.outerShadow ?? null,
|
|
478
|
+
glow: segment.pictureEffects?.glow ?? null,
|
|
467
479
|
}),
|
|
468
480
|
];
|
|
469
481
|
}
|
|
@@ -583,6 +595,7 @@ function buildTable(
|
|
|
583
595
|
{
|
|
584
596
|
styleId: block.styleId ?? null,
|
|
585
597
|
gridColumns: block.gridColumns,
|
|
598
|
+
gridColumnsRelative: block.gridColumnsRelative ?? null,
|
|
586
599
|
alignment: block.alignment ?? null,
|
|
587
600
|
tblLookFirstRow: block.tblLook?.firstRow ?? false,
|
|
588
601
|
tblLookLastRow: block.tblLook?.lastRow ?? false,
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* fill; `noLine: true` → `stroke: "none"`.
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import { EMU_PER_PX } from "../../runtime/units";
|
|
20
|
+
import { EMU_PER_PX, ROTATION_UNITS_PER_DEGREE } from "../../runtime/units";
|
|
21
21
|
|
|
22
22
|
const SUPPORTED_GEOMETRIES = new Set(["rect", "ellipse", "roundRect"]);
|
|
23
23
|
|
|
@@ -81,9 +81,18 @@ export function resolveLineCss(line: ShapeLine | undefined): ResolvedLineCss {
|
|
|
81
81
|
return { stroke, strokeWidth };
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
export type GradientFill = {
|
|
85
|
+
kind: "gradient";
|
|
86
|
+
stops: Array<{ pos: number; color: string; colorType: "srgbClr" | "schemeClr" }>;
|
|
87
|
+
direction:
|
|
88
|
+
| { kind: "linear"; angle: number; scaled?: boolean }
|
|
89
|
+
| { kind: "path"; path: "circle" | "rect" | "shape" };
|
|
90
|
+
rotWithShape?: boolean;
|
|
91
|
+
};
|
|
92
|
+
|
|
84
93
|
export interface ShapeSegmentLike {
|
|
85
94
|
geometry?: string;
|
|
86
|
-
fill?: ShapeFill;
|
|
95
|
+
fill?: ShapeFill | GradientFill;
|
|
87
96
|
line?: ShapeLine;
|
|
88
97
|
}
|
|
89
98
|
|
|
@@ -94,6 +103,44 @@ export interface ShapeSegmentLike {
|
|
|
94
103
|
*/
|
|
95
104
|
export type SvgSpec = readonly [string, Record<string, string>, ...SvgSpec[]];
|
|
96
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Build an SVG `<defs>` element containing a `<linearGradient>` or
|
|
108
|
+
* `<radialGradient>` for the given gradient fill. `id` is the gradient
|
|
109
|
+
* element ID referenced via `fill="url(#id)"` on the geometry element.
|
|
110
|
+
*
|
|
111
|
+
* OOXML linear gradient angle: 60000ths of a degree, clockwise from north.
|
|
112
|
+
* Converted to SVG objectBoundingBox coordinates via:
|
|
113
|
+
* x1 = 0.5 − 0.5·sin(θ), y1 = 0.5 + 0.5·cos(θ)
|
|
114
|
+
* x2 = 0.5 + 0.5·sin(θ), y2 = 0.5 − 0.5·cos(θ)
|
|
115
|
+
* where θ is in radians.
|
|
116
|
+
*
|
|
117
|
+
* OOXML stop pos: 0–100000 (= 0–100%).
|
|
118
|
+
*/
|
|
119
|
+
function renderGradientDefs(fill: GradientFill, id: string): SvgSpec {
|
|
120
|
+
const stopEls: SvgSpec[] = fill.stops.map(
|
|
121
|
+
(s): SvgSpec => [
|
|
122
|
+
"stop",
|
|
123
|
+
{
|
|
124
|
+
offset: `${(s.pos / 1000).toFixed(2)}%`,
|
|
125
|
+
"stop-color": s.colorType === "srgbClr" ? `#${s.color}` : "currentColor",
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
let gradEl: SvgSpec;
|
|
131
|
+
if (fill.direction.kind === "linear") {
|
|
132
|
+
const rad = (fill.direction.angle / ROTATION_UNITS_PER_DEGREE) * (Math.PI / 180);
|
|
133
|
+
const x1 = (0.5 - 0.5 * Math.sin(rad)).toFixed(4);
|
|
134
|
+
const y1 = (0.5 + 0.5 * Math.cos(rad)).toFixed(4);
|
|
135
|
+
const x2 = (0.5 + 0.5 * Math.sin(rad)).toFixed(4);
|
|
136
|
+
const y2 = (0.5 - 0.5 * Math.cos(rad)).toFixed(4);
|
|
137
|
+
gradEl = ["linearGradient", { id, x1, y1, x2, y2, gradientUnits: "objectBoundingBox" }, ...stopEls];
|
|
138
|
+
} else {
|
|
139
|
+
gradEl = ["radialGradient", { id, cx: "50%", cy: "50%", r: "50%", gradientUnits: "objectBoundingBox" }, ...stopEls];
|
|
140
|
+
}
|
|
141
|
+
return ["defs", {}, gradEl];
|
|
142
|
+
}
|
|
143
|
+
|
|
97
144
|
/**
|
|
98
145
|
* Render a supported geometry into a PM-compatible DOMOutputSpec tree
|
|
99
146
|
* for an inline `<svg>`. Returns `null` when the geometry is unsupported
|
|
@@ -101,6 +148,9 @@ export type SvgSpec = readonly [string, Record<string, string>, ...SvgSpec[]];
|
|
|
101
148
|
*
|
|
102
149
|
* The SVG is sized 1:1 to its container; the wrapper span owns the
|
|
103
150
|
* outer `width:Xpx; height:Ypx`.
|
|
151
|
+
*
|
|
152
|
+
* Gradient fills: emits `<defs><linearGradient>` / `<radialGradient>` and
|
|
153
|
+
* references it via `fill="url(#g0)"` on the geometry element.
|
|
104
154
|
*/
|
|
105
155
|
export function renderShapeSvg(
|
|
106
156
|
segment: ShapeSegmentLike,
|
|
@@ -111,7 +161,19 @@ export function renderShapeSvg(
|
|
|
111
161
|
if (!segment.geometry || !SUPPORTED_GEOMETRIES.has(segment.geometry)) {
|
|
112
162
|
return null;
|
|
113
163
|
}
|
|
114
|
-
|
|
164
|
+
|
|
165
|
+
let fillAttr: string;
|
|
166
|
+
let gradDefs: SvgSpec | null = null;
|
|
167
|
+
if (segment.fill && segment.fill.kind === "gradient") {
|
|
168
|
+
gradDefs = renderGradientDefs(segment.fill, "g0");
|
|
169
|
+
fillAttr = "url(#g0)";
|
|
170
|
+
} else {
|
|
171
|
+
fillAttr = resolveFillCss(segment.fill as ShapeFill | undefined).fill;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Keep resolveFillCss call for non-gradient so the isSchemePlaceholder
|
|
175
|
+
// warning path (future) still fires when needed.
|
|
176
|
+
const fillCss = { fill: fillAttr, isSchemePlaceholder: false };
|
|
115
177
|
const lineCss = resolveLineCss(segment.line);
|
|
116
178
|
const sw = lineCss.strokeWidth;
|
|
117
179
|
// Inset the geometry by half the stroke so the stroke paints inside
|
|
@@ -184,17 +246,17 @@ export function renderShapeSvg(
|
|
|
184
246
|
// `xmlns` *attribute* after createElement() is meaningless — the
|
|
185
247
|
// resulting node is HTMLUnknownElement and won't paint as SVG.
|
|
186
248
|
// Children inherit the namespace, so the geometry tag stays bare.
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
];
|
|
249
|
+
const svgAttrs = {
|
|
250
|
+
viewBox: `0 0 ${widthPx} ${heightPx}`,
|
|
251
|
+
width: String(widthPx),
|
|
252
|
+
height: String(heightPx),
|
|
253
|
+
preserveAspectRatio: "none",
|
|
254
|
+
"aria-hidden": "true",
|
|
255
|
+
};
|
|
256
|
+
if (gradDefs) {
|
|
257
|
+
return ["http://www.w3.org/2000/svg svg", svgAttrs, gradDefs, geometryEl];
|
|
258
|
+
}
|
|
259
|
+
return ["http://www.w3.org/2000/svg svg", svgAttrs, geometryEl];
|
|
198
260
|
}
|
|
199
261
|
|
|
200
262
|
/**
|
|
@@ -71,6 +71,20 @@ export function resolveMarkerJustificationCss(raw: string | undefined): string {
|
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
export function resolveMarkerAlignCss(raw: string | undefined): React.CSSProperties["textAlign"] {
|
|
75
|
+
switch (raw) {
|
|
76
|
+
case "left":
|
|
77
|
+
return "left";
|
|
78
|
+
case "center":
|
|
79
|
+
return "center";
|
|
80
|
+
case "right":
|
|
81
|
+
case "both":
|
|
82
|
+
case "distribute":
|
|
83
|
+
default:
|
|
84
|
+
return "right";
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
74
88
|
/** Build CSSProperties for a paragraph block from spacing/indent/alignment. */
|
|
75
89
|
export function buildParagraphStyle(
|
|
76
90
|
block: Extract<SurfaceBlockSnapshot, { kind: "paragraph" }>,
|
|
@@ -144,11 +158,12 @@ export function buildMarkerStyle(
|
|
|
144
158
|
suffix: "tab" | "space" | "nothing" | undefined,
|
|
145
159
|
markerRunProperties: CanonicalRunFormatting | undefined,
|
|
146
160
|
markerWidth: number | undefined,
|
|
161
|
+
markerStart: number | undefined,
|
|
147
162
|
markerJustification: string | undefined,
|
|
148
163
|
): React.CSSProperties {
|
|
149
164
|
const style: React.CSSProperties = {
|
|
150
165
|
fontVariantNumeric: "tabular-nums",
|
|
151
|
-
|
|
166
|
+
textAlign: resolveMarkerAlignCss(markerJustification),
|
|
152
167
|
};
|
|
153
168
|
|
|
154
169
|
if (markerRunProperties) {
|
|
@@ -180,8 +195,10 @@ export function buildMarkerStyle(
|
|
|
180
195
|
style.width = `${markerWidthPt}pt`;
|
|
181
196
|
style.minWidth = `${markerWidthPt}pt`;
|
|
182
197
|
style.flexBasis = `${markerWidthPt}pt`;
|
|
198
|
+
style.marginLeft = `-${markerWidthPt}pt`;
|
|
183
199
|
style.marginRight = 0;
|
|
184
200
|
style.overflow = "visible";
|
|
201
|
+
void markerStart; // consumed via paragraph padding-left geometry
|
|
185
202
|
} else {
|
|
186
203
|
const fallbackMinWidth = Math.min(Math.max(prefix.length + 1, 4), 14);
|
|
187
204
|
const fallbackMarginRight =
|
|
@@ -142,6 +142,7 @@ function ParagraphBlock({
|
|
|
142
142
|
const resolvedNumbering = block.resolvedNumbering;
|
|
143
143
|
const markerRunProperties = resolvedNumbering?.markerRunProperties;
|
|
144
144
|
const markerWidth = resolvedNumbering?.geometry?.markerLane?.width;
|
|
145
|
+
const markerStart = resolvedNumbering?.geometry?.markerLane?.start;
|
|
145
146
|
const markerJustification = resolvedNumbering?.geometry?.markerJustification;
|
|
146
147
|
|
|
147
148
|
const prefixSpan =
|
|
@@ -164,6 +165,7 @@ function ParagraphBlock({
|
|
|
164
165
|
numberingSuffix,
|
|
165
166
|
markerRunProperties,
|
|
166
167
|
markerWidth,
|
|
168
|
+
markerStart,
|
|
167
169
|
markerJustification,
|
|
168
170
|
)}
|
|
169
171
|
>
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import type { Node as PMNode } from "prosemirror-model";
|
|
12
12
|
import type { NodeViewConstructor, ViewMutationRecord } from "prosemirror-view";
|
|
13
|
+
import { PERCENTAGE_PARTS } from "../../runtime/units.ts";
|
|
13
14
|
|
|
14
15
|
// R2c: band class styles live in ./tw-table-bands.module.css. Consumers import
|
|
15
16
|
// that stylesheet through their build pipeline (same pattern as editor-theme.css).
|
|
@@ -325,7 +326,7 @@ function applyTableAttrs(table: HTMLTableElement, node: PMNode): void {
|
|
|
325
326
|
const tableWidthType = node.attrs.tableWidthType as string | null | undefined;
|
|
326
327
|
let baseClasses = "border-collapse w-full my-2 text-sm";
|
|
327
328
|
if (tableWidthType === "pct" && typeof tableWidth === "number") {
|
|
328
|
-
// OOXML pct widths are fiftieths of a percent (
|
|
329
|
+
// OOXML pct widths are fiftieths of a percent (PERCENTAGE_PARTS = 100%).
|
|
329
330
|
table.style.width = `${tableWidth / 50}%`;
|
|
330
331
|
baseClasses = "border-collapse my-2 text-sm";
|
|
331
332
|
} else if (tableWidthType === "dxa" && typeof tableWidth === "number") {
|
|
@@ -406,6 +407,14 @@ function syncColgroup(table: HTMLTableElement, node: PMNode): void {
|
|
|
406
407
|
const gridColumns = Array.isArray(node.attrs.gridColumns)
|
|
407
408
|
? (node.attrs.gridColumns as number[])
|
|
408
409
|
: [];
|
|
410
|
+
// SOW gap G1 — percent widths win when the table itself is sized in
|
|
411
|
+
// percent. The relative array sums to 100 and comes from
|
|
412
|
+
// `computeRelativeGridColumns` in surface-projection so the column
|
|
413
|
+
// proportions track the container instead of the absolute `pt` widths
|
|
414
|
+
// sliding against it. `null` (the default) keeps the legacy pt path.
|
|
415
|
+
const gridColumnsRelative = Array.isArray(node.attrs.gridColumnsRelative)
|
|
416
|
+
? (node.attrs.gridColumnsRelative as number[])
|
|
417
|
+
: null;
|
|
409
418
|
const existing = Array.from(table.children).find(
|
|
410
419
|
(child): child is HTMLTableColElement =>
|
|
411
420
|
child instanceof (table.ownerDocument?.defaultView?.HTMLTableColElement ??
|
|
@@ -429,12 +438,19 @@ function syncColgroup(table: HTMLTableElement, node: PMNode): void {
|
|
|
429
438
|
while (colgroup.childElementCount > desired) {
|
|
430
439
|
colgroup.lastElementChild?.remove();
|
|
431
440
|
}
|
|
441
|
+
const usePct =
|
|
442
|
+
gridColumnsRelative !== null && gridColumnsRelative.length === desired;
|
|
432
443
|
for (let i = 0; i < desired; i += 1) {
|
|
433
444
|
const col = colgroup.children[i] as HTMLTableColElement;
|
|
434
445
|
const twips = gridColumns[i] ?? 0;
|
|
435
446
|
col.setAttribute("data-col-index", String(i));
|
|
436
447
|
col.setAttribute("data-col-twips", String(twips));
|
|
437
|
-
|
|
448
|
+
if (usePct) {
|
|
449
|
+
const pct = gridColumnsRelative[i] ?? 0;
|
|
450
|
+
col.style.width = pct > 0 ? `${pct.toFixed(4)}%` : "";
|
|
451
|
+
} else {
|
|
452
|
+
col.style.width = twips > 0 ? `${twips / 20}pt` : "";
|
|
453
|
+
}
|
|
438
454
|
}
|
|
439
455
|
|
|
440
456
|
if (!existing) {
|
package/src/ui-tailwind/index.ts
CHANGED
|
@@ -67,6 +67,15 @@ export {
|
|
|
67
67
|
type CommandPaletteItem,
|
|
68
68
|
type TwCommandPaletteProps,
|
|
69
69
|
} from "./chrome/tw-command-palette";
|
|
70
|
+
export {
|
|
71
|
+
TwCommandPaletteMount,
|
|
72
|
+
type TwCommandPaletteMountProps,
|
|
73
|
+
} from "./chrome/tw-command-palette-mount";
|
|
74
|
+
export {
|
|
75
|
+
useContainerBreakpoint,
|
|
76
|
+
resolveBreakpoint,
|
|
77
|
+
type BreakpointMap,
|
|
78
|
+
} from "./chrome/use-container-breakpoint";
|
|
70
79
|
|
|
71
80
|
// Collab chrome (P9) — mount when chromePreset === "collab"; each
|
|
72
81
|
// component is pure presentational and takes snapshots + callbacks.
|
|
@@ -3,6 +3,16 @@ import type {
|
|
|
3
3
|
PageLayoutSnapshot,
|
|
4
4
|
SurfaceBlockSnapshot,
|
|
5
5
|
} from "../api/public-types.ts";
|
|
6
|
+
import { findPageForOffset } from "../runtime/document-navigation.ts";
|
|
7
|
+
import {
|
|
8
|
+
DEFAULT_PAGE_ESTIMATE_PX_PER_TWIP,
|
|
9
|
+
estimateBlockHeight,
|
|
10
|
+
estimateParagraphLineCount,
|
|
11
|
+
estimateParagraphLineHeight,
|
|
12
|
+
getUsableColumnWidth,
|
|
13
|
+
} from "../runtime/page-layout-estimation.ts";
|
|
14
|
+
|
|
15
|
+
const DOCUMENT_CONTENT_TOP_PADDING_PX = 40;
|
|
6
16
|
|
|
7
17
|
export interface LineMarker {
|
|
8
18
|
id: string;
|
|
@@ -14,14 +24,76 @@ export function computeLineMarkersIfEnabled(input: {
|
|
|
14
24
|
pageLayout: PageLayoutSnapshot | undefined;
|
|
15
25
|
surfaceBlocks: readonly SurfaceBlockSnapshot[];
|
|
16
26
|
pages: ReadonlyArray<DocumentNavigationSnapshot["pages"][number]>;
|
|
17
|
-
buildLineNumberMarkers: (
|
|
18
|
-
blocks: readonly SurfaceBlockSnapshot[],
|
|
19
|
-
pages: ReadonlyArray<DocumentNavigationSnapshot["pages"][number]>,
|
|
20
|
-
) => LineMarker[];
|
|
21
27
|
}): LineMarker[] {
|
|
22
28
|
if (!input.pageLayout?.lineNumbering) {
|
|
23
29
|
return [];
|
|
24
30
|
}
|
|
25
31
|
|
|
26
|
-
return
|
|
32
|
+
return buildLineNumberMarkers(input.surfaceBlocks, input.pages);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function buildLineNumberMarkers(
|
|
36
|
+
blocks: readonly SurfaceBlockSnapshot[],
|
|
37
|
+
pages: ReadonlyArray<DocumentNavigationSnapshot["pages"][number]>,
|
|
38
|
+
): LineMarker[] {
|
|
39
|
+
const markers: LineMarker[] = [];
|
|
40
|
+
if (pages.length === 0) {
|
|
41
|
+
return markers;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let currentTopTwips = 0;
|
|
45
|
+
let lineNumber = 1;
|
|
46
|
+
let lastPageIndex = -1;
|
|
47
|
+
let lastSectionIndex = -1;
|
|
48
|
+
|
|
49
|
+
for (const block of blocks) {
|
|
50
|
+
const pageIndex = findPageForOffset(pages, block.from);
|
|
51
|
+
const page = pages[pageIndex];
|
|
52
|
+
if (!page) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const lineNumbering = page.layout.lineNumbering;
|
|
57
|
+
const restartMode = lineNumbering?.restart ?? "newPage";
|
|
58
|
+
const restartStart = lineNumbering?.start ?? 1;
|
|
59
|
+
const countBy = Math.max(1, lineNumbering?.countBy ?? 1);
|
|
60
|
+
const columnWidth = getUsableColumnWidth(page.layout);
|
|
61
|
+
|
|
62
|
+
if (pageIndex !== lastPageIndex) {
|
|
63
|
+
if (restartMode === "newPage" || lastPageIndex === -1) {
|
|
64
|
+
lineNumber = restartStart;
|
|
65
|
+
}
|
|
66
|
+
lastPageIndex = pageIndex;
|
|
67
|
+
}
|
|
68
|
+
if (page.sectionIndex !== lastSectionIndex) {
|
|
69
|
+
if (restartMode === "newSection" || lastSectionIndex === -1) {
|
|
70
|
+
lineNumber = restartStart;
|
|
71
|
+
}
|
|
72
|
+
lastSectionIndex = page.sectionIndex;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (block.kind === "paragraph" && lineNumbering) {
|
|
76
|
+
const lineCount = estimateParagraphLineCount(block, columnWidth);
|
|
77
|
+
const lineHeight = estimateParagraphLineHeight(block);
|
|
78
|
+
const suppress = block.suppressLineNumbers === true;
|
|
79
|
+
for (let lineIndex = 0; lineIndex < lineCount; lineIndex += 1) {
|
|
80
|
+
if (!suppress && (lineNumber - restartStart) % countBy === 0) {
|
|
81
|
+
markers.push({
|
|
82
|
+
id: `${block.blockId}-${lineIndex}`,
|
|
83
|
+
label: String(lineNumber),
|
|
84
|
+
topPx:
|
|
85
|
+
DOCUMENT_CONTENT_TOP_PADDING_PX +
|
|
86
|
+
(currentTopTwips + lineIndex * lineHeight) * DEFAULT_PAGE_ESTIMATE_PX_PER_TWIP,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
if (!suppress) {
|
|
90
|
+
lineNumber += 1;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
currentTopTwips += estimateBlockHeight(block, columnWidth);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return markers;
|
|
27
99
|
}
|