$ show Submitty/pdf-annotate.js
pdf-annotate.js is a JavaScript library that provides an annotation layer for PDF.js, enabling users to add various types of annotations (highlight, underline, strikethrough, freehand drawing, text boxes, points, shapes) to PDF documents. The library features a modular architecture with separate concerns for UI interactions, rendering, storage adapters, and accessibility. It integrates with PDF.js to render PDF pages and overlays annotation SVGs, handling coordinate transformations that account for viewport scaling and rotation. The library provides a storage adapter pattern for persisting annotations (with implementations for localStorage), comprehensive UI components for annotation creation and editing, and accessibility features for screen reader users through clever DOM manipulation techniques.
The LLM identified 3 files worth highlighting for reuse:
Contains essential coordinate transformation functions for working with PDF.js viewports and SVG overlays. Handles the complex math of converting between screen coordinates, SVG coordinates, and PDF page coordinates while accounting for zoom, rotation, and browser-specific rendering differences (particularly Firefox). These functions are used throughout the codebase to correctly position annotations and demonstrate non-obvious skill in handling the intricacies of PDF.js viewport transformations.
Implements a sophisticated algorithm to insert screen-reader-only text at a specific visual position within a PDF text layer without breaking HTML tags. Measures text width to find the exact insertion point, handles nested elements, and ensures the inserted content is positioned correctly for screen readers while remaining visually hidden. This solves the challenging problem of making PDF annotations accessible to assistive technologies by placing hidden semantic content in the precise visual location of the corresponding annotation.
Defines an abstract storage adapter interface that enables pluggable persistence mechanisms for annotations. Concrete implementations (like LocalStoreAdapter) define how to interact with specific storage systems, while the base class automatically fires appropriate events (annotation:add, annotation:edit, etc.) when methods are called. This clean implementation of the adapter pattern reduces boilerplate for storage implementers and ensures consistent event signaling across different storage backends.