Skip to content

fix(pdf-server): import highlight/underline/strike from existing PDFs - #592

Merged
ochafik merged 3 commits into
mainfrom
ochafik/pdf-import-quadpoints-flat
Apr 2, 2026
Merged

fix(pdf-server): import highlight/underline/strike from existing PDFs#592
ochafik merged 3 commits into
mainfrom
ochafik/pdf-import-quadpoints-flat

Conversation

@ochafik

@ochafikochafik commented Apr 2, 2026

Copy link
Copy Markdown
Contributor

What

importPdfjsAnnotation parses quadPoints as a flat Float32Array (8 numbers per quad, the shape pdf.js actually emits) instead of nested arrays. Falls through to ann.rect if no quads. Highlight/underline/strike annotations from a loaded PDF now reach annotationMap and are selectable/editable.

Tests

Existing fixtures switched to Float32Array; added a 2-quad multi-line case and a rect-fallback case.

Follow-up

Appearance-stream stamps and types we don't model still double-render (canvas + our DOM) — separate PR will rasterize per-annotation via annotationCanvasMap.

importPdfjsAnnotation expected nested quadPoints arrays, but pdf.js
emits a flat Float32Array (8 numbers per quad). Iterating it yielded
numbers, qp.length was undefined, rects stayed empty, and the function
returned null - so every quad-based annotation in a loaded PDF was
dropped from annotationMap and rendered only as unselectable canvas
pixels. Shipped this way in #506; not a regression.
Parse the flat array in 8-stride chunks, fall through to ann.rect if
that yields nothing. Existing tests used the (wrong) nested fixture
shape; switched them to Float32Array and added a 2-quad case + a
rect-fallback case.
@pkg-pr-new

pkg-pr-newBot commented Apr 2, 2026

Copy link
Copy Markdown

Open in StackBlitz

@modelcontextprotocol/ext-apps

npm i https://pkg.pr.new/@modelcontextprotocol/ext-apps@592

@modelcontextprotocol/server-basic-preact

npm i https://pkg.pr.new/@modelcontextprotocol/server-basic-preact@592

@modelcontextprotocol/server-basic-react

npm i https://pkg.pr.new/@modelcontextprotocol/server-basic-react@592

@modelcontextprotocol/server-basic-solid

npm i https://pkg.pr.new/@modelcontextprotocol/server-basic-solid@592

@modelcontextprotocol/server-basic-svelte

npm i https://pkg.pr.new/@modelcontextprotocol/server-basic-svelte@592

@modelcontextprotocol/server-basic-vanillajs

npm i https://pkg.pr.new/@modelcontextprotocol/server-basic-vanillajs@592

@modelcontextprotocol/server-basic-vue

npm i https://pkg.pr.new/@modelcontextprotocol/server-basic-vue@592

@modelcontextprotocol/server-budget-allocator

npm i https://pkg.pr.new/@modelcontextprotocol/server-budget-allocator@592

@modelcontextprotocol/server-cohort-heatmap

npm i https://pkg.pr.new/@modelcontextprotocol/server-cohort-heatmap@592

@modelcontextprotocol/server-customer-segmentation

npm i https://pkg.pr.new/@modelcontextprotocol/server-customer-segmentation@592

@modelcontextprotocol/server-debug

npm i https://pkg.pr.new/@modelcontextprotocol/server-debug@592

@modelcontextprotocol/server-map

npm i https://pkg.pr.new/@modelcontextprotocol/server-map@592

@modelcontextprotocol/server-pdf

npm i https://pkg.pr.new/@modelcontextprotocol/server-pdf@592

@modelcontextprotocol/server-scenario-modeler

npm i https://pkg.pr.new/@modelcontextprotocol/server-scenario-modeler@592

@modelcontextprotocol/server-shadertoy

npm i https://pkg.pr.new/@modelcontextprotocol/server-shadertoy@592

@modelcontextprotocol/server-sheet-music

npm i https://pkg.pr.new/@modelcontextprotocol/server-sheet-music@592

@modelcontextprotocol/server-system-monitor

npm i https://pkg.pr.new/@modelcontextprotocol/server-system-monitor@592

@modelcontextprotocol/server-threejs

npm i https://pkg.pr.new/@modelcontextprotocol/server-threejs@592

@modelcontextprotocol/server-transcript

npm i https://pkg.pr.new/@modelcontextprotocol/server-transcript@592

@modelcontextprotocol/server-video-resource

npm i https://pkg.pr.new/@modelcontextprotocol/server-video-resource@592

@modelcontextprotocol/server-wiki-explorer

npm i https://pkg.pr.new/@modelcontextprotocol/server-wiki-explorer@592

commit: ba71bd1

page.render() defaulted to AnnotationMode.ENABLE, painting every
annotation's appearance stream onto the canvas. We ALSO import them into
annotationMap and render DOM versions in #annotation-layer, so the user
saw two representations: an unclickable canvas pixel that looked right,
and our clickable DOM element that looked like our styling. Clicking the
'real' one did nothing.
Set annotationMode: DISABLE so the canvas is page-content-only and our
layer is the single source of truth for markup. Imported annotations now
behave identically to user-created ones (select/drag/delete). Form
widgets are unaffected (#form-layer via AnnotationLayer.render).
get_screenshot keeps ENABLE_STORAGE so screenshots still show
annotations.
Known trade-offs:
- Stamps with image appearance streams degrade to our text-label render
(no rasterization yet).
- Annotation types we don't import (Ink, Polygon, Caret, ...) no longer
ghost on the canvas - they're invisible. They were never editable
anyway; loadBaselineAnnotations already logs them.
@ochafikochafik changed the title fix(pdf-server): import highlight/underline/strike from existing PDFsfix(pdf-server): make imported markup annotations editableApr 2, 2026
@ochafikochafik changed the title fix(pdf-server): make imported markup annotations editablefix(pdf-server): import highlight/underline/strike from existing PDFsApr 2, 2026
@ochafik
ochafik merged commit f3bf106 into mainApr 2, 2026
20 checks passed
@ochafikochafik mentioned this pull request Apr 2, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@ochafik