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 +47 -0
- package/README.md +185 -165
- package/dist/src/annotations/AnnotationManager.d.ts +90 -0
- package/dist/src/annotations/geometry.d.ts +51 -0
- package/dist/src/annotations/ids.d.ts +6 -0
- package/dist/src/annotations/types.d.ts +28 -1
- package/dist/src/annotations/xfdf.d.ts +18 -0
- package/dist/src/components/CompareCurtainSlider.d.ts +8 -0
- package/dist/src/components/CompareToolbar.d.ts +13 -0
- package/dist/src/components/DiffHighlightOverlay.d.ts +11 -0
- package/dist/src/components/DiffSummarySidebar.d.ts +13 -0
- package/dist/src/components/DocumentViewer.d.ts +4 -1
- package/dist/src/components/Header.d.ts +2 -1
- package/dist/src/components/SideBySideViewer.d.ts +29 -0
- package/dist/src/core/ViewerInstance.d.ts +12 -6
- package/dist/src/core/export.d.ts +4 -1
- package/dist/src/core/print.d.ts +12 -0
- package/dist/src/core/types.d.ts +5 -0
- package/dist/src/index.d.ts +12 -1
- package/dist/src/types/compare.d.ts +56 -0
- package/dist/src/utils/pdfDiffEngine.d.ts +37 -0
- package/dist/style.css +1 -1
- package/dist/teamsync-pdf-viewer.js +4006 -1655
- package/dist/teamsync-pdf-viewer.js.map +1 -1
- package/package.json +2 -2
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
|

|
|
9
6
|
|
|
7
|
+
### **100% Open Source. 100% Client-Side. Enterprise PDF Engine.**
|
|
8
|
+
|
|
10
9
|
[](LICENSE)
|
|
11
10
|
[](https://pdfviewer.teamsync.com/)
|
|
12
|
-
[](https://www.npmjs.com/package/teamsync-pdf-viewer)
|
|
12
|
+
[](https://reactjs.org/)
|
|
13
|
+
[](https://vitejs.dev/)
|
|
14
|
+
[](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
|
-
##
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
- **
|
|
26
|
-
- **
|
|
27
|
-
|
|
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
|
-
|
|
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
|
-
|
|
50
|
-
`lucide-react` are installed automatically.
|
|
49
|
+
### Installing from GitHub Packages (Alternative)
|
|
51
50
|
|
|
52
|
-
|
|
51
|
+
If installing from GitHub Packages registry:
|
|
53
52
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
62
|
+
---
|
|
60
63
|
|
|
61
|
-
|
|
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
|
-
|
|
66
|
-
```
|
|
66
|
+
Explore detailed SDK guides, parameter options, code snippets, and architecture deep dives:
|
|
67
67
|
|
|
68
|
-
**
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
86
|
-
|
|
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
|
-
###
|
|
108
|
+
### Standard WebViewer Initialization
|
|
93
109
|
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
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
|
-
|
|
153
|
+
function App() {
|
|
101
154
|
return (
|
|
102
|
-
<div style={{ height: '
|
|
103
|
-
<
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
122
|
-
it inside a client component — the package itself never touches `window` at import time.
|
|
123
|
-
|
|
124
|
-
### Imperative API
|
|
167
|
+
---
|
|
125
168
|
|
|
126
|
-
|
|
127
|
-
import { createWebViewer } from 'teamsync-pdf-viewer';
|
|
128
|
-
import 'teamsync-pdf-viewer/style.css';
|
|
169
|
+
## 🛠️ Local Development Quick Start
|
|
129
170
|
|
|
130
|
-
|
|
131
|
-
{ initialDoc: '/sample.pdf', fileName: 'sample.pdf', permissions: { canRedact: true } },
|
|
132
|
-
document.getElementById('viewer-container')!
|
|
133
|
-
);
|
|
171
|
+
### 1. Clone & Install
|
|
134
172
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
179
|
+
### 2. Launch Dev Server
|
|
141
180
|
|
|
142
|
-
|
|
181
|
+
```bash
|
|
182
|
+
npm run dev
|
|
183
|
+
```
|
|
143
184
|
|
|
144
|
-
|
|
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
|
-
##
|
|
189
|
+
## 🔌 Extensible Plugin Architecture
|
|
185
190
|
|
|
186
|
-
|
|
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
|
-
|
|
205
|
+
```typescript
|
|
206
|
+
import { WebViewer } from 'teamsync-pdf-viewer';
|
|
207
|
+
import { myCustomPlugin } from './myCustomPlugin';
|
|
194
208
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
222
|
-
|
|
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
|
-
##
|
|
241
|
+
## 📄 License
|
|
227
242
|
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
|
|
246
|
+
---
|
|
233
247
|
|
|
234
|
-
|
|
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;
|