teamsync-pdf-viewer 1.1.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,53 @@
2
2
 
3
3
  All notable changes to `teamsync-pdf-viewer` are documented here.
4
4
 
5
+ ## [1.2.1] — 2026-09-01
6
+
7
+ ### Fixed
8
+ - **The stylesheet no longer restyles the host application.** `dist/style.css` bundles
9
+ `pdfjs-dist/web/pdf_viewer.css`, ~900 selectors written for pdf.js's own standalone viewer —
10
+ including bare generic class names such as `.sidebar`, `.dialog`, `.toggle-button`,
11
+ `.closeButton` and `.messageBar`. Only this package's own rules were scoped under `.tspdf-root`;
12
+ the pdf.js ones shipped globally. Because the stylesheet is injected at runtime it won every
13
+ cascade tie, so a host with its own `.sidebar` had it silently restyled (wrong width, white
14
+ background, `position: relative` + `inset-block-start`) as soon as the viewer module loaded —
15
+ which happens on preference selection, before any PDF is opened.
16
+
17
+ Every selector is now scoped at build time, after nesting is flattened. `:root`/`html`/`body`
18
+ are re-targeted to `.tspdf-root` rather than prefixed, so the custom properties the viewer's
19
+ descendants read still resolve. `npm run verify:package` fails the build if any selector is
20
+ unscoped, or prefixed more than once (which would match nothing and break the viewer's own
21
+ styling).
22
+
23
+ ## [1.2.0] — 2026-08-31
24
+
25
+ ### Added
26
+ - **`AnnotationManager`** (`instance.annotationManager`, also `instance.Core.annotationManager`):
27
+ the canonical annotation list with undo/redo, granular `annotationChanged` events
28
+ (`add` | `modify` | `delete`, with an `imported` flag), authorship stamping
29
+ (`currentUser`), edit permissions (read-only viewer, read-only annotation, other authors —
30
+ `permissions.canEditOthers`) and programmatic `addAnnotations` / `updateAnnotation` /
31
+ `deleteAnnotations` / `getAnnotationById`.
32
+ - **Native XFDF**: `annotationManager.importAnnotations(xfdf)` and
33
+ `exportAnnotations({ annotList?, useDisplayAuthor? })` (bare fragments per annotation, or a full
34
+ document). Reads Acrobat/Apryse XFDF (square, circle, line/arrow, ink, highlight quads, freetext,
35
+ callout, sticky note, stamp with image data, link) and keeps everything else as read-only
36
+ `opaque` annotations that re-export verbatim; unknown attributes/children (e.g. Apryse
37
+ `trn-custom-data`) are preserved across a round-trip. Stable UUID ids double as the XFDF `name`.
38
+ - **Page geometry module** (`createPageGeometry`, `rectToPdf`, `quadPointsToRects`, …): the single
39
+ authority for base-page-space ↔ PDF-user-space conversion, mirroring pdf.js's viewport transform.
40
+ - **Print** (menu item enabled, `instance.print()`): the exported document is printed through the
41
+ browser's PDF viewer in a hidden frame.
42
+ - `instance.on('annotationChanged', …)` typed event; `Annotation.author/authorId/createdAt/
43
+ modifiedAt/readOnly/strokes/rects` fields; multi-stroke ink and multi-rect highlights render.
44
+
45
+ ### Fixed
46
+ - Export geometry: annotations are baked exactly where they were drawn (any page size, CropBox
47
+ offset or intrinsic /Rotate) — replaces the hard-coded `scale = 1.5` conversion.
48
+ - Mixed page sizes: layout, scrolling, hit-testing and fit-to-width/page now use per-page
49
+ dimensions instead of page 1's.
50
+ - Annotation ids were timestamps (collision-prone); now `crypto.randomUUID()`.
51
+
5
52
  ## [1.1.0] — 2026-08-31
6
53
 
7
54
  ### Breaking
package/README.md CHANGED
@@ -1,234 +1,254 @@
1
1
  # TeamSync PDF Viewer SDK
2
2
 
