ng2-pdfjs-viewer 26.1.0 → 26.2.0

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/README.md CHANGED
@@ -16,8 +16,12 @@
16
16
  [![PDF.js](https://img.shields.io/badge/PDF.js-6.0.227-green?style=flat-square&logo=mozilla)](https://github.com/mozilla/pdf.js)
17
17
  [![license](https://img.shields.io/badge/license-Apache--2.0%20%28Commons%20Clause%29-blue?style=flat-square)](https://github.com/intbot/ng2-pdfjs-viewer/blob/master/LICENSE)
18
18
  [![stars](https://img.shields.io/github/stars/intbot/ng2-pdfjs-viewer?style=flat-square&logo=github)](https://github.com/intbot/ng2-pdfjs-viewer)
19
+ [![Open in StackBlitz](https://img.shields.io/badge/Open%20in-StackBlitz-1389FD?style=flat-square&logo=stackblitz&logoColor=white)](https://stackblitz.com/github/intbot/ng2-pdfjs-viewer/tree/master/examples/quickstart)
20
+ [![Edit on CodeSandbox](https://img.shields.io/badge/Edit%20on-CodeSandbox-151515?style=flat-square&logo=codesandbox&logoColor=white)](https://codesandbox.io/p/sandbox/github/intbot/ng2-pdfjs-viewer/tree/master/examples/quickstart)
19
21
 
20
- [**Live demo**](https://angular-pdf-viewer-demo.vercel.app/) · [**Documentation**](https://angular-pdf-viewer-docs.vercel.app/) · [**API reference**](https://angular-pdf-viewer-docs.vercel.app/docs/api/component-inputs) · [**Changelog**](https://github.com/intbot/ng2-pdfjs-viewer/blob/master/CHANGELOG.md)
22
+ [**Documentation**](https://angularpdf.com/) · [**API reference**](https://angularpdf.com/docs/api/component-inputs) · [**Live demo**](https://demo.angularpdf.com/) · [**Showcase**](https://angularpdf.com/showcase) · [**Changelog**](https://github.com/intbot/ng2-pdfjs-viewer/blob/master/CHANGELOG.md)
23
+
24
+ ⭐ **Find it useful? [Star it on GitHub](https://github.com/intbot/ng2-pdfjs-viewer)** — it helps more Angular developers discover it.
21
25
 
22
26
  </div>
23
27
 
@@ -31,6 +35,9 @@ AI assistant — all driven by typed `@Input()`s and `@Output()` events, no ifra
31
35
  Shipping since **2018**, **8.3+ million downloads**, mobile-first, and built & verified on **Angular 22**
32
36
  while keeping a wide `>=10` peer range so existing apps upgrade without churn.
33
37
 
38
+ From France's data-protection regulator to Switzerland's federal tech institute, it's in
39
+ production on five continents — [see who's using it ↓](#-used-in-production).
40
+
34
41
  ```bash
35
42
  npm install ng2-pdfjs-viewer
36
43
  ```
@@ -41,6 +48,22 @@ npm install ng2-pdfjs-viewer
41
48
 
42
49
  That's the whole integration. [Wire up the assets](#-quick-start) and you have a full viewer.
43
50
 
51
+ ## 🌍 Used in production
52
+
53
+ National regulators, public universities, research infrastructure, and fintech platforms render
54
+ PDFs with ng2-pdfjs-viewer — on five continents. Among them:
55
+
56
+ | | |
57
+ |---|---|
58
+ | <img src="https://flagcdn.com/20x15/ch.png" width="20" alt="Switzerland"> **EPFL** | Switzerland's federal institute of technology — the Infoscience research portal |
59
+ | <img src="https://flagcdn.com/20x15/fr.png" width="20" alt="France"> **CNIL** | France's national data-protection authority |
60
+ | <img src="https://flagcdn.com/20x15/fi.png" width="20" alt="Finland"> **Finnish National Agency for Education** | the country's open learning-materials library (AOE) |
61
+ | <img src="https://flagcdn.com/20x15/au.png" width="20" alt="Australia"> **AuScope** | Australia's national geoscience research infrastructure |
62
+ | <img src="https://flagcdn.com/20x15/es.png" width="20" alt="Spain"> **Spain's Ministry of Culture** | the Travesía cultural-heritage platform |
63
+ | <img src="https://flagcdn.com/20x15/us.png" width="20" alt="United States"> **University of Virginia** | the Supporting Transformative Autism Research (DRIVE) program |
64
+
65
+ Part of **8.3M+ installs** worldwide. [See the full showcase →](https://angularpdf.com/showcase)
66
+
44
67
  ## ✨ Highlights
45
68
 
46
69
  | | |
@@ -52,11 +75,11 @@ That's the whole integration. [Wire up the assets](#-quick-start) and you have a
52
75
  | 🤖 **AI assistant (BYO)** | Point it at any OpenAI-compatible endpoint (OpenAI, Azure, Ollama, vLLM…). Answers cite pages and click through. **The library never calls an AI service on its own.** |
53
76
  | 🔊 **Read aloud** | Browser speech synthesis reads sentence by sentence, highlighting the spoken text and reporting progress. |
54
77
  | 🗂️ **Organize pages** | Reorder, delete, cut/copy/paste, extract, and merge pages in the viewer's "Manage pages" panel. |
55
- | 🎨 **Make it yours** | CSS-variable theming, true dark-mode page rendering, and your own Angular templates for the toolbar, sidebar, and per-page overlays. |
78
+ | 🎨 **Make it yours** | CSS-variable theming, true dark-mode page rendering, and your own Angular templates for the toolbar, sidebar, and per-page overlays — or go `chromeless` to hide the chrome for an embedded, pages-only view. |
56
79
  | 🛡️ **Protect content** | Block print/download, disable selection, and stamp watermarks (honest client-side deterrence — not DRM). |
57
80
  | ♿ **Accessible** | Screen-reader friendly, tagged-PDF aware, keyboard navigable — with a [WCAG / EAA guide](https://github.com/intbot/ng2-pdfjs-viewer/blob/master/ACCESSIBILITY.md). |
58
81
 
59
- → Explore every feature with live code on the **[documentation site](https://angular-pdf-viewer-docs.vercel.app/)** and **[demo](https://angular-pdf-viewer-demo.vercel.app/)**.
82
+ → Explore every feature with live code on the **[documentation site](https://angularpdf.com/)** and **[demo](https://demo.angularpdf.com/)**.
60
83
 
61
84
  ## 🚀 Quick start
62
85
 
@@ -95,13 +118,13 @@ export class AppModule {}
95
118
 
96
119
  > **Production note:** PDF.js 6 ships ES modules (`.mjs`), localization (`.ftl`), and WebAssembly
97
120
  > (`.wasm`) assets. Make sure your web server returns the correct MIME types for them — see the
98
- > [deployment guide](https://angular-pdf-viewer-docs.vercel.app/docs/getting-started) for the
121
+ > [deployment guide](https://angularpdf.com/docs/getting-started) for the
99
122
  > nginx/IIS snippets.
100
123
 
101
124
  ## 🧩 What you can build
102
125
 
103
126
  A few of the things the component makes one-liners. Full, runnable versions live in the
104
- [feature guides](https://angular-pdf-viewer-docs.vercel.app/docs/features/overview).
127
+ [feature guides](https://angularpdf.com/docs/features/overview).
105
128
 
106
129
  **Annotate, sign, and save the result**
107
130
 
@@ -157,15 +180,15 @@ The README is the front door — the deep reference lives on the docs site and s
157
180
 
158
181
  | | |
159
182
  |---|---|
160
- | 🏁 [Getting started](https://angular-pdf-viewer-docs.vercel.app/docs/getting-started) | Install, assets, first viewer, production deployment |
161
- | 🧭 [Feature guides](https://angular-pdf-viewer-docs.vercel.app/docs/features/overview) | Annotation, forms, search, AI, read-aloud, custom UI, protection, and more |
162
- | 🔧 [API reference](https://angular-pdf-viewer-docs.vercel.app/docs/api/component-inputs) | Every `@Input()`, `@Output()`, and method, with types |
183
+ | 🏁 [Getting started](https://angularpdf.com/docs/getting-started) | Install, assets, first viewer, production deployment |
184
+ | 🧭 [Feature guides](https://angularpdf.com/docs/features/overview) | Annotation, forms, search, AI, read-aloud, custom UI, protection, and more |
185
+ | 🔧 [API reference](https://angularpdf.com/docs/api/component-inputs) | Every `@Input()`, `@Output()`, and method, with types |
163
186
  | ♿ [Accessibility](https://github.com/intbot/ng2-pdfjs-viewer/blob/master/ACCESSIBILITY.md) | Screen readers, tagged PDFs, keyboard nav, WCAG / EAA |
164
187
  | 🖥️ [Server-side examples](https://github.com/intbot/ng2-pdfjs-viewer/blob/master/Server-Side-Examples.md) | Streaming, authenticated fetch, signed URLs |
165
188
  | 📝 [Changelog](https://github.com/intbot/ng2-pdfjs-viewer/blob/master/CHANGELOG.md) | What changed, and behavior notes when upgrading |
166
189
 
167
190
  The component exposes **30+ inputs**, **24+ events**, and **19+ Promise-returning methods**.
168
- The [API reference](https://angular-pdf-viewer-docs.vercel.app/docs/api/component-inputs) is the
191
+ The [API reference](https://angularpdf.com/docs/api/component-inputs) is the
169
192
  complete, typed list.
170
193
 
171
194
  ## 🔌 Loading documents
@@ -185,7 +208,7 @@ attach credentials and track progress:
185
208
 
186
209
  For large files, linearize ("fast web view") and serve with HTTP range support so the first pages
187
210
  render before the whole document downloads. Details in the
188
- [loading guide](https://angular-pdf-viewer-docs.vercel.app/docs/features/loading-documents).
211
+ [loading guide](https://angularpdf.com/docs/features/loading-documents).
189
212
 
190
213
  ## 🛡️ Security
191
214
 
@@ -209,6 +232,12 @@ test.bat # build the lib, link it, and serve the demo on http://localho
209
232
  See [CONTRIBUTING.md](https://github.com/intbot/ng2-pdfjs-viewer/blob/master/CONTRIBUTING.md) for the full setup, and look for
210
233
  [`good first issue`](https://github.com/intbot/ng2-pdfjs-viewer/labels/good%20first%20issue) to get started.
211
234
 
235
+ ## 🏗️ Showcase
236
+
237
+ Shipped something with ng2-pdfjs-viewer? [Add it to the showcase](https://angularpdf.com/showcase) — submitted projects are listed next to other production apps using the viewer.
238
+
239
+ Using it somewhere that won't show up in public code — an internal tool, a hospital system, a government portal? I'd like to hear about it: email **codehippie1@gmail.com** with a line about what you're building. Teams in healthcare, finance, education, and public-sector software already have. (Bugs and feature requests are best filed as [an issue](https://github.com/intbot/ng2-pdfjs-viewer/issues).)
240
+
212
241
  ## 📄 License
213
242
 
214
243
  [Apache-2.0 (Commons Clause)](https://github.com/intbot/ng2-pdfjs-viewer/blob/master/LICENSE). Free to use, modify, and self-host; the Commons
@@ -223,6 +252,6 @@ years by a community of contributors and 8.3+ million downloads' worth of real-w
223
252
 
224
253
  <div align="center">
225
254
 
226
- [Documentation](https://angular-pdf-viewer-docs.vercel.app/) · [Live demo](https://angular-pdf-viewer-demo.vercel.app/) · [npm](https://www.npmjs.com/package/ng2-pdfjs-viewer) · [Issues](https://github.com/intbot/ng2-pdfjs-viewer/issues)
255
+ [Documentation](https://angularpdf.com/) · [Live demo](https://demo.angularpdf.com/) · [npm](https://www.npmjs.com/package/ng2-pdfjs-viewer) · [Issues](https://github.com/intbot/ng2-pdfjs-viewer/issues)
227
256
 
228
257
  </div>
@@ -40,7 +40,7 @@ class PdfAiAssistant {
40
40
  * of PdfJsViewerComponent.getDocumentText(); `history` carries prior turns
41
41
  * for multi-turn chat.
42
42
  */
43
- async ask(question, documentText, history = [], signal) {
43
+ async ask(question, documentText, history = [], signal, onToken) {
44
44
  const context = this.buildContext(documentText);
45
45
  const messages = [
46
46
  {
@@ -53,14 +53,20 @@ class PdfAiAssistant {
53
53
  ...history,
54
54
  { role: "user", content: question },
55
55
  ];
56
- return this.complete(messages, signal);
56
+ return this.complete(messages, signal, onToken);
57
57
  }
58
58
  /** One-shot document summary. */
59
59
  async summarize(documentText) {
60
60
  return this.ask("Summarize this document concisely. Lead with what it is, then the key points.", documentText);
61
61
  }
62
- /** Raw chat-completions call for custom prompting. */
63
- async complete(messages, signal) {
62
+ /**
63
+ * Raw chat-completions call for custom prompting. Pass `onToken` to stream the
64
+ * answer token-by-token (the callback receives the running full text and the
65
+ * latest delta); the Promise still resolves to the complete text. Streaming is
66
+ * requested only when `onToken` is given and `config.stream !== false`, and it
67
+ * falls back to a single JSON response if the endpoint doesn't stream.
68
+ */
69
+ async complete(messages, signal, onToken) {
64
70
  const headers = {
65
71
  "Content-Type": "application/json",
66
72
  ...(this.config.headers ?? {}),
@@ -68,6 +74,7 @@ class PdfAiAssistant {
68
74
  if (this.config.apiKey) {
69
75
  headers["Authorization"] = `Bearer ${this.config.apiKey}`;
70
76
  }
77
+ const wantStream = !!onToken && this.config.stream !== false;
71
78
  const response = await fetch(this.config.endpoint, {
72
79
  method: "POST",
73
80
  headers,
@@ -76,19 +83,81 @@ class PdfAiAssistant {
76
83
  model: this.config.model,
77
84
  temperature: this.config.temperature ?? 0.2,
78
85
  messages,
86
+ ...(wantStream ? { stream: true } : {}),
79
87
  }),
80
88
  });
81
89
  if (!response.ok) {
82
90
  const body = await response.text().catch(() => "");
83
91
  throw new Error(`AI endpoint returned ${response.status}: ${body.slice(0, 300)}`);
84
92
  }
93
+ const contentType = response.headers?.get?.("Content-Type") ?? "";
94
+ if (wantStream && response.body && contentType.includes("text/event-stream")) {
95
+ return this.readStream(response, onToken);
96
+ }
97
+ // Non-streaming response, or the endpoint ignored `stream`: one JSON body.
85
98
  const json = await response.json();
86
99
  const content = json?.choices?.[0]?.message?.content;
87
100
  if (typeof content !== "string") {
88
101
  throw new Error("AI endpoint returned an unexpected response shape");
89
102
  }
103
+ // Emit once so callers wired for streaming still receive their update.
104
+ if (onToken)
105
+ onToken(content, content);
90
106
  return content;
91
107
  }
108
+ /**
109
+ * Read an OpenAI-style Server-Sent Events stream, accumulating
110
+ * choices[0].delta.content and emitting each delta through onToken. Returns the
111
+ * full concatenated text. Tolerates chunk boundaries that split SSE lines, and
112
+ * skips frames it can't parse (keep-alive comments, non-`data:` lines) rather
113
+ * than failing the whole stream.
114
+ */
115
+ async readStream(response, onToken) {
116
+ const reader = response.body.getReader();
117
+ const decoder = new TextDecoder();
118
+ let buffer = "";
119
+ let full = "";
120
+ const handleLine = (raw) => {
121
+ const line = raw.trim();
122
+ if (!line.startsWith("data:"))
123
+ return false;
124
+ const data = line.slice(5).trim();
125
+ if (data === "[DONE]")
126
+ return true;
127
+ try {
128
+ const delta = JSON.parse(data)?.choices?.[0]?.delta?.content;
129
+ if (typeof delta === "string" && delta) {
130
+ full += delta;
131
+ onToken(full, delta);
132
+ }
133
+ }
134
+ catch {
135
+ // Ignore an unparseable frame rather than aborting the stream.
136
+ }
137
+ return false;
138
+ };
139
+ try {
140
+ for (;;) {
141
+ const { done, value } = await reader.read();
142
+ if (done)
143
+ break;
144
+ buffer += decoder.decode(value, { stream: true });
145
+ let nl;
146
+ while ((nl = buffer.indexOf("\n")) !== -1) {
147
+ const line = buffer.slice(0, nl);
148
+ buffer = buffer.slice(nl + 1);
149
+ if (handleLine(line))
150
+ return full; // [DONE]
151
+ }
152
+ }
153
+ if (buffer.trim())
154
+ handleLine(buffer); // trailing line without a newline
155
+ }
156
+ finally {
157
+ reader.releaseLock();
158
+ }
159
+ return full;
160
+ }
92
161
  buildContext(documentText) {
93
162
  const max = this.config.maxContextChars ?? 100_000;
94
163
  let out = "";
@@ -0,0 +1,74 @@
1
+ import { computed } from '@angular/core';
2
+ import { toSignal } from '@angular/core/rxjs-interop';
3
+
4
+ // Secondary entry point: ng2-pdfjs-viewer/signals
5
+ //
6
+ // Read-only Angular signals projected from the viewer's @Output() events, for
7
+ // zoneless and OnPush apps that prefer reading state from signals over wiring up
8
+ // a handful of (event)="..." bindings and EventEmitter subscriptions.
9
+ //
10
+ // It reads the same outputs the component already emits - it adds no new viewer
11
+ // behaviour and sends nothing anywhere. Each signal holds the latest value from
12
+ // its source output and starts as `undefined` until that output first fires
13
+ // (`loaded`/`totalPages` are derived from page initialization).
14
+ //
15
+ // Requires Angular 16+ (signals + `@angular/core/rxjs-interop`). The base package
16
+ // keeps its `>=10` peer range; only this entry point needs 16+, so import it from
17
+ // the subpath and apps on older Angular never load it:
18
+ //
19
+ // import { pdfViewerSignals } from "ng2-pdfjs-viewer/signals";
20
+ //
21
+ // A @ViewChild viewer is only populated in ngAfterViewInit, which is NOT an
22
+ // injection context, so pass an Injector captured earlier:
23
+ //
24
+ // @ViewChild('viewer') viewer!: PdfJsViewerComponent;
25
+ // private injector = inject(Injector);
26
+ // signals!: PdfViewerSignals;
27
+ //
28
+ // ngAfterViewInit() {
29
+ // this.signals = pdfViewerSignals(this.viewer, { injector: this.injector });
30
+ // }
31
+ // // template: {{ signals.page() }} / {{ signals.totalPages() }} / {{ signals.loaded() }}
32
+ //
33
+ // Called from a constructor or field initializer (where an injection context
34
+ // exists), the `injector` option can be omitted. Subscriptions are torn down with
35
+ // the injector's DestroyRef - i.e. when the host component is destroyed.
36
+ /**
37
+ * Project the viewer's outputs into read-only signals. See the file header for
38
+ * usage and the injection-context rules. The returned signals are live: each
39
+ * updates when its source output next fires.
40
+ */
41
+ function pdfViewerSignals(viewer, options = {}) {
42
+ const { injector } = options;
43
+ const latest = (source) => toSignal(source, { initialValue: undefined, injector });
44
+ const pages = latest(viewer.onPagesInit);
45
+ return {
46
+ page: latest(viewer.onPageChange),
47
+ scale: latest(viewer.onScaleChange),
48
+ totalPages: computed(() => pages()?.pagesCount),
49
+ loaded: computed(() => pages() !== undefined),
50
+ error: latest(viewer.onDocumentError),
51
+ rotation: latest(viewer.onRotationChange),
52
+ findMatches: latest(viewer.onUpdateFindMatchesCount),
53
+ readAloud: latest(viewer.onReadAloudStateChange),
54
+ annotationEditor: latest(viewer.onAnnotationEditorStateChange),
55
+ annotationEditorMode: latest(viewer.annotationEditorChange),
56
+ sidebar: latest(viewer.onSidebarViewChanged),
57
+ metadata: latest(viewer.onMetadataLoaded),
58
+ outline: latest(viewer.onOutlineLoaded),
59
+ formData: latest(viewer.formDataChange),
60
+ presentationMode: latest(viewer.onPresentationModeChanged),
61
+ zoom: latest(viewer.zoomChange),
62
+ cursor: latest(viewer.cursorChange),
63
+ scroll: latest(viewer.scrollChange),
64
+ spread: latest(viewer.spreadChange),
65
+ pageMode: latest(viewer.pageModeChange),
66
+ };
67
+ }
68
+
69
+ /**
70
+ * Generated bundle index. Do not edit.
71
+ */
72
+
73
+ export { pdfViewerSignals };
74
+ //# sourceMappingURL=ng2-pdfjs-viewer-signals.mjs.map
@@ -1702,6 +1702,18 @@ class PdfJsViewerComponent {
1702
1702
  if (this.diagnosticLogs)
1703
1703
  console.debug("PdfJsViewer: The document has now been loaded!");
1704
1704
  this.onDocumentLoad.emit();
1705
+ // Project onPagesInit here. PDF.js fires the real 'pagesinit' once,
1706
+ // BEFORE this handler map finishes registering (it lands between
1707
+ // 'pagesinit' and 'scalechanging'), and the postMessage wrapper's own
1708
+ // pagesInit relay is wired on 'documentloaded' too late for the
1709
+ // one-shot - so onPagesInit never fired and the signals entry point's
1710
+ // loaded()/totalPages() stayed empty. 'documentloaded' is reliably
1711
+ // caught (it drives onDocumentLoad) and pagesCount is set by now.
1712
+ const app = this.PDFViewerApplication;
1713
+ const pagesCount = app?.pagesCount ?? app?.pdfDocument?.numPages;
1714
+ if (typeof pagesCount === "number" && pagesCount > 0) {
1715
+ this.onPagesInit.emit({ pagesCount });
1716
+ }
1705
1717
  // Queue auto-actions with the property values current at THIS load
1706
1718
  this.queueAutoActionsForDocumentLoad();
1707
1719
  // Execute all queued auto-actions
@@ -2100,6 +2112,9 @@ class PdfJsViewerComponent {
2100
2112
  this.aiAbort = new AbortController();
2101
2113
  const signal = this.aiAbort.signal;
2102
2114
  this.aiMessages.push({ role: "user", content: q, parts: [{ text: q }] });
2115
+ // Placeholder assistant turn we stream tokens into as they arrive.
2116
+ const assistant = { role: "assistant", content: "", parts: [] };
2117
+ this.aiMessages.push(assistant);
2103
2118
  this.cdr.markForCheck();
2104
2119
  try {
2105
2120
  if (!this.aiClient || this.aiClientConfig !== config) {
@@ -2110,29 +2125,28 @@ class PdfJsViewerComponent {
2110
2125
  this.aiDocText = await this.getDocumentText();
2111
2126
  }
2112
2127
  const history = this.aiMessages
2113
- .slice(0, -1)
2128
+ .slice(0, -2)
2114
2129
  .filter((m) => !m.error)
2115
2130
  .map((m) => ({ role: m.role, content: m.content }));
2116
- const answer = await this.aiClient.ask(q, this.aiDocText, history, signal);
2131
+ const answer = await this.aiClient.ask(q, this.aiDocText, history, signal, (full) => {
2132
+ if (generation !== this.aiGeneration) {
2133
+ return; // stale stream - ignore late tokens
2134
+ }
2135
+ assistant.content = full;
2136
+ assistant.parts = this.parseAiCitations(full);
2137
+ this.cdr.markForCheck();
2138
+ });
2117
2139
  if (generation !== this.aiGeneration) {
2118
2140
  return; // document changed mid-flight - stale answer
2119
2141
  }
2120
- this.aiMessages.push({
2121
- role: "assistant",
2122
- content: answer,
2123
- parts: this.parseAiCitations(answer),
2124
- });
2142
+ assistant.content = answer;
2143
+ assistant.parts = this.parseAiCitations(answer);
2125
2144
  }
2126
2145
  catch (e) {
2127
2146
  if (generation !== this.aiGeneration) {
2128
2147
  return; // aborted by invalidation - already cleaned up
2129
2148
  }
2130
- this.aiMessages.push({
2131
- role: "assistant",
2132
- content: "",
2133
- error: e?.message || "AI request failed",
2134
- parts: [],
2135
- });
2149
+ assistant.error = e?.message || "AI request failed";
2136
2150
  }
2137
2151
  finally {
2138
2152
  if (generation === this.aiGeneration) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ng2-pdfjs-viewer",
3
- "version": "26.1.0",
3
+ "version": "26.2.0",
4
4
  "description": "The most comprehensive Angular PDF viewer, powered by Mozilla PDF.js 6 — view, annotate, sign, fill forms, search, and read aloud from one component. 8.3M+ downloads, mobile-first, production-ready.",
5
5
  "author": {
6
6
  "name": "Aneesh Goapalakrishnan",
@@ -20,34 +20,37 @@
20
20
  "@angular/core": ">=10.0.0"
21
21
  },
22
22
  "$schema": "./node_modules/ng-packagr/package.schema.json",
23
- "homepage": "https://angular-pdf-viewer-docs.vercel.app/",
23
+ "homepage": "https://angularpdf.com/",
24
24
  "bugs": {
25
25
  "url": "https://github.com/intbot/ng2-pdfjs-viewer/issues"
26
26
  },
27
27
  "keywords": [
28
+ "angular pdf viewer",
28
29
  "angular",
29
30
  "angular-pdf-viewer",
31
+ "ng2-pdfjs-viewer",
30
32
  "pdf-viewer",
31
33
  "pdf-reader",
32
34
  "PDF",
33
- "PDF Viewer",
34
35
  "pdfjs",
35
- "viewerjs",
36
- "ng2",
36
+ "pdf.js",
37
37
  "angular-component",
38
38
  "angular-pdf",
39
39
  "ng2-pdf",
40
- "angular 2 - 22",
41
40
  "angular 22",
42
41
  "angular-library",
43
42
  "typescript",
44
43
  "javascript",
45
44
  "pdf-rendering",
46
45
  "pdf-annotations",
46
+ "pdf-forms",
47
+ "pdf-signature",
48
+ "esign",
49
+ "pdf-search",
47
50
  "pdf-zoom",
48
51
  "pdf-printing",
49
- "responsive-ui",
50
- "custom PDF Viewer",
52
+ "text-to-speech",
53
+ "mobile-first",
51
54
  "accessibility",
52
55
  "open-source"
53
56
  ],
@@ -64,6 +67,10 @@
64
67
  "./ai": {
65
68
  "types": "./types/ng2-pdfjs-viewer-ai.d.ts",
66
69
  "default": "./fesm2022/ng2-pdfjs-viewer-ai.mjs"
70
+ },
71
+ "./signals": {
72
+ "types": "./types/ng2-pdfjs-viewer-signals.d.ts",
73
+ "default": "./fesm2022/ng2-pdfjs-viewer-signals.mjs"
67
74
  }
68
75
  },
69
76
  "sideEffects": false,
@@ -9,6 +9,7 @@ interface PdfAiAssistantConfig {
9
9
  headers?: Record<string, string>;
10
10
  maxContextChars?: number;
11
11
  temperature?: number;
12
+ stream?: boolean;
12
13
  }
13
14
  interface PdfAiMessage {
14
15
  role: "system" | "user" | "assistant";
@@ -35,11 +36,25 @@ declare class PdfAiAssistant {
35
36
  * of PdfJsViewerComponent.getDocumentText(); `history` carries prior turns
36
37
  * for multi-turn chat.
37
38
  */
38
- ask(question: string, documentText: PdfPageText[], history?: PdfAiMessage[], signal?: AbortSignal): Promise<string>;
39
+ ask(question: string, documentText: PdfPageText[], history?: PdfAiMessage[], signal?: AbortSignal, onToken?: (full: string, delta: string) => void): Promise<string>;
39
40
  /** One-shot document summary. */
40
41
  summarize(documentText: PdfPageText[]): Promise<string>;
41
- /** Raw chat-completions call for custom prompting. */
42
- complete(messages: PdfAiMessage[], signal?: AbortSignal): Promise<string>;
42
+ /**
43
+ * Raw chat-completions call for custom prompting. Pass `onToken` to stream the
44
+ * answer token-by-token (the callback receives the running full text and the
45
+ * latest delta); the Promise still resolves to the complete text. Streaming is
46
+ * requested only when `onToken` is given and `config.stream !== false`, and it
47
+ * falls back to a single JSON response if the endpoint doesn't stream.
48
+ */
49
+ complete(messages: PdfAiMessage[], signal?: AbortSignal, onToken?: (full: string, delta: string) => void): Promise<string>;
50
+ /**
51
+ * Read an OpenAI-style Server-Sent Events stream, accumulating
52
+ * choices[0].delta.content and emitting each delta through onToken. Returns the
53
+ * full concatenated text. Tolerates chunk boundaries that split SSE lines, and
54
+ * skips frames it can't parse (keep-alive comments, non-`data:` lines) rather
55
+ * than failing the whole stream.
56
+ */
57
+ private readStream;
43
58
  private buildContext;
44
59
  }
45
60
 
@@ -0,0 +1,77 @@
1
+ import { EventEmitter, Signal, Injector } from '@angular/core';
2
+ import { ChangedRotation, PagesInfo, DocumentError, FindMatchesCount, ReadAloudState, AnnotationEditorState, AnnotationEditorMode, SidebarViewChange, DocumentMetadata, DocumentOutline, FormDataMap, PresentationMode } from 'ng2-pdfjs-viewer';
3
+
4
+ interface PdfViewerSignalSource {
5
+ onPageChange: EventEmitter<number>;
6
+ onScaleChange: EventEmitter<number>;
7
+ onRotationChange: EventEmitter<ChangedRotation>;
8
+ onPagesInit: EventEmitter<PagesInfo>;
9
+ onDocumentError: EventEmitter<DocumentError>;
10
+ onUpdateFindMatchesCount: EventEmitter<FindMatchesCount>;
11
+ onReadAloudStateChange: EventEmitter<ReadAloudState>;
12
+ onAnnotationEditorStateChange: EventEmitter<AnnotationEditorState>;
13
+ annotationEditorChange: EventEmitter<AnnotationEditorMode>;
14
+ onSidebarViewChanged: EventEmitter<SidebarViewChange>;
15
+ onMetadataLoaded: EventEmitter<DocumentMetadata>;
16
+ onOutlineLoaded: EventEmitter<DocumentOutline>;
17
+ formDataChange: EventEmitter<FormDataMap>;
18
+ onPresentationModeChanged: EventEmitter<PresentationMode>;
19
+ zoomChange: EventEmitter<string>;
20
+ cursorChange: EventEmitter<string>;
21
+ scrollChange: EventEmitter<string>;
22
+ spreadChange: EventEmitter<string>;
23
+ pageModeChange: EventEmitter<string>;
24
+ }
25
+ interface PdfViewerSignals {
26
+ /** Current 1-based page number; undefined until the first page change. */
27
+ page: Signal<number | undefined>;
28
+ /** Current zoom scale factor (1 = 100%); undefined until the first scale change. */
29
+ scale: Signal<number | undefined>;
30
+ /** Total page count, available once the document's pages initialize. */
31
+ totalPages: Signal<number | undefined>;
32
+ /** True once the document's pages have initialized. */
33
+ loaded: Signal<boolean>;
34
+ /** Last document-load error, or undefined if none. */
35
+ error: Signal<DocumentError | undefined>;
36
+ /** Page rotation state ({ rotation, page }). */
37
+ rotation: Signal<ChangedRotation | undefined>;
38
+ /** Live find/search match counts ({ current, total }). */
39
+ findMatches: Signal<FindMatchesCount | undefined>;
40
+ /** Read-aloud progress ({ status, page, sentence? }). */
41
+ readAloud: Signal<ReadAloudState | undefined>;
42
+ /** Annotation editor undo/empty/selection state. */
43
+ annotationEditor: Signal<AnnotationEditorState | undefined>;
44
+ /** Active annotation editor tool (highlight / ink / freetext / ...). */
45
+ annotationEditorMode: Signal<AnnotationEditorMode | undefined>;
46
+ /** Current sidebar panel ({ view }). */
47
+ sidebar: Signal<SidebarViewChange | undefined>;
48
+ /** Document metadata (title / author / ...). */
49
+ metadata: Signal<DocumentMetadata | undefined>;
50
+ /** Document outline / bookmarks ({ items?, hasOutline }). */
51
+ outline: Signal<DocumentOutline | undefined>;
52
+ /** Current AcroForm field values. */
53
+ formData: Signal<FormDataMap | undefined>;
54
+ /** Presentation (fullscreen) mode ({ active }). */
55
+ presentationMode: Signal<PresentationMode | undefined>;
56
+ /** Zoom mode string (e.g. 'auto', 'page-fit', '125'). */
57
+ zoom: Signal<string | undefined>;
58
+ /** Cursor tool ('select' | 'hand'). */
59
+ cursor: Signal<string | undefined>;
60
+ /** Scroll mode ('vertical' | 'horizontal' | 'wrapped' | 'page'). */
61
+ scroll: Signal<string | undefined>;
62
+ /** Spread mode ('none' | 'odd' | 'even'). */
63
+ spread: Signal<string | undefined>;
64
+ /** Page (view) mode. */
65
+ pageMode: Signal<string | undefined>;
66
+ }
67
+ /**
68
+ * Project the viewer's outputs into read-only signals. See the file header for
69
+ * usage and the injection-context rules. The returned signals are live: each
70
+ * updates when its source output next fires.
71
+ */
72
+ declare function pdfViewerSignals(viewer: PdfViewerSignalSource, options?: {
73
+ injector?: Injector;
74
+ }): PdfViewerSignals;
75
+
76
+ export { pdfViewerSignals };
77
+ export type { PdfViewerSignalSource, PdfViewerSignals };