Skip to content

Commit a490373

Browse files
committed
fix(pdf-server): render page before O(numPages) annotation scans
PR #506 added loadBaselineAnnotations() and buildFieldNameMap() between showViewer() and renderPage(). Both iterate every page calling getPage()+getAnnotations(), so the viewer sat with an unsized empty canvas for ~400ms (15-page doc, scales linearly) before painting. Neither scan blocks the canvas — page.render() only needs canvasContext + viewport. renderAnnotationsForPage() reads annotationMap (graceful empty), AnnotationLayer.render() takes cachedFieldObjects (graceful null). So: render first, scan after, re-render to overlay annotations + form values. Same fix at the reload path.
1 parent f987bc4 commit a490373

File tree

1 file changed

+22
-10
lines changed

1 file changed

+22
-10
lines changed

examples/pdf-server/src/mcp-app.ts

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3850,11 +3850,16 @@ async function reloadPdf(): Promise<void> {
38503850
log.info("PDF reloaded:", totalPages, "pages,", totalBytes, "bytes");
38513851

38523852
showViewer();
3853+
// Render immediately — annotation/form scans below are O(numPages) and
3854+
// do NOT block the canvas. See same pattern in the initial-load path.
3855+
await renderPage();
3856+
38533857
await loadBaselineAnnotations(document);
38543858
await buildFieldNameMap(document);
38553859
syncFormValuesToStorage();
38563860
updateAnnotationsBadge();
38573861
renderAnnotationPanel();
3862+
38583863
renderPage();
38593864
startPreloading();
38603865
} catch (err) {
@@ -4057,9 +4062,18 @@ app.ontoolresult = async (result: CallToolResult) => {
40574062
downloadBtn.style.display = app.getHostCapabilities()?.downloadFile
40584063
? ""
40594064
: "none";
4060-
// Save button visibility driven by setDirty()/updateSaveBtn();
4061-
// restoreAnnotations() above may have already shown it via setDirty(true).
4062-
updateSaveBtn();
4065+
4066+
// Compute fit + render IMMEDIATELY for fast first paint. The canvas is
4067+
// unsized until renderPage() runs — anything async between showViewer()
4068+
// and here makes the empty viewer visible. The annotation/form scans
4069+
// below are O(numPages) and do NOT block the canvas (page.render only
4070+
// needs canvasContext+viewport), so they run after.
4071+
const fitScale = await computeFitToWidthScale();
4072+
if (fitScale !== null) {
4073+
scale = fitScale;
4074+
log.info("Fit-to-width scale:", scale);
4075+
}
4076+
await renderPage();
40634077

40644078
// Import annotations from the PDF to establish baseline
40654079
await loadBaselineAnnotations(document);
@@ -4072,14 +4086,12 @@ app.ontoolresult = async (result: CallToolResult) => {
40724086
syncFormValuesToStorage();
40734087

40744088
updateAnnotationsBadge();
4089+
// Save button visibility driven by setDirty()/updateSaveBtn();
4090+
// restoreAnnotations() may have just flipped it via setDirty(true).
4091+
updateSaveBtn();
40754092

4076-
// Compute fit-to-width scale for narrow containers (e.g. mobile)
4077-
const fitScale = await computeFitToWidthScale();
4078-
if (fitScale !== null) {
4079-
scale = fitScale;
4080-
log.info("Fit-to-width scale:", scale);
4081-
}
4082-
4093+
// Re-render to overlay PDF-baseline annotations + restored form values.
4094+
// For PDFs with neither, the canvas is identical → no flicker.
40834095
renderPage();
40844096
// Start background preloading of all pages for text extraction
40854097
startPreloading();

0 commit comments

Comments
 (0)