Skip to content

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

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

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

Conversation

@ochafik
Copy link
Copy Markdown
Contributor

@ochafik ochafik commented Apr 2, 2026

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
Copy link
Copy Markdown

pkg-pr-new bot commented Apr 2, 2026

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.
@ochafik ochafik changed the title fix(pdf-server): import highlight/underline/strike from existing PDFs fix(pdf-server): make imported markup annotations editable Apr 2, 2026
@ochafik ochafik changed the title fix(pdf-server): make imported markup annotations editable fix(pdf-server): import highlight/underline/strike from existing PDFs Apr 2, 2026
@ochafik ochafik merged commit f3bf106 into main Apr 2, 2026
20 checks passed
Sign up for free to 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