superdoc 2.4.0-next.62 → 2.4.0-next.64

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.
@@ -1874,6 +1874,23 @@ INLINE_PROPERTY_REGISTRY.filter((entry) => entry.storage === "mark").map((entry)
1874
1874
  * `false` when the surface is unavailable (view mode, a pre-ready editor, or a
1875
1875
  * host that has not exposed a Document API facade) rather than throwing.
1876
1876
  */
1877
+ function uiBenchNowMs() {
1878
+ return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
1879
+ }
1880
+ function readUiBenchRuntime() {
1881
+ const benchGlobal = globalThis;
1882
+ return {
1883
+ sink: typeof benchGlobal.__superdocV2BenchPipelineTiming === "function" ? benchGlobal.__superdocV2BenchPipelineTiming : null,
1884
+ workerContext: benchGlobal.__superdocV2BenchWorkerMessageContext ?? null
1885
+ };
1886
+ }
1887
+ function emitUiBenchTiming(event) {
1888
+ const { sink } = readUiBenchRuntime();
1889
+ if (!sink) return;
1890
+ try {
1891
+ sink(event);
1892
+ } catch {}
1893
+ }
1877
1894
  var sharedUiTrackedChangesCatalogByHost = /* @__PURE__ */ new WeakMap();
1878
1895
  function acquireSharedUiTrackedChangesCatalog(host) {
1879
1896
  let state = sharedUiTrackedChangesCatalogByHost.get(host);
@@ -3716,12 +3733,13 @@ function createSuperDocUI(options) {
3716
3733
  let foregroundAsyncRetryTimer = null;
3717
3734
  let pendingSelectionSeedValidationToken = null;
3718
3735
  let typingContentInvalidationTimer = null;
3736
+ const pendingAsyncReadSettlements = [];
3719
3737
  const scheduleAsyncRefresh = () => {
3720
3738
  if (asyncRefreshScheduled || disposed) return;
3721
3739
  asyncRefreshScheduled = true;
3722
3740
  const run = () => {
3723
3741
  asyncRefreshScheduled = false;
3724
- if (!disposed) recompute();
3742
+ if (!disposed) recompute("async-read-settled");
3725
3743
  };
3726
3744
  if (typeof queueMicrotask === "function") queueMicrotask(run);
3727
3745
  else Promise.resolve().then(run);
@@ -4275,6 +4293,24 @@ function createSuperDocUI(options) {
4275
4293
  failureCount: 0,
4276
4294
  retryAtMs: 0
4277
4295
  });
4296
+ const { sink, workerContext } = readUiBenchRuntime();
4297
+ if (sink) {
4298
+ const settledAtMs = uiBenchNowMs();
4299
+ pendingAsyncReadSettlements.push({
4300
+ key,
4301
+ settledAtMs,
4302
+ commandId: workerContext?.commandId ?? null,
4303
+ commandKind: workerContext?.commandKind ?? null
4304
+ });
4305
+ emitUiBenchTiming({
4306
+ stage: "superdoc-ui-async-read-settled",
4307
+ atMs: settledAtMs,
4308
+ key,
4309
+ settledAtMs,
4310
+ commandId: workerContext?.commandId ?? null,
4311
+ commandKind: workerContext?.commandKind ?? null
4312
+ });
4313
+ }
4278
4314
  scheduleAsyncRefresh();
4279
4315
  }, () => {
4280
4316
  const entry = asyncReads.get(key);
@@ -6535,31 +6571,57 @@ function createSuperDocUI(options) {
6535
6571
  diagnostics
6536
6572
  };
6537
6573
  };
6538
- const computeState = () => {
6539
- const documentMode = readDocumentMode();
6540
- const selection = computeSelection();
6574
+ const computeState = (reason = "direct") => {
6575
+ const { sink } = readUiBenchRuntime();
6576
+ const startedAtMs = sink ? uiBenchNowMs() : 0;
6577
+ const phaseMs = {};
6578
+ const measure = (phase, run) => {
6579
+ if (!sink) return run();
6580
+ const phaseStartedAtMs = uiBenchNowMs();
6581
+ try {
6582
+ return run();
6583
+ } finally {
6584
+ phaseMs[phase] = uiBenchNowMs() - phaseStartedAtMs;
6585
+ }
6586
+ };
6587
+ const documentMode = measure("documentMode", readDocumentMode);
6588
+ const selection = measure("selection", computeSelection);
6541
6589
  if (selection.status === "ready") pruneSelectionScopedAsyncReads(selection);
6542
6590
  reconcilePendingInlineFormat(selection);
6543
6591
  const selectionSignature = selectionInlineValueSignature(selection);
6544
6592
  for (const [commandId, optimistic] of optimisticInlineToggles) if (!selectionSignature || optimistic.selectionSignature !== selectionSignature) optimisticInlineToggles.delete(commandId);
6545
6593
  else if (optimistic.settled && selection.status === "ready") optimisticInlineToggles.delete(commandId);
6546
- return {
6594
+ const nextState = {
6547
6595
  ready: getEditor() != null,
6548
6596
  documentMode,
6549
- document: computeDocument(),
6597
+ document: measure("document", computeDocument),
6550
6598
  selection,
6551
6599
  toolbar: {
6552
6600
  context: documentMode,
6553
- commands: computeCommandStates(selection),
6601
+ commands: measure("toolbarCommands", () => computeCommandStates(selection)),
6554
6602
  copyFormatActive: painter.mode !== "idle"
6555
6603
  },
6556
- comments: computeComments(selection),
6557
- trackChanges: computeTrackChanges(selection),
6558
- contentControls: computeContentControls(selection),
6559
- zoom: computeZoom(),
6560
- fonts: computeFonts(),
6561
- styles: computeStyles(selection)
6562
- };
6604
+ comments: measure("comments", () => computeComments(selection)),
6605
+ trackChanges: measure("trackChanges", () => computeTrackChanges(selection)),
6606
+ contentControls: measure("contentControls", () => computeContentControls(selection)),
6607
+ zoom: measure("zoom", computeZoom),
6608
+ fonts: measure("fonts", computeFonts),
6609
+ styles: measure("styles", () => computeStyles(selection))
6610
+ };
6611
+ if (sink) {
6612
+ const completedAtMs = uiBenchNowMs();
6613
+ emitUiBenchTiming({
6614
+ stage: "superdoc-ui-compute-state",
6615
+ atMs: completedAtMs,
6616
+ reason,
6617
+ startedAtMs,
6618
+ completedAtMs,
6619
+ durationMs: completedAtMs - startedAtMs,
6620
+ phaseMs,
6621
+ asyncReadSettlements: pendingAsyncReadSettlements.splice(0)
6622
+ });
6623
+ }
6624
+ return nextState;
6563
6625
  };
6564
6626
  const syncOptimisticInlineSelection = (selection) => {
6565
6627
  const nextSignature = selectionInlineValueSignature(selection);
@@ -6644,7 +6706,7 @@ function createSuperDocUI(options) {
6644
6706
  const unsubscribe = next.subscribe((snapshot) => {
6645
6707
  selectionEpoch += 1;
6646
6708
  seedCaretSelectionFromHost(snapshot);
6647
- recompute();
6709
+ recompute("host-selection");
6648
6710
  });
6649
6711
  if (typeof unsubscribe === "function") detachHostSelection = unsubscribe;
6650
6712
  } catch {
@@ -6752,7 +6814,7 @@ function createSuperDocUI(options) {
6752
6814
  };
6753
6815
  syncCoordinatorEditor();
6754
6816
  syncTrackedChangeFocusFromHostReviewTarget(readHostActiveReviewTarget(readHostReviewSource()));
6755
- state = computeState();
6817
+ state = computeState("initial");
6756
6818
  lastOptimisticInlineSelectionSignature = selectionInlineValueSignature(state.selection);
6757
6819
  const syncHostEventsSubscription = () => {
6758
6820
  const events = (getEditor()?.host)?.events;
@@ -6820,8 +6882,10 @@ function createSuperDocUI(options) {
6820
6882
  currentHostEventsSource = null;
6821
6883
  }
6822
6884
  };
6823
- const recompute = () => {
6885
+ const recompute = (reason = "unspecified") => {
6824
6886
  if (disposed) return;
6887
+ const { sink } = readUiBenchRuntime();
6888
+ const startedAtMs = sink ? uiBenchNowMs() : 0;
6825
6889
  editCommandsRecomputeQueued = false;
6826
6890
  syncCoordinatorEditor();
6827
6891
  syncHostSelectionSubscription();
@@ -6830,10 +6894,36 @@ function createSuperDocUI(options) {
6830
6894
  syncHostReviewSubscription();
6831
6895
  syncDocumentSelectionFallbackSubscription();
6832
6896
  syncHostEventsSubscription();
6833
- const nextState = computeState();
6897
+ const nextState = computeState(reason);
6834
6898
  syncOptimisticInlineSelection(nextState.selection);
6835
6899
  state = nextState;
6836
- for (const listener of [...listeners]) listener(state);
6900
+ let listenerTotalMs = 0;
6901
+ let listenerMaxMs = 0;
6902
+ for (const listener of [...listeners]) {
6903
+ if (!sink) {
6904
+ listener(state);
6905
+ continue;
6906
+ }
6907
+ const listenerStartedAtMs = uiBenchNowMs();
6908
+ listener(state);
6909
+ const listenerMs = uiBenchNowMs() - listenerStartedAtMs;
6910
+ listenerTotalMs += listenerMs;
6911
+ listenerMaxMs = Math.max(listenerMaxMs, listenerMs);
6912
+ }
6913
+ if (sink) {
6914
+ const completedAtMs = uiBenchNowMs();
6915
+ emitUiBenchTiming({
6916
+ stage: "superdoc-ui-recompute",
6917
+ atMs: completedAtMs,
6918
+ reason,
6919
+ startedAtMs,
6920
+ completedAtMs,
6921
+ durationMs: completedAtMs - startedAtMs,
6922
+ listenerCount: listeners.size,
6923
+ listenerTotalMs,
6924
+ listenerMaxMs
6925
+ });
6926
+ }
6837
6927
  };
6838
6928
  /**
6839
6929
  * Work that is bound to one specific active editor and cannot survive a swap.
@@ -7423,7 +7513,7 @@ function createSuperDocUI(options) {
7423
7513
  if (options && options.success === false) return options;
7424
7514
  if (!selection.selectionTarget) return options ? op(input, options) : op(input);
7425
7515
  return op(input, {
7426
- ...options ?? {},
7516
+ ...options,
7427
7517
  offsetSpace: "selection"
7428
7518
  });
7429
7519
  };
@@ -1874,6 +1874,23 @@ INLINE_PROPERTY_REGISTRY.filter((entry) => entry.storage === "mark").map((entry)
1874
1874
  * `false` when the surface is unavailable (view mode, a pre-ready editor, or a
1875
1875
  * host that has not exposed a Document API facade) rather than throwing.
1876
1876
  */
1877
+ function uiBenchNowMs() {
1878
+ return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
1879
+ }
1880
+ function readUiBenchRuntime() {
1881
+ const benchGlobal = globalThis;
1882
+ return {
1883
+ sink: typeof benchGlobal.__superdocV2BenchPipelineTiming === "function" ? benchGlobal.__superdocV2BenchPipelineTiming : null,
1884
+ workerContext: benchGlobal.__superdocV2BenchWorkerMessageContext ?? null
1885
+ };
1886
+ }
1887
+ function emitUiBenchTiming(event) {
1888
+ const { sink } = readUiBenchRuntime();
1889
+ if (!sink) return;
1890
+ try {
1891
+ sink(event);
1892
+ } catch {}
1893
+ }
1877
1894
  var sharedUiTrackedChangesCatalogByHost = /* @__PURE__ */ new WeakMap();
1878
1895
  function acquireSharedUiTrackedChangesCatalog(host) {
1879
1896
  let state = sharedUiTrackedChangesCatalogByHost.get(host);
@@ -3716,12 +3733,13 @@ function createSuperDocUI(options) {
3716
3733
  let foregroundAsyncRetryTimer = null;
3717
3734
  let pendingSelectionSeedValidationToken = null;
3718
3735
  let typingContentInvalidationTimer = null;
3736
+ const pendingAsyncReadSettlements = [];
3719
3737
  const scheduleAsyncRefresh = () => {
3720
3738
  if (asyncRefreshScheduled || disposed) return;
3721
3739
  asyncRefreshScheduled = true;
3722
3740
  const run = () => {
3723
3741
  asyncRefreshScheduled = false;
3724
- if (!disposed) recompute();
3742
+ if (!disposed) recompute("async-read-settled");
3725
3743
  };
3726
3744
  if (typeof queueMicrotask === "function") queueMicrotask(run);
3727
3745
  else Promise.resolve().then(run);
@@ -4275,6 +4293,24 @@ function createSuperDocUI(options) {
4275
4293
  failureCount: 0,
4276
4294
  retryAtMs: 0
4277
4295
  });
4296
+ const { sink, workerContext } = readUiBenchRuntime();
4297
+ if (sink) {
4298
+ const settledAtMs = uiBenchNowMs();
4299
+ pendingAsyncReadSettlements.push({
4300
+ key,
4301
+ settledAtMs,
4302
+ commandId: workerContext?.commandId ?? null,
4303
+ commandKind: workerContext?.commandKind ?? null
4304
+ });
4305
+ emitUiBenchTiming({
4306
+ stage: "superdoc-ui-async-read-settled",
4307
+ atMs: settledAtMs,
4308
+ key,
4309
+ settledAtMs,
4310
+ commandId: workerContext?.commandId ?? null,
4311
+ commandKind: workerContext?.commandKind ?? null
4312
+ });
4313
+ }
4278
4314
  scheduleAsyncRefresh();
4279
4315
  }, () => {
4280
4316
  const entry = asyncReads.get(key);
@@ -6535,31 +6571,57 @@ function createSuperDocUI(options) {
6535
6571
  diagnostics
6536
6572
  };
6537
6573
  };
6538
- const computeState = () => {
6539
- const documentMode = readDocumentMode();
6540
- const selection = computeSelection();
6574
+ const computeState = (reason = "direct") => {
6575
+ const { sink } = readUiBenchRuntime();
6576
+ const startedAtMs = sink ? uiBenchNowMs() : 0;
6577
+ const phaseMs = {};
6578
+ const measure = (phase, run) => {
6579
+ if (!sink) return run();
6580
+ const phaseStartedAtMs = uiBenchNowMs();
6581
+ try {
6582
+ return run();
6583
+ } finally {
6584
+ phaseMs[phase] = uiBenchNowMs() - phaseStartedAtMs;
6585
+ }
6586
+ };
6587
+ const documentMode = measure("documentMode", readDocumentMode);
6588
+ const selection = measure("selection", computeSelection);
6541
6589
  if (selection.status === "ready") pruneSelectionScopedAsyncReads(selection);
6542
6590
  reconcilePendingInlineFormat(selection);
6543
6591
  const selectionSignature = selectionInlineValueSignature(selection);
6544
6592
  for (const [commandId, optimistic] of optimisticInlineToggles) if (!selectionSignature || optimistic.selectionSignature !== selectionSignature) optimisticInlineToggles.delete(commandId);
6545
6593
  else if (optimistic.settled && selection.status === "ready") optimisticInlineToggles.delete(commandId);
6546
- return {
6594
+ const nextState = {
6547
6595
  ready: getEditor() != null,
6548
6596
  documentMode,
6549
- document: computeDocument(),
6597
+ document: measure("document", computeDocument),
6550
6598
  selection,
6551
6599
  toolbar: {
6552
6600
  context: documentMode,
6553
- commands: computeCommandStates(selection),
6601
+ commands: measure("toolbarCommands", () => computeCommandStates(selection)),
6554
6602
  copyFormatActive: painter.mode !== "idle"
6555
6603
  },
6556
- comments: computeComments(selection),
6557
- trackChanges: computeTrackChanges(selection),
6558
- contentControls: computeContentControls(selection),
6559
- zoom: computeZoom(),
6560
- fonts: computeFonts(),
6561
- styles: computeStyles(selection)
6562
- };
6604
+ comments: measure("comments", () => computeComments(selection)),
6605
+ trackChanges: measure("trackChanges", () => computeTrackChanges(selection)),
6606
+ contentControls: measure("contentControls", () => computeContentControls(selection)),
6607
+ zoom: measure("zoom", computeZoom),
6608
+ fonts: measure("fonts", computeFonts),
6609
+ styles: measure("styles", () => computeStyles(selection))
6610
+ };
6611
+ if (sink) {
6612
+ const completedAtMs = uiBenchNowMs();
6613
+ emitUiBenchTiming({
6614
+ stage: "superdoc-ui-compute-state",
6615
+ atMs: completedAtMs,
6616
+ reason,
6617
+ startedAtMs,
6618
+ completedAtMs,
6619
+ durationMs: completedAtMs - startedAtMs,
6620
+ phaseMs,
6621
+ asyncReadSettlements: pendingAsyncReadSettlements.splice(0)
6622
+ });
6623
+ }
6624
+ return nextState;
6563
6625
  };
6564
6626
  const syncOptimisticInlineSelection = (selection) => {
6565
6627
  const nextSignature = selectionInlineValueSignature(selection);
@@ -6644,7 +6706,7 @@ function createSuperDocUI(options) {
6644
6706
  const unsubscribe = next.subscribe((snapshot) => {
6645
6707
  selectionEpoch += 1;
6646
6708
  seedCaretSelectionFromHost(snapshot);
6647
- recompute();
6709
+ recompute("host-selection");
6648
6710
  });
6649
6711
  if (typeof unsubscribe === "function") detachHostSelection = unsubscribe;
6650
6712
  } catch {
@@ -6752,7 +6814,7 @@ function createSuperDocUI(options) {
6752
6814
  };
6753
6815
  syncCoordinatorEditor();
6754
6816
  syncTrackedChangeFocusFromHostReviewTarget(readHostActiveReviewTarget(readHostReviewSource()));
6755
- state = computeState();
6817
+ state = computeState("initial");
6756
6818
  lastOptimisticInlineSelectionSignature = selectionInlineValueSignature(state.selection);
6757
6819
  const syncHostEventsSubscription = () => {
6758
6820
  const events = (getEditor()?.host)?.events;
@@ -6820,8 +6882,10 @@ function createSuperDocUI(options) {
6820
6882
  currentHostEventsSource = null;
6821
6883
  }
6822
6884
  };
6823
- const recompute = () => {
6885
+ const recompute = (reason = "unspecified") => {
6824
6886
  if (disposed) return;
6887
+ const { sink } = readUiBenchRuntime();
6888
+ const startedAtMs = sink ? uiBenchNowMs() : 0;
6825
6889
  editCommandsRecomputeQueued = false;
6826
6890
  syncCoordinatorEditor();
6827
6891
  syncHostSelectionSubscription();
@@ -6830,10 +6894,36 @@ function createSuperDocUI(options) {
6830
6894
  syncHostReviewSubscription();
6831
6895
  syncDocumentSelectionFallbackSubscription();
6832
6896
  syncHostEventsSubscription();
6833
- const nextState = computeState();
6897
+ const nextState = computeState(reason);
6834
6898
  syncOptimisticInlineSelection(nextState.selection);
6835
6899
  state = nextState;
6836
- for (const listener of [...listeners]) listener(state);
6900
+ let listenerTotalMs = 0;
6901
+ let listenerMaxMs = 0;
6902
+ for (const listener of [...listeners]) {
6903
+ if (!sink) {
6904
+ listener(state);
6905
+ continue;
6906
+ }
6907
+ const listenerStartedAtMs = uiBenchNowMs();
6908
+ listener(state);
6909
+ const listenerMs = uiBenchNowMs() - listenerStartedAtMs;
6910
+ listenerTotalMs += listenerMs;
6911
+ listenerMaxMs = Math.max(listenerMaxMs, listenerMs);
6912
+ }
6913
+ if (sink) {
6914
+ const completedAtMs = uiBenchNowMs();
6915
+ emitUiBenchTiming({
6916
+ stage: "superdoc-ui-recompute",
6917
+ atMs: completedAtMs,
6918
+ reason,
6919
+ startedAtMs,
6920
+ completedAtMs,
6921
+ durationMs: completedAtMs - startedAtMs,
6922
+ listenerCount: listeners.size,
6923
+ listenerTotalMs,
6924
+ listenerMaxMs
6925
+ });
6926
+ }
6837
6927
  };
6838
6928
  /**
6839
6929
  * Work that is bound to one specific active editor and cannot survive a swap.
@@ -7423,7 +7513,7 @@ function createSuperDocUI(options) {
7423
7513
  if (options && options.success === false) return options;
7424
7514
  if (!selection.selectionTarget) return options ? op(input, options) : op(input);
7425
7515
  return op(input, {
7426
- ...options ?? {},
7516
+ ...options,
7427
7517
  offsetSpace: "selection"
7428
7518
  });
7429
7519
  };
@@ -19,7 +19,7 @@ var COLLABORATION_UPGRADE_ENGINE_MINIMUM_NODE_MAJOR = 20;
19
19
  var PRIVATE_ENGINE_INFO = (0, _superdoc_docx_engine_collaboration_upgrade_engine.getCollaborationUpgradeEngineInfo)();
20
20
  var ENGINE_INFO = Object.freeze({
21
21
  ...PRIVATE_ENGINE_INFO,
22
- superdocVersion: "2.4.0-next.62",
22
+ superdocVersion: "2.4.0-next.64",
23
23
  roomSchemaVersion: Object.freeze({ ...PRIVATE_ENGINE_INFO.roomSchemaVersion }),
24
24
  supportedBundleVersions: SUPPORTED_COLLABORATION_UPGRADE_BUNDLE_VERSIONS,
25
25
  supportedV1ReaderContractVersions: SUPPORTED_V1_READER_CONTRACT_VERSIONS
@@ -18,7 +18,7 @@ var COLLABORATION_UPGRADE_ENGINE_MINIMUM_NODE_MAJOR = 20;
18
18
  var PRIVATE_ENGINE_INFO = getCollaborationUpgradeEngineInfo$1();
19
19
  var ENGINE_INFO = Object.freeze({
20
20
  ...PRIVATE_ENGINE_INFO,
21
- superdocVersion: "2.4.0-next.62",
21
+ superdocVersion: "2.4.0-next.64",
22
22
  roomSchemaVersion: Object.freeze({ ...PRIVATE_ENGINE_INFO.roomSchemaVersion }),
23
23
  supportedBundleVersions: SUPPORTED_COLLABORATION_UPGRADE_BUNDLE_VERSIONS,
24
24
  supportedV1ReaderContractVersions: SUPPORTED_V1_READER_CONTRACT_VERSIONS
@@ -318,6 +318,8 @@ export type RunMarks = {
318
318
  * Rendering normalizes a shift of zero to "no explicit shift".
319
319
  */
320
320
  baselineShift?: number;
321
+ /** Paint-only Word 2010+ text effects (`w14:textFill`, outline, shadow, reflection). */
322
+ textEffects?: TextEffects;
321
323
  };
322
324
  export type PageReferenceRelativePositionText = 'above' | 'below';
323
325
  export type FieldResultFormat = 'charformat' | 'mergeformat';
@@ -473,6 +475,18 @@ export type ImageHyperlink = {
473
475
  * baseline next to the surrounding text instead of floating above it.
474
476
  */
475
477
  export type ImageRunVerticalAlign = 'top' | 'bottom' | 'baseline';
478
+ /**
479
+ * Explicit fail-closed rendering metadata for content that keeps its authored
480
+ * layout box but cannot be painted faithfully.
481
+ *
482
+ * The producer owns the diagnostic identity; painters only expose it on the
483
+ * visible, accessible placeholder. This keeps support decisions out of the DOM
484
+ * layer and makes degraded output observable in browser regression proofs.
485
+ */
486
+ export type RenderPlaceholder = {
487
+ diagnosticIds: string[];
488
+ accessibleName: string;
489
+ };
476
490
  /**
477
491
  * Inline image run for images that flow with text on the same line.
478
492
  * Unlike ImageBlock (anchored/floating images), ImageRun is part of the paragraph's run array
@@ -499,10 +513,16 @@ export type ImageRun = {
499
513
  width: number;
500
514
  /** Image height in pixels. */
501
515
  height: number;
516
+ /** Font family of the owning OOXML run, used to compose an image-only line box. */
517
+ fontFamily?: string;
518
+ /** Font size of the owning OOXML run, used to compose an image-only line box. */
519
+ fontSize?: number;
502
520
  /** Alternative text for accessibility. */
503
521
  alt?: string;
504
522
  /** Image title (tooltip). */
505
523
  title?: string;
524
+ /** Visible fail-closed replacement when the image source cannot be painted. */
525
+ placeholder?: RenderPlaceholder;
506
526
  /** DrawingML docPr/@id of the picture (used to target the Document API for interactive resize). */
507
527
  imageId?: string;
508
528
  /** Clip-path value for cropped images. */
@@ -891,6 +911,8 @@ export type ImageBlock = {
891
911
  height?: number;
892
912
  alt?: string;
893
913
  title?: string;
914
+ /** Visible fail-closed replacement when the image source cannot be painted. */
915
+ placeholder?: RenderPlaceholder;
894
916
  /** DrawingML docPr/@id of the picture (used to target the Document API for interactive resize). */
895
917
  imageId?: string;
896
918
  objectFit?: 'contain' | 'cover' | 'fill' | 'scale-down';
@@ -986,6 +1008,52 @@ export type TextFormatting = {
986
1008
  fontSize?: number;
987
1009
  fontFamily?: string;
988
1010
  letterSpacing?: number;
1011
+ /** Paint-only Word 2010+ text effects shared with ordinary text runs. */
1012
+ textEffects?: TextEffects;
1013
+ };
1014
+ /** Solid color used by a text effect, with optional opacity. */
1015
+ export type TextEffectColor = {
1016
+ color: string;
1017
+ alpha?: number;
1018
+ };
1019
+ /** Word 2010+ text outline (`w14:textOutline`). */
1020
+ export type TextOutlineEffect = {
1021
+ /** Outline width converted from EMU to CSS pixels. */
1022
+ width: number;
1023
+ fill: FillColor;
1024
+ };
1025
+ /** Word 2010+ outer text shadow (`w14:shadow`). */
1026
+ export type TextShadowEffect = {
1027
+ color: TextEffectColor;
1028
+ /** Blur radius converted from EMU to CSS pixels. */
1029
+ blurRadius: number;
1030
+ /** Shadow distance converted from EMU to CSS pixels. */
1031
+ distance: number;
1032
+ /** Direction in DrawingML degrees (`0` points right, `90` points down). */
1033
+ direction: number;
1034
+ };
1035
+ /** Word 2010+ reflected-text mask (`w14:reflection`). */
1036
+ export type TextReflectionEffect = {
1037
+ blurRadius: number;
1038
+ distance: number;
1039
+ direction: number;
1040
+ startAlpha: number;
1041
+ startPosition: number;
1042
+ endAlpha: number;
1043
+ endPosition: number;
1044
+ scaleX: number;
1045
+ scaleY: number;
1046
+ };
1047
+ /**
1048
+ * Paint-only text effects shared by paragraph runs and flattened shape text.
1049
+ * These effects do not change glyph advances, so layout measurement continues
1050
+ * to use the run's ordinary typography contract.
1051
+ */
1052
+ export type TextEffects = {
1053
+ fill?: FillColor;
1054
+ outline?: TextOutlineEffect;
1055
+ shadow?: TextShadowEffect;
1056
+ reflection?: TextReflectionEffect;
989
1057
  };
990
1058
  /** A single text part with optional formatting. */
991
1059
  export type TextPart = {
@@ -1041,6 +1109,20 @@ export type ShapeTextContent = {
1041
1109
  /** Horizontal text alignment within the shape. */
1042
1110
  horizontalAlign?: 'left' | 'center' | 'right';
1043
1111
  };
1112
+ /** DrawingML textbox flow/overflow semantics from `a:bodyPr`. */
1113
+ export type ShapeTextLayout = {
1114
+ /** `a:bodyPr/@wrap`; `none` keeps authored paragraphs on explicit lines only. */
1115
+ wrap?: 'square' | 'none';
1116
+ /** `a:bodyPr/@horzOverflow`. */
1117
+ horizontalOverflow?: 'overflow' | 'clip';
1118
+ /** `a:bodyPr/@vertOverflow`. */
1119
+ verticalOverflow?: 'overflow' | 'clip' | 'ellipsis';
1120
+ };
1121
+ /** Resolve the line-breaking width for DrawingML textbox content. */
1122
+ export declare function resolveShapeTextContentMeasureWidth(shapeWidth: number, insets: {
1123
+ left: number;
1124
+ right: number;
1125
+ }, layout: ShapeTextLayout | undefined, autoFitBoundaryWidth?: number): number;
1044
1126
  export type LineEnd = {
1045
1127
  type?: string;
1046
1128
  width?: string;
@@ -1056,8 +1138,38 @@ export type EffectExtent = {
1056
1138
  right: number;
1057
1139
  bottom: number;
1058
1140
  };
1141
+ /**
1142
+ * DrawingML relative rectangle in the source 1000ths-of-a-percent units.
1143
+ * Values may be negative: ECMA-376 allows an outset as well as an inset.
1144
+ */
1145
+ export type ShapeImageFillRect = {
1146
+ left: number;
1147
+ top: number;
1148
+ right: number;
1149
+ bottom: number;
1150
+ };
1151
+ /** DrawingML `a:tile` parameters, preserved without painter-specific conversion. */
1152
+ export type ShapeImageFillTile = {
1153
+ offsetX?: number;
1154
+ offsetY?: number;
1155
+ scaleX?: number;
1156
+ scaleY?: number;
1157
+ flip?: string;
1158
+ alignment?: string;
1159
+ };
1160
+ /** Resolved bitmap used as the fill paint for vector geometry. */
1161
+ export type ShapeImageFill = {
1162
+ src: string;
1163
+ mode: 'stretch' | 'tile';
1164
+ sourceRect?: ShapeImageFillRect;
1165
+ fillRect?: ShapeImageFillRect;
1166
+ tile?: ShapeImageFillTile;
1167
+ dpi?: number;
1168
+ rotateWithShape?: boolean;
1169
+ };
1059
1170
  export type VectorShapeStyle = {
1060
1171
  fillColor?: FillColor;
1172
+ imageFill?: ShapeImageFill;
1061
1173
  strokeColor?: StrokeColor;
1062
1174
  strokeWidth?: number;
1063
1175
  /** Physical CSS-pixel dash/gap lengths resolved from the source stroke. */
@@ -1068,6 +1180,7 @@ export type VectorShapeStyle = {
1068
1180
  borders?: CellBorders;
1069
1181
  lineEnds?: LineEnds;
1070
1182
  textContent?: ShapeTextContent;
1183
+ textLayout?: ShapeTextLayout;
1071
1184
  textAlign?: string;
1072
1185
  textVerticalAlign?: 'top' | 'center' | 'bottom';
1073
1186
  /** Legacy VML textbox text-flow semantics. */
@@ -1129,6 +1242,8 @@ export type DrawingBlockBase = {
1129
1242
  drawingContent?: DrawingContentSnapshot;
1130
1243
  attrs?: Record<string, unknown>;
1131
1244
  sourceAnchor?: SourceAnchor;
1245
+ /** Visible fail-closed replacement metadata owned by the projection layer. */
1246
+ placeholder?: RenderPlaceholder;
1132
1247
  };
1133
1248
  /**
1134
1249
  * Custom geometry path data extracted from a:custGeom/a:pathLst.
@@ -1150,6 +1265,7 @@ export type VectorShapeDrawing = DrawingBlockBase & {
1150
1265
  shapeKind?: string;
1151
1266
  customGeometry?: CustomGeometryData;
1152
1267
  fillColor?: FillColor;
1268
+ imageFill?: ShapeImageFill;
1153
1269
  strokeColor?: StrokeColor;
1154
1270
  strokeWidth?: number;
1155
1271
  strokeDashArray?: number[];
@@ -1158,6 +1274,7 @@ export type VectorShapeDrawing = DrawingBlockBase & {
1158
1274
  lineEnds?: LineEnds;
1159
1275
  effectExtent?: EffectExtent;
1160
1276
  textContent?: ShapeTextContent;
1277
+ textLayout?: ShapeTextLayout;
1161
1278
  textAlign?: string;
1162
1279
  textVerticalAlign?: 'top' | 'center' | 'bottom';
1163
1280
  textFlow?: 'horizontal' | 'vertical' | 'vertical-ideographic' | 'horizontal-ideographic' | 'bottom-to-top';
@@ -1176,6 +1293,7 @@ export type TextboxDrawing = DrawingBlockBase & {
1176
1293
  shapeKind?: string;
1177
1294
  customGeometry?: CustomGeometryData;
1178
1295
  fillColor?: FillColor;
1296
+ imageFill?: ShapeImageFill;
1179
1297
  strokeColor?: StrokeColor;
1180
1298
  strokeWidth?: number;
1181
1299
  strokeDashArray?: number[];
@@ -1184,6 +1302,7 @@ export type TextboxDrawing = DrawingBlockBase & {
1184
1302
  lineEnds?: LineEnds;
1185
1303
  effectExtent?: EffectExtent;
1186
1304
  textContent?: ShapeTextContent;
1305
+ textLayout?: ShapeTextLayout;
1187
1306
  textAlign?: string;
1188
1307
  textVerticalAlign?: 'top' | 'center' | 'bottom';
1189
1308
  textFlow?: 'horizontal' | 'vertical' | 'vertical-ideographic' | 'horizontal-ideographic' | 'bottom-to-top';
@@ -1,13 +1,13 @@
1
- import { ChartModel, DrawingGeometry } from '../../../contracts/src/index.js';
1
+ import { ChartModel, DrawingGeometry, RenderPlaceholder } from '../../../contracts/src/index.js';
2
2
  /**
3
3
  * Create a chart element from a ChartDrawing block.
4
4
  * Routes to the correct renderer based on chart type, with placeholder fallback.
5
5
  */
6
- export declare function createChartElement(doc: Document, chartData: ChartModel | undefined, geometry: DrawingGeometry): HTMLElement;
6
+ export declare function createChartElement(doc: Document, chartData: ChartModel | undefined, geometry: DrawingGeometry, placeholder?: RenderPlaceholder): HTMLElement;
7
7
  /**
8
8
  * Create a placeholder for charts with missing data or unsupported types.
9
9
  * Preserves layout dimensions and is print-visible.
10
10
  */
11
- export declare function createChartPlaceholder(doc: Document, container: HTMLElement, label: string): HTMLElement;
11
+ export declare function createChartPlaceholder(doc: Document, container: HTMLElement, label: string, placeholder?: RenderPlaceholder): HTMLElement;
12
12
  /** Format a numeric tick value for chart axis labels. */
13
13
  export declare function formatTickValue(value: number): string;
@@ -0,0 +1,12 @@
1
+ import { RenderPlaceholder } from '../../../../contracts/src/index.js';
2
+ export type CreateRenderPlaceholderOptions = {
3
+ doc: Document;
4
+ placeholder: RenderPlaceholder;
5
+ };
6
+ /** Stamp the shared diagnostic and accessibility contract on a placeholder. */
7
+ export declare const applyRenderPlaceholderSemantics: (element: HTMLElement, placeholder: RenderPlaceholder) => void;
8
+ /**
9
+ * Paint an observable, print-visible replacement for content that the
10
+ * projection layer deliberately kept fail-closed.
11
+ */
12
+ export declare const createRenderPlaceholder: ({ doc, placeholder }: CreateRenderPlaceholderOptions) => HTMLSpanElement;