Add addTextOnlyPdfPage(): a PDF page with recognized text but no image - #34
Merged
Conversation
addPdfPage() embeds the full page image plus the invisible RTR text layer, correct for a PDF the user actually opens/exports. But some callers only need the resulting PDF to hand to pdf.js for getTextContent()/getViewport() (e.g. to build a search/select text layer over a page that's actually displayed some other way, like a directly rendered canvas) and never call pdf.js's render() against it — for them, the embedded image is pure dead weight. pdf-lib's PNG embedding always fully decodes and recompresses the image at save() time regardless of whether the source PNG was already compressed, which is real, size-proportional cost: profiling against a real 12-page, ~1404x1872, partly user-background note showed ~2.9s and ~4.5MB for the full image+text PDF, versus ~40ms and ~43KB for text-only, with identical extracted text. Refactors the recognition-text-drawing loop out of addPdfPage() into a shared drawRecognitionText() so both functions stay in sync.
philips-clanker
added a commit
to philips/supernote-obsidian-plugin
that referenced
this pull request
Jul 28, 2026
The PDF built for pdf.js's getTextContent()/getViewport() (search and text selection) was unconditionally paying the full cost of embedding every page's real image, even though pdf.js's render() is never called against it — the image is never actually shown. pdf-lib's PNG embedding always fully decodes and recompresses the source PNG at save() time regardless of it already being compressed, which is real, size-proportional cost. Bump the supernote-typescript submodule to pick up addTextOnlyPdfPage() (philips/supernote-typescript#34) and use it here instead of assemblePdfFromImages(). Profiled against a real 12-page note with several user-uploaded-background pages: ~2.9s/~4.5MB before, down to ~40ms/~43KB after, with identical extracted/searchable text. This was the dominant cost behind "opening an already-cached note still feels slow" — confirmed by splitting the earlier timing logs into per-phase numbers and finding the "decode" and "assemble" timings were nearly identical, i.e. competing for the same main thread. assemblePdfFromImages() (real images) is unchanged and still used by the user-facing "export/attach as PDF" commands, which need actual page content in their output.
4 tasks
pull Bot
pushed a commit
to ben-vargas/supernote-obsidian-plugin
that referenced
this pull request
Jul 28, 2026
The PDF built for pdf.js's getTextContent()/getViewport() (search and text selection) was unconditionally paying the full cost of embedding every page's real image, even though pdf.js's render() is never called against it — the image is never actually shown. pdf-lib's PNG embedding always fully decodes and recompresses the source PNG at save() time regardless of it already being compressed, which is real, size-proportional cost. Bump the supernote-typescript submodule to pick up addTextOnlyPdfPage() (philips/supernote-typescript#34) and use it here instead of assemblePdfFromImages(). Profiled against a real 12-page note with several user-uploaded-background pages: ~2.9s/~4.5MB before, down to ~40ms/~43KB after, with identical extracted/searchable text. Also moves the pdfDocPromise kickoff to after the page-render loop instead of before it: it's fire-and-forget but still runs on the same single JS thread, so starting it first let it compete with (and inflate the apparent cost of) the render loop. assemblePdfFromImages() (real images) is unchanged and still used by the user-facing "export/attach as PDF" commands, which need actual page content in their output. Also ignores personal .note test files dropped at the repo root for manual testing.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
addTextOnlyPdfPage(ctx, page, pageWidth, pageHeight, options?): builds a PDF page with the invisible recognized-text (RTR) layer, same asaddPdfPage(), but embeds no image at all.addPdfPage()into a shareddrawRecognitionText()helper used by both functions, so they can't drift out of sync.src/index.ts.Why
addPdfPage()is correct for a PDF the user actually opens or exports — it needs the real page image. But some callers only build a PDF to hand to pdf.js purely forgetTextContent()/getViewport()(e.g. building a search/selectable text layer over a page that's actually displayed some other way, like a directly-rendered<canvas>) and never call pdf.js'srender()against it. For those, the embedded image is pure dead weight — and not a cheap kind.pdf-lib's PNG embedding always fully decodes the PNG and recompresses it from scratch at.save()time (PngEmbeddercallsPNG.load()then re-flateStreams the raw RGB/alpha planes), regardless of the source PNG already being compressed. This is real, size-proportional cost.Profiled against a real 12-page, ~1404x1872px note with several user-uploaded-background pages:
addPdfPage(image + text): ~2.9s total (embedPng~0.8s,.save()~2.1s), ~4.5MB output.addTextOnlyPdfPage(text only): ~40ms total, ~43KB output.pdf-parse).This is being consumed by philips/supernote-obsidian-plugin's
SupernoteView, which was unconditionally paying the full image-embedding cost on every single note open just to get a text layer for search/selection, even though the image was never rendered.Test plan
npx vitest run(submodule) — 26 passed, including one new test assertingaddTextOnlyPdfPageproduces the same extracted text asaddPdfPagefor the same note, and that its output is under 1/10th the byte size.npm run lint— cleannpm run build(tsc) — clean