3
- Client-side PDF viewing, annotation, search, redaction and watermarking for React — built on
4
- [pdf.js](https://mozilla.github.io/pdf.js/) and [pdf-lib](https://pdf-lib.js.org/).
5
-
6
3
  <div align="center">
7
4
 
8
5
  ![TeamSync PDF Viewer UI](https://raw.githubusercontent.com/angelbot-ai/TeamSync-PDF-Viewer/main/docs/images/viewer_ui.png)
9
6
 
7
+ ### **100% Open Source. 100% Client-Side. Enterprise PDF Engine.**
8
+
10
9
  [![License: CPAL-1.0](https://img.shields.io/badge/License-CPAL--1.0-blue.svg)](LICENSE)
11
10
  [![Live Demo](https://img.shields.io/badge/Live%20Demo-pdfviewer.teamsync.com-000000.svg)](https://pdfviewer.teamsync.com/)
12
- [![npm](https://img.shields.io/npm/v/teamsync-pdf-viewer.svg)](https://www.npmjs.com/package/teamsync-pdf-viewer)
13
- [![React 18/19](https://img.shields.io/badge/React-18%20%7C%2019-61dafb.svg)](https://react.dev/)
14
- [![TypeScript](https://img.shields.io/badge/TypeScript-types%20included-blue.svg)](https://www.typescriptlang.org/)
11
+ [![NPM Version](https://img.shields.io/badge/npm-teamsync--pdf--viewer-blue.svg)](https://www.npmjs.com/package/teamsync-pdf-viewer)
12
+ [![React 19](https://img.shields.io/badge/React-19-61dafb.svg)](https://reactjs.org/)
13
+ [![Vite 8](https://img.shields.io/badge/Vite-8.2-646cff.svg)](https://vitejs.dev/)
14
+ [![TypeScript](https://img.shields.io/badge/TypeScript-6.0-blue.svg)](https://www.typescriptlang.org/)
15
+
16
+ *No paywalls. No vendor lock-in. No server dependencies.*
17
+ Try the **[Interactive Live Demo](https://pdfviewer.teamsync.com/)** or integrate the high-performance PDF Viewer SDK directly into your React applications.
15
18
 
16
19
  </div>
17
20
 
18
21
  ---
19
22
 
20
- ## Features
21
-
22
- - **Viewing** — continuous or page-by-page scrolling, single / double / cover-facing layouts,
23
- thumbnails panel, zoom 10 % – 6400 % with fit-to-width / fit-to-page, pinch and wheel zoom,
24
- rotation, pan tool, high-DPI canvas rendering, progressive loading of linearized PDFs (HTTP Range).
25
- - **Text** — real selectable text layer, client-side search with highlighting and a results panel.
26
- - **Annotations** — highlighter, freehand ink, shapes (rectangle, ellipse, line, arrow), text,
27
- sticky notes, callouts, hyperlinks (page-jump or URL), signature images, undo/redo, keyboard shortcuts.
28
- - **Redaction** — manual and regex/PII-driven redactions, burned in on export by rasterizing the
29
- affected pages so the underlying content is destroyed; redacted text is also stripped from the
30
- text layer and search index.
31
- - **Watermarks** — single or tiled forensic watermarks, rendered live and baked on export.
32
- - **Export** — download the annotated / redacted / watermarked document; no server round-trip.
33
- - **Embeddable** — `<TeamSyncViewer>` sizes to its container, several viewers can share a page,
34
- keyboard shortcuts apply to the focused viewer only, and importing the package is side-effect free
35
- (safe under SSR).
36
-
37
- Not yet supported: printing (planned for 1.2), AcroForm filling, rendering of annotations already
38
- embedded in the PDF, document outline/bookmarks, password prompts (password-protected files raise
39
- `onPasswordRequired`).
23
+ ## 🚀 Why TeamSync PDF Viewer?
24
+
25
+ Traditional commercial PDF viewers force developers into expensive per-domain licenses, opaque sales calls, and heavy server-side processing dependencies. **TeamSync PDF Viewer** is built from the ground up to solve these pain points.
26
+
27
+ - 💰 **Zero-Cost, Transparent Licensing**: Uncapped usage, no per-server fees, and no commercial paywalls. 100% free and open-source under CPAL 1.0.
28
+ - ⚡ **100% Client-Side WebAssembly**: Documents never leave the browser sandbox. Process, redact, and render PDFs entirely on the client—guaranteeing instant **HIPAA**, **GDPR**, and **FINRA** compliance.
29
+ - 🔌 **Universal WebViewer API**: Clean, intuitive `WebViewer({...})` API interface that makes integrating into your web applications effortless.
30
+ - 🎨 **Headless & Customizable**: Built for modern React 19 SPA lifecycles. No bloated iFrames to fight—customize toolbars, sidebars, context menus, and controls natively.
40
31
 
41
32
  ---
42
33
 
43
- ## Installation
34
+ ## 📦 Installation via NPM
35
+
36
+ Install the package into your React / TypeScript project:
44
37
 
45
38
  ```bash
46
- npm install teamsync-pdf-viewer pdfjs-dist
39
+ # Using npm
40
+ npm install teamsync-pdf-viewer
41
+
42
+ # Using yarn
43
+ yarn add teamsync-pdf-viewer
44
+
45
+ # Using pnpm
46
+ pnpm add teamsync-pdf-viewer
47
47
  ```
48
48
 
49
- `react`, `react-dom` (18 or 19) and `pdfjs-dist` (6.x) are **peer dependencies**. `pdf-lib` and
50
- `lucide-react` are installed automatically.
49
+ ### Installing from GitHub Packages (Alternative)
51
50
 
52
- ### Serve the pdf.js assets
51
+ If installing from GitHub Packages registry:
53
52
 
54
- The library never bundles the pdf.js worker (bundling it produced a 5 MB package with a `data:`
55
- URL worker that strict Content-Security-Policies reject). Serve the worker — and, for full font /
56
- CJK / JPEG 2000 support, the CMaps, standard fonts and wasm decoders — from your own origin and tell
57
- the viewer where they are.
53
+ 1. Create or update your `.npmrc` file:
54
+ ```ini
55
+ @angelbot-ai:registry=https://npm.pkg.github.com
56
+ ```
57
+ 2. Install via npm:
58
+ ```bash
59
+ npm install teamsync-pdf-viewer
60
+ ```
58
61
 
59
- **Vite / Rollup**
62
+ ---
60
63
 
61
- ```ts
62
- import workerSrc from 'pdfjs-dist/build/pdf.worker.min.mjs?url';
63
- import { configurePdfAssets } from 'teamsync-pdf-viewer';
64
+ ## 📖 Official Documentation & Wiki
64
65
 
65
- configurePdfAssets({ workerSrc });
66
- ```
66
+ Explore detailed SDK guides, parameter options, code snippets, and architecture deep dives:
67
67
 
68
- **Next.js (or any static host)** — copy the files at build time and reference them by URL:
68
+ - 🏠 **[Wiki Home](docs/wiki/Home.md)**: Main documentation index & overview.
69
+ - 🚀 **[Getting Started Guide](docs/wiki/Getting-Started.md)**: Installation, CDN, and framework setups.
70
+ - ⚙️ **[API Reference & Options](docs/wiki/API-Reference-&-Options.md)**: Complete parameter list for `WebViewerOptions` & `SDKPermissions`.
71
+ - 🎨 **[Annotations & Markup Guide](docs/wiki/Annotations-&-Markup-Guide.md)**: Freehand drawing, vector shapes, callouts & links.
72
+ - 🛡️ **[Redactions & PII Sanitization Guide](docs/wiki/Redactions-&-PII-Sanitization.md)**: Binary redactions, regex PII scanning & discard options.
73
+ - 💧 **[Forensic Watermarking Guide](docs/wiki/Forensic-Watermarking.md)**: Dynamic single and tiled watermark setup.
74
+ - 🔌 **[Plugin Architecture Guide](docs/wiki/Plugin-Architecture.md)**: Writing custom extension plugins.
69
75
 
70
- ```bash
71
- # e.g. in a prebuild script
72
- cp node_modules/pdfjs-dist/build/pdf.worker.min.mjs public/pdfjs/
73
- cp -r node_modules/pdfjs-dist/cmaps node_modules/pdfjs-dist/standard_fonts node_modules/pdfjs-dist/wasm public/pdfjs/
74
- ```
76
+ ---
75
77
 
76
- ```ts
77
- configurePdfAssets({
78
- workerSrc: '/pdfjs/pdf.worker.min.mjs',
79
- cMapUrl: '/pdfjs/cmaps/',
80
- standardFontDataUrl: '/pdfjs/standard_fonts/',
81
- wasmUrl: '/pdfjs/wasm/',
82
- });
83
- ```
78
+ ## ✨ Uncompromised Feature Set
84
79
 
85
- The worker **must** come from the same `pdfjs-dist` version the viewer runs against
86
- (`import { pdfjsVersion } from 'teamsync-pdf-viewer'` tells you which one is loaded).
80
+ Everything you need to build collaborative, secure document workflows.
81
+
82
+ ### 🎨 Smart Markup & Annotations
83
+ - **Full Drawing Toolkit**: Freehand ink (`brush`), highlighters, geometric shapes (rectangles, ellipses), arrows, and lines.
84
+ - **Notes & Callouts**: Sticky notes, callout text boxes with directional arrows, and text annotations.
85
+ - **Interactive Hyperlinks**: Create internal page-jump links (`#page=N`) or external web URL links directly on document selections.
86
+
87
+ ### 🛡️ Military-Grade Secure Redaction
88
+ - **Binary-Level Data Obliteration**: We don't just place black boxes over text—redactions are permanently rasterized and burned into the underlying PDF vector structure.
89
+ - **Text Layer Sanitization**: Redacted text is automatically stripped from the DOM `textLayer` and PDF content streams, preventing copy/paste extraction and search indexing.
90
+ - **Automatic Regex Redactions**: Programmatically locate and redact sensitive PII (Aadhaar numbers, SSNs, credit cards, dates of birth) in a single click.
91
+ - **Discard Unapplied Redactions**: Easily discard individual pending redactions or bulk-discard all unapplied redaction marks before committing.
92
+
93
+ ### 📐 Dynamic Layout & Viewport Controls
94
+ - **Fit to Width & Fit to Page**: Container-aware dynamic scaling calculations for responsive reading on any screen size.
95
+ - **Synchronized Page Rotation**: 360° page rotation with real-time vector coordinate transforms so annotations rotate seamlessly with document content.
96
+
97
+ ### 🔍 High-DPI Canvas Rendering & Search
98
+ - **Retina 60 FPS Zoom & Pan**: High-DPI `devicePixelRatio` scaling eliminates blurred text. Micro-debounced rendering enables buttery-smooth 60 FPS trackpad pinching and 360° mouse drag panning.
99
+ - **Contextual Document Search**: Instant client-side text search with real-time match highlighting, result jumping, and match counting.
100
+
101
+ ### 💧 Dynamic Forensic Watermarking
102
+ - Programmatic, non-destructive watermarks rendered on the fly (single centered or full-page tiled) with customizable text, color, opacity, font size, and rotation.
87
103
 
88
104
  ---
89
105
 
90
- ## Usage
106
+ ## 💻 Developer SDK Usage
91
107
 
92
- ### React component
108
+ ### Standard WebViewer Initialization
93
109
 
94
- ```tsx
95
- 'use client'; // Next.js App Router: the viewer is client-only
110
+ Drop the SDK into any container element with a single function call:
111
+
112
+ ```typescript
113
+ import { WebViewer } from 'teamsync-pdf-viewer';
114
+ import 'teamsync-pdf-viewer/style.css';
115
+
116
+ WebViewer({
117
+ initialDoc: '/sample.pdf',
118
+ initialScale: 1.0, // 100% natural size
119
+ enableAnnotations: true,
120
+ permissions: {
121
+ canRedact: true,
122
+ canAddAnnotations: true
123
+ },
124
+ watermark: {
125
+ text: 'CONFIDENTIAL',
126
+ mode: 'single',
127
+ size: 48,
128
+ opacity: 0.1,
129
+ color: '#dc2626'
130
+ }
131
+ }, document.getElementById('viewer-container')).then((instance) => {
132
+ console.log('TeamSync PDF Viewer is ready!', instance);
133
+
134
+ // Programmatic Viewport Controls
135
+ instance.UI.fitWidth();
136
+ instance.UI.fitPage();
137
+
138
+ // Export annotated PDF buffer
139
+ instance.Core.documentViewer.getDocument().getFileData().then((pdfBytes) => {
140
+ console.log('Exported PDF byte length:', pdfBytes.length);
141
+ });
142
+ });
143
+ ```
96
144
 
97
- import { TeamSyncViewer, type WebViewerInstance } from 'teamsync-pdf-viewer';
145
+ ### Native React Component Usage
146
+
147
+ For native React integration, use the `<DocumentViewer />` component:
148
+
149
+ ```tsx
150
+ import { DocumentViewer } from 'teamsync-pdf-viewer';
98
151
  import 'teamsync-pdf-viewer/style.css';
99
152
 
100
- export function ContractViewer({ url }: { url: string }) {
153
+ function App() {
101
154
  return (
102
- <div style={{ height: '80vh' }}>
103
- <TeamSyncViewer
104
- fileUrl={url}
105
- fileName="contract.pdf"
106
- assets={{ workerSrc: '/pdfjs/pdf.worker.min.mjs', cMapUrl: '/pdfjs/cmaps/' }}
107
- currentUser={{ id: 'u-42', name: 'Jane Doe' }}
108
- initialScale="fit-width"
109
- watermark={{ text: 'CONFIDENTIAL', mode: 'tiled', opacity: 0.08 }}
110
- onReady={(instance: WebViewerInstance) => console.log('ready', instance)}
111
- onDocumentLoadError={(error, retry) => {
112
- // e.g. mint a fresh presigned URL and retry
113
- retry(/* newUrl */);
114
- }}
155
+ <div style={{ width: '100vw', height: '100vh' }}>
156
+ <DocumentViewer
157
+ initialDoc="/contract.pdf"
158
+ scale={1.0}
159
+ enableAnnotations={true}
160
+ permissions={{ canRedact: true, canAddAnnotations: true }}
115
161
  />
116
162
  </div>
117
163
  );
118
164
  }
119
165
  ```
120
166
 
121
- In Next.js, load it with `next/dynamic(() => import('./ContractViewer'), { ssr: false })` or keep
122
- it inside a client component — the package itself never touches `window` at import time.
123
-
124
- ### Imperative API
167
+ ---
125
168
 
126
- ```ts
127
- import { createWebViewer } from 'teamsync-pdf-viewer';
128
- import 'teamsync-pdf-viewer/style.css';
169
+ ## 🛠️ Local Development Quick Start
129
170
 
130
- const instance = await createWebViewer(
131
- { initialDoc: '/sample.pdf', fileName: 'sample.pdf', permissions: { canRedact: true } },
132
- document.getElementById('viewer-container')!
133
- );
171
+ ### 1. Clone & Install
134
172
 
135
- instance.on('documentLoaded', ({ numPages }) => console.log(numPages, 'pages'));
136
- const pdfBytes = await instance.getFileData(); // annotated / redacted / watermarked Uint8Array
137
- instance.destroy(); // unmounts and releases every listener
173
+ ```bash
174
+ git clone https://github.com/angelbot-ai/TeamSync-PDF-Viewer.git
175
+ cd TeamSync-PDF-Viewer
176
+ npm install
138
177
  ```
139
178
 
140
- `WebViewer()` remains available as a deprecated alias of `createWebViewer()`.
179
+ ### 2. Launch Dev Server
141
180
 
142
- ---
181
+ ```bash
182
+ npm run dev
183
+ ```
143
184
 
144
- ## API overview
145
-
146
- ### `<TeamSyncViewer>` props / `WebViewerOptions`
147
-
148
- | Prop | Type | Default | Description |
149
- | :--- | :--- | :--- | :--- |
150
- | `fileUrl` (`initialDoc`) | `string` | — | URL of the PDF. Fetched by pdf.js (CORS + Range supported). |
151
- | `fileName` | `string` | `annotated_document.pdf` | Name used for downloads. |
152
- | `assets` | `PdfAssetPaths` | — | Worker / CMap / font / wasm locations (or call `configurePdfAssets`). |
153
- | `withCredentials` | `boolean` | `false` | Send cookies with the document request. |
154
- | `initialScale` | `number \| 'fit-width' \| 'fit-page'` | `1` | Initial zoom. |
155
- | `initialPage` | `number` | `1` | 1-based page to open. |
156
- | `readOnly` | `boolean` | `false` | Disables all annotation / redaction editing. |
157
- | `permissions` | `SDKPermissions` | all `true` | `canAddAnnotations`, `canEditAnnotations`, `canDeleteAnnotations`, `canRedact`. |
158
- | `currentUser` | `{ id, name }` | — | Author of annotations created in this viewer. |
159
- | `watermark` | `WatermarkOptions` | — | `{ text, mode: 'single' \| 'tiled', size, opacity, color }`. |
160
- | `redactions`, `regexRedactions` | `Redaction[]`, `RegExp[]` | `[]` | Pre-set and pattern-driven redactions. |
161
- | `toolbar`, `sidebars`, `leftPanelOpen` | `boolean` | `true` | Chrome toggles. |
162
- | `autoFocus` | `boolean` | `false` | Focus the viewer on mount (keyboard shortcuts). |
163
- | `plugins` | `ViewerPlugin[]` | `[]` | Extension plugins. |
164
-
165
- Callbacks: `onReady(instance)`, `onDocumentLoaded({ url, numPages })`,
166
- `onDocumentLoadError(error, retry(newUrl?), { url, passwordRequired })`, `onFirstPageRendered`,
167
- `onPasswordRequired`, `onPageChange(page, numPages)`, `onAnnotationsChange(annotations)`.
168
-
169
- ### `WebViewerInstance`
170
-
171
- - `on(type, listener)` / `off` — typed events: `documentLoaded`, `documentLoadError`,
172
- `firstPageRendered`, `pageChanged`, `annotationsChanged`, `toolChanged`, `destroy`.
173
- - `loadDocument(url)` — load or reload (resolves on load, rejects on error).
174
- - `getFileData({ explicitAnnotations? })` — export the document as `Uint8Array`.
175
- - `getAnnotations()`, `getCurrentPage()`, `getPageCount()`, `getPdfDocument()`.
176
- - `destroy()` — unmount (imperative API) and release every listener.
177
- - `UI.*` / `Core.*` — legacy facades (`UI.openElements`, `UI.fitWidth`,
178
- `Core.annotationManager.exportAnnotations()`, `Core.documentViewer.getDocument().getFileData()`).
179
-
180
- Full reference: [docs/wiki/API-Reference-&-Options.md](docs/wiki/API-Reference-&-Options.md).
185
+ Open `http://localhost:5173` in your browser to launch the live SDK viewer.
181
186
 
182
187
  ---
183
188
 
184
- ## Documentation
189
+ ## 🔌 Extensible Plugin Architecture
185
190
 
186
- - [Getting Started](docs/wiki/Getting-Started.md) · [API Reference & Options](docs/wiki/API-Reference-&-Options.md)
187
- - [Annotations & Markup](docs/wiki/Annotations-&-Markup-Guide.md) · [Redactions & PII Sanitization](docs/wiki/Redactions-&-PII-Sanitization.md)
188
- - [Forensic Watermarking](docs/wiki/Forensic-Watermarking.md) · [Plugin Architecture](docs/wiki/Plugin-Architecture.md)
189
- - [Changelog](CHANGELOG.md)
191
+ TeamSync PDF Viewer features a decoupled **Plugin Architecture**. Core PDF rendering, smart annotations, redaction engines, and search remain lightweight and modular in the main engine, while enterprise extensions plug in as independent modules.
190
192
 
191
- ---
193
+ ```
194
+ ┌─────────────────────────────────────────────────────────┐
195
+ │ TeamSync PDF Viewer Core │
196
+ │ (Page Rendering, Annotations, Redactions, Search) │
197
+ └───────────────────────────┬─────────────────────────────┘
198
+ │ Plugins Registry API
199
+ ┌───────────────────────────▼─────────────────────────────┐
200
+ │ Custom Extension │
201
+ │ (Custom Modals, Action Buttons, Export Hooks) │
202
+ └─────────────────────────────────────────────────────────┘
203
+ ```
192
204
 
193
- ## Development
205
+ ```typescript
206
+ import { WebViewer } from 'teamsync-pdf-viewer';
207
+ import { myCustomPlugin } from './myCustomPlugin';
194
208
 
195
- ```bash
196
- git clone https://github.com/angelbot-ai/TeamSync-PDF-Viewer.git
197
- cd TeamSync-PDF-Viewer
198
- npm install
199
- npm run dev # demo application at http://localhost:5173
200
- npm test # unit tests (vitest)
201
- npm run build # library -> dist/
202
- npm run verify:package # publint + arethetypeswrong + artifact guards
203
- npm run build:app # demo / iframe package build (Vercel)
209
+ WebViewer({
210
+ initialDoc: '/sample.pdf',
211
+ plugins: [
212
+ myCustomPlugin({ enableFeature: true })
213
+ ]
214
+ }, document.getElementById('viewer-container'));
204
215
  ```
205
216
 
206
- Layout:
217
+ ---
207
218
 
208
- ```
209
- src/
210
- ├── index.ts # public entry (side-effect free)
211
- ├── core/ # createWebViewer, WebViewerInstance, event bus, pdf.js assets, export
212
- ├── components/ # TeamSyncViewer + internal UI (DocumentViewer, Header, sidebars, modals)
213
- ├── annotations/ # annotation data model
214
- ├── hooks/, utils/ # search, shortcuts, rotation helpers, regex redaction
215
- ├── plugins/ # plugin interfaces
216
- └── demo/ # demo & iframe-package bootstrap (not published)
217
- ```
219
+ ## 🏛️ Architecture & Tech Stack
218
220
 
219
- ### Iframe package (script-tag hosts)
221
+ - **Core Engine**: HTML5, TypeScript, WebAssembly (PDF.js + pdf-lib)
222
+ - **UI & State**: React 19, Lucide Icons, Pure CSS Modules
223
+ - **Build System**: Vite 8 & Oxlint (Sub-500ms tree-shakable builds)
220
224
 
221
- `npm run build:app` produces a self-contained viewer; `public/webviewer.js` wraps it in an iframe
222
- with a `postMessage` bridge and exposes a `WebViewer(options, element)` function for non-React hosts.
225
+ ```
226
+ TeamSync-PDF-Viewer/
227
+ ├── src/
228
+ │ ├── components/ # Native React UI (Header, DocumentViewer, Sidebars, Modals)
229
+ │ ├── hooks/ # Search & Keyboard shortcut hooks
230
+ │ ├── utils/ # Redaction algorithms, rotation transforms & vector helpers
231
+ │ ├── plugins/ # Plugin API interfaces & registry
232
+ │ ├── index.ts # Main SDK package exports
233
+ │ ├── main.tsx # WebViewer SDK entry point & public API bridge
234
+ │ └── App.tsx # Demo Application Shell
235
+ ├── public/ # Static PDF assets & PDF.js workers
236
+ └── docs/images/ # High-res UI documentation screenshots
237
+ ```
223
238
 
224
239
  ---
225
240
 
226
- ## TypeScript notes
241
+ ## 📄 License
227
242
 
228
- Declarations ship in `dist/src/`. They resolve under `moduleResolution: "bundler"` (Next.js, Vite,
229
- webpack) and `node10`; `node16`/`nodenext` resolution is not supported yet because the emitted
230
- declarations use extension-less relative imports.
243
+ Distributed under the **CPAL 1.0 (Common Public Attribution License)**.
244
+ 100% Free and Open Source for personal and commercial usage.
231
245
 
232
- ## License
246
+ ---
233
247
 
234
- [CPAL-1.0](LICENSE) — Copyright © 2026 AngelBot Ai Pvt Ltd.
248
+ <div align="center">
249
+ <b>AngelBot AI • TeamSync • </b> <a href="https://www.teamsync.com" target="_blank" rel="noopener noreferrer">https://www.teamsync.com</a>
250
+ </div>
251
+
252
+ <div align="center">
253
+ <b>TeamSync PDF Viewer Engine</b> • Engineered with ❤️ for the Open Web.
254
+ </div>
@@ -0,0 +1,90 @@
1
+ import { PDFDocumentProxy } from 'pdfjs-dist';
2
+ import { Annotation, AnnotationInput } from './types';
3
+ import { XfdfExportOptions } from './xfdf';
4
+ export type AnnotationAction = 'add' | 'modify' | 'delete';
5
+ export interface AnnotationChangedEvent {
6
+ annotations: Annotation[];
7
+ action: AnnotationAction;
8
+ /** True when the change came from importAnnotations()/programmatic APIs flagged as imported. */
9
+ imported: boolean;
10
+ }
11
+ export interface CommitOptions {
12
+ /** Mark the change as an import round-trip (not persisted by host bridges, not undoable). */
13
+ imported?: boolean;
14
+ /** Bypass read-only / authorship checks (programmatic host operations). */
15
+ force?: boolean;
16
+ /** Record in the undo history (default: true unless imported). */
17
+ undoable?: boolean;
18
+ }
19
+ export interface ViewerUserInfo {
20
+ id?: string;
21
+ name?: string;
22
+ }
23
+ type ChangedListener = (event: AnnotationChangedEvent) => void;
24
+ export declare class AnnotationManager {
25
+ private list;
26
+ private past;
27
+ private future;
28
+ private changedListeners;
29
+ private listListeners;
30
+ private user;
31
+ private readOnly;
32
+ private canEditOthers;
33
+ private resolver;
34
+ setDocument(doc: PDFDocumentProxy | null): void;
35
+ hasDocument(): boolean;
36
+ setCurrentUser(name: string, id?: string): void;
37
+ setCurrentUserInfo(user: ViewerUserInfo | undefined): void;
38
+ getCurrentUser(): string;
39
+ getCurrentUserInfo(): ViewerUserInfo;
40
+ setReadOnly(readOnly: boolean): void;
41
+ isReadOnly(): boolean;
42
+ /** Allow editing/deleting annotations authored by other users (default false). */
43
+ setCanEditOthers(value: boolean): void;
44
+ /** Whether the current user may modify or delete this annotation through the UI. */
45
+ canEdit(a: Annotation): boolean;
46
+ canAdd(): boolean;
47
+ /** The current list. The array reference is stable until the next change. */
48
+ getAnnotationsList(): Annotation[];
49
+ getAnnotationById(id: string): Annotation | undefined;
50
+ /** React `useSyncExternalStore` pair. */
51
+ subscribe: (listener: () => void) => (() => void);
52
+ getSnapshot: () => Annotation[];
53
+ addEventListener(type: 'annotationChanged', listener: ChangedListener): () => void;
54
+ removeEventListener(type: 'annotationChanged', listener: ChangedListener): void;
55
+ /** Apryse-compat no-op: React re-renders from the list. */
56
+ drawAnnotationsFromList(_annotations: Annotation[]): void;
57
+ get canUndo(): boolean;
58
+ get canRedo(): boolean;
59
+ undo(): boolean;
60
+ redo(): boolean;
61
+ /**
62
+ * Replace the whole list (the UI's edit path). Diffs against the current list, enforces
63
+ * permissions (rejected edits keep the previous version), records history and emits one
64
+ * `annotationChanged` event per action.
65
+ */
66
+ commit(next: Annotation[], opts?: CommitOptions): Annotation[];
67
+ addAnnotations(inputs: AnnotationInput[], opts?: CommitOptions): Annotation[];
68
+ updateAnnotation(id: string, patch: Partial<AnnotationInput>, opts?: CommitOptions): Annotation | undefined;
69
+ deleteAnnotations(targets: Array<Annotation | string>, opts?: CommitOptions): void;
70
+ /** Remove every annotation (e.g. on document switch). Not undoable, emits deletes as imported. */
71
+ clear(): void;
72
+ /**
73
+ * Import an XFDF document or bare fragments. Annotations whose `name` already exists are
74
+ * replaced (modify), new ones added; the change is flagged `imported` and is not undoable.
75
+ * Returns the imported annotations as they now exist in the list.
76
+ */
77
+ importAnnotations(xfdf: string): Promise<Annotation[]>;
78
+ /**
79
+ * Export as XFDF. With `annotList`, returns the bare fragment(s) for those annotations (the
80
+ * form hosts persist per annotation); without it, a complete XFDF document.
81
+ */
82
+ exportAnnotations(opts?: XfdfExportOptions): Promise<string>;
83
+ /** Legacy bespoke JSON export (pre-1.2 `exportAnnotations()` shape). */
84
+ exportAnnotationsLegacyJson(): string;
85
+ private requireResolver;
86
+ private apply;
87
+ private stampNew;
88
+ private emit;
89
+ }
90
+ export {};
@@ -0,0 +1,51 @@
1
+ import { PDFDocumentProxy, PDFPageProxy } from 'pdfjs-dist';
2
+ export type Matrix = [number, number, number, number, number, number];
3
+ export interface PageGeometry {
4
+ /** 1-based page number */
5
+ pageNumber: number;
6
+ /** PDF user-space bounding box [x0, y0, x1, y1] (the CropBox pdf.js renders). */
7
+ viewBox: [number, number, number, number];
8
+ /** Intrinsic page rotation (/Rotate), normalized to 0/90/180/270. */
9
+ rotate: number;
10
+ /** Base page size in points (already swapped for 90/270). */
11
+ width: number;
12
+ height: number;
13
+ /** Base-space (viewer) -> PDF user-space transform is the inverse of this viewport transform. */
14
+ transform: Matrix;
15
+ }
16
+ export interface Rect {
17
+ x: number;
18
+ y: number;
19
+ width: number;
20
+ height: number;
21
+ }
22
+ export interface Point {
23
+ x: number;
24
+ y: number;
25
+ }
26
+ /** PDF rectangle as XFDF writes it: [llx, lly, urx, ury]. */
27
+ export type PdfRect = [number, number, number, number];
28
+ /** Build the geometry for a page from its viewBox and intrinsic rotation (mirrors pdf.js PageViewport at scale 1). */
29
+ export declare function createPageGeometry(pageNumber: number, viewBox: [number, number, number, number], rotate: number): PageGeometry;
30
+ export declare function geometryFromPage(page: PDFPageProxy): PageGeometry;
31
+ /** Small per-document cache so callers can resolve geometry for many annotations cheaply. */
32
+ export declare function createGeometryResolver(pdfDoc: PDFDocumentProxy): (pageNumber: number) => Promise<PageGeometry>;
33
+ export type GeometryResolver = (pageNumber: number) => Promise<PageGeometry>;
34
+ export declare function toPdfPoint(g: PageGeometry, p: Point): Point;
35
+ export declare function fromPdfPoint(g: PageGeometry, p: Point): Point;
36
+ /** Viewer rect (top-left origin) -> PDF rect [llx, lly, urx, ury]. */
37
+ export declare function rectToPdf(g: PageGeometry, r: Rect): PdfRect;
38
+ /** PDF rect [llx, lly, urx, ury] -> viewer rect (top-left origin). */
39
+ export declare function rectFromPdf(g: PageGeometry, r: PdfRect): Rect;
40
+ export declare function pathToPdf(g: PageGeometry, points: Point[]): Point[];
41
+ export declare function pathFromPdf(g: PageGeometry, points: Point[]): Point[];
42
+ /**
43
+ * XFDF QuadPoints for a viewer rect: x1,y1 (upper-left) x2,y2 (upper-right) x3,y3 (lower-left)
44
+ * x4,y4 (lower-right) in PDF space — the order Acrobat and Apryse write for text markup.
45
+ */
46
+ export declare function rectToQuadPoints(g: PageGeometry, r: Rect): number[];
47
+ /** Bounding viewer rect of one or more XFDF quads (8 numbers each). */
48
+ export declare function quadPointsToRects(g: PageGeometry, quads: number[]): Rect[];
49
+ export declare function unionRects(rects: Rect[]): Rect;
50
+ /** Round to 3 decimals for XFDF output (Apryse writes 3, Acrobat up to 6). */
51
+ export declare const fmt: (n: number) => string;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * © 2026 AngelBot Ai Pvt Ltd. All rights reserved.
3
+ * Stable, collision-free annotation ids. They double as the XFDF `name` attribute, which hosts use
4
+ * to match persisted rows to live annotations, so they must never be timestamps.
5
+ */
6
+ export declare function newAnnotationId(): string;