@a3s-lab/office 0.9.0 → 0.9.2

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.
@@ -395,8 +395,9 @@ projection, a host-injected live CLI transport session, authenticated browser
395
395
  suggestion-update authorization, and typed Markdown, Document
396
396
  content/comment/suggestion, Spreadsheet cell, Presentation scene-element, PDF
397
397
  annotation/form-value, and PDF redaction/page-operation review mutation surfaces
398
- are implemented; the remaining format mutations and native presence projection
399
- are pending.
398
+ are implemented. The live CLI session also projects ephemeral native Presence
399
+ and format-specific locations through standard Yjs Awareness; remaining typed
400
+ format mutations are pending.
400
401
 
401
402
  - Yrs `0.27.3` now uses 53-bit Yjs-compatible client IDs and exchanges standard
402
403
  Yjs v1 updates, state vectors, and y-sync `SyncStep1`/`Update` messages with
@@ -426,6 +427,14 @@ are pending.
426
427
  stdin/stdout for a coding-agent or product host. The host still owns the
427
428
  WebSocket or IPC channel, room selection, authentication, authorization,
428
429
  buffering, and delivery identities; the CLI never opens a provider itself.
430
+ - Supplying `--actor-name` gives the live session a separate in-memory Yrs
431
+ Awareness peer. Typed active/idle/away state and Document, Markdown,
432
+ Spreadsheet, Presentation, or PDF locations are emitted as bounded
433
+ `outbound-awareness` records, while validated remote updates and peer-left
434
+ events become sorted participant snapshots. Reconnect clears stale remote
435
+ state and republishes the local participant; orderly close emits an
436
+ Awareness tombstone. No Presence state or clock enters the replica,
437
+ checkpoint, durable update log, or operation receipts.
429
438
  - Each replica binds one stable actor ID, actor kind, permission mode, artifact
430
439
  identity/kind, namespace, and client ID. Apply/checkpoint/leave require the
431
440
  same identity plus a stable operation ID and accept an optional state-vector
@@ -518,8 +527,6 @@ Remaining:
518
527
  structural/rich-text operations plus PDF signatures; deepen Document
519
528
  mutations to additional nested structures and complete table/list/section
520
529
  revision workflows.
521
- - Project editor-visible presence and selection state while keeping Awareness
522
- ephemeral and outside native replica persistence.
523
530
 
524
531
  Exit criterion: a browser, `office` CLI, MCP client, and `a3s code` agent edit
525
532
  the same artifact concurrently, converge at the shared-model level, reconnect
package/README.md CHANGED
@@ -415,7 +415,10 @@ interaction model.
415
415
  edit/preview/PDF typography and page-chrome placement. Editing, read-only
416
416
  preview, and browser PDF export share one live pagination result. Tail edits
417
417
  reuse the stable physical-page prefix, including resolved first/odd/even
418
- headers, footers, page numbers, and navigation descriptors.
418
+ headers, footers, page numbers, and navigation descriptors. Browser DOM
419
+ measurement runs in bounded 32 ms slices and yields only between top-level
420
+ blocks; document edits abort stale passes before a snapshot is committed, so
421
+ long repagination work does not monopolize the browser event loop.
419
422
  Real DOCX fixtures provide deterministic A3S Test evidence for inherited
420
423
  table styling across edit and preview, a centered 62.5% table with table and
421
424
  cell margin overrides, all four Table Properties tabs at 390 px, exact
@@ -1039,7 +1042,7 @@ cargo run -p a3s-office-cli -- collab diff .a3s/report.replica \
1039
1042
 
1040
1043
  # Bridge that replica to a host-owned room over machine-readable JSONL.
1041
1044
  cargo run -p a3s-office-cli -- collab session .a3s/report.replica \
1042
- --poll-ms 100 --json
1045
+ --poll-ms 100 --actor-name "A3S Agent" --json
1043
1046
 
1044
1047
  # Make a typed local change in an initialized Markdown replica. A running
1045
1048
  # session publishes the resulting incremental Yjs update automatically.
@@ -1177,7 +1180,11 @@ controller as a shared participant roster, remote canvas projection, and
1177
1180
  participant-to-location navigation; neither component creates an account or
1178
1181
  backend. The native CLI's JSONL session bridges the same host-channel envelope,
1179
1182
  including reconnect handshakes, durable agent updates, and remote-echo
1180
- suppression, without opening its own network provider. Native Rust `project`,
1183
+ suppression, without opening its own network provider. With `--actor-name`, it
1184
+ also emits standard, bounded `outbound-awareness` records for ephemeral agent
1185
+ activity and format-specific locations, accepts remote Awareness and peer-left
1186
+ records, and publishes participant snapshots without changing the durable
1187
+ replica. Native Rust `project`,
1181
1188
  `collab read`, and `office_collaboration_read` return the exact canonical
1182
1189
  Markdown source or an Office-owned bounded Document projection with stable
1183
1190
  paragraph/text identities, structural ancestry, option fields, subordinate
@@ -1320,6 +1327,8 @@ without hard-coded return URLs.
1320
1327
 
1321
1328
  - [Live Playground](https://a3s-lab.github.io/Office/)
1322
1329
  - [Documentation center](https://a3s-lab.github.io/Office/docs/)
1330
+ - [A3S Office 0.9.2 documentation](https://a3s-lab.github.io/Office/docs/0.9.2/)
1331
+ - [A3S Office 0.9.1 documentation](https://a3s-lab.github.io/Office/docs/0.9.1/)
1323
1332
  - [A3S Office 0.9.0 documentation](https://a3s-lab.github.io/Office/docs/0.9.0/)
1324
1333
  - [A3S Office 0.8.1 documentation](https://a3s-lab.github.io/Office/docs/0.8.1/)
1325
1334
  - [A3S Office 0.8.0 documentation](https://a3s-lab.github.io/Office/docs/0.8.0/)
@@ -19,7 +19,7 @@ import { TableMap, isInTable, selectedRect } from "@tiptap/pm/tables";
19
19
  import { useOfficeTaskPaneEscape, normalizeOfficeFontFamily, OfficeCheckbox, useOfficeDialog, useOfficeTaskPaneModal, CommittedOfficeNumberField, officeFontFamilyLabel, officeFontFamilies, CollectionState, createOfficeKernelClient, OfficeNumberField } from "./0~7048.js";
20
20
  import { office_text_field_OfficeTextArea, Popover, officeOverlayPortalRoot, button_Button, useOfficePublishPresenceLocation, OfficeTiptapPresenceLayer, icon_button_IconButton, office_text_field_OfficeTextField, useDialogFocusScope, useOfficeEditorWheelZoom, office_text_field_OfficeFileInput } from "./0~6090.js";
21
21
  import { WorkOfficePreviewBar, isWorkspaceContextMenuKeyboardEvent, Tabs, WorkOfficeZoomControls, WorkOfficeStatusBar, Dialog, WorkOfficeRibbon, workspaceContextMenuPosition, OfficeSelect, WorkspaceContextMenu, WorkOfficeRibbonGroup, WorkOfficeRibbonButton } from "./0~4595.js";
22
- import { documentTableSizing, documentTableRowOptions, documentCommentViews, documentParagraphSpacing, documentPageBordersVisible, DOCUMENT_PAGE_BORDER_EDGES, normalizeDocumentColumns, clampDocumentMargin, normalizeDocumentTabStops, DOCUMENT_BOOKMARK_DUPLICATE_MESSAGE, createWorkDocumentModel, nextDocumentTabAlignment, documentBookmarkNameExists, normalizedTabPosition, DocumentImage, collectDocumentChanges, documentCitationCount, isDocumentParagraphArtBorderStyle, resolveDocumentPageMargins, updateDocumentPageMarginMode, resolveDocumentPageBorders, canSetDocumentTableRowRepeatHeader, MAX_DOCUMENT_IMAGE_RELATIVE_HEIGHT, retainAnchoredDocumentComments, measureDocumentLayoutBlocks, DocumentTableRowIdentity, work_document_page_margins_twipsToMillimeters, DEFAULT_DOCUMENT_TABLE_CELL_FORMAT, resolveWorkDocumentEditorInput, collectDocumentTextLayoutParagraphs, millimetersToPixels as work_document_layout_millimetersToPixels, documentBulletListStyle, readOfficeDocumentCollaboration as readWorkOfficeDocumentCollaboration, documentTabLeaderLabel, documentOrderedListState, normalizeDocumentPageChrome, documentPageMarginsForLayout, documentTableCellFormat, updateDocumentCustomPageMillimeters, documentCommentDraftRange, normalizeDocumentPageGeometry, documentParagraphTabStops, normalizeDocumentImageLayer, documentImageProperties, documentParagraphIndent, documentTableHorizontalAlignment, isValidDocumentCitationTag, canInsertDocumentComment, sanitizeDocumentPageChromeHtml, pageTwipsToMillimeters, normalizeDocumentPageBorders, DocumentParagraphFormatting, activeDocumentSection, documentTextLayoutBatches, normalizeDocumentImageWrapSide, normalizeDocumentHtml, documentParagraphDirection, documentParagraphPagination, updateDocumentPageOrientation, documentCitationStyleDetails, documentInitialSectionLayout, documentPageChromeLegacyFields, setCustomDocumentColumns, collectDocumentCommentAnchors, documentCitationStyle, updateDocumentPageMarginMillimeters, canChangeDocumentIndent, validateDocumentBookmarkName, DEFAULT_DOCUMENT_TABLE_CELL_MARGINS, DocumentSuperscript, documentSectionById, DOCUMENT_TABLE_STYLE_OPTIONS, updateDocumentColumnWidth, updateDocumentPaperSizePreset, DocumentEquation, documentPageMetrics, normalizeDocumentParagraphIndent, updateDocumentGutterPosition, mountWorkLiveDocumentCapture, activeDocumentBookmark, createDocumentBibliography, DocumentSubscript, DocumentParagraphIdentity, syncDocumentContentFromHtml, updateDocumentPageChromeVariant, editorDocumentCaptionTargets, resolveDocumentPageChrome, updateDocumentMirrorMargins, documentImageLayoutOptions, MAX_DOCUMENT_NUMBERING_START, resolveDocumentPageSize, activeDocumentTableStyle, editorDocumentBookmarkReferenceTargets, createOfficeDocumentCollaborationBinding as createWorkOfficeDocumentCollaborationBinding, createWorkDocumentExtensions } from "./8928.js";
22
+ import { documentTableSizing, documentTableRowOptions, documentCommentViews, documentParagraphSpacing, documentPageBordersVisible, DOCUMENT_PAGE_BORDER_EDGES, normalizeDocumentColumns, clampDocumentMargin, normalizeDocumentTabStops, DOCUMENT_BOOKMARK_DUPLICATE_MESSAGE, createWorkDocumentModel, nextDocumentTabAlignment, documentBookmarkNameExists, normalizedTabPosition, DocumentImage, collectDocumentChanges, documentCitationCount, isDocumentParagraphArtBorderStyle, resolveDocumentPageMargins, updateDocumentPageMarginMode, resolveDocumentPageBorders, canSetDocumentTableRowRepeatHeader, MAX_DOCUMENT_IMAGE_RELATIVE_HEIGHT, retainAnchoredDocumentComments, DocumentTableRowIdentity, work_document_page_margins_twipsToMillimeters, DEFAULT_DOCUMENT_TABLE_CELL_FORMAT, resolveWorkDocumentEditorInput, collectDocumentTextLayoutParagraphs, millimetersToPixels as work_document_layout_millimetersToPixels, documentBulletListStyle, readOfficeDocumentCollaboration as readWorkOfficeDocumentCollaboration, documentTabLeaderLabel, documentOrderedListState, normalizeDocumentPageChrome, documentPageMarginsForLayout, documentTableCellFormat, updateDocumentCustomPageMillimeters, documentCommentDraftRange, normalizeDocumentPageGeometry, documentParagraphTabStops, normalizeDocumentImageLayer, documentImageProperties, documentParagraphIndent, documentTableHorizontalAlignment, isValidDocumentCitationTag, canInsertDocumentComment, sanitizeDocumentPageChromeHtml, pageTwipsToMillimeters, normalizeDocumentPageBorders, DocumentParagraphFormatting, activeDocumentSection, documentTextLayoutBatches, normalizeDocumentImageWrapSide, normalizeDocumentHtml, documentParagraphDirection, documentParagraphPagination, updateDocumentPageOrientation, documentCitationStyleDetails, documentInitialSectionLayout, documentPageChromeLegacyFields, setCustomDocumentColumns, collectDocumentCommentAnchors, documentCitationStyle, updateDocumentPageMarginMillimeters, canChangeDocumentIndent, validateDocumentBookmarkName, DEFAULT_DOCUMENT_TABLE_CELL_MARGINS, DocumentSuperscript, documentSectionById, DOCUMENT_TABLE_STYLE_OPTIONS, updateDocumentColumnWidth, updateDocumentPaperSizePreset, DocumentEquation, documentPageMetrics, normalizeDocumentParagraphIndent, updateDocumentGutterPosition, mountWorkLiveDocumentCapture, activeDocumentBookmark, createDocumentBibliography, DocumentSubscript, DocumentParagraphIdentity, syncDocumentContentFromHtml, updateDocumentPageChromeVariant, editorDocumentCaptionTargets, resolveDocumentPageChrome, updateDocumentMirrorMargins, documentImageLayoutOptions, MAX_DOCUMENT_NUMBERING_START, resolveDocumentPageSize, activeDocumentTableStyle, editorDocumentBookmarkReferenceTargets, createOfficeDocumentCollaborationBinding as createWorkOfficeDocumentCollaborationBinding, measureDocumentLayoutBlocksIncrementally, createWorkDocumentExtensions } from "./8928.js";
23
23
  import { createOfficeId as createWorkId } from "./5184.js";
24
24
  import { normalizeDocumentHref, DOCUMENT_LINK_VALIDATION_MESSAGE } from "./0~work-document-links.js";
25
25
  import { OfficeColorPicker, isOfficeShortcutBlocked } from "./0~3635.js";
@@ -13382,6 +13382,44 @@ function incrementalBoundaryBreak(previous, next) {
13382
13382
  spacerHeight: remainingBodyHeight + previous.page.marginBottom + previous.page.pageGap + next.page.marginTop
13383
13383
  };
13384
13384
  }
13385
+ function createDocumentPaginationMeasurementRange() {
13386
+ let activePass = null;
13387
+ let dirtyFrom = 0;
13388
+ const restore = (pass)=>{
13389
+ if (activePass !== pass) return;
13390
+ dirtyFrom = Math.min(dirtyFrom, pass.from);
13391
+ activePass = null;
13392
+ };
13393
+ return {
13394
+ begin: ()=>{
13395
+ const pass = {
13396
+ from: Number.isFinite(dirtyFrom) ? dirtyFrom : 0
13397
+ };
13398
+ activePass = pass;
13399
+ dirtyFrom = 1 / 0;
13400
+ return pass;
13401
+ },
13402
+ commit: (pass)=>{
13403
+ if (activePass === pass) activePass = null;
13404
+ },
13405
+ ensureDirty: ()=>{
13406
+ if (Number.isFinite(dirtyFrom)) return;
13407
+ dirtyFrom = activePass?.from ?? 0;
13408
+ },
13409
+ invalidate: (position)=>{
13410
+ const normalizedPosition = Number.isFinite(position) ? Math.max(0, position) : 0;
13411
+ dirtyFrom = Math.min(dirtyFrom, normalizedPosition);
13412
+ },
13413
+ reset: ()=>{
13414
+ activePass = null;
13415
+ dirtyFrom = 0;
13416
+ },
13417
+ restore,
13418
+ restoreActive: ()=>{
13419
+ if (activePass) restore(activePass);
13420
+ }
13421
+ };
13422
+ }
13385
13423
  function createDocumentPaginationRunCoordinator({ cancelFrame, onAbort, onCoalescedRequest, onError, onRunFinish, onRunStart, requestFrame, run }) {
13386
13424
  let activeController = null;
13387
13425
  let disposed = false;
@@ -13455,7 +13493,9 @@ function useDocumentPagination({ editor, documentRevision, enabled, layoutKey, p
13455
13493
  const paginationCache = useRef(null);
13456
13494
  const measurementEditor = useRef(null);
13457
13495
  const measurementLayoutKey = useRef('');
13458
- const dirtyMeasurementFrom = useRef(0);
13496
+ const measurementRangeRef = useRef(null);
13497
+ measurementRangeRef.current ??= createDocumentPaginationMeasurementRange();
13498
+ const measurementRange = measurementRangeRef.current;
13459
13499
  const observedDocumentRevision = useRef(documentRevision);
13460
13500
  const [pagination, setPagination] = useState(null);
13461
13501
  const pageKey = pageMetricsKey(page);
@@ -13470,10 +13510,10 @@ function useDocumentPagination({ editor, documentRevision, enabled, layoutKey, p
13470
13510
  paginationCache.current = null;
13471
13511
  measurementEditor.current = editor;
13472
13512
  measurementLayoutKey.current = measurementKey;
13473
- dirtyMeasurementFrom.current = 0;
13513
+ measurementRange.reset();
13474
13514
  }
13475
13515
  if (observedDocumentRevision.current !== documentRevision) {
13476
- if (!Number.isFinite(dirtyMeasurementFrom.current)) dirtyMeasurementFrom.current = 0;
13516
+ measurementRange.ensureDirty();
13477
13517
  observedDocumentRevision.current = documentRevision;
13478
13518
  }
13479
13519
  if (!editor || !editorMounted || editor.isDestroyed || !enabled || !client) {
@@ -13518,7 +13558,7 @@ function useDocumentPagination({ editor, documentRevision, enabled, layoutKey, p
13518
13558
  if (!enabled) {
13519
13559
  measurementCache.current = null;
13520
13560
  paginationCache.current = null;
13521
- dirtyMeasurementFrom.current = 0;
13561
+ measurementRange.reset();
13522
13562
  }
13523
13563
  setPagination(null);
13524
13564
  return;
@@ -13586,143 +13626,155 @@ function useDocumentPagination({ editor, documentRevision, enabled, layoutKey, p
13586
13626
  editorDom.dataset.paginationReusedPageChrome = '0';
13587
13627
  editorDom.dataset.paginationDerivedPageChrome = '0';
13588
13628
  delete editorDom.dataset.paginationError;
13589
- const measurementStart = dirtyMeasurementFrom.current;
13590
- dirtyMeasurementFrom.current = 1 / 0;
13591
- const textLayoutCollection = collectDocumentTextLayoutParagraphs(editor, layoutFonts, loadedLayoutFontIds, measurementCache.current, measurementStart);
13592
- editorDom.dataset.paginationTextCandidates = String(textLayoutCollection.paragraphs.length);
13593
- editorDom.dataset.paginationTextRuns = String(textLayoutCollection.paragraphs.reduce((count, paragraph)=>count + paragraph.runs.length, 0));
13594
- const textLayouts = new Map();
13595
- let fallbackGlyphCount = 0;
13596
- if (textLayoutCollection.paragraphs.length) {
13597
- editorDom.dataset.paginationState = 'shaping';
13598
- try {
13599
- let textLayoutEngine = '';
13600
- let unsupportedTextLayoutCount = 0;
13601
- const textLayoutBatches = documentTextLayoutBatches(textLayoutCollection.paragraphs);
13602
- editorDom.dataset.paginationTextBatches = String(textLayoutBatches.length);
13603
- for (const [batchIndex, paragraphs] of textLayoutBatches.entries()){
13604
- editorDom.dataset.paginationTextBatch = String(batchIndex + 1);
13605
- const textLayout = await client.textLayout({
13606
- revision: nextRevision,
13607
- documentRevision,
13608
- paragraphs
13609
- }, signal);
13610
- if (disposed || signal.aborted || nextRevision !== revision.current || editor.isDestroyed) return;
13611
- textLayoutEngine ||= textLayout.engine;
13612
- unsupportedTextLayoutCount += textLayout.unsupportedParagraphIds.length + textLayout.layouts.filter((layout)=>layout.missingGlyphCount > 0).length;
13613
- for (const layout of textLayout.layouts)if (0 === layout.missingGlyphCount) {
13614
- textLayouts.set(layout.id, layout);
13615
- fallbackGlyphCount += layout.fallbackGlyphCount;
13629
+ const measurementPass = measurementRange.begin();
13630
+ const measurementStart = measurementPass.from;
13631
+ try {
13632
+ const textLayoutCollection = collectDocumentTextLayoutParagraphs(editor, layoutFonts, loadedLayoutFontIds, measurementCache.current, measurementStart);
13633
+ editorDom.dataset.paginationTextCandidates = String(textLayoutCollection.paragraphs.length);
13634
+ editorDom.dataset.paginationTextRuns = String(textLayoutCollection.paragraphs.reduce((count, paragraph)=>count + paragraph.runs.length, 0));
13635
+ const textLayouts = new Map();
13636
+ let fallbackGlyphCount = 0;
13637
+ if (textLayoutCollection.paragraphs.length) {
13638
+ editorDom.dataset.paginationState = 'shaping';
13639
+ try {
13640
+ let textLayoutEngine = '';
13641
+ let unsupportedTextLayoutCount = 0;
13642
+ const textLayoutBatches = documentTextLayoutBatches(textLayoutCollection.paragraphs);
13643
+ editorDom.dataset.paginationTextBatches = String(textLayoutBatches.length);
13644
+ for (const [batchIndex, paragraphs] of textLayoutBatches.entries()){
13645
+ editorDom.dataset.paginationTextBatch = String(batchIndex + 1);
13646
+ const textLayout = await client.textLayout({
13647
+ revision: nextRevision,
13648
+ documentRevision,
13649
+ paragraphs
13650
+ }, signal);
13651
+ if (disposed || signal.aborted || nextRevision !== revision.current || editor.isDestroyed) return;
13652
+ textLayoutEngine ||= textLayout.engine;
13653
+ unsupportedTextLayoutCount += textLayout.unsupportedParagraphIds.length + textLayout.layouts.filter((layout)=>layout.missingGlyphCount > 0).length;
13654
+ for (const layout of textLayout.layouts)if (0 === layout.missingGlyphCount) {
13655
+ textLayouts.set(layout.id, layout);
13656
+ fallbackGlyphCount += layout.fallbackGlyphCount;
13657
+ }
13616
13658
  }
13659
+ editorDom.dataset.paginationTextEngine = textLayoutEngine;
13660
+ editorDom.dataset.paginationUnsupportedText = String(unsupportedTextLayoutCount);
13661
+ } catch (error) {
13662
+ if (signal.aborted || error instanceof DOMException && 'AbortError' === error.name) return;
13663
+ textLayouts.clear();
13664
+ fallbackGlyphCount = 0;
13665
+ editorDom.dataset.paginationTextEngine = 'dom';
13666
+ editorDom.dataset.paginationUnsupportedText = String(textLayoutCollection.paragraphs.length);
13617
13667
  }
13618
- editorDom.dataset.paginationTextEngine = textLayoutEngine;
13619
- editorDom.dataset.paginationUnsupportedText = String(unsupportedTextLayoutCount);
13620
- } catch (error) {
13621
- if (signal.aborted || error instanceof DOMException && 'AbortError' === error.name) return;
13622
- textLayouts.clear();
13623
- fallbackGlyphCount = 0;
13668
+ } else {
13624
13669
  editorDom.dataset.paginationTextEngine = 'dom';
13625
- editorDom.dataset.paginationUnsupportedText = String(textLayoutCollection.paragraphs.length);
13670
+ editorDom.dataset.paginationUnsupportedText = '0';
13671
+ editorDom.dataset.paginationTextBatch = '0';
13672
+ editorDom.dataset.paginationTextBatches = '0';
13626
13673
  }
13627
- } else {
13628
- editorDom.dataset.paginationTextEngine = 'dom';
13629
- editorDom.dataset.paginationUnsupportedText = '0';
13630
- editorDom.dataset.paginationTextBatch = '0';
13631
- editorDom.dataset.paginationTextBatches = '0';
13632
- }
13633
- editorDom.dataset.paginationShapedParagraphs = String(textLayouts.size);
13634
- editorDom.dataset.paginationFallbackGlyphs = String(fallbackGlyphCount);
13635
- editorDom.dataset.paginationShapedRuns = String(textLayoutCollection.paragraphs.reduce((count, paragraph)=>count + (textLayouts.has(paragraph.id) ? paragraph.runs.length : 0), 0));
13636
- const snapshot = measureDocumentLayoutBlocks(editor, measurementCache.current, measurementStart, textLayouts);
13637
- measurementCache.current = snapshot;
13638
- editorDom.dataset.paginationBlocks = String(snapshot.blocks.length);
13639
- editorDom.dataset.paginationFlows = String(new Set(snapshot.blocks.flatMap((block)=>block.block.flowId ? [
13640
- block.block.flowId
13641
- ] : [])).size);
13642
- editorDom.dataset.paginationMeasuredBlocks = String(snapshot.measuredBlockCount);
13643
- editorDom.dataset.paginationReusedBlocks = String(snapshot.reusedBlockCount);
13644
- if (snapshot.unsupportedLayout || !snapshot.blocks.length) {
13645
- observeBlocks(snapshot.blocks);
13646
- editorDom.dataset.paginationState = snapshot.unsupportedLayout ? 'unsupported' : 'empty';
13647
- setPagination(null);
13648
- paginationCache.current = null;
13649
- return;
13650
- }
13651
- const previousPagination = paginationCache.current;
13652
- const layoutPlan = planIncrementalDocumentLayout(previousPagination, snapshot.blocks, measurementStart);
13653
- editorDom.dataset.paginationLaidOutBlocks = String(layoutPlan.blocks.length);
13654
- editorDom.dataset.paginationReusedPages = String(layoutPlan.reusedPageCount);
13655
- try {
13656
- editorDom.dataset.paginationState = 'layout';
13657
- const partialLayout = await client.layout({
13658
- revision: nextRevision,
13659
- documentRevision,
13660
- startPageIndex: layoutPlan.startPageIndex,
13661
- page,
13662
- pageStyles: snapshot.pageStyles,
13663
- blocks: layoutPlan.blocks
13664
- }, signal);
13665
- if (disposed || signal.aborted || nextRevision !== revision.current || editor.isDestroyed) return;
13666
- const layout = previousPagination && layoutPlan.startPageIndex > 0 ? mergeIncrementalDocumentLayout(previousPagination.layout, partialLayout) : partialLayout;
13667
- const blockById = new Map(snapshot.blocks.map((block)=>[
13668
- block.block.id,
13669
- block
13670
- ]));
13671
- const pageDescriptorDerivation = deriveDocumentPaginationPageDescriptors(layout, snapshot.blocks, editor.state.doc, previousPagination?.pages, layoutPlan.reusedPageCount);
13672
- const pages = pageDescriptorDerivation.pages;
13673
- editorDom.dataset.paginationReusedPageChrome = String(pageDescriptorDerivation.reusedPageCount);
13674
- editorDom.dataset.paginationDerivedPageChrome = String(pageDescriptorDerivation.derivedPageCount);
13675
- const pageByIndex = new Map(pages.map((descriptor)=>[
13676
- descriptor.pageIndex,
13677
- descriptor
13678
- ]));
13679
- editor.commands.applyDocumentPagination(nextRevision, layout.breaks.flatMap((pageBreak)=>{
13680
- const block = blockById.get(pageBreak.beforeBlockId);
13681
- return block ? [
13682
- {
13683
- ...pageBreak,
13684
- previousPage: pageByIndex.get(pageBreak.pageIndex - 1)?.page ?? layout.pages[pageBreak.pageIndex - 1]?.page ?? page,
13685
- nextPage: pageByIndex.get(pageBreak.pageIndex)?.page ?? layout.pages[pageBreak.pageIndex]?.page ?? page,
13686
- position: block.from,
13687
- inlineOffsetLeft: block.inlineOffsetLeft,
13688
- inlineOffsetRight: block.inlineOffsetRight,
13689
- previousPageChrome: visualPageChrome(pageByIndex.get(pageBreak.pageIndex - 1)),
13690
- nextPageChrome: visualPageChrome(pageByIndex.get(pageBreak.pageIndex)),
13691
- tableBreak: block.tableBreak
13692
- }
13693
- ] : [];
13694
- }));
13695
- observeBlocks(snapshot.blocks);
13696
- editorDom.dataset.paginationEngine = layout.engine;
13697
- editorDom.dataset.paginationDocumentRevision = String(layout.documentRevision);
13698
- editorDom.dataset.paginationPages = String(layout.pages.length);
13699
- editorDom.dataset.paginationState = 'ready';
13700
- delete editorDom.dataset.paginationError;
13701
- const nextPagination = {
13702
- layout,
13703
- blocks: snapshot.blocks,
13704
- pages,
13705
- pageByBlockId: new Map(layout.pages.flatMap((page)=>page.placements.map((placement)=>[
13706
- placement.blockId,
13707
- page.index + 1
13708
- ])))
13709
- };
13710
- paginationCache.current = nextPagination;
13711
- setPagination(nextPagination);
13712
- } catch (error) {
13713
- if (disposed || signal.aborted || error instanceof DOMException && 'AbortError' === error.name) return;
13714
- editorDom.dataset.paginationState = 'error';
13715
- editorDom.dataset.paginationError = error instanceof Error ? `${error.name}: ${error.message}` : 'UnknownError';
13716
- editor.commands.clearDocumentPagination(nextRevision);
13717
- paginationCache.current = null;
13718
- setPagination(null);
13674
+ editorDom.dataset.paginationShapedParagraphs = String(textLayouts.size);
13675
+ editorDom.dataset.paginationFallbackGlyphs = String(fallbackGlyphCount);
13676
+ editorDom.dataset.paginationShapedRuns = String(textLayoutCollection.paragraphs.reduce((count, paragraph)=>count + (textLayouts.has(paragraph.id) ? paragraph.runs.length : 0), 0));
13677
+ const snapshot = await measureDocumentLayoutBlocksIncrementally(editor, measurementCache.current, measurementStart, textLayouts, 1000000, {
13678
+ signal
13679
+ });
13680
+ measurementCache.current = snapshot;
13681
+ editorDom.dataset.paginationBlocks = String(snapshot.blocks.length);
13682
+ editorDom.dataset.paginationFlows = String(new Set(snapshot.blocks.flatMap((block)=>block.block.flowId ? [
13683
+ block.block.flowId
13684
+ ] : [])).size);
13685
+ editorDom.dataset.paginationMeasuredBlocks = String(snapshot.measuredBlockCount);
13686
+ editorDom.dataset.paginationReusedBlocks = String(snapshot.reusedBlockCount);
13687
+ if (snapshot.unsupportedLayout || !snapshot.blocks.length) {
13688
+ observeBlocks(snapshot.blocks);
13689
+ editorDom.dataset.paginationState = snapshot.unsupportedLayout ? 'unsupported' : 'empty';
13690
+ setPagination(null);
13691
+ paginationCache.current = null;
13692
+ measurementRange.commit(measurementPass);
13693
+ return;
13694
+ }
13695
+ const previousPagination = paginationCache.current;
13696
+ const layoutPlan = planIncrementalDocumentLayout(previousPagination, snapshot.blocks, measurementStart);
13697
+ editorDom.dataset.paginationLaidOutBlocks = String(layoutPlan.blocks.length);
13698
+ editorDom.dataset.paginationReusedPages = String(layoutPlan.reusedPageCount);
13699
+ try {
13700
+ editorDom.dataset.paginationState = 'layout';
13701
+ const partialLayout = await client.layout({
13702
+ revision: nextRevision,
13703
+ documentRevision,
13704
+ startPageIndex: layoutPlan.startPageIndex,
13705
+ page,
13706
+ pageStyles: snapshot.pageStyles,
13707
+ blocks: layoutPlan.blocks
13708
+ }, signal);
13709
+ if (disposed || signal.aborted || nextRevision !== revision.current || editor.isDestroyed) return;
13710
+ const layout = previousPagination && layoutPlan.startPageIndex > 0 ? mergeIncrementalDocumentLayout(previousPagination.layout, partialLayout) : partialLayout;
13711
+ const blockById = new Map(snapshot.blocks.map((block)=>[
13712
+ block.block.id,
13713
+ block
13714
+ ]));
13715
+ const pageDescriptorDerivation = deriveDocumentPaginationPageDescriptors(layout, snapshot.blocks, editor.state.doc, previousPagination?.pages, layoutPlan.reusedPageCount);
13716
+ const pages = pageDescriptorDerivation.pages;
13717
+ editorDom.dataset.paginationReusedPageChrome = String(pageDescriptorDerivation.reusedPageCount);
13718
+ editorDom.dataset.paginationDerivedPageChrome = String(pageDescriptorDerivation.derivedPageCount);
13719
+ const pageByIndex = new Map(pages.map((descriptor)=>[
13720
+ descriptor.pageIndex,
13721
+ descriptor
13722
+ ]));
13723
+ editor.commands.applyDocumentPagination(nextRevision, layout.breaks.flatMap((pageBreak)=>{
13724
+ const block = blockById.get(pageBreak.beforeBlockId);
13725
+ return block ? [
13726
+ {
13727
+ ...pageBreak,
13728
+ previousPage: pageByIndex.get(pageBreak.pageIndex - 1)?.page ?? layout.pages[pageBreak.pageIndex - 1]?.page ?? page,
13729
+ nextPage: pageByIndex.get(pageBreak.pageIndex)?.page ?? layout.pages[pageBreak.pageIndex]?.page ?? page,
13730
+ position: block.from,
13731
+ inlineOffsetLeft: block.inlineOffsetLeft,
13732
+ inlineOffsetRight: block.inlineOffsetRight,
13733
+ previousPageChrome: visualPageChrome(pageByIndex.get(pageBreak.pageIndex - 1)),
13734
+ nextPageChrome: visualPageChrome(pageByIndex.get(pageBreak.pageIndex)),
13735
+ tableBreak: block.tableBreak
13736
+ }
13737
+ ] : [];
13738
+ }));
13739
+ observeBlocks(snapshot.blocks);
13740
+ editorDom.dataset.paginationEngine = layout.engine;
13741
+ editorDom.dataset.paginationDocumentRevision = String(layout.documentRevision);
13742
+ editorDom.dataset.paginationPages = String(layout.pages.length);
13743
+ editorDom.dataset.paginationState = 'ready';
13744
+ delete editorDom.dataset.paginationError;
13745
+ const nextPagination = {
13746
+ layout,
13747
+ blocks: snapshot.blocks,
13748
+ pages,
13749
+ pageByBlockId: new Map(layout.pages.flatMap((page)=>page.placements.map((placement)=>[
13750
+ placement.blockId,
13751
+ page.index + 1
13752
+ ])))
13753
+ };
13754
+ paginationCache.current = nextPagination;
13755
+ measurementRange.commit(measurementPass);
13756
+ setPagination(nextPagination);
13757
+ } catch (error) {
13758
+ if (disposed || signal.aborted || error instanceof DOMException && 'AbortError' === error.name) return;
13759
+ editorDom.dataset.paginationState = 'error';
13760
+ editorDom.dataset.paginationError = error instanceof Error ? `${error.name}: ${error.message}` : 'UnknownError';
13761
+ editor.commands.clearDocumentPagination(nextRevision);
13762
+ paginationCache.current = null;
13763
+ setPagination(null);
13764
+ }
13765
+ } finally{
13766
+ measurementRange.restore(measurementPass);
13719
13767
  }
13720
13768
  };
13721
13769
  const coordinator = createDocumentPaginationRunCoordinator({
13722
13770
  cancelFrame: cancelAnimationFrame,
13723
- onAbort: ()=>updateDiagnostic('aborts', 'paginationAborts'),
13771
+ onAbort: ()=>{
13772
+ measurementRange.restoreActive();
13773
+ updateDiagnostic('aborts', 'paginationAborts');
13774
+ },
13724
13775
  onCoalescedRequest: ()=>updateDiagnostic('coalescedRequests', 'paginationCoalescedRequests'),
13725
13776
  onError: (error)=>{
13777
+ measurementRange.restoreActive();
13726
13778
  if (disposed || editor.isDestroyed) return;
13727
13779
  editorDom.dataset.paginationState = 'error';
13728
13780
  editorDom.dataset.paginationError = error instanceof Error ? `${error.name}: ${error.message}` : 'UnknownError';
@@ -13745,7 +13797,7 @@ function useDocumentPagination({ editor, documentRevision, enabled, layoutKey, p
13745
13797
  });
13746
13798
  };
13747
13799
  const markDirty = (position)=>{
13748
- dirtyMeasurementFrom.current = Math.min(dirtyMeasurementFrom.current, Math.max(0, position));
13800
+ measurementRange.invalidate(position);
13749
13801
  };
13750
13802
  const handleDocumentUpdate = ({ transaction })=>{
13751
13803
  updateDiagnostic('documentTriggers', 'paginationDocumentTriggers');
@@ -14029,7 +14081,10 @@ function pageMetricsKey(page) {
14029
14081
  page.headerHeight,
14030
14082
  page.footerHeight,
14031
14083
  page.pageGap
14032
- ].join(':');
14084
+ ].map(canonicalPageMetric).join(':');
14085
+ }
14086
+ function canonicalPageMetric(value) {
14087
+ return Math.round(1000000 * value) / 1000000;
14033
14088
  }
14034
14089
  function earliestChangedPosition(transaction) {
14035
14090
  let earliest = 1 / 0;
package/dist/8928.js CHANGED
@@ -7672,7 +7672,7 @@ function documentSectionNodeAttributes(layout, id) {
7672
7672
  documentGridLinePitch: normalizedDocumentGrid(layout.documentGrid)?.linePitch ?? null
7673
7673
  };
7674
7674
  }
7675
- function documentSectionLayoutFromNodeAttributes(attributes, fallback) {
7675
+ function work_document_section_documentSectionLayoutFromNodeAttributes(attributes, fallback) {
7676
7676
  const base = fallback ?? {
7677
7677
  pageSize: 'a4',
7678
7678
  orientation: 'portrait',
@@ -7765,7 +7765,7 @@ function documentSectionDomAttributes(layout, id) {
7765
7765
  };
7766
7766
  }
7767
7767
  function documentSectionLayoutFromElement(element, fallback) {
7768
- return documentSectionLayoutFromNodeAttributes({
7768
+ return work_document_section_documentSectionLayoutFromNodeAttributes({
7769
7769
  pageSize: element.dataset.sectionPageSize,
7770
7770
  orientation: element.dataset.sectionOrientation,
7771
7771
  marginTop: numberValue(element.dataset.sectionMarginTop),
@@ -7911,7 +7911,7 @@ function activeDocumentSectionFromState(state) {
7911
7911
  count: sections.length,
7912
7912
  position,
7913
7913
  node,
7914
- layout: documentSectionLayoutFromNodeAttributes(node.attrs)
7914
+ layout: work_document_section_documentSectionLayoutFromNodeAttributes(node.attrs)
7915
7915
  };
7916
7916
  }
7917
7917
  function updateActiveDocumentSection(layout, commandContext) {
@@ -7933,7 +7933,7 @@ function updateDocumentSection(sectionId, layout, commandContext) {
7933
7933
  const globals = documentPageMarginsForLayout(next);
7934
7934
  for (const [index, candidate] of directDocumentSections(state.doc).entries()){
7935
7935
  const id = documentSectionId(candidate.node, index);
7936
- const current = documentSectionLayoutFromNodeAttributes(candidate.node.attrs);
7936
+ const current = work_document_section_documentSectionLayoutFromNodeAttributes(candidate.node.attrs);
7937
7937
  const synchronized = synchronizeDocumentPageMarginGlobals(id === sectionId ? next : current, globals);
7938
7938
  commandContext.tr.setNodeMarkup(candidate.position, void 0, documentSectionNodeAttributes(synchronized, id));
7939
7939
  }
@@ -8008,7 +8008,7 @@ function documentSectionByIdInDocument(document1, sectionId) {
8008
8008
  count: sections.length,
8009
8009
  position: section.position,
8010
8010
  node: section.node,
8011
- layout: documentSectionLayoutFromNodeAttributes(section.node.attrs)
8011
+ layout: work_document_section_documentSectionLayoutFromNodeAttributes(section.node.attrs)
8012
8012
  };
8013
8013
  }
8014
8014
  function directDocumentSections(document1) {
@@ -12967,30 +12967,37 @@ function sanitizeRepeatedDocumentTableElement(clone) {
12967
12967
  pageBreak.remove();
12968
12968
  });
12969
12969
  }
12970
- function measureDocumentLayoutBlocks(editor, previous = null, dirtyFrom = 0, textLayouts = new Map(), maximumFragmentedTableRowHeight = 1000000) {
12970
+ const DOCUMENT_LAYOUT_MEASUREMENT_SLICE_MS = 32;
12971
+ async function measureDocumentLayoutBlocksIncrementally(editor, previous = null, dirtyFrom = 0, textLayouts = new Map(), maximumFragmentedTableRowHeight = 1000000, options = {}) {
12971
12972
  const blocks = [];
12972
12973
  const pageStyleByMetrics = new Map();
12973
12974
  const counts = {
12974
12975
  measured: 0,
12975
12976
  reused: 0
12976
12977
  };
12978
+ const checkpoint = options.checkpoint ?? createDocumentLayoutMeasurementCheckpoint();
12979
+ throwIfDocumentMeasurementAborted(options.signal);
12977
12980
  let unsupportedLayout = false;
12978
- editor.state.doc.forEach((section, sectionPosition, sectionIndex)=>{
12979
- if ('documentSection' !== section.type.name) return;
12980
- const layout = documentSectionLayoutFromNodeAttributes(section.attrs);
12981
- if (layout.columns.count > 1) unsupportedLayout = true;
12982
- const page = documentPageMetrics(layout);
12983
- const metricsKey = documentPageMetricsKey(page);
12984
- let pageStyle = pageStyleByMetrics.get(metricsKey);
12985
- if (!pageStyle) {
12986
- pageStyle = {
12987
- id: `document-page-style-${pageStyleByMetrics.size + 1}`,
12988
- page
12989
- };
12990
- pageStyleByMetrics.set(metricsKey, pageStyle);
12981
+ let sectionPosition = 0;
12982
+ for(let sectionIndex = 0; sectionIndex < editor.state.doc.childCount; sectionIndex += 1){
12983
+ const section = editor.state.doc.child(sectionIndex);
12984
+ if ('documentSection' === section.type.name) {
12985
+ const layout = work_document_section_documentSectionLayoutFromNodeAttributes(section.attrs);
12986
+ if (layout.columns.count > 1) unsupportedLayout = true;
12987
+ const page = documentPageMetrics(layout);
12988
+ const metricsKey = documentPageMetricsKey(page);
12989
+ let pageStyle = pageStyleByMetrics.get(metricsKey);
12990
+ if (!pageStyle) {
12991
+ pageStyle = {
12992
+ id: `document-page-style-${pageStyleByMetrics.size + 1}`,
12993
+ page
12994
+ };
12995
+ pageStyleByMetrics.set(metricsKey, pageStyle);
12996
+ }
12997
+ await measureSectionBlocksIncrementally(editor, section, sectionPosition, sectionIndex, layout, pageStyle, previous?.blocks ?? [], dirtyFrom, textLayouts, Math.min(maximumFragmentedTableRowHeight, documentPageBodyHeight(pageStyle.page)), counts, blocks, checkpoint, options.signal);
12991
12998
  }
12992
- measureSectionBlocks(editor, section, sectionPosition, sectionIndex, layout, pageStyle, previous?.blocks ?? [], dirtyFrom, textLayouts, Math.min(maximumFragmentedTableRowHeight, documentPageBodyHeight(pageStyle.page)), counts, blocks);
12993
- });
12999
+ sectionPosition += section.nodeSize;
13000
+ }
12994
13001
  return {
12995
13002
  blocks,
12996
13003
  pageStyles: [
@@ -13022,63 +13029,74 @@ function documentPageMetrics(layout, physicalPage = 1) {
13022
13029
  function documentPageBodyHeight(page) {
13023
13030
  return Math.max(1, page.height - page.marginTop - page.marginBottom);
13024
13031
  }
13025
- function measureSectionBlocks(editor, section, sectionPosition, sectionIndex, layout, pageStyle, previous, dirtyFrom, textLayouts, maximumFragmentedTableRowHeight, counts, result) {
13032
+ async function measureSectionBlocksIncrementally(editor, section, sectionPosition, sectionIndex, layout, pageStyle, previous, dirtyFrom, textLayouts, maximumFragmentedTableRowHeight, counts, result, checkpoint, signal) {
13026
13033
  const sectionBlocks = [];
13027
- section.forEach((node, offset, index)=>{
13028
- const position = sectionPosition + offset + 1;
13029
- const element = elementForNode(editor, position);
13030
- if (!element) return;
13031
- const id = documentBlockId(sectionPosition, index, position);
13032
- const paragraphPagination = documentNodeParagraphPagination(node);
13033
- if (isDocumentListNode(node)) {
13034
- const reused = reusableDocumentListLayoutBlocks(previous, id, position + node.nodeSize, dirtyFrom);
13035
- if (reused.length) {
13036
- counts.reused += reused.length;
13037
- sectionBlocks.push(...reused);
13038
- return;
13039
- }
13040
- const listBlocks = measureDocumentListBlocks(editor, node, element, id, position, textLayouts, maximumFragmentedTableRowHeight);
13041
- if (listBlocks.length) {
13042
- counts.measured += listBlocks.length;
13043
- sectionBlocks.push(...listBlocks);
13044
- return;
13045
- }
13046
- }
13047
- const reused = reusableDocumentLayoutBlocks(previous, id, element, position + node.nodeSize, dirtyFrom);
13034
+ let offset = 0;
13035
+ for(let index = 0; index < section.childCount; index += 1){
13036
+ await checkpoint(signal);
13037
+ throwIfDocumentMeasurementAborted(signal);
13038
+ const node = section.child(index);
13039
+ measureSectionBlock(editor, node, offset, index, sectionPosition, previous, dirtyFrom, textLayouts, maximumFragmentedTableRowHeight, counts, sectionBlocks);
13040
+ offset += node.nodeSize;
13041
+ }
13042
+ finishMeasuredSection(section, sectionPosition, sectionIndex, layout, pageStyle, sectionBlocks, result);
13043
+ }
13044
+ function measureSectionBlock(editor, node, offset, index, sectionPosition, previous, dirtyFrom, textLayouts, maximumFragmentedTableRowHeight, counts, sectionBlocks) {
13045
+ const position = sectionPosition + offset + 1;
13046
+ const element = elementForNode(editor, position);
13047
+ if (!element) return;
13048
+ const id = documentBlockId(sectionPosition, index, position);
13049
+ const paragraphPagination = documentNodeParagraphPagination(node);
13050
+ if (isDocumentListNode(node)) {
13051
+ const reused = reusableDocumentListLayoutBlocks(previous, id, position + node.nodeSize, dirtyFrom);
13048
13052
  if (reused.length) {
13049
13053
  counts.reused += reused.length;
13050
13054
  sectionBlocks.push(...reused);
13051
13055
  return;
13052
13056
  }
13053
- if ('table' === node.type.name) {
13054
- const tableRows = measureDocumentTableRows(editor, node, element, id, position, maximumFragmentedTableRowHeight);
13055
- if (tableRows.length) {
13056
- counts.measured += tableRows.length;
13057
- sectionBlocks.push(...tableRows);
13058
- return;
13059
- }
13057
+ const listBlocks = measureDocumentListBlocks(editor, node, element, id, position, textLayouts, maximumFragmentedTableRowHeight);
13058
+ if (listBlocks.length) {
13059
+ counts.measured += listBlocks.length;
13060
+ sectionBlocks.push(...listBlocks);
13061
+ return;
13060
13062
  }
13061
- const lineFragments = measureParagraphLineFragments(editor, node, element, id, position, position + node.nodeSize, paragraphPagination, textLayouts.get(id));
13062
- if (lineFragments.length > 1) {
13063
- counts.measured += lineFragments.length;
13064
- sectionBlocks.push(...lineFragments);
13063
+ }
13064
+ const reused = reusableDocumentLayoutBlocks(previous, id, element, position + node.nodeSize, dirtyFrom);
13065
+ if (reused.length) {
13066
+ counts.reused += reused.length;
13067
+ sectionBlocks.push(...reused);
13068
+ return;
13069
+ }
13070
+ if ('table' === node.type.name) {
13071
+ const tableRows = measureDocumentTableRows(editor, node, element, id, position, maximumFragmentedTableRowHeight);
13072
+ if (tableRows.length) {
13073
+ counts.measured += tableRows.length;
13074
+ sectionBlocks.push(...tableRows);
13065
13075
  return;
13066
13076
  }
13067
- counts.measured += 1;
13068
- sectionBlocks.push(measuredDocumentBlock({
13069
- block: {
13070
- id,
13071
- height: documentBlockFlowHeight(node, element),
13072
- breakBefore: paragraphPagination.pageBreakBefore,
13073
- breakAfter: 'pageBreak' === node.type.name,
13074
- keepTogether: paragraphPagination.keepLines || shouldKeepDocumentBlockTogether(node),
13075
- keepWithNext: paragraphPagination.keepWithNext
13076
- },
13077
- element,
13078
- from: position,
13079
- to: position + node.nodeSize
13080
- }));
13081
- });
13077
+ }
13078
+ const lineFragments = measureParagraphLineFragments(editor, node, element, id, position, position + node.nodeSize, paragraphPagination, textLayouts.get(id));
13079
+ if (lineFragments.length > 1) {
13080
+ counts.measured += lineFragments.length;
13081
+ sectionBlocks.push(...lineFragments);
13082
+ return;
13083
+ }
13084
+ counts.measured += 1;
13085
+ sectionBlocks.push(measuredDocumentBlock({
13086
+ block: {
13087
+ id,
13088
+ height: documentBlockFlowHeight(node, element),
13089
+ breakBefore: paragraphPagination.pageBreakBefore,
13090
+ breakAfter: 'pageBreak' === node.type.name,
13091
+ keepTogether: paragraphPagination.keepLines || shouldKeepDocumentBlockTogether(node),
13092
+ keepWithNext: paragraphPagination.keepWithNext
13093
+ },
13094
+ element,
13095
+ from: position,
13096
+ to: position + node.nodeSize
13097
+ }));
13098
+ }
13099
+ function finishMeasuredSection(section, sectionPosition, sectionIndex, layout, pageStyle, sectionBlocks, result) {
13082
13100
  const last = sectionBlocks.at(-1);
13083
13101
  if (last && 'continuous' !== layout.breakAfter && 'nextColumn' !== layout.breakAfter) last.block.breakAfter = true;
13084
13102
  const sectionMetadata = {
@@ -13095,6 +13113,41 @@ function measureSectionBlocks(editor, section, sectionPosition, sectionIndex, la
13095
13113
  }
13096
13114
  result.push(...sectionBlocks);
13097
13115
  }
13116
+ function createDocumentLayoutMeasurementCheckpoint() {
13117
+ let deadline = documentMeasurementNow() + DOCUMENT_LAYOUT_MEASUREMENT_SLICE_MS;
13118
+ return async (signal)=>{
13119
+ throwIfDocumentMeasurementAborted(signal);
13120
+ if (documentMeasurementNow() < deadline) return;
13121
+ await yieldDocumentMeasurement();
13122
+ throwIfDocumentMeasurementAborted(signal);
13123
+ deadline = documentMeasurementNow() + DOCUMENT_LAYOUT_MEASUREMENT_SLICE_MS;
13124
+ };
13125
+ }
13126
+ function documentMeasurementNow() {
13127
+ return globalThis.performance?.now?.() ?? Date.now();
13128
+ }
13129
+ function yieldDocumentMeasurement() {
13130
+ if ("u" > typeof MessageChannel) return new Promise((resolve)=>{
13131
+ const channel = new MessageChannel();
13132
+ channel.port1.addEventListener('message', ()=>{
13133
+ channel.port1.close();
13134
+ channel.port2.close();
13135
+ resolve();
13136
+ }, {
13137
+ once: true
13138
+ });
13139
+ channel.port1.start();
13140
+ channel.port2.postMessage(void 0);
13141
+ });
13142
+ return new Promise((resolve)=>globalThis.setTimeout(resolve, 0));
13143
+ }
13144
+ function throwIfDocumentMeasurementAborted(signal) {
13145
+ if (!signal?.aborted) return;
13146
+ if (signal.reason instanceof Error) throw signal.reason;
13147
+ const error = new Error('Document layout measurement was aborted');
13148
+ error.name = 'AbortError';
13149
+ throw error;
13150
+ }
13098
13151
  function documentPageMetricsKey(page) {
13099
13152
  return [
13100
13153
  page.width,
@@ -13337,7 +13390,7 @@ const DocumentSection = core_Node.create({
13337
13390
  ];
13338
13391
  },
13339
13392
  renderHTML ({ node, HTMLAttributes }) {
13340
- const layout = documentSectionLayoutFromNodeAttributes(node.attrs);
13393
+ const layout = work_document_section_documentSectionLayoutFromNodeAttributes(node.attrs);
13341
13394
  const id = 'string' == typeof node.attrs.id && node.attrs.id ? node.attrs.id : 'document-section';
13342
13395
  const columns = layout.columns;
13343
13396
  const page = documentPageMetrics(layout);
@@ -17415,4 +17468,4 @@ function capturePages(surface) {
17415
17468
  width: surface.pageWidth
17416
17469
  }));
17417
17470
  }
17418
- export { DEFAULT_DOCUMENT_TABLE_CELL_FORMAT, DEFAULT_DOCUMENT_TABLE_CELL_MARGINS, DEFAULT_DOCUMENT_TABLE_GEOMETRY, DOCUMENT_BOOKMARK_DUPLICATE_MESSAGE, DOCUMENT_PAGE_BORDER_EDGES, DOCUMENT_PAGE_MARGIN_KEYS, DOCUMENT_PARAGRAPH_BORDER_EDGES, DOCUMENT_PARAGRAPH_BORDER_STYLES, DOCUMENT_PARAGRAPH_SHADING_PATTERNS, DOCUMENT_TABLE_ROW_ID_ATTRIBUTE, DOCUMENT_TABLE_ROW_TEXT_ID_ATTRIBUTE, DOCUMENT_TABLE_STYLE_OPTIONS, DocumentEquation, DocumentImage, DocumentParagraphFormatting, DocumentParagraphIdentity, DocumentSubscript, DocumentSuperscript, DocumentTableRowIdentity, DocxThemePatchCollector, MAX_DOCUMENT_IMAGE_RELATIVE_HEIGHT, MAX_DOCUMENT_NUMBERING_START, activeDocumentBookmark, activeDocumentSection, activeDocumentTableStyle, applyDocumentImageCropToElement, applyDocumentImageIdentityToElement, applyDocumentImageLayerToElement, applyDocumentImageWrapContourToElement, applyDocumentPageGeometry, applyDocumentParagraphIdentityToElement, applyDocumentTableGeometryToElement, applyDocumentTableRowIdentityToElement, canChangeDocumentIndent, canInsertDocumentComment, canSetDocumentTableRowRepeatHeader, clampDocumentMargin, collectDocumentChanges, collectDocumentCommentAnchors, collectDocumentNotes, collectDocumentTextLayoutParagraphs, createDocumentBibliography, createDocumentEquationElement, createDocumentImageIdentityRegistry, createDocumentNoteElement, createDocumentParagraphIdentityRegistry, createWorkDocumentExtensions, createWorkDocumentModel, createWorkDocumentModelFromContent, createWorkOfficeDocumentCollaborationBinding as createOfficeDocumentCollaborationBinding, defaultDocumentImageWrapContour, documentAutoLineHeight, documentBookmarkNameExists, documentBookmarkReferenceInstruction, documentBulletListStyle, documentCaptionKind, documentCaptionLabel, documentCitationCount, documentCitationInstruction, documentCitationStyle, documentCitationStyleDetails, documentCitationTags, documentCitationTagsFromInstruction, documentCommentDraftRange, documentCommentViews, documentContentLayoutProperties, documentEquationFromElement, documentEquationText, documentImageCropFromElement, documentImageIdentityFromElement, documentImageLayerFromElement, documentImageLayoutFromElement, documentImageLayoutOptions, documentImagePositionFromElement, documentImageProperties, documentImageWrapContourFromElement, documentInitialSectionLayout, documentModelForContent, documentNoteKey, documentNoteKind, documentOrderedListState, documentPageBordersVisible, documentPageChromeLegacyFields, documentPageGeometryForLayout, documentPageHorizontalMarginTwips, documentPageMarginBody, documentPageMarginsForLayout, documentPageMetrics, documentParagraphBordersDomAttributes, documentParagraphDirection, documentParagraphIdentityFromElement, documentParagraphIndent, documentParagraphPagination, documentParagraphShadingDomAttributes, documentParagraphSpacing, documentParagraphTabStops, documentSectionById, documentSectionDomAttributes, documentSections, documentTabLeaderLabel, documentTableBordersFromElement, documentTableCellFormat, documentTableCellMarginOverridesFromElement, documentTableColumnPercentagesFromElement, documentTableGeometryFromElement, documentTableHorizontalAlignment, documentTableRowIdentityFromElement, documentTableRowOptions, documentTableSizing, documentTextLayoutBatches, documentWordLineHeightFactor, docxBookmarkReferenceTarget, docxDocumentFieldKind, editorDocumentBookmarkReferenceTargets, editorDocumentCaptionTargets, initializeWorkOfficeDocumentCollaboration as initializeOfficeDocumentCollaboration, isContourImageLayout, isDocumentParagraphArtBorderStyle, isValidDocumentCitationTag, materializeWorkDocumentContent, measureDocumentLayoutBlocks, millimetersToPixels, mountWorkLiveDocumentCapture, nextDocumentTabAlignment, normalizeDocumentBookmarkName, normalizeDocumentBookmarkNativeId, normalizeDocumentBookmarkReferencesHtml, normalizeDocumentBookmarksHtml, normalizeDocumentCaptionsHtml, normalizeDocumentCitationsHtml, normalizeDocumentColumns, normalizeDocumentEquation, normalizeDocumentFieldsHtml, normalizeDocumentHtml, normalizeDocumentImageAlignment, normalizeDocumentImageCrop, normalizeDocumentImageIdentity, normalizeDocumentImageLayer, normalizeDocumentImageLayoutOptions, normalizeDocumentImagePosition, normalizeDocumentImageWrapContour, normalizeDocumentImageWrapSide, normalizeDocumentNotesHtml, normalizeDocumentPageBorders, normalizeDocumentPageChrome, normalizeDocumentPageGeometry, normalizeDocumentPageMargins, normalizeDocumentPaperSource, normalizeDocumentParagraphBorder, normalizeDocumentParagraphBorders, normalizeDocumentParagraphId, normalizeDocumentParagraphIdentity, normalizeDocumentParagraphIndent, normalizeDocumentTabStops, normalizeDocumentTableBorderStyle, normalizeDocumentTableBorderWidth, normalizeDocumentTableRowHeightRule, normalizeDocumentTableRowIdentity, normalizeDocumentTableVerticalAlign, normalizeTableColor, normalizedTabPosition, pageTwipsToMillimeters, parseDocumentParagraphBordersElement, parseDocumentParagraphShadingElement, parseDocxThemeReference, patchDocxThemeReferences, positionWorkLiveDocumentCapture, readWorkOfficeDocumentCollaboration as readOfficeDocumentCollaboration, renderDocumentAutoLineHeight, renderDocumentTableBorders, renderDocumentTableCellMarginOverrides, resolveDocumentPageBorders, resolveDocumentPageChrome, resolveDocumentPageMargins, resolveDocumentPageSize, resolveWorkDocumentEditorInput, retainAnchoredDocumentComments, sanitizeDocumentPageChromeHtml, serializeDocumentTabStops, serializeDocxThemeReference, serializeWorkDocumentNode, setCustomDocumentColumns, supportedDocxBookmarkReferenceInstruction, syncDocumentContentFromHtml, uniqueDocumentImageIdentity, uniqueDocumentParagraphIdentity, updateDocumentColumnWidth, updateDocumentCustomPageMillimeters, updateDocumentGutterPosition, updateDocumentMirrorMargins, updateDocumentPageChromeVariant, updateDocumentPageMarginMillimeters, updateDocumentPageMarginMode, updateDocumentPageOrientation, updateDocumentPaperSizePreset, validateDocumentBookmarkName, workDocumentSchema, workOfficeDocumentCollaborationFragment as officeDocumentCollaborationFragment, work_document_page_margins_twipsToMillimeters, wrapsBesideImage };
17471
+ export { DEFAULT_DOCUMENT_TABLE_CELL_FORMAT, DEFAULT_DOCUMENT_TABLE_CELL_MARGINS, DEFAULT_DOCUMENT_TABLE_GEOMETRY, DOCUMENT_BOOKMARK_DUPLICATE_MESSAGE, DOCUMENT_PAGE_BORDER_EDGES, DOCUMENT_PAGE_MARGIN_KEYS, DOCUMENT_PARAGRAPH_BORDER_EDGES, DOCUMENT_PARAGRAPH_BORDER_STYLES, DOCUMENT_PARAGRAPH_SHADING_PATTERNS, DOCUMENT_TABLE_ROW_ID_ATTRIBUTE, DOCUMENT_TABLE_ROW_TEXT_ID_ATTRIBUTE, DOCUMENT_TABLE_STYLE_OPTIONS, DocumentEquation, DocumentImage, DocumentParagraphFormatting, DocumentParagraphIdentity, DocumentSubscript, DocumentSuperscript, DocumentTableRowIdentity, DocxThemePatchCollector, MAX_DOCUMENT_IMAGE_RELATIVE_HEIGHT, MAX_DOCUMENT_NUMBERING_START, activeDocumentBookmark, activeDocumentSection, activeDocumentTableStyle, applyDocumentImageCropToElement, applyDocumentImageIdentityToElement, applyDocumentImageLayerToElement, applyDocumentImageWrapContourToElement, applyDocumentPageGeometry, applyDocumentParagraphIdentityToElement, applyDocumentTableGeometryToElement, applyDocumentTableRowIdentityToElement, canChangeDocumentIndent, canInsertDocumentComment, canSetDocumentTableRowRepeatHeader, clampDocumentMargin, collectDocumentChanges, collectDocumentCommentAnchors, collectDocumentNotes, collectDocumentTextLayoutParagraphs, createDocumentBibliography, createDocumentEquationElement, createDocumentImageIdentityRegistry, createDocumentNoteElement, createDocumentParagraphIdentityRegistry, createWorkDocumentExtensions, createWorkDocumentModel, createWorkDocumentModelFromContent, createWorkOfficeDocumentCollaborationBinding as createOfficeDocumentCollaborationBinding, defaultDocumentImageWrapContour, documentAutoLineHeight, documentBookmarkNameExists, documentBookmarkReferenceInstruction, documentBulletListStyle, documentCaptionKind, documentCaptionLabel, documentCitationCount, documentCitationInstruction, documentCitationStyle, documentCitationStyleDetails, documentCitationTags, documentCitationTagsFromInstruction, documentCommentDraftRange, documentCommentViews, documentContentLayoutProperties, documentEquationFromElement, documentEquationText, documentImageCropFromElement, documentImageIdentityFromElement, documentImageLayerFromElement, documentImageLayoutFromElement, documentImageLayoutOptions, documentImagePositionFromElement, documentImageProperties, documentImageWrapContourFromElement, documentInitialSectionLayout, documentModelForContent, documentNoteKey, documentNoteKind, documentOrderedListState, documentPageBordersVisible, documentPageChromeLegacyFields, documentPageGeometryForLayout, documentPageHorizontalMarginTwips, documentPageMarginBody, documentPageMarginsForLayout, documentPageMetrics, documentParagraphBordersDomAttributes, documentParagraphDirection, documentParagraphIdentityFromElement, documentParagraphIndent, documentParagraphPagination, documentParagraphShadingDomAttributes, documentParagraphSpacing, documentParagraphTabStops, documentSectionById, documentSectionDomAttributes, documentSections, documentTabLeaderLabel, documentTableBordersFromElement, documentTableCellFormat, documentTableCellMarginOverridesFromElement, documentTableColumnPercentagesFromElement, documentTableGeometryFromElement, documentTableHorizontalAlignment, documentTableRowIdentityFromElement, documentTableRowOptions, documentTableSizing, documentTextLayoutBatches, documentWordLineHeightFactor, docxBookmarkReferenceTarget, docxDocumentFieldKind, editorDocumentBookmarkReferenceTargets, editorDocumentCaptionTargets, initializeWorkOfficeDocumentCollaboration as initializeOfficeDocumentCollaboration, isContourImageLayout, isDocumentParagraphArtBorderStyle, isValidDocumentCitationTag, materializeWorkDocumentContent, measureDocumentLayoutBlocksIncrementally, millimetersToPixels, mountWorkLiveDocumentCapture, nextDocumentTabAlignment, normalizeDocumentBookmarkName, normalizeDocumentBookmarkNativeId, normalizeDocumentBookmarkReferencesHtml, normalizeDocumentBookmarksHtml, normalizeDocumentCaptionsHtml, normalizeDocumentCitationsHtml, normalizeDocumentColumns, normalizeDocumentEquation, normalizeDocumentFieldsHtml, normalizeDocumentHtml, normalizeDocumentImageAlignment, normalizeDocumentImageCrop, normalizeDocumentImageIdentity, normalizeDocumentImageLayer, normalizeDocumentImageLayoutOptions, normalizeDocumentImagePosition, normalizeDocumentImageWrapContour, normalizeDocumentImageWrapSide, normalizeDocumentNotesHtml, normalizeDocumentPageBorders, normalizeDocumentPageChrome, normalizeDocumentPageGeometry, normalizeDocumentPageMargins, normalizeDocumentPaperSource, normalizeDocumentParagraphBorder, normalizeDocumentParagraphBorders, normalizeDocumentParagraphId, normalizeDocumentParagraphIdentity, normalizeDocumentParagraphIndent, normalizeDocumentTabStops, normalizeDocumentTableBorderStyle, normalizeDocumentTableBorderWidth, normalizeDocumentTableRowHeightRule, normalizeDocumentTableRowIdentity, normalizeDocumentTableVerticalAlign, normalizeTableColor, normalizedTabPosition, pageTwipsToMillimeters, parseDocumentParagraphBordersElement, parseDocumentParagraphShadingElement, parseDocxThemeReference, patchDocxThemeReferences, positionWorkLiveDocumentCapture, readWorkOfficeDocumentCollaboration as readOfficeDocumentCollaboration, renderDocumentAutoLineHeight, renderDocumentTableBorders, renderDocumentTableCellMarginOverrides, resolveDocumentPageBorders, resolveDocumentPageChrome, resolveDocumentPageMargins, resolveDocumentPageSize, resolveWorkDocumentEditorInput, retainAnchoredDocumentComments, sanitizeDocumentPageChromeHtml, serializeDocumentTabStops, serializeDocxThemeReference, serializeWorkDocumentNode, setCustomDocumentColumns, supportedDocxBookmarkReferenceInstruction, syncDocumentContentFromHtml, uniqueDocumentImageIdentity, uniqueDocumentParagraphIdentity, updateDocumentColumnWidth, updateDocumentCustomPageMillimeters, updateDocumentGutterPosition, updateDocumentMirrorMargins, updateDocumentPageChromeVariant, updateDocumentPageMarginMillimeters, updateDocumentPageMarginMode, updateDocumentPageOrientation, updateDocumentPaperSizePreset, validateDocumentBookmarkName, workDocumentSchema, workOfficeDocumentCollaborationFragment as officeDocumentCollaborationFragment, work_document_page_margins_twipsToMillimeters, wrapsBesideImage };
@@ -0,0 +1,18 @@
1
+ export interface DocumentPaginationMeasurementPass {
2
+ readonly from: number;
3
+ }
4
+ export interface DocumentPaginationMeasurementRange {
5
+ begin: () => DocumentPaginationMeasurementPass;
6
+ commit: (pass: DocumentPaginationMeasurementPass) => void;
7
+ ensureDirty: () => void;
8
+ invalidate: (position: number) => void;
9
+ reset: () => void;
10
+ restore: (pass: DocumentPaginationMeasurementPass) => void;
11
+ restoreActive: () => void;
12
+ }
13
+ /**
14
+ * Tracks the earliest document position that the next pagination pass must
15
+ * measure. An active pass consumes that position temporarily, then either
16
+ * commits it or restores it when cooperative work is aborted.
17
+ */
18
+ export declare function createDocumentPaginationMeasurementRange(): DocumentPaginationMeasurementRange;
@@ -2,7 +2,18 @@ import type { Editor } from '@tiptap/core';
2
2
  import type { OfficeKernelPageMetrics, OfficeKernelTextLayoutParagraphResult } from '../../kernel/office-kernel-protocol';
3
3
  import type { DocumentPaginationSnapshot } from './work-document-pagination-types';
4
4
  import type { WorkDocumentSectionLayout } from './work-types';
5
+ export interface IncrementalDocumentLayoutMeasurementOptions {
6
+ checkpoint?: (signal?: AbortSignal) => Promise<void>;
7
+ signal?: AbortSignal;
8
+ }
5
9
  export declare function measureDocumentLayoutBlocks(editor: Editor, previous?: DocumentPaginationSnapshot | null, dirtyFrom?: number, textLayouts?: ReadonlyMap<string, OfficeKernelTextLayoutParagraphResult>, maximumFragmentedTableRowHeight?: number): DocumentPaginationSnapshot;
10
+ /**
11
+ * Measures the same canonical editor DOM as `measureDocumentLayoutBlocks`
12
+ * while yielding between top-level blocks. The browser keeps input, media,
13
+ * and animation tasks schedulable during large-document pagination without
14
+ * weakening the layout result or replacing the controlled editor model.
15
+ */
16
+ export declare function measureDocumentLayoutBlocksIncrementally(editor: Editor, previous?: DocumentPaginationSnapshot | null, dirtyFrom?: number, textLayouts?: ReadonlyMap<string, OfficeKernelTextLayoutParagraphResult>, maximumFragmentedTableRowHeight?: number, options?: IncrementalDocumentLayoutMeasurementOptions): Promise<DocumentPaginationSnapshot>;
6
17
  export declare function documentPageMetrics(layout: WorkDocumentSectionLayout, physicalPage?: number): OfficeKernelPageMetrics;
7
18
  export declare function documentPageBodyHeight(page: OfficeKernelPageMetrics): number;
8
19
  export declare function documentPaginationSurfaceHeight(pageCount: number, page: OfficeKernelPageMetrics): number;
@@ -2,7 +2,8 @@ import { Extension } from '@tiptap/core';
2
2
  import type { DocumentPaginationVisualBreak } from './work-document-pagination-types';
3
3
  export { createDocumentLineFragments, createShapedDocumentLineFragments, findDocumentLineStartOffset, } from './work-document-line-measurement';
4
4
  export { reusableDocumentLayoutBlocks } from './work-document-pagination-dom';
5
- export { documentPageBodyHeight, documentPageMetrics, documentPaginationSurfaceHeight, measureDocumentLayoutBlocks, } from './work-document-pagination-measurement';
5
+ export { documentPageBodyHeight, documentPageMetrics, documentPaginationSurfaceHeight, measureDocumentLayoutBlocks, measureDocumentLayoutBlocksIncrementally, } from './work-document-pagination-measurement';
6
+ export type { IncrementalDocumentLayoutMeasurementOptions } from './work-document-pagination-measurement';
6
7
  export type { DocumentPaginationSection, DocumentPaginationSnapshot, DocumentPaginationVisualBreak, DocumentPaginationVisualPageChrome, DocumentTableCellBoundary, DocumentTableCellFragmentMeasurement, DocumentTableCellPageBreak, DocumentTablePaginationBreak, DocumentTableRowFragmentPlan, DocumentTextLayoutCollection, MeasuredDocumentLayoutBlock, } from './work-document-pagination-types';
7
8
  export { createDocumentTableRowFragmentPlan } from './work-document-table-pagination';
8
9
  export { collectDocumentTextLayoutParagraphs, collectDocumentTextLayoutRuns, documentTextLayoutBatches, } from './work-document-text-layout';
Binary file
@@ -844,6 +844,13 @@ text validation, UTF-16 offset mapping, shaping, and their tests live in
844
844
  focused modules. This keeps the fallback and WASM paths independently
845
845
  testable without duplicating their public protocol.
846
846
 
847
+ Live DOM measurement is cooperatively scheduled in bounded 32 ms slices. It
848
+ yields only between top-level ProseMirror blocks, so one block is always
849
+ measured atomically against its rendered element. Document invalidation aborts
850
+ the stale pass before it can replace the reusable snapshot, while the existing
851
+ single-flight coordinator coalesces one fresh run. The synchronous measurement
852
+ path remains available for deterministic callers and parity tests.
853
+
847
854
  Layout requests carry a bounded, deduplicated page-style table and let each
848
855
  measured block reference its section geometry. A geometry transition opens a
849
856
  new physical page in both the JavaScript fallback and Rust/WASM kernel, and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a3s-lab/office",
3
- "version": "0.9.0",
3
+ "version": "0.9.2",
4
4
  "description": "Open-source collaborative browser editors for documents, Markdown, spreadsheets, presentations, and PDFs.",
5
5
  "keywords": [
6
6
  "office",