Totals are provisional
While layoutComplete is false, pageCount means pages
available so far, not the final total. Do not snapshot it when load()
resolves.
DOCX
The examples below render a sample Word document (.docx) in your browser and show
the corresponding TypeScript.
Story · Demo
Hand DocxViewer a canvas and it manages parsing, page layout and the current page. Step through with the built-in nextPage() / prevPage().
import { DocxViewer } from '@silurus/ooxml/docx';
// The built-in viewer tracks the current page for you.
const viewer = new DocxViewer(canvas, { width: 960, useGoogleFonts: true });
await viewer.load('/sample.docx');
nextBtn.addEventListener('click', () => viewer.nextPage());
prevBtn.addEventListener('click', () => viewer.prevPage()); Story · DocxScrollViewer
Use DocxScrollViewer for a virtualized, ready-made long-document reader. For a custom scrolling UI, combine DocxViewer with DocxDocument and let your application own the container and navigation.
import { DocxScrollViewer } from '@silurus/ooxml/docx';
// The built-in scroll viewer virtualizes a long document for you.
const scroller = document.querySelector('#scroller') as HTMLElement;
const viewer = new DocxScrollViewer(scroller, {
enableTextSelection: true,
useGoogleFonts: true,
});
await viewer.load('/sample.docx');
window.addEventListener('pagehide', (event) => {
if (event.persisted) return;
viewer.destroy();
}); Large documents
Set progressiveLayout: true on DocxViewer,
DocxScrollViewer, or DocxDocument.load(). For a large
document, load() resolves when the opening pages are paintable while the
same paginator continues in the background. This works in both render modes; worker
mode also keeps the remaining layout and paint work off the UI thread.
While layoutComplete is false, pageCount means pages
available so far, not the final total. Do not snapshot it when load()
resolves.
Use onPageChange or onVisiblePageChange for live navigation
UI. Await waitUntilLayoutComplete() before printing, exporting, or relying on
the authoritative total.
In-document NUMPAGES fields are repainted with the authoritative value
after pagination converges. Application-owned “Page X of Y” UI should use the
callback’s layoutComplete flag to mark provisional totals.
import { DocxScrollViewer } from '@silurus/ooxml/docx';
const container = document.querySelector('#document') as HTMLElement;
const pager = document.querySelector('#pager') as HTMLElement;
const viewer = new DocxScrollViewer(container, {
progressiveLayout: true,
mode: 'worker',
onVisiblePageChange(pageIndex, availablePages, layoutComplete) {
pager.textContent =
`Page ${pageIndex + 1} of ${availablePages}${layoutComplete ? '' : '…'}`;
pager.setAttribute('aria-busy', String(!layoutComplete));
},
});
await viewer.load('/document.docx');
// Print, export, and final-page-count UI need the converged layout.
await viewer.waitUntilLayoutComplete();
console.log('Final page count:', viewer.pageCount); Story · ThumbnailGrid
The same engine renders pages at any size. Lay them out in a grid at thumbnail width for a quick overview, with click-to-navigate.
import { DocxDocument } from '@silurus/ooxml/docx';
// Render each page small, wire up navigation.
const document = await DocxDocument.load('/sample.docx');
for (let i = 0; i < document.pageCount; i++) {
const thumb = document.createElement('canvas');
thumb.addEventListener('click', () => open(i));
grid.appendChild(thumb);
await document.renderPage(thumb, i, { width: 320 });
} Story · MasterDetail
Combine both: a DocxDocument for the thumbnail rail and a DocxViewer for the detail pane. Click a thumbnail to jump the preview with goToPage().
import { DocxDocument, DocxViewer } from '@silurus/ooxml/docx';
// Parse once, then lend the loaded engine to every view that needs it.
const document = await DocxDocument.load('/sample.docx');
// A large preview on the right borrows the engine and cannot acquire another source.
const viewer = DocxViewer.fromDocument(detailCanvas, document, {
width: 960,
enableTextSelection: true,
});
await viewer.goToPage(0);
// The thumbnail rail on the left renders from that same engine.
for (let i = 0; i < document.pageCount; i++) {
const thumb = document.createElement('canvas');
thumb.addEventListener('click', () => viewer.goToPage(i)); // jump the preview
rail.appendChild(thumb);
await document.renderPage(thumb, i, { width: 200 });
}
window.addEventListener('pagehide', (event) => {
if (event.persisted) return;
viewer.destroy();
document.destroy(); // borrowed engines remain caller-owned
});
Live · Built-in comment UI
Comments in context, rendered by ScrollViewer.
Turn on the built-in read-only UI when a conventional page-side comment margin is enough. The Viewer owns positioning, highlights, markers, zoom, and scrolling. Comment UI implementation guide →Loading sample-1.docx…
Comments appear beside the second page.