vue-pdfjs-headless 4.3.3 → 4.5.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 +43 -4
- package/dist/index.d.ts +116 -44
- package/dist/index.js +326 -82
- package/package.json +8 -7
package/README.md
CHANGED
|
@@ -5,25 +5,64 @@
|
|
|
5
5
|
[![NPM downloads][badge-npm-downloads]][url-npm]
|
|
6
6
|
[![NPM Unpacked Size (with version)][badge-npm-unpacked-size]][url-npm]
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
This package provides headless Vue composables over pdfjs-dist. The composables load a document, draw its pages and track the viewer's state, with no styling and no renderer of their own.
|
|
9
|
+
|
|
10
|
+
Design notes live in [the specs](https://github.com/Esposter/Esposter/tree/main/openspec/specs/vue-pdfjs-headless).
|
|
9
11
|
|
|
10
12
|
## Table of Contents
|
|
11
13
|
|
|
12
14
|
- 🚀 [Getting Started](#getting-started)
|
|
13
|
-
-
|
|
15
|
+
- 🧩 [Key Exports](#key-exports)
|
|
14
16
|
- ⚖️ [License](#license)
|
|
15
17
|
|
|
16
18
|
---
|
|
17
19
|
|
|
18
20
|
## <a name="getting-started">🚀 Getting Started</a>
|
|
19
21
|
|
|
22
|
+
1. Install the package.
|
|
23
|
+
|
|
20
24
|
```bash
|
|
21
25
|
pnpm i vue-pdfjs-headless
|
|
22
26
|
```
|
|
23
27
|
|
|
24
|
-
|
|
28
|
+
2. Add this code to the `<script setup>` of a component.
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import * as pdfjs from "pdfjs-dist";
|
|
32
|
+
import { shallowRef } from "vue";
|
|
33
|
+
import { usePdfDocument, usePdfPage } from "vue-pdfjs-headless";
|
|
34
|
+
|
|
35
|
+
const canvas = shallowRef<HTMLCanvasElement>();
|
|
36
|
+
const { pdfDocument } = usePdfDocument({
|
|
37
|
+
renderer: {
|
|
38
|
+
api: pdfjs,
|
|
39
|
+
cMapUrl: "/cmaps/",
|
|
40
|
+
standardFontDataUrl: "/fonts/",
|
|
41
|
+
wasmUrl: "/wasm/",
|
|
42
|
+
workerSrc: "/pdf.worker.mjs",
|
|
43
|
+
},
|
|
44
|
+
source: "/document.pdf",
|
|
45
|
+
});
|
|
46
|
+
usePdfPage({ canvas, pageNumber: 1, pdfDocument });
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
3. Bind `canvas` to a `<canvas ref="canvas" />` in the template. The first page appears in the canvas.
|
|
50
|
+
|
|
51
|
+
## <a name="key-exports">🧩 Key Exports</a>
|
|
25
52
|
|
|
26
|
-
|
|
53
|
+
| Export | Kind | Purpose |
|
|
54
|
+
| ------------------- | ---------- | -------------------------------------------------------------------------------------------------------------- |
|
|
55
|
+
| `PdfRenderer` | interface | The renderer: its API module, its data folder URLs and its worker. The consumer supplies it. |
|
|
56
|
+
| `PdfSource` | type | A document, given by its URL, its bytes or a file that the reader picked |
|
|
57
|
+
| `PdfViewerState` | interface | The viewer's pages, zoom, rotation, search, download and print, as state and actions |
|
|
58
|
+
| `readXfdf` | function | Reads the free-text annotations of an XFDF file, or returns an error that names what is wrong with it |
|
|
59
|
+
| `usePdfAnnotations` | composable | Lists the reader's comments once they are wanted, and imports and exports free texts as XFDF |
|
|
60
|
+
| `usePdfDocument` | composable | Loads a document from a source through the consumer's renderer. Its password prompt and its failure are state. |
|
|
61
|
+
| `usePdfPage` | composable | Draws one page of a loaded document into a canvas, at the canvas's width and the device's pixel ratio |
|
|
62
|
+
| `usePdfThumbnails` | composable | One thumbnail per page. A thumbnail's page is loaded and drawn only while its row is in view of the sidebar. |
|
|
63
|
+
| `usePdfViewer` | composable | Builds the renderer's viewer for a loaded document. Its pages, zoom and search are state. |
|
|
64
|
+
| `usePdfZoomGesture` | composable | Zooms the viewer about the pointer on a pinch or a Ctrl-wheel. The content under the pointer stays in place. |
|
|
65
|
+
| `writeXfdf` | function | Writes free-text annotations as XFDF |
|
|
27
66
|
|
|
28
67
|
## <a name="license">⚖️ License</a>
|
|
29
68
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,79 @@
|
|
|
1
|
+
import { Result } from "neverthrow";
|
|
1
2
|
import { ComputedRef, MaybeRefOrGetter, Ref, ShallowRef } from "vue";
|
|
2
3
|
import * as PdfJs from "pdfjs-dist";
|
|
3
|
-
import { PDFDocumentLoadingTask, PDFDocumentProxy, PageViewport } from "pdfjs-dist";
|
|
4
|
+
import { PDFDocumentLoadingTask, PDFDocumentProxy, PDFPageProxy, PageViewport } from "pdfjs-dist";
|
|
4
5
|
import * as PdfJsViewer from "pdfjs-dist/web/pdf_viewer.mjs";
|
|
5
6
|
import { PDFViewer } from "pdfjs-dist/web/pdf_viewer.mjs";
|
|
7
|
+
export declare enum PdfEditorMode {
|
|
8
|
+
None = 0,
|
|
9
|
+
FreeText = 3,
|
|
10
|
+
Highlight = 9,
|
|
11
|
+
Stamp = 13,
|
|
12
|
+
Ink = 15
|
|
13
|
+
}
|
|
14
|
+
export interface PdfAnnotationChange {
|
|
15
|
+
kind: PdfEditorMode;
|
|
16
|
+
pageNumber: number;
|
|
17
|
+
}
|
|
18
|
+
export interface PdfComment {
|
|
19
|
+
contents: string;
|
|
20
|
+
pageNumber: number;
|
|
21
|
+
}
|
|
22
|
+
export interface PdfAnnotationsState {
|
|
23
|
+
comments: ShallowRef<PdfComment[] | undefined>;
|
|
24
|
+
exportXfdf: () => string;
|
|
25
|
+
importXfdf: (xfdf: string) => Result<void, string>;
|
|
26
|
+
setEditorMode: (mode: PdfEditorMode) => void;
|
|
27
|
+
}
|
|
28
|
+
export interface PdfMatchesCount {
|
|
29
|
+
current: number;
|
|
30
|
+
total: number;
|
|
31
|
+
}
|
|
32
|
+
export type PdfOutline = NonNullable<Awaited<ReturnType<PDFDocumentProxy["getOutline"]>>>;
|
|
33
|
+
export interface PdfPageOverlay {
|
|
34
|
+
element: HTMLElement;
|
|
35
|
+
pageNumber: number;
|
|
36
|
+
viewport: PageViewport;
|
|
37
|
+
}
|
|
38
|
+
export interface PdfViewerState {
|
|
39
|
+
actualSize: () => void;
|
|
40
|
+
currentPageNumber: Ref<number>;
|
|
41
|
+
download: (filename: string, watermark?: string) => void;
|
|
42
|
+
drawStoredEditors: () => void;
|
|
43
|
+
editorMode: Ref<PdfEditorMode>;
|
|
44
|
+
findNext: () => void;
|
|
45
|
+
findPrevious: () => void;
|
|
46
|
+
fitPage: () => void;
|
|
47
|
+
fitWidth: () => void;
|
|
48
|
+
followBookmark: (item: PdfOutline[number]) => void;
|
|
49
|
+
goToPage: (pageNumber: number) => void;
|
|
50
|
+
isMatchCase: Ref<boolean>;
|
|
51
|
+
isNotFound: Ref<boolean>;
|
|
52
|
+
isWholeWord: Ref<boolean>;
|
|
53
|
+
isZoomInDisabled: ComputedRef<boolean>;
|
|
54
|
+
isZoomOutDisabled: ComputedRef<boolean>;
|
|
55
|
+
matchesCount: Ref<PdfMatchesCount>;
|
|
56
|
+
nextPage: () => void;
|
|
57
|
+
outline: ShallowRef<PdfOutline | undefined>;
|
|
58
|
+
overlays: ShallowRef<PdfPageOverlay[]>;
|
|
59
|
+
pagesCount: Ref<number>;
|
|
60
|
+
pagesRotation: Ref<number>;
|
|
61
|
+
previousPage: () => void;
|
|
62
|
+
print: () => void;
|
|
63
|
+
resetZoom: () => void;
|
|
64
|
+
rotate: (degrees: number) => void;
|
|
65
|
+
scale: Ref<number>;
|
|
66
|
+
searchQuery: Ref<string>;
|
|
67
|
+
zoomIn: () => void;
|
|
68
|
+
zoomOut: () => void;
|
|
69
|
+
}
|
|
70
|
+
interface UsePdfAnnotationsOptions {
|
|
71
|
+
isCommentsWanted?: MaybeRefOrGetter<boolean>;
|
|
72
|
+
onChange?: (change: PdfAnnotationChange) => void;
|
|
73
|
+
pdfDocument: MaybeRefOrGetter<PDFDocumentProxy | undefined>;
|
|
74
|
+
viewer: PdfViewerState;
|
|
75
|
+
}
|
|
76
|
+
export declare const usePdfAnnotations: ({ isCommentsWanted, onChange, pdfDocument, viewer }: UsePdfAnnotationsOptions) => PdfAnnotationsState;
|
|
6
77
|
export type PdfApi = Pick<typeof PdfJs, "getDocument" | "GlobalWorkerOptions" | "PasswordResponses">;
|
|
7
78
|
export type PdfDocumentParameters = Partial<Parameters<PdfApi["getDocument"]>[0]>;
|
|
8
79
|
export interface PdfLoadingProgress {
|
|
@@ -33,11 +104,13 @@ interface UsePdfDocumentOptions {
|
|
|
33
104
|
export declare const usePdfDocument: ({ parameters, renderer, source }: UsePdfDocumentOptions) => PdfDocumentState;
|
|
34
105
|
interface UsePdfPageOptions {
|
|
35
106
|
canvas: MaybeRefOrGetter<HTMLCanvasElement | undefined>;
|
|
107
|
+
isActive?: MaybeRefOrGetter<boolean>;
|
|
36
108
|
isVisible?: MaybeRefOrGetter<boolean>;
|
|
37
109
|
pageNumber: MaybeRefOrGetter<number>;
|
|
110
|
+
pagesRotation?: MaybeRefOrGetter<number>;
|
|
38
111
|
pdfDocument: MaybeRefOrGetter<PDFDocumentProxy | undefined>;
|
|
39
112
|
}
|
|
40
|
-
export declare const usePdfPage: ({ canvas, isVisible, pageNumber, pdfDocument }: UsePdfPageOptions) => void;
|
|
113
|
+
export declare const usePdfPage: ({ canvas, isActive, isVisible, pageNumber, pagesRotation, pdfDocument }: UsePdfPageOptions) => void;
|
|
41
114
|
export interface PdfThumbnail {
|
|
42
115
|
canvas: ShallowRef<HTMLCanvasElement | undefined>;
|
|
43
116
|
isCurrent: ComputedRef<boolean>;
|
|
@@ -46,75 +119,70 @@ export interface PdfThumbnail {
|
|
|
46
119
|
}
|
|
47
120
|
interface UsePdfThumbnailsOptions {
|
|
48
121
|
currentPageNumber: MaybeRefOrGetter<number>;
|
|
122
|
+
pagesRotation: MaybeRefOrGetter<number>;
|
|
49
123
|
pdfDocument: MaybeRefOrGetter<PDFDocumentProxy | undefined>;
|
|
50
124
|
root: MaybeRefOrGetter<HTMLElement | undefined>;
|
|
51
125
|
}
|
|
52
|
-
export declare const usePdfThumbnails: ({ currentPageNumber, pdfDocument, root }: UsePdfThumbnailsOptions) => ShallowRef<PdfThumbnail[]>;
|
|
126
|
+
export declare const usePdfThumbnails: ({ currentPageNumber, pagesRotation, pdfDocument, root }: UsePdfThumbnailsOptions) => ShallowRef<PdfThumbnail[]>;
|
|
53
127
|
export type PdfViewerApi = Pick<typeof PdfJsViewer, "EventBus" | "FindState" | "LinkTarget" | "PDFFindController" | "PDFLinkService" | "PDFViewer">;
|
|
54
|
-
export interface PdfMatchesCount {
|
|
55
|
-
current: number;
|
|
56
|
-
total: number;
|
|
57
|
-
}
|
|
58
|
-
export type PdfOutline = NonNullable<Awaited<ReturnType<PDFDocumentProxy["getOutline"]>>>;
|
|
59
|
-
export interface PdfPageOverlay {
|
|
60
|
-
element: HTMLElement;
|
|
61
|
-
pageNumber: number;
|
|
62
|
-
viewport: PageViewport;
|
|
63
|
-
}
|
|
64
|
-
export interface PdfViewerState {
|
|
65
|
-
actualSize: () => void;
|
|
66
|
-
currentPageNumber: Ref<number>;
|
|
67
|
-
download: (filename: string) => void;
|
|
68
|
-
findNext: () => void;
|
|
69
|
-
findPrevious: () => void;
|
|
70
|
-
fitPage: () => void;
|
|
71
|
-
fitWidth: () => void;
|
|
72
|
-
followBookmark: (item: PdfOutline[number]) => void;
|
|
73
|
-
goToPage: (pageNumber: number) => void;
|
|
74
|
-
isFullscreen: Ref<boolean>;
|
|
75
|
-
isMatchCase: Ref<boolean>;
|
|
76
|
-
isNotFound: Ref<boolean>;
|
|
77
|
-
isWholeWord: Ref<boolean>;
|
|
78
|
-
isZoomInDisabled: ComputedRef<boolean>;
|
|
79
|
-
matchesCount: Ref<PdfMatchesCount>;
|
|
80
|
-
nextPage: () => void;
|
|
81
|
-
outline: ShallowRef<PdfOutline>;
|
|
82
|
-
overlays: ShallowRef<PdfPageOverlay[]>;
|
|
83
|
-
pagesCount: Ref<number>;
|
|
84
|
-
pagesRotation: Ref<number>;
|
|
85
|
-
previousPage: () => void;
|
|
86
|
-
print: () => void;
|
|
87
|
-
rotate: () => void;
|
|
88
|
-
scale: Ref<number>;
|
|
89
|
-
searchQuery: Ref<string>;
|
|
90
|
-
toggleFullscreen: () => void;
|
|
91
|
-
zoomIn: () => void;
|
|
92
|
-
zoomOut: () => void;
|
|
93
|
-
}
|
|
94
128
|
interface UsePdfViewerOptions {
|
|
95
129
|
container: MaybeRefOrGetter<HTMLDivElement | undefined>;
|
|
96
130
|
highlightTerms?: MaybeRefOrGetter<string[]>;
|
|
131
|
+
imageResourcesPath: string;
|
|
97
132
|
pdfDocument: MaybeRefOrGetter<PDFDocumentProxy | undefined>;
|
|
98
133
|
viewer: MaybeRefOrGetter<HTMLDivElement | undefined>;
|
|
99
134
|
viewerApi: MaybeRefOrGetter<PdfViewerApi | undefined>;
|
|
100
135
|
}
|
|
101
|
-
export declare const usePdfViewer: ({ container, highlightTerms, pdfDocument, viewer, viewerApi }: UsePdfViewerOptions) => PdfViewerState;
|
|
136
|
+
export declare const usePdfViewer: ({ container, highlightTerms, imageResourcesPath, pdfDocument, viewer, viewerApi }: UsePdfViewerOptions) => PdfViewerState;
|
|
102
137
|
interface UsePdfZoomGestureOptions {
|
|
103
138
|
container: MaybeRefOrGetter<HTMLDivElement | undefined>;
|
|
104
139
|
getViewer: () => PDFViewer | undefined;
|
|
105
140
|
}
|
|
106
141
|
export declare const usePdfZoomGesture: ({ container, getViewer }: UsePdfZoomGestureOptions) => void;
|
|
142
|
+
export interface PdfFreeText {
|
|
143
|
+
contents: string;
|
|
144
|
+
id: string;
|
|
145
|
+
pageNumber: number;
|
|
146
|
+
rect: [number, number, number, number];
|
|
147
|
+
}
|
|
148
|
+
export interface PdfFreeTextEntry {
|
|
149
|
+
annotationType: PdfEditorMode.FreeText;
|
|
150
|
+
color: number[];
|
|
151
|
+
deleted: false;
|
|
152
|
+
fontSize: number;
|
|
153
|
+
id: string;
|
|
154
|
+
pageIndex: number;
|
|
155
|
+
rect: [number, number, number, number];
|
|
156
|
+
rotation: 0;
|
|
157
|
+
value: string;
|
|
158
|
+
}
|
|
107
159
|
export declare const PDF_MAX_CANVAS_AREA = 16777216;
|
|
108
160
|
export declare const PDF_MIN_SCALE = .1;
|
|
109
161
|
export declare const PDF_MAX_SCALE = 10;
|
|
162
|
+
export declare const PDF_ZOOM_STEP = 1.1;
|
|
163
|
+
export declare const PDF_OPENING_SCALE = "auto";
|
|
110
164
|
export declare const PDF_ROTATION_STEP = 90;
|
|
111
165
|
export declare const PDF_FULL_TURN = 360;
|
|
112
166
|
export declare const PDF_PRINT_SCALE = 2;
|
|
113
167
|
export declare const PDF_PRINT_BATCH_SIZE = 10;
|
|
114
168
|
export declare const PDF_PRINT_CLASS = "pdf-print";
|
|
115
169
|
export declare const PDF_MIME_TYPE = "application/pdf";
|
|
170
|
+
export declare const PDF_EDITOR_KEY_PREFIX = "pdfjs_internal_editor_";
|
|
171
|
+
export declare const PDF_FREE_TEXT_FONT_SIZE = 12;
|
|
172
|
+
export declare const PDF_FREE_TEXT_COLOR: number[];
|
|
173
|
+
export declare const PDF_WATERMARK_FONT_SIZE = 48;
|
|
174
|
+
export declare const PDF_WATERMARK_COLOR: number[];
|
|
116
175
|
export declare const PDF_PRINT_STYLE: string;
|
|
117
176
|
export declare const PDF_WHEEL_ZOOM_DIVISOR = 500;
|
|
177
|
+
interface CreateFreeTextEntryParameters {
|
|
178
|
+
color: number[];
|
|
179
|
+
fontSize: number;
|
|
180
|
+
id: string;
|
|
181
|
+
pageIndex: number;
|
|
182
|
+
rect: [number, number, number, number];
|
|
183
|
+
value: string;
|
|
184
|
+
}
|
|
185
|
+
export declare const createFreeTextEntry: ({ color, fontSize, id, pageIndex, rect, value }: CreateFreeTextEntryParameters) => PdfFreeTextEntry;
|
|
118
186
|
export declare const destroyLoadingTask: (task: PDFDocumentLoadingTask) => void;
|
|
119
187
|
interface Props {
|
|
120
188
|
cssWidth: number;
|
|
@@ -123,6 +191,8 @@ interface Props {
|
|
|
123
191
|
pixelRatio: number;
|
|
124
192
|
}
|
|
125
193
|
export declare const getCanvasScale: ({ cssWidth, pageHeight, pageWidth, pixelRatio }: Props) => number;
|
|
194
|
+
export declare const getDownloadBytes: (pdfDocument: PDFDocumentProxy, watermark: string | undefined) => Promise<Uint8Array<ArrayBuffer>>;
|
|
195
|
+
export declare const getPdfPages: (pdfDocument: PDFDocumentProxy) => Promise<PDFPageProxy[]>;
|
|
126
196
|
export declare const getSourceParameters: (source: PdfSource) => Promise<{
|
|
127
197
|
data: Uint8Array;
|
|
128
198
|
} | {
|
|
@@ -136,4 +206,6 @@ export declare const getZoomedScroll: ({ point, ratio, scroll }: {
|
|
|
136
206
|
export declare const loadPdfDocument: (renderer: PdfRenderer, source: PdfSource, parameters: PdfDocumentParameters) => Promise<PDFDocumentLoadingTask>;
|
|
137
207
|
export declare const renderPrintImage: (pdfDocument: PDFDocumentProxy, pageNumber: number) => Promise<HTMLImageElement>;
|
|
138
208
|
export declare const renderPrintSheets: (pdfDocument: PDFDocumentProxy, checkIsCancelled: () => boolean) => Promise<HTMLDivElement | undefined>;
|
|
209
|
+
export declare const readXfdf: (xfdf: string, takenIds: ReadonlySet<string>) => Result<PdfFreeText[], string>;
|
|
210
|
+
export declare const writeXfdf: (freeTexts: PdfFreeText[]) => string;
|
|
139
211
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,181 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Result, err, ok } from "neverthrow";
|
|
2
|
+
import { downloadFile, getResultAsync, getSynchronizedFunction, noop, withFinalizerAsync } from "@esposter/shared";
|
|
2
3
|
import { computed, effectScope, onScopeDispose, ref, shallowRef, toValue, watch } from "vue";
|
|
4
|
+
// src/models/PdfEditorMode.ts
|
|
5
|
+
let PdfEditorMode = /* @__PURE__ */ function(PdfEditorMode) {
|
|
6
|
+
PdfEditorMode[PdfEditorMode["None"] = 0] = "None";
|
|
7
|
+
PdfEditorMode[PdfEditorMode["FreeText"] = 3] = "FreeText";
|
|
8
|
+
PdfEditorMode[PdfEditorMode["Highlight"] = 9] = "Highlight";
|
|
9
|
+
PdfEditorMode[PdfEditorMode["Stamp"] = 13] = "Stamp";
|
|
10
|
+
PdfEditorMode[PdfEditorMode["Ink"] = 15] = "Ink";
|
|
11
|
+
return PdfEditorMode;
|
|
12
|
+
}({});
|
|
13
|
+
// src/services/constants.ts
|
|
14
|
+
const PDF_MAX_CANVAS_AREA = 16777216;
|
|
15
|
+
const PDF_MIN_SCALE = .1;
|
|
16
|
+
const PDF_MAX_SCALE = 10;
|
|
17
|
+
const PDF_ZOOM_STEP = 1.1;
|
|
18
|
+
const PDF_OPENING_SCALE = "auto";
|
|
19
|
+
const PDF_ROTATION_STEP = 90;
|
|
20
|
+
const PDF_FULL_TURN = 360;
|
|
21
|
+
const PDF_PRINT_SCALE = 2;
|
|
22
|
+
const PDF_PRINT_BATCH_SIZE = 10;
|
|
23
|
+
const PDF_PRINT_CLASS = "pdf-print";
|
|
24
|
+
const PDF_MIME_TYPE = "application/pdf";
|
|
25
|
+
const PDF_EDITOR_KEY_PREFIX = "pdfjs_internal_editor_";
|
|
26
|
+
const PDF_FREE_TEXT_FONT_SIZE = 12;
|
|
27
|
+
const PDF_FREE_TEXT_COLOR = [
|
|
28
|
+
0,
|
|
29
|
+
0,
|
|
30
|
+
0
|
|
31
|
+
];
|
|
32
|
+
const PDF_WATERMARK_FONT_SIZE = 48;
|
|
33
|
+
const PDF_WATERMARK_COLOR = [
|
|
34
|
+
128,
|
|
35
|
+
128,
|
|
36
|
+
128
|
|
37
|
+
];
|
|
38
|
+
const PDF_PRINT_STYLE = `@page { margin: 3mm } .${PDF_PRINT_CLASS} { display: none } .${PDF_PRINT_CLASS} img { display: block; page-break-after: always; width: 100% } @media print { .${PDF_PRINT_CLASS} { display: block } body > :not(.${PDF_PRINT_CLASS}) { display: none !important } }`;
|
|
39
|
+
const PDF_WHEEL_ZOOM_DIVISOR = 500;
|
|
40
|
+
// src/services/createFreeTextEntry.ts
|
|
41
|
+
const createFreeTextEntry = ({ color, fontSize, id, pageIndex, rect, value }) => ({
|
|
42
|
+
annotationType: 3,
|
|
43
|
+
color,
|
|
44
|
+
deleted: false,
|
|
45
|
+
fontSize,
|
|
46
|
+
id,
|
|
47
|
+
pageIndex,
|
|
48
|
+
rect,
|
|
49
|
+
rotation: 0,
|
|
50
|
+
value
|
|
51
|
+
});
|
|
52
|
+
// src/services/getPdfPages.ts
|
|
53
|
+
const getPdfPages = (pdfDocument) => Promise.all(Array.from({ length: pdfDocument.numPages }, (_value, pageIndex) => pdfDocument.getPage(pageIndex + 1)));
|
|
54
|
+
// src/xfdf/readXfdf.ts
|
|
55
|
+
const parseCoordinate = (text) => text.trim() === "" ? NaN : Number(text);
|
|
56
|
+
const checkRectangle = (coordinates) => coordinates.length === 4 && coordinates.every((coordinate) => Number.isFinite(coordinate));
|
|
57
|
+
const readFreeText = (element, index, usedIds) => {
|
|
58
|
+
const page = element.getAttribute("page") ?? "";
|
|
59
|
+
const coordinates = (element.getAttribute("rect") ?? "").split(",").map((text) => parseCoordinate(text));
|
|
60
|
+
if (!/^\d+$/u.test(page)) return err(`Free text ${index + 1} has no valid page`);
|
|
61
|
+
if (!checkRectangle(coordinates)) return err(`Free text ${index + 1} has no valid rect`);
|
|
62
|
+
const contents = element.querySelector("contents")?.textContent ?? "";
|
|
63
|
+
if (contents.trim() === "") return err(`Free text ${index + 1} has no contents`);
|
|
64
|
+
const name = element.getAttribute("name") ?? "";
|
|
65
|
+
const id = name === "" || usedIds.has(name) ? crypto.randomUUID() : name;
|
|
66
|
+
usedIds.add(id);
|
|
67
|
+
return ok({
|
|
68
|
+
contents,
|
|
69
|
+
id,
|
|
70
|
+
pageNumber: Number(page) + 1,
|
|
71
|
+
rect: coordinates
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
const readXfdf = (xfdf, takenIds) => {
|
|
75
|
+
const xfdfDocument = new DOMParser().parseFromString(xfdf, "application/xml");
|
|
76
|
+
if (xfdfDocument.querySelector("parsererror")) return err("The XFDF is not well-formed XML");
|
|
77
|
+
const usedIds = new Set(takenIds);
|
|
78
|
+
return Result.combine(Array.from(xfdfDocument.querySelectorAll("freetext"), (element, index) => readFreeText(element, index, usedIds)));
|
|
79
|
+
};
|
|
80
|
+
// src/xfdf/writeXfdf.ts
|
|
81
|
+
const escapeXml = (text) => text.replaceAll(/[\u0000-\u0008\u000B\u000C\u000E-\u001F]/gu, "").replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """);
|
|
82
|
+
const writeXfdf = (freeTexts) => {
|
|
83
|
+
return `<?xml version="1.0" encoding="UTF-8"?>\n<xfdf xmlns="http://ns.adobe.com/xfdf/" xml:space="preserve"><annots>${freeTexts.map(({ contents, id, pageNumber, rect }) => `<freetext name="${escapeXml(id)}" page="${pageNumber - 1}" rect="${rect.join(",")}"><contents>${escapeXml(contents)}</contents></freetext>`).join("")}</annots></xfdf>`;
|
|
84
|
+
};
|
|
85
|
+
// src/composables/usePdfAnnotations.ts
|
|
86
|
+
const readComments = async (loaded) => {
|
|
87
|
+
const pages = await getPdfPages(loaded);
|
|
88
|
+
return (await Promise.all(pages.map(async (page, pageIndex) => {
|
|
89
|
+
const annotations = await page.getAnnotations();
|
|
90
|
+
page.cleanup();
|
|
91
|
+
return annotations.filter(({ subtype }) => subtype === "Text").map(({ contentsObj }) => ({
|
|
92
|
+
contents: contentsObj?.str ?? "",
|
|
93
|
+
pageNumber: pageIndex + 1
|
|
94
|
+
}));
|
|
95
|
+
}))).flat();
|
|
96
|
+
};
|
|
97
|
+
const setEditorHook = (loaded, onEditor) => {
|
|
98
|
+
Object.assign(loaded.annotationStorage, { onAnnotationEditor: onEditor });
|
|
99
|
+
};
|
|
100
|
+
const readFreeTexts = (loaded) => Array.from(loaded.annotationStorage, ([key, value]) => {
|
|
101
|
+
const data = typeof value.serialize === "function" ? value.serialize() : value;
|
|
102
|
+
if (data?.annotationType !== 3 || !data.rect) return [];
|
|
103
|
+
const id = data.id ?? key.slice(22);
|
|
104
|
+
return [{
|
|
105
|
+
contents: data.value ?? "",
|
|
106
|
+
id,
|
|
107
|
+
pageNumber: (data.pageIndex ?? 0) + 1,
|
|
108
|
+
rect: data.rect
|
|
109
|
+
}];
|
|
110
|
+
}).flat();
|
|
111
|
+
const storeFreeTexts = (loaded, freeTexts) => {
|
|
112
|
+
for (const { contents, id, pageNumber, rect } of freeTexts) loaded.annotationStorage.setValue(`${PDF_EDITOR_KEY_PREFIX}xfdf${id}`, {
|
|
113
|
+
...createFreeTextEntry({
|
|
114
|
+
color: PDF_FREE_TEXT_COLOR,
|
|
115
|
+
fontSize: 12,
|
|
116
|
+
id,
|
|
117
|
+
pageIndex: pageNumber - 1,
|
|
118
|
+
rect,
|
|
119
|
+
value: contents
|
|
120
|
+
}),
|
|
121
|
+
isClone: true,
|
|
122
|
+
isCopy: true
|
|
123
|
+
});
|
|
124
|
+
};
|
|
125
|
+
const usePdfAnnotations = ({ isCommentsWanted = false, onChange, pdfDocument, viewer }) => {
|
|
126
|
+
const comments = shallowRef();
|
|
127
|
+
const loadComments = getSynchronizedFunction(async (loaded) => {
|
|
128
|
+
await getResultAsync(async () => {
|
|
129
|
+
const loadedComments = await readComments(loaded);
|
|
130
|
+
if (loaded === toValue(pdfDocument)) comments.value = loadedComments;
|
|
131
|
+
}).match(noop, (error) => {
|
|
132
|
+
console.error(error);
|
|
133
|
+
if (loaded === toValue(pdfDocument)) comments.value = [];
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
const setEditorMode = (mode) => {
|
|
137
|
+
viewer.editorMode.value = mode;
|
|
138
|
+
};
|
|
139
|
+
const exportXfdf = () => {
|
|
140
|
+
const loaded = toValue(pdfDocument);
|
|
141
|
+
return writeXfdf(loaded ? readFreeTexts(loaded) : []);
|
|
142
|
+
};
|
|
143
|
+
const importXfdf = (xfdf) => {
|
|
144
|
+
const loaded = toValue(pdfDocument);
|
|
145
|
+
if (!loaded) return err("No document is open to import into");
|
|
146
|
+
return readXfdf(xfdf, new Set(readFreeTexts(loaded).map(({ id }) => id))).andThen((freeTexts) => {
|
|
147
|
+
const stray = freeTexts.find(({ pageNumber }) => pageNumber > loaded.numPages);
|
|
148
|
+
if (stray) return err(`The XFDF has a free text on page ${stray.pageNumber}, and the document ends at page ${loaded.numPages}`);
|
|
149
|
+
storeFreeTexts(loaded, freeTexts);
|
|
150
|
+
viewer.drawStoredEditors();
|
|
151
|
+
return ok(void 0);
|
|
152
|
+
});
|
|
153
|
+
};
|
|
154
|
+
watch(() => toValue(pdfDocument), (loaded) => {
|
|
155
|
+
comments.value = void 0;
|
|
156
|
+
if (!loaded) return;
|
|
157
|
+
setEditorHook(loaded, (kind) => {
|
|
158
|
+
if (kind === null) return;
|
|
159
|
+
const drawn = [...loaded.annotationStorage].at(-1)?.[1];
|
|
160
|
+
if (drawn) onChange?.({
|
|
161
|
+
kind,
|
|
162
|
+
pageNumber: drawn.pageIndex + 1
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
}, { immediate: true });
|
|
166
|
+
let commentsDocument;
|
|
167
|
+
watch([() => toValue(pdfDocument), () => toValue(isCommentsWanted)], ([loaded, isWanted]) => {
|
|
168
|
+
if (!loaded || !isWanted || loaded === commentsDocument) return;
|
|
169
|
+
commentsDocument = loaded;
|
|
170
|
+
loadComments(loaded);
|
|
171
|
+
}, { immediate: true });
|
|
172
|
+
return {
|
|
173
|
+
comments,
|
|
174
|
+
exportXfdf,
|
|
175
|
+
importXfdf,
|
|
176
|
+
setEditorMode
|
|
177
|
+
};
|
|
178
|
+
};
|
|
3
179
|
// src/services/destroyLoadingTask.ts
|
|
4
180
|
const destroyLoadingTask = getSynchronizedFunction(async (task) => {
|
|
5
181
|
await task.destroy();
|
|
@@ -98,18 +274,6 @@ const usePdfDocument = ({ parameters = {}, renderer, source }) => {
|
|
|
98
274
|
submitPassword
|
|
99
275
|
};
|
|
100
276
|
};
|
|
101
|
-
// src/services/constants.ts
|
|
102
|
-
const PDF_MAX_CANVAS_AREA = 16777216;
|
|
103
|
-
const PDF_MIN_SCALE = .1;
|
|
104
|
-
const PDF_MAX_SCALE = 10;
|
|
105
|
-
const PDF_ROTATION_STEP = 90;
|
|
106
|
-
const PDF_FULL_TURN = 360;
|
|
107
|
-
const PDF_PRINT_SCALE = 2;
|
|
108
|
-
const PDF_PRINT_BATCH_SIZE = 10;
|
|
109
|
-
const PDF_PRINT_CLASS = "pdf-print";
|
|
110
|
-
const PDF_MIME_TYPE = "application/pdf";
|
|
111
|
-
const PDF_PRINT_STYLE = `@page { margin: 3mm } .${PDF_PRINT_CLASS} { display: none } .${PDF_PRINT_CLASS} img { display: block; page-break-after: always; width: 100% } @media print { .${PDF_PRINT_CLASS} { display: block } body > :not(.${PDF_PRINT_CLASS}) { display: none !important } }`;
|
|
112
|
-
const PDF_WHEEL_ZOOM_DIVISOR = 500;
|
|
113
277
|
// src/services/getCanvasScale.ts
|
|
114
278
|
const getCanvasScale = ({ cssWidth, pageHeight, pageWidth, pixelRatio }) => {
|
|
115
279
|
const scale = cssWidth / pageWidth * pixelRatio;
|
|
@@ -117,7 +281,7 @@ const getCanvasScale = ({ cssWidth, pageHeight, pageWidth, pixelRatio }) => {
|
|
|
117
281
|
return Math.sqrt(PDF_MAX_CANVAS_AREA / (pageWidth * pageHeight));
|
|
118
282
|
};
|
|
119
283
|
// src/composables/usePdfPage.ts
|
|
120
|
-
const usePdfPage = ({ canvas, isVisible = true, pageNumber, pdfDocument }) => {
|
|
284
|
+
const usePdfPage = ({ canvas, isActive = true, isVisible = true, pageNumber, pagesRotation = 0, pdfDocument }) => {
|
|
121
285
|
let cssWidth = 0;
|
|
122
286
|
let loadGeneration = 0;
|
|
123
287
|
let page;
|
|
@@ -135,13 +299,20 @@ const usePdfPage = ({ canvas, isVisible = true, pageNumber, pdfDocument }) => {
|
|
|
135
299
|
const currentCanvas = toValue(canvas);
|
|
136
300
|
if (!page || !currentCanvas || cssWidth === 0 || !toValue(isVisible)) return;
|
|
137
301
|
cancelRender();
|
|
138
|
-
const
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
302
|
+
const rotation = (page.rotate + toValue(pagesRotation)) % 360;
|
|
303
|
+
const { height, width } = page.getViewport({
|
|
304
|
+
rotation,
|
|
305
|
+
scale: 1
|
|
306
|
+
});
|
|
307
|
+
const viewport = page.getViewport({
|
|
308
|
+
rotation,
|
|
309
|
+
scale: getCanvasScale({
|
|
310
|
+
cssWidth,
|
|
311
|
+
pageHeight: height,
|
|
312
|
+
pageWidth: width,
|
|
313
|
+
pixelRatio: window.devicePixelRatio
|
|
314
|
+
})
|
|
315
|
+
});
|
|
145
316
|
currentCanvas.width = viewport.width;
|
|
146
317
|
currentCanvas.height = viewport.height;
|
|
147
318
|
const task = page.render({
|
|
@@ -151,11 +322,19 @@ const usePdfPage = ({ canvas, isVisible = true, pageNumber, pdfDocument }) => {
|
|
|
151
322
|
renderTask = task;
|
|
152
323
|
awaitRender(task);
|
|
153
324
|
};
|
|
154
|
-
|
|
325
|
+
const isWanted = ref(false);
|
|
326
|
+
watch(() => toValue(isActive), (active) => {
|
|
327
|
+
if (active) isWanted.value = true;
|
|
328
|
+
}, { immediate: true });
|
|
329
|
+
watch([
|
|
330
|
+
() => toValue(pdfDocument),
|
|
331
|
+
() => toValue(pageNumber),
|
|
332
|
+
isWanted
|
|
333
|
+
], getSynchronizedFunction(async ([currentDocument, currentPageNumber, currentIsWanted]) => {
|
|
155
334
|
const generation = ++loadGeneration;
|
|
156
335
|
cancelRender();
|
|
157
336
|
page = void 0;
|
|
158
|
-
if (!currentDocument) return;
|
|
337
|
+
if (!currentDocument || !currentIsWanted) return;
|
|
159
338
|
await getResultAsync(async () => {
|
|
160
339
|
const loadedPage = await currentDocument.getPage(currentPageNumber);
|
|
161
340
|
if (generation !== loadGeneration) return;
|
|
@@ -185,18 +364,22 @@ const usePdfPage = ({ canvas, isVisible = true, pageNumber, pdfDocument }) => {
|
|
|
185
364
|
watch(() => toValue(isVisible), (visible) => {
|
|
186
365
|
if (visible) drawPage();
|
|
187
366
|
});
|
|
367
|
+
watch(() => toValue(pagesRotation), () => {
|
|
368
|
+
drawPage();
|
|
369
|
+
});
|
|
188
370
|
onScopeDispose(() => {
|
|
189
371
|
loadGeneration++;
|
|
190
372
|
cancelRender();
|
|
373
|
+
page?.cleanup();
|
|
191
374
|
});
|
|
192
375
|
};
|
|
193
376
|
// src/composables/usePdfThumbnails.ts
|
|
194
|
-
const usePdfThumbnails = ({ currentPageNumber, pdfDocument, root }) => {
|
|
377
|
+
const usePdfThumbnails = ({ currentPageNumber, pagesRotation, pdfDocument, root }) => {
|
|
195
378
|
const thumbnails = shallowRef([]);
|
|
196
379
|
const rows = /* @__PURE__ */ new Map();
|
|
197
380
|
let observer;
|
|
198
381
|
let scope;
|
|
199
|
-
const createThumbnail = (pageNumber, currentDocument
|
|
382
|
+
const createThumbnail = (pageNumber, currentDocument) => {
|
|
200
383
|
const canvas = shallowRef();
|
|
201
384
|
const isVisible = ref(false);
|
|
202
385
|
const thumbnail = {
|
|
@@ -207,41 +390,45 @@ const usePdfThumbnails = ({ currentPageNumber, pdfDocument, root }) => {
|
|
|
207
390
|
};
|
|
208
391
|
usePdfPage({
|
|
209
392
|
canvas: () => canvas.value,
|
|
393
|
+
isActive: () => isVisible.value,
|
|
210
394
|
isVisible: () => isVisible.value,
|
|
211
395
|
pageNumber,
|
|
396
|
+
pagesRotation: () => toValue(pagesRotation),
|
|
212
397
|
pdfDocument: currentDocument
|
|
213
398
|
});
|
|
214
399
|
watch(canvas, (element, _previousElement, onCleanup) => {
|
|
215
400
|
if (!element) return;
|
|
216
401
|
rows.set(element, thumbnail);
|
|
217
|
-
|
|
402
|
+
observer?.observe(element);
|
|
218
403
|
onCleanup(() => {
|
|
219
|
-
|
|
404
|
+
observer?.unobserve(element);
|
|
220
405
|
rows.delete(element);
|
|
221
406
|
});
|
|
222
407
|
});
|
|
223
408
|
return thumbnail;
|
|
224
409
|
};
|
|
225
|
-
|
|
226
|
-
scope?.stop();
|
|
410
|
+
watch(() => toValue(root), (rootElement) => {
|
|
227
411
|
observer?.disconnect();
|
|
228
|
-
|
|
229
|
-
watch(() => toValue(pdfDocument), (loaded) => {
|
|
230
|
-
disposeThumbnails();
|
|
231
|
-
rows.clear();
|
|
232
|
-
thumbnails.value = [];
|
|
233
|
-
if (!loaded) return;
|
|
234
|
-
const currentObserver = new IntersectionObserver((entries) => {
|
|
412
|
+
observer = new IntersectionObserver((entries) => {
|
|
235
413
|
for (const entry of entries) {
|
|
236
414
|
const row = rows.get(entry.target);
|
|
237
415
|
if (row) row.isVisible.value = entry.isIntersecting;
|
|
238
416
|
}
|
|
239
|
-
}, { root:
|
|
240
|
-
|
|
417
|
+
}, { root: rootElement ?? null });
|
|
418
|
+
for (const element of rows.keys()) observer.observe(element);
|
|
419
|
+
}, { immediate: true });
|
|
420
|
+
watch(() => toValue(pdfDocument), (loaded) => {
|
|
421
|
+
scope?.stop();
|
|
422
|
+
rows.clear();
|
|
423
|
+
thumbnails.value = [];
|
|
424
|
+
if (!loaded) return;
|
|
241
425
|
scope = effectScope();
|
|
242
|
-
thumbnails.value = scope.run(() => Array.from({ length: loaded.numPages }, (_value, index) => createThumbnail(index + 1, loaded
|
|
426
|
+
thumbnails.value = scope.run(() => Array.from({ length: loaded.numPages }, (_value, index) => createThumbnail(index + 1, loaded))) ?? [];
|
|
243
427
|
}, { immediate: true });
|
|
244
|
-
onScopeDispose(
|
|
428
|
+
onScopeDispose(() => {
|
|
429
|
+
scope?.stop();
|
|
430
|
+
observer?.disconnect();
|
|
431
|
+
});
|
|
245
432
|
return thumbnails;
|
|
246
433
|
};
|
|
247
434
|
// src/services/getZoomedScroll.ts
|
|
@@ -342,6 +529,39 @@ const usePdfZoomGesture = ({ container, getViewer }) => {
|
|
|
342
529
|
abortController.abort();
|
|
343
530
|
});
|
|
344
531
|
};
|
|
532
|
+
// src/services/getDownloadBytes.ts
|
|
533
|
+
const addWatermark = async (pdfDocument, watermark) => {
|
|
534
|
+
const { annotationStorage } = pdfDocument;
|
|
535
|
+
return (await getPdfPages(pdfDocument)).map((page, pageIndex) => {
|
|
536
|
+
const [left = 0, bottom = 0, right = 0, top = 0] = page.view;
|
|
537
|
+
page.cleanup();
|
|
538
|
+
const middle = (bottom + top) / 2;
|
|
539
|
+
const id = `watermark${pageIndex}`;
|
|
540
|
+
const key = `${PDF_EDITOR_KEY_PREFIX}${id}`;
|
|
541
|
+
annotationStorage.setValue(key, createFreeTextEntry({
|
|
542
|
+
color: PDF_WATERMARK_COLOR,
|
|
543
|
+
fontSize: 48,
|
|
544
|
+
id,
|
|
545
|
+
pageIndex,
|
|
546
|
+
rect: [
|
|
547
|
+
left,
|
|
548
|
+
middle - 24,
|
|
549
|
+
right,
|
|
550
|
+
middle + 24
|
|
551
|
+
],
|
|
552
|
+
value: watermark
|
|
553
|
+
}));
|
|
554
|
+
return key;
|
|
555
|
+
});
|
|
556
|
+
};
|
|
557
|
+
const getDownloadBytes = async (pdfDocument, watermark) => {
|
|
558
|
+
const { annotationStorage } = pdfDocument;
|
|
559
|
+
if (annotationStorage.size === 0 && !watermark) return new Uint8Array(await pdfDocument.getData());
|
|
560
|
+
const watermarkKeys = watermark ? await addWatermark(pdfDocument, watermark) : [];
|
|
561
|
+
return withFinalizerAsync(() => pdfDocument.saveDocument(), () => {
|
|
562
|
+
for (const key of watermarkKeys) annotationStorage.remove(key);
|
|
563
|
+
});
|
|
564
|
+
};
|
|
345
565
|
// src/services/renderPrintImage.ts
|
|
346
566
|
const renderPrintImage = async (pdfDocument, pageNumber) => {
|
|
347
567
|
const page = await pdfDocument.getPage(pageNumber);
|
|
@@ -382,9 +602,9 @@ const renderPrintSheets = async (pdfDocument, checkIsCancelled) => {
|
|
|
382
602
|
return checkIsCancelled() ? void 0 : printRoot;
|
|
383
603
|
};
|
|
384
604
|
// src/composables/usePdfViewer.ts
|
|
385
|
-
const usePdfViewer = ({ container, highlightTerms = [], pdfDocument, viewer, viewerApi }) => {
|
|
605
|
+
const usePdfViewer = ({ container, highlightTerms = [], imageResourcesPath, pdfDocument, viewer, viewerApi }) => {
|
|
386
606
|
const currentPageNumber = ref(0);
|
|
387
|
-
const
|
|
607
|
+
const editorMode = ref(0);
|
|
388
608
|
const isMatchCase = ref(false);
|
|
389
609
|
const isNotFound = ref(false);
|
|
390
610
|
const isWholeWord = ref(false);
|
|
@@ -392,23 +612,25 @@ const usePdfViewer = ({ container, highlightTerms = [], pdfDocument, viewer, vie
|
|
|
392
612
|
current: 0,
|
|
393
613
|
total: 0
|
|
394
614
|
});
|
|
395
|
-
const outline = shallowRef(
|
|
615
|
+
const outline = shallowRef();
|
|
396
616
|
const overlays = shallowRef([]);
|
|
397
617
|
const pagesCount = ref(0);
|
|
398
618
|
const pagesRotation = ref(0);
|
|
399
619
|
const scale = ref(0);
|
|
400
620
|
const searchQuery = ref("");
|
|
621
|
+
const documentGeneration = ref(0);
|
|
401
622
|
const isZoomInDisabled = computed(() => scale.value >= 10);
|
|
623
|
+
const isZoomOutDisabled = computed(() => scale.value <= PDF_MIN_SCALE);
|
|
402
624
|
let abortController;
|
|
403
|
-
let containerElement;
|
|
404
625
|
let eventBus;
|
|
405
626
|
let linkService;
|
|
406
627
|
let pdfViewer;
|
|
407
628
|
let resizeObserver;
|
|
408
629
|
let printRoot;
|
|
409
630
|
let printGeneration = 0;
|
|
410
|
-
|
|
411
|
-
|
|
631
|
+
let editorManager;
|
|
632
|
+
const applyEditorMode = () => {
|
|
633
|
+
if (pdfViewer && editorManager) pdfViewer.annotationEditorMode = { mode: editorMode.value };
|
|
412
634
|
};
|
|
413
635
|
const createViewer = (currentViewerApi, containerValue, viewerElement) => {
|
|
414
636
|
const { EventBus, FindState, LinkTarget, PDFFindController, PDFLinkService, PDFViewer } = currentViewerApi;
|
|
@@ -428,18 +650,22 @@ const usePdfViewer = ({ container, highlightTerms = [], pdfDocument, viewer, vie
|
|
|
428
650
|
container: containerValue,
|
|
429
651
|
eventBus: bus,
|
|
430
652
|
findController: controller,
|
|
653
|
+
imageResourcesPath,
|
|
431
654
|
linkService: links,
|
|
432
655
|
maxCanvasPixels: PDF_MAX_CANVAS_AREA,
|
|
433
656
|
viewer: viewerElement
|
|
434
657
|
});
|
|
435
658
|
links.setViewer(instance);
|
|
436
659
|
abortController = viewerAbortController;
|
|
437
|
-
containerElement = containerValue;
|
|
438
660
|
eventBus = bus;
|
|
439
661
|
linkService = links;
|
|
440
662
|
pdfViewer = instance;
|
|
441
663
|
bus.on("pagesinit", () => {
|
|
442
|
-
instance.currentScaleValue =
|
|
664
|
+
instance.currentScaleValue = PDF_OPENING_SCALE;
|
|
665
|
+
});
|
|
666
|
+
bus.on("annotationeditoruimanager", (event) => {
|
|
667
|
+
editorManager = event.uiManager;
|
|
668
|
+
applyEditorMode();
|
|
443
669
|
});
|
|
444
670
|
bus.on("pagechanging", (event) => {
|
|
445
671
|
currentPageNumber.value = event.pageNumber;
|
|
@@ -471,9 +697,6 @@ const usePdfViewer = ({ container, highlightTerms = [], pdfDocument, viewer, vie
|
|
|
471
697
|
if (currentScaleValue) pdfViewer.currentScaleValue = currentScaleValue;
|
|
472
698
|
});
|
|
473
699
|
resizeObserver.observe(containerValue);
|
|
474
|
-
window.document.addEventListener("fullscreenchange", () => {
|
|
475
|
-
onFullscreenChange();
|
|
476
|
-
}, { signal });
|
|
477
700
|
window.addEventListener("afterprint", () => {
|
|
478
701
|
removePrint();
|
|
479
702
|
}, { signal });
|
|
@@ -483,17 +706,22 @@ const usePdfViewer = ({ container, highlightTerms = [], pdfDocument, viewer, vie
|
|
|
483
706
|
await getResultAsync(async () => {
|
|
484
707
|
const items = await loaded.getOutline();
|
|
485
708
|
if (loaded === toValue(pdfDocument)) outline.value = items ?? [];
|
|
486
|
-
}).match(noop,
|
|
709
|
+
}).match(noop, (error) => {
|
|
710
|
+
console.error(error);
|
|
711
|
+
if (loaded === toValue(pdfDocument)) outline.value = [];
|
|
712
|
+
});
|
|
487
713
|
});
|
|
488
714
|
const applyDocument = (loaded) => {
|
|
489
715
|
if (!pdfViewer || !linkService) return;
|
|
490
716
|
pagesCount.value = loaded?.numPages ?? 0;
|
|
491
717
|
overlays.value = [];
|
|
492
|
-
outline.value =
|
|
718
|
+
outline.value = void 0;
|
|
719
|
+
editorManager = void 0;
|
|
493
720
|
pdfViewer.setDocument(loaded ?? null);
|
|
494
721
|
linkService.setDocument(loaded ?? null);
|
|
495
722
|
currentPageNumber.value = 1;
|
|
496
723
|
pagesRotation.value = 0;
|
|
724
|
+
searchQuery.value = "";
|
|
497
725
|
matchesCount.value = {
|
|
498
726
|
current: 0,
|
|
499
727
|
total: 0
|
|
@@ -501,8 +729,8 @@ const usePdfViewer = ({ container, highlightTerms = [], pdfDocument, viewer, vie
|
|
|
501
729
|
isNotFound.value = false;
|
|
502
730
|
if (!loaded) return;
|
|
503
731
|
loadOutline(loaded);
|
|
504
|
-
|
|
505
|
-
|
|
732
|
+
searchQuery.value = toValue(highlightTerms).join(" ");
|
|
733
|
+
documentGeneration.value++;
|
|
506
734
|
};
|
|
507
735
|
const findText = ({ isPrevious = false, query, type = "" }) => {
|
|
508
736
|
eventBus?.dispatch("find", {
|
|
@@ -515,14 +743,24 @@ const usePdfViewer = ({ container, highlightTerms = [], pdfDocument, viewer, vie
|
|
|
515
743
|
type
|
|
516
744
|
});
|
|
517
745
|
};
|
|
746
|
+
watch([
|
|
747
|
+
searchQuery,
|
|
748
|
+
isMatchCase,
|
|
749
|
+
isWholeWord,
|
|
750
|
+
documentGeneration
|
|
751
|
+
], () => {
|
|
752
|
+
if (toValue(pdfDocument)) findText({ query: searchQuery.value });
|
|
753
|
+
});
|
|
518
754
|
const removePrint = () => {
|
|
519
755
|
printGeneration++;
|
|
520
756
|
printRoot?.remove();
|
|
521
757
|
printRoot = void 0;
|
|
522
758
|
};
|
|
523
|
-
const followBookmark = (item) => {
|
|
524
|
-
|
|
525
|
-
|
|
759
|
+
const followBookmark = getSynchronizedFunction(async (item) => {
|
|
760
|
+
await getResultAsync(async () => {
|
|
761
|
+
if (item.dest) await linkService?.goToDestination(item.dest);
|
|
762
|
+
}).match(noop, console.error);
|
|
763
|
+
});
|
|
526
764
|
const goToPage = (pageNumber) => {
|
|
527
765
|
if (pdfViewer && Number.isInteger(pageNumber) && pageNumber >= 1 && pageNumber <= pagesCount.value) pdfViewer.currentPageNumber = pageNumber;
|
|
528
766
|
};
|
|
@@ -532,11 +770,17 @@ const usePdfViewer = ({ container, highlightTerms = [], pdfDocument, viewer, vie
|
|
|
532
770
|
const nextPage = () => {
|
|
533
771
|
pdfViewer?.nextPage();
|
|
534
772
|
};
|
|
773
|
+
const zoomTo = (nextScale) => {
|
|
774
|
+
if (pdfViewer) pdfViewer.currentScale = Math.min(Math.max(nextScale, PDF_MIN_SCALE), 10);
|
|
775
|
+
};
|
|
535
776
|
const zoomIn = () => {
|
|
536
|
-
pdfViewer
|
|
777
|
+
if (pdfViewer && pdfViewer.currentScale > 0) zoomTo(pdfViewer.currentScale * PDF_ZOOM_STEP);
|
|
537
778
|
};
|
|
538
779
|
const zoomOut = () => {
|
|
539
|
-
pdfViewer
|
|
780
|
+
if (pdfViewer && pdfViewer.currentScale > 0) zoomTo(pdfViewer.currentScale / PDF_ZOOM_STEP);
|
|
781
|
+
};
|
|
782
|
+
const resetZoom = () => {
|
|
783
|
+
if (pdfViewer) pdfViewer.currentScaleValue = PDF_OPENING_SCALE;
|
|
540
784
|
};
|
|
541
785
|
const fitWidth = () => {
|
|
542
786
|
if (pdfViewer) pdfViewer.currentScaleValue = "page-width";
|
|
@@ -547,8 +791,8 @@ const usePdfViewer = ({ container, highlightTerms = [], pdfDocument, viewer, vie
|
|
|
547
791
|
const actualSize = () => {
|
|
548
792
|
if (pdfViewer) pdfViewer.currentScaleValue = "page-actual";
|
|
549
793
|
};
|
|
550
|
-
const rotate = () => {
|
|
551
|
-
if (pdfViewer) pdfViewer.pagesRotation = (pdfViewer.pagesRotation +
|
|
794
|
+
const rotate = (degrees) => {
|
|
795
|
+
if (pdfViewer) pdfViewer.pagesRotation = ((pdfViewer.pagesRotation + degrees) % 360 + 360) % 360;
|
|
552
796
|
};
|
|
553
797
|
const findNext = () => {
|
|
554
798
|
findText({
|
|
@@ -563,19 +807,11 @@ const usePdfViewer = ({ container, highlightTerms = [], pdfDocument, viewer, vie
|
|
|
563
807
|
type: "again"
|
|
564
808
|
});
|
|
565
809
|
};
|
|
566
|
-
const
|
|
567
|
-
const element = containerElement;
|
|
568
|
-
if (!element) return;
|
|
569
|
-
await getResultAsync(async () => {
|
|
570
|
-
if (window.document.fullscreenElement) await window.document.exitFullscreen();
|
|
571
|
-
else await element.requestFullscreen();
|
|
572
|
-
}).match(noop, console.error);
|
|
573
|
-
});
|
|
574
|
-
const download = getSynchronizedFunction(async (filename) => {
|
|
810
|
+
const download = getSynchronizedFunction(async (filename, watermark) => {
|
|
575
811
|
const downloaded = toValue(pdfDocument);
|
|
576
812
|
if (!downloaded) return;
|
|
577
813
|
await getResultAsync(async () => {
|
|
578
|
-
downloadFile(filename,
|
|
814
|
+
downloadFile(filename, await getDownloadBytes(downloaded, watermark), PDF_MIME_TYPE);
|
|
579
815
|
}).match(noop, console.error);
|
|
580
816
|
});
|
|
581
817
|
const print = getSynchronizedFunction(async () => {
|
|
@@ -591,6 +827,18 @@ const usePdfViewer = ({ container, highlightTerms = [], pdfDocument, viewer, vie
|
|
|
591
827
|
window.print();
|
|
592
828
|
}).match(noop, console.error);
|
|
593
829
|
});
|
|
830
|
+
const drawStoredEditors = getSynchronizedFunction(async () => {
|
|
831
|
+
const manager = editorManager;
|
|
832
|
+
if (!manager) return;
|
|
833
|
+
await getResultAsync(async () => {
|
|
834
|
+
await Promise.all(Array.from({ length: pagesCount.value }, async (_value, pageIndex) => {
|
|
835
|
+
const layer = manager.getLayer(pageIndex);
|
|
836
|
+
if (!layer) return;
|
|
837
|
+
await manager.findClonesForPage(layer);
|
|
838
|
+
layer.div.hidden = layer.isEmpty;
|
|
839
|
+
}));
|
|
840
|
+
}).match(noop, console.error);
|
|
841
|
+
});
|
|
594
842
|
usePdfZoomGesture({
|
|
595
843
|
container,
|
|
596
844
|
getViewer: () => pdfViewer
|
|
@@ -606,13 +854,7 @@ const usePdfViewer = ({ container, highlightTerms = [], pdfDocument, viewer, vie
|
|
|
606
854
|
watch(() => toValue(pdfDocument), (loaded) => {
|
|
607
855
|
applyDocument(loaded);
|
|
608
856
|
});
|
|
609
|
-
watch(
|
|
610
|
-
searchQuery,
|
|
611
|
-
isMatchCase,
|
|
612
|
-
isWholeWord
|
|
613
|
-
], () => {
|
|
614
|
-
findText({ query: searchQuery.value });
|
|
615
|
-
});
|
|
857
|
+
watch(editorMode, applyEditorMode);
|
|
616
858
|
onScopeDispose(() => {
|
|
617
859
|
removePrint();
|
|
618
860
|
if (!pdfViewer) return;
|
|
@@ -624,17 +866,19 @@ const usePdfViewer = ({ container, highlightTerms = [], pdfDocument, viewer, vie
|
|
|
624
866
|
actualSize,
|
|
625
867
|
currentPageNumber,
|
|
626
868
|
download,
|
|
869
|
+
drawStoredEditors,
|
|
870
|
+
editorMode,
|
|
627
871
|
findNext,
|
|
628
872
|
findPrevious,
|
|
629
873
|
fitPage,
|
|
630
874
|
fitWidth,
|
|
631
875
|
followBookmark,
|
|
632
876
|
goToPage,
|
|
633
|
-
isFullscreen,
|
|
634
877
|
isMatchCase,
|
|
635
878
|
isNotFound,
|
|
636
879
|
isWholeWord,
|
|
637
880
|
isZoomInDisabled,
|
|
881
|
+
isZoomOutDisabled,
|
|
638
882
|
matchesCount,
|
|
639
883
|
nextPage,
|
|
640
884
|
outline,
|
|
@@ -643,12 +887,12 @@ const usePdfViewer = ({ container, highlightTerms = [], pdfDocument, viewer, vie
|
|
|
643
887
|
pagesRotation,
|
|
644
888
|
previousPage,
|
|
645
889
|
print,
|
|
890
|
+
resetZoom,
|
|
646
891
|
rotate,
|
|
647
892
|
scale,
|
|
648
893
|
searchQuery,
|
|
649
|
-
toggleFullscreen,
|
|
650
894
|
zoomIn,
|
|
651
895
|
zoomOut
|
|
652
896
|
};
|
|
653
897
|
};
|
|
654
|
-
export { PDF_FULL_TURN, PDF_MAX_CANVAS_AREA, PDF_MAX_SCALE, PDF_MIME_TYPE, PDF_MIN_SCALE, PDF_PRINT_BATCH_SIZE, PDF_PRINT_CLASS, PDF_PRINT_SCALE, PDF_PRINT_STYLE, PDF_ROTATION_STEP, PDF_WHEEL_ZOOM_DIVISOR, destroyLoadingTask, getCanvasScale, getSourceParameters, getZoomedScroll, loadPdfDocument, renderPrintImage, renderPrintSheets, usePdfDocument, usePdfPage, usePdfThumbnails, usePdfViewer, usePdfZoomGesture };
|
|
898
|
+
export { PDF_EDITOR_KEY_PREFIX, PDF_FREE_TEXT_COLOR, PDF_FREE_TEXT_FONT_SIZE, PDF_FULL_TURN, PDF_MAX_CANVAS_AREA, PDF_MAX_SCALE, PDF_MIME_TYPE, PDF_MIN_SCALE, PDF_OPENING_SCALE, PDF_PRINT_BATCH_SIZE, PDF_PRINT_CLASS, PDF_PRINT_SCALE, PDF_PRINT_STYLE, PDF_ROTATION_STEP, PDF_WATERMARK_COLOR, PDF_WATERMARK_FONT_SIZE, PDF_WHEEL_ZOOM_DIVISOR, PDF_ZOOM_STEP, PdfEditorMode, createFreeTextEntry, destroyLoadingTask, getCanvasScale, getDownloadBytes, getPdfPages, getSourceParameters, getZoomedScroll, loadPdfDocument, readXfdf, renderPrintImage, renderPrintSheets, usePdfAnnotations, usePdfDocument, usePdfPage, usePdfThumbnails, usePdfViewer, usePdfZoomGesture, writeXfdf };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vue-pdfjs-headless",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.5.0",
|
|
4
4
|
"description": "Headless Vue composables over pdfjs-dist: a document's loading, a page's drawing and the viewer's state, with no styling and no renderer of its own.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"headless",
|
|
@@ -43,20 +43,21 @@
|
|
|
43
43
|
"lint": "eslint .",
|
|
44
44
|
"lint:fix": "eslint --fix .",
|
|
45
45
|
"test": "vitest",
|
|
46
|
-
"typecheck": "
|
|
46
|
+
"typecheck": "tsrs --fast"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@esposter/shared": "4.
|
|
49
|
+
"@esposter/shared": "4.5.0",
|
|
50
|
+
"neverthrow": "^8.2.0"
|
|
50
51
|
},
|
|
51
52
|
"devDependencies": {
|
|
52
|
-
"@esposter/configuration": "4.
|
|
53
|
+
"@esposter/configuration": "4.5.0",
|
|
53
54
|
"@vue/test-utils": "^2.5.1",
|
|
54
55
|
"happy-dom": "^21.0.4",
|
|
55
|
-
"tsdown": "^0.23.
|
|
56
|
+
"tsdown": "^0.23.5"
|
|
56
57
|
},
|
|
57
58
|
"peerDependencies": {
|
|
58
|
-
"pdfjs-dist": "^6.
|
|
59
|
+
"pdfjs-dist": "^6.5.438",
|
|
59
60
|
"vue": "^3.5.44"
|
|
60
61
|
},
|
|
61
|
-
"gitHead": "
|
|
62
|
+
"gitHead": "e30c001f3b4b15693e75fb53092d62a97ade4d2b"
|
|
62
63
|
}
|