@ankhorage/reader 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/dist/features/reader/ReaderViewport.d.ts +4 -0
- package/dist/features/reader/ReaderViewport.d.ts.map +1 -0
- package/dist/features/reader/ReaderViewport.js +210 -0
- package/dist/features/reader/ReaderViewport.js.map +1 -0
- package/dist/features/reader/adapters/ReaderView.d.ts +4 -0
- package/dist/features/reader/adapters/ReaderView.d.ts.map +1 -0
- package/dist/features/reader/adapters/ReaderView.js +5 -0
- package/dist/features/reader/adapters/ReaderView.js.map +1 -0
- package/dist/features/reader/adapters/ReaderView.native.d.ts +5 -0
- package/dist/features/reader/adapters/ReaderView.native.d.ts.map +1 -0
- package/dist/features/reader/adapters/ReaderView.native.js +22 -0
- package/dist/features/reader/adapters/ReaderView.native.js.map +1 -0
- package/dist/features/reader/adapters/ReaderView.web.d.ts +5 -0
- package/dist/features/reader/adapters/ReaderView.web.d.ts.map +1 -0
- package/dist/features/reader/adapters/ReaderView.web.js +16 -0
- package/dist/features/reader/adapters/ReaderView.web.js.map +1 -0
- package/dist/features/reader/drivers/EpubReaderDriver.d.ts +20 -0
- package/dist/features/reader/drivers/EpubReaderDriver.d.ts.map +1 -0
- package/dist/features/reader/drivers/EpubReaderDriver.js +259 -0
- package/dist/features/reader/drivers/EpubReaderDriver.js.map +1 -0
- package/dist/features/reader/drivers/PdfReaderDriver.d.ts +18 -0
- package/dist/features/reader/drivers/PdfReaderDriver.d.ts.map +1 -0
- package/dist/features/reader/drivers/PdfReaderDriver.js +198 -0
- package/dist/features/reader/drivers/PdfReaderDriver.js.map +1 -0
- package/dist/features/reader/epub/EpubArchive.d.ts +11 -0
- package/dist/features/reader/epub/EpubArchive.d.ts.map +1 -0
- package/dist/features/reader/epub/EpubArchive.js +65 -0
- package/dist/features/reader/epub/EpubArchive.js.map +1 -0
- package/dist/features/reader/epub/EpubArchiveFetcher.d.ts +11 -0
- package/dist/features/reader/epub/EpubArchiveFetcher.d.ts.map +1 -0
- package/dist/features/reader/epub/EpubArchiveFetcher.js +44 -0
- package/dist/features/reader/epub/EpubArchiveFetcher.js.map +1 -0
- package/dist/features/reader/epub/mediaTypes.d.ts +3 -0
- package/dist/features/reader/epub/mediaTypes.d.ts.map +1 -0
- package/dist/features/reader/epub/mediaTypes.js +26 -0
- package/dist/features/reader/epub/mediaTypes.js.map +1 -0
- package/dist/features/reader/epub/parseEpubPackage.d.ts +22 -0
- package/dist/features/reader/epub/parseEpubPackage.d.ts.map +1 -0
- package/dist/features/reader/epub/parseEpubPackage.js +98 -0
- package/dist/features/reader/epub/parseEpubPackage.js.map +1 -0
- package/dist/features/reader/epub/path.d.ts +4 -0
- package/dist/features/reader/epub/path.d.ts.map +1 -0
- package/dist/features/reader/epub/path.js +28 -0
- package/dist/features/reader/epub/path.js.map +1 -0
- package/dist/features/reader/epub/sanitizeEpubDocument.d.ts +3 -0
- package/dist/features/reader/epub/sanitizeEpubDocument.d.ts.map +1 -0
- package/dist/features/reader/epub/sanitizeEpubDocument.js +90 -0
- package/dist/features/reader/epub/sanitizeEpubDocument.js.map +1 -0
- package/dist/features/reader/epub/validateEpubArchiveEntriesAsync.d.ts +4 -0
- package/dist/features/reader/epub/validateEpubArchiveEntriesAsync.d.ts.map +1 -0
- package/dist/features/reader/epub/validateEpubArchiveEntriesAsync.js +50 -0
- package/dist/features/reader/epub/validateEpubArchiveEntriesAsync.js.map +1 -0
- package/dist/features/reader/errors/ReaderDocumentError.d.ts +6 -0
- package/dist/features/reader/errors/ReaderDocumentError.d.ts.map +1 -0
- package/dist/features/reader/errors/ReaderDocumentError.js +9 -0
- package/dist/features/reader/errors/ReaderDocumentError.js.map +1 -0
- package/dist/features/reader/loadReaderSourceAsync.d.ts +2 -0
- package/dist/features/reader/loadReaderSourceAsync.d.ts.map +1 -0
- package/dist/features/reader/loadReaderSourceAsync.js +49 -0
- package/dist/features/reader/loadReaderSourceAsync.js.map +1 -0
- package/dist/features/reader/navigation/resolveReaderKeyboardIntent.d.ts +9 -0
- package/dist/features/reader/navigation/resolveReaderKeyboardIntent.d.ts.map +1 -0
- package/dist/features/reader/navigation/resolveReaderKeyboardIntent.js +19 -0
- package/dist/features/reader/navigation/resolveReaderKeyboardIntent.js.map +1 -0
- package/dist/features/reader/navigation/resolveReaderNavigationIntent.d.ts +11 -0
- package/dist/features/reader/navigation/resolveReaderNavigationIntent.d.ts.map +1 -0
- package/dist/features/reader/navigation/resolveReaderNavigationIntent.js +16 -0
- package/dist/features/reader/navigation/resolveReaderNavigationIntent.js.map +1 -0
- package/dist/features/reader/readerViewportPresentation.d.ts +4 -0
- package/dist/features/reader/readerViewportPresentation.d.ts.map +1 -0
- package/dist/features/reader/readerViewportPresentation.js +25 -0
- package/dist/features/reader/readerViewportPresentation.js.map +1 -0
- package/dist/features/reader/toReaderViewportProps.d.ts +4 -0
- package/dist/features/reader/toReaderViewportProps.d.ts.map +1 -0
- package/dist/features/reader/toReaderViewportProps.js +24 -0
- package/dist/features/reader/toReaderViewportProps.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/index.native.d.ts +3 -0
- package/dist/index.native.d.ts.map +1 -0
- package/dist/index.native.js +2 -0
- package/dist/index.native.js.map +1 -0
- package/dist/index.web.d.ts +3 -0
- package/dist/index.web.d.ts.map +1 -0
- package/dist/index.web.js +2 -0
- package/dist/index.web.js.map +1 -0
- package/dist/types/reader.d.ts +93 -0
- package/dist/types/reader.d.ts.map +1 -0
- package/dist/types/reader.js +2 -0
- package/dist/types/reader.js.map +1 -0
- package/package.json +105 -4
- package/README.md +0 -3
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/* eslint-disable max-lines-per-function, complexity -- EPUB assembly keeps archive cleanup transactional. */
|
|
2
|
+
import { EpubNavigator } from '@readium/navigator';
|
|
3
|
+
import { Locator, Manifest, Publication } from '@readium/shared';
|
|
4
|
+
import { EpubArchive } from '../epub/EpubArchive.js';
|
|
5
|
+
import { EpubArchiveFetcher } from '../epub/EpubArchiveFetcher.js';
|
|
6
|
+
import { HTML_MEDIA_TYPES, inferEpubMediaType } from '../epub/mediaTypes.js';
|
|
7
|
+
import { parseEpubPackage } from '../epub/parseEpubPackage.js';
|
|
8
|
+
import { sanitizeEpubDocument, sanitizeEpubStylesheet } from '../epub/sanitizeEpubDocument.js';
|
|
9
|
+
export class EpubReaderDriver {
|
|
10
|
+
direction;
|
|
11
|
+
#archive;
|
|
12
|
+
#navigator;
|
|
13
|
+
#objectUrls;
|
|
14
|
+
#onExternalLink;
|
|
15
|
+
#onStateChange;
|
|
16
|
+
#publication;
|
|
17
|
+
#fixedLayout;
|
|
18
|
+
#page = 1;
|
|
19
|
+
#suppressStateNotifications = false;
|
|
20
|
+
constructor(args) {
|
|
21
|
+
this.#archive = args.archive;
|
|
22
|
+
this.direction = args.direction;
|
|
23
|
+
this.#fixedLayout = args.fixedLayout;
|
|
24
|
+
this.#navigator = args.navigator;
|
|
25
|
+
this.#objectUrls = args.objectUrls;
|
|
26
|
+
this.#onExternalLink = args.onExternalLink;
|
|
27
|
+
this.#onStateChange = args.onStateChange;
|
|
28
|
+
this.#publication = args.publication;
|
|
29
|
+
}
|
|
30
|
+
static async create(args) {
|
|
31
|
+
const archive = await EpubArchive.open(args.bytes, args.signal);
|
|
32
|
+
const objectUrls = [];
|
|
33
|
+
try {
|
|
34
|
+
const packageData = await parseEpubPackage(archive, args.signal);
|
|
35
|
+
const { publication, objectUrls: createdUrls } = await createPublication(archive, packageData, args.signal);
|
|
36
|
+
objectUrls.push(...createdUrls);
|
|
37
|
+
const holder = {};
|
|
38
|
+
const listeners = {
|
|
39
|
+
frameLoaded: (window) => attachFrameLinkPolicy(window, (url) => {
|
|
40
|
+
if (holder.driver !== undefined)
|
|
41
|
+
holder.driver.#onExternalLink(url);
|
|
42
|
+
}),
|
|
43
|
+
positionChanged: () => {
|
|
44
|
+
if (holder.driver !== undefined)
|
|
45
|
+
holder.driver.#notifyState();
|
|
46
|
+
},
|
|
47
|
+
timelineItemChanged: () => {
|
|
48
|
+
if (holder.driver !== undefined)
|
|
49
|
+
holder.driver.#notifyState();
|
|
50
|
+
},
|
|
51
|
+
tap: () => false,
|
|
52
|
+
click: () => false,
|
|
53
|
+
zoom: noop,
|
|
54
|
+
miscPointer: noop,
|
|
55
|
+
scroll: noop,
|
|
56
|
+
customEvent: noop,
|
|
57
|
+
handleLocator: () => false,
|
|
58
|
+
textSelected: noop,
|
|
59
|
+
contentProtection: noop,
|
|
60
|
+
contextMenu: noop,
|
|
61
|
+
peripheral: noop,
|
|
62
|
+
};
|
|
63
|
+
const initialPosition = parseInitialLocator(args.initialLocation);
|
|
64
|
+
const navigator = new EpubNavigator(args.container, publication, listeners, [], initialPosition, {
|
|
65
|
+
defaults: {},
|
|
66
|
+
preferences: resolvePreferences(args.appearance),
|
|
67
|
+
injectables: { allowedDomains: [], rules: [] },
|
|
68
|
+
});
|
|
69
|
+
const driver = new EpubReaderDriver({
|
|
70
|
+
archive,
|
|
71
|
+
direction: packageData.direction,
|
|
72
|
+
fixedLayout: packageData.fixedLayout,
|
|
73
|
+
navigator,
|
|
74
|
+
objectUrls,
|
|
75
|
+
onExternalLink: args.onExternalLink,
|
|
76
|
+
onStateChange: args.onStateChange,
|
|
77
|
+
publication,
|
|
78
|
+
});
|
|
79
|
+
holder.driver = driver;
|
|
80
|
+
await navigator.load();
|
|
81
|
+
driver.#notifyState();
|
|
82
|
+
return driver;
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
objectUrls.forEach((url) => URL.revokeObjectURL(url));
|
|
86
|
+
await archive.close();
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
getState() {
|
|
91
|
+
const locator = this.#navigator.currentLocator;
|
|
92
|
+
const spineIndex = Math.max(0, this.#publication.readingOrder.items.findIndex((link) => link.href === locator.href));
|
|
93
|
+
const progressionInResource = locator.locations.progression ?? 0;
|
|
94
|
+
const totalProgression = locator.locations.totalProgression ??
|
|
95
|
+
(spineIndex + progressionInResource) / this.#publication.readingOrder.items.length;
|
|
96
|
+
if (this.#fixedLayout)
|
|
97
|
+
this.#page = spineIndex + 1;
|
|
98
|
+
return {
|
|
99
|
+
canGoNext: this.#navigator.canGoForward,
|
|
100
|
+
canGoPrevious: this.#navigator.canGoBackward,
|
|
101
|
+
chapterId: locator.href,
|
|
102
|
+
...(locator.title === undefined ? {} : { chapterTitle: locator.title }),
|
|
103
|
+
locator: JSON.stringify(locator.serialize()),
|
|
104
|
+
page: this.#page,
|
|
105
|
+
...(this.#fixedLayout ? { pageCount: this.#publication.readingOrder.items.length } : {}),
|
|
106
|
+
progression: Math.max(0, Math.min(1, totalProgression)),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
async goNext(_trigger) {
|
|
110
|
+
if (!this.#navigator.canGoForward)
|
|
111
|
+
return;
|
|
112
|
+
this.#suppressStateNotifications = true;
|
|
113
|
+
try {
|
|
114
|
+
await navigate((done) => this.#navigator.goForward(shouldAnimateNavigation(), done));
|
|
115
|
+
if (!this.#fixedLayout)
|
|
116
|
+
this.#page += 1;
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
this.#suppressStateNotifications = false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async goPrevious(_trigger) {
|
|
123
|
+
if (!this.#navigator.canGoBackward)
|
|
124
|
+
return;
|
|
125
|
+
this.#suppressStateNotifications = true;
|
|
126
|
+
try {
|
|
127
|
+
await navigate((done) => this.#navigator.goBackward(shouldAnimateNavigation(), done));
|
|
128
|
+
if (!this.#fixedLayout)
|
|
129
|
+
this.#page = Math.max(1, this.#page - 1);
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
this.#suppressStateNotifications = false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async destroy() {
|
|
136
|
+
await this.#navigator.destroy();
|
|
137
|
+
await this.#archive.close();
|
|
138
|
+
this.#objectUrls.forEach((url) => URL.revokeObjectURL(url));
|
|
139
|
+
}
|
|
140
|
+
#notifyState() {
|
|
141
|
+
if (!this.#suppressStateNotifications)
|
|
142
|
+
this.#onStateChange(this.getState());
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
async function createPublication(archive, packageData, signal) {
|
|
146
|
+
const objectUrls = [];
|
|
147
|
+
const resourceUrls = new Map();
|
|
148
|
+
const itemByPath = new Map([...packageData.readingOrder, ...packageData.resources].map((item) => [item.href, item]));
|
|
149
|
+
for (const path of archive.paths()) {
|
|
150
|
+
const mediaType = itemByPath.get(path)?.mediaType ?? inferEpubMediaType(path);
|
|
151
|
+
if (HTML_MEDIA_TYPES.has(mediaType) || mediaType === 'text/css')
|
|
152
|
+
continue;
|
|
153
|
+
const resource = await archive.read(path, signal);
|
|
154
|
+
const resourceBuffer = resource.buffer.slice(resource.byteOffset, resource.byteOffset + resource.byteLength);
|
|
155
|
+
const url = URL.createObjectURL(new Blob([resourceBuffer], { type: mediaType }));
|
|
156
|
+
objectUrls.push(url);
|
|
157
|
+
resourceUrls.set(path, url);
|
|
158
|
+
}
|
|
159
|
+
const overrides = new Map();
|
|
160
|
+
for (const path of archive.paths()) {
|
|
161
|
+
const mediaType = itemByPath.get(path)?.mediaType ?? inferEpubMediaType(path);
|
|
162
|
+
if (mediaType !== 'text/css')
|
|
163
|
+
continue;
|
|
164
|
+
const css = sanitizeEpubStylesheet(await archive.readText(path, signal), path, (resourcePath) => resourceUrls.get(resourcePath));
|
|
165
|
+
const url = URL.createObjectURL(new Blob([css], { type: mediaType }));
|
|
166
|
+
objectUrls.push(url);
|
|
167
|
+
resourceUrls.set(path, url);
|
|
168
|
+
overrides.set(path, new TextEncoder().encode(css));
|
|
169
|
+
}
|
|
170
|
+
for (const [path, item] of itemByPath) {
|
|
171
|
+
if (!HTML_MEDIA_TYPES.has(item.mediaType))
|
|
172
|
+
continue;
|
|
173
|
+
const source = sanitizeEpubDocument(await archive.readText(path, signal), path, (resourcePath) => resourceUrls.get(resourcePath));
|
|
174
|
+
overrides.set(path, new TextEncoder().encode(source));
|
|
175
|
+
}
|
|
176
|
+
const manifest = Manifest.deserialize({
|
|
177
|
+
metadata: {
|
|
178
|
+
conformsTo: ['https://readium.org/webpub-manifest/profiles/epub'],
|
|
179
|
+
language: packageData.language,
|
|
180
|
+
layout: packageData.fixedLayout ? 'fixed' : 'reflowable',
|
|
181
|
+
readingProgression: packageData.direction,
|
|
182
|
+
title: packageData.title,
|
|
183
|
+
},
|
|
184
|
+
links: [
|
|
185
|
+
{
|
|
186
|
+
href: 'https://reader.invalid/manifest.json',
|
|
187
|
+
rel: 'self',
|
|
188
|
+
type: 'application/webpub+json',
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
readingOrder: packageData.readingOrder.map(toManifestLink),
|
|
192
|
+
resources: packageData.resources.map(toManifestLink),
|
|
193
|
+
toc: packageData.tableOfContents.map((item) => ({
|
|
194
|
+
href: item.href,
|
|
195
|
+
title: item.title,
|
|
196
|
+
type: 'application/xhtml+xml',
|
|
197
|
+
})),
|
|
198
|
+
});
|
|
199
|
+
if (manifest === undefined)
|
|
200
|
+
throw new Error('Could not construct the EPUB publication manifest.');
|
|
201
|
+
const links = [...manifest.readingOrder.items, ...(manifest.resources?.items ?? [])];
|
|
202
|
+
return {
|
|
203
|
+
publication: new Publication({
|
|
204
|
+
fetcher: new EpubArchiveFetcher(archive, links, overrides),
|
|
205
|
+
manifest,
|
|
206
|
+
}),
|
|
207
|
+
objectUrls,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function toManifestLink(item) {
|
|
211
|
+
return {
|
|
212
|
+
href: item.href,
|
|
213
|
+
type: item.mediaType,
|
|
214
|
+
properties: item.properties.length === 0
|
|
215
|
+
? undefined
|
|
216
|
+
: Object.fromEntries(item.properties.map((key) => [key, true])),
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
function resolvePreferences(appearance) {
|
|
220
|
+
const dark = appearance.colorScheme === 'dark';
|
|
221
|
+
const sepia = appearance.colorScheme === 'sepia';
|
|
222
|
+
return {
|
|
223
|
+
backgroundColor: dark ? '#111111' : sepia ? '#f4ecd8' : '#ffffff',
|
|
224
|
+
fontSize: appearance.fontScale,
|
|
225
|
+
lineHeight: appearance.lineHeight,
|
|
226
|
+
textColor: dark ? '#f5f5f5' : sepia ? '#3f3527' : '#111111',
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function parseInitialLocator(value) {
|
|
230
|
+
if (!value)
|
|
231
|
+
return undefined;
|
|
232
|
+
try {
|
|
233
|
+
return Locator.deserialize(JSON.parse(value));
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
function attachFrameLinkPolicy(window, onExternalLink) {
|
|
240
|
+
window.document.addEventListener('click', (event) => {
|
|
241
|
+
const target = event.target instanceof Element ? event.target.closest('a') : null;
|
|
242
|
+
const externalUrl = target?.getAttribute('data-reader-external-href');
|
|
243
|
+
if (!externalUrl)
|
|
244
|
+
return;
|
|
245
|
+
event.preventDefault();
|
|
246
|
+
event.stopPropagation();
|
|
247
|
+
onExternalLink(externalUrl);
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
function navigate(start) {
|
|
251
|
+
return new Promise((resolve) => start(() => resolve()));
|
|
252
|
+
}
|
|
253
|
+
function shouldAnimateNavigation() {
|
|
254
|
+
return !window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
255
|
+
}
|
|
256
|
+
function noop() {
|
|
257
|
+
// Readium requires a complete listener object; these events are outside the reader contract.
|
|
258
|
+
}
|
|
259
|
+
//# sourceMappingURL=EpubReaderDriver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EpubReaderDriver.js","sourceRoot":"","sources":["../../../../src/features/reader/drivers/EpubReaderDriver.ts"],"names":[],"mappings":"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","sourcesContent":["/* eslint-disable max-lines-per-function, complexity -- EPUB assembly keeps archive cleanup transactional. */\nimport { EpubNavigator, type EpubNavigatorListeners } from '@readium/navigator';\nimport { Locator, Manifest, Publication } from '@readium/shared';\n\nimport type { ReaderAppearance, ReaderDriver, ReaderDriverState } from '../../../types/reader.js';\nimport { EpubArchive } from '../epub/EpubArchive.js';\nimport { EpubArchiveFetcher } from '../epub/EpubArchiveFetcher.js';\nimport { HTML_MEDIA_TYPES, inferEpubMediaType } from '../epub/mediaTypes.js';\nimport { type ParsedEpubPackage, parseEpubPackage } from '../epub/parseEpubPackage.js';\nimport { sanitizeEpubDocument, sanitizeEpubStylesheet } from '../epub/sanitizeEpubDocument.js';\n\nexport class EpubReaderDriver implements ReaderDriver {\n readonly direction: 'ltr' | 'rtl';\n readonly #archive: EpubArchive;\n readonly #navigator: EpubNavigator;\n readonly #objectUrls: string[];\n readonly #onExternalLink: (url: string) => void;\n readonly #onStateChange: (state: ReaderDriverState) => void;\n readonly #publication: Publication;\n readonly #fixedLayout: boolean;\n #page = 1;\n #suppressStateNotifications = false;\n\n private constructor(args: {\n archive: EpubArchive;\n direction: 'ltr' | 'rtl';\n fixedLayout: boolean;\n navigator: EpubNavigator;\n objectUrls: string[];\n onExternalLink: (url: string) => void;\n onStateChange: (state: ReaderDriverState) => void;\n publication: Publication;\n }) {\n this.#archive = args.archive;\n this.direction = args.direction;\n this.#fixedLayout = args.fixedLayout;\n this.#navigator = args.navigator;\n this.#objectUrls = args.objectUrls;\n this.#onExternalLink = args.onExternalLink;\n this.#onStateChange = args.onStateChange;\n this.#publication = args.publication;\n }\n\n static async create(args: {\n appearance: ReaderAppearance;\n bytes: Uint8Array;\n container: HTMLElement;\n initialLocation?: string;\n onExternalLink: (url: string) => void;\n onStateChange: (state: ReaderDriverState) => void;\n signal: AbortSignal;\n }): Promise<EpubReaderDriver> {\n const archive = await EpubArchive.open(args.bytes, args.signal);\n const objectUrls: string[] = [];\n try {\n const packageData = await parseEpubPackage(archive, args.signal);\n const { publication, objectUrls: createdUrls } = await createPublication(\n archive,\n packageData,\n args.signal,\n );\n objectUrls.push(...createdUrls);\n const holder: { driver?: EpubReaderDriver } = {};\n const listeners: EpubNavigatorListeners = {\n frameLoaded: (window) =>\n attachFrameLinkPolicy(window, (url) => {\n if (holder.driver !== undefined) holder.driver.#onExternalLink(url);\n }),\n positionChanged: () => {\n if (holder.driver !== undefined) holder.driver.#notifyState();\n },\n timelineItemChanged: () => {\n if (holder.driver !== undefined) holder.driver.#notifyState();\n },\n tap: () => false,\n click: () => false,\n zoom: noop,\n miscPointer: noop,\n scroll: noop,\n customEvent: noop,\n handleLocator: () => false,\n textSelected: noop,\n contentProtection: noop,\n contextMenu: noop,\n peripheral: noop,\n };\n const initialPosition = parseInitialLocator(args.initialLocation);\n const navigator = new EpubNavigator(\n args.container,\n publication,\n listeners,\n [],\n initialPosition,\n {\n defaults: {},\n preferences: resolvePreferences(args.appearance),\n injectables: { allowedDomains: [], rules: [] },\n },\n );\n const driver = new EpubReaderDriver({\n archive,\n direction: packageData.direction,\n fixedLayout: packageData.fixedLayout,\n navigator,\n objectUrls,\n onExternalLink: args.onExternalLink,\n onStateChange: args.onStateChange,\n publication,\n });\n holder.driver = driver;\n await navigator.load();\n driver.#notifyState();\n return driver;\n } catch (error) {\n objectUrls.forEach((url) => URL.revokeObjectURL(url));\n await archive.close();\n throw error;\n }\n }\n\n getState(): ReaderDriverState {\n const locator = this.#navigator.currentLocator;\n const spineIndex = Math.max(\n 0,\n this.#publication.readingOrder.items.findIndex((link) => link.href === locator.href),\n );\n const progressionInResource = locator.locations.progression ?? 0;\n const totalProgression =\n locator.locations.totalProgression ??\n (spineIndex + progressionInResource) / this.#publication.readingOrder.items.length;\n if (this.#fixedLayout) this.#page = spineIndex + 1;\n return {\n canGoNext: this.#navigator.canGoForward,\n canGoPrevious: this.#navigator.canGoBackward,\n chapterId: locator.href,\n ...(locator.title === undefined ? {} : { chapterTitle: locator.title }),\n locator: JSON.stringify(locator.serialize()),\n page: this.#page,\n ...(this.#fixedLayout ? { pageCount: this.#publication.readingOrder.items.length } : {}),\n progression: Math.max(0, Math.min(1, totalProgression)),\n };\n }\n\n async goNext(_trigger: Parameters<ReaderDriver['goNext']>[0]): Promise<void> {\n if (!this.#navigator.canGoForward) return;\n this.#suppressStateNotifications = true;\n try {\n await navigate((done) => this.#navigator.goForward(shouldAnimateNavigation(), done));\n if (!this.#fixedLayout) this.#page += 1;\n } finally {\n this.#suppressStateNotifications = false;\n }\n }\n\n async goPrevious(_trigger: Parameters<ReaderDriver['goPrevious']>[0]): Promise<void> {\n if (!this.#navigator.canGoBackward) return;\n this.#suppressStateNotifications = true;\n try {\n await navigate((done) => this.#navigator.goBackward(shouldAnimateNavigation(), done));\n if (!this.#fixedLayout) this.#page = Math.max(1, this.#page - 1);\n } finally {\n this.#suppressStateNotifications = false;\n }\n }\n\n async destroy(): Promise<void> {\n await this.#navigator.destroy();\n await this.#archive.close();\n this.#objectUrls.forEach((url) => URL.revokeObjectURL(url));\n }\n\n #notifyState(): void {\n if (!this.#suppressStateNotifications) this.#onStateChange(this.getState());\n }\n}\n\nasync function createPublication(\n archive: EpubArchive,\n packageData: ParsedEpubPackage,\n signal: AbortSignal,\n): Promise<{ publication: Publication; objectUrls: string[] }> {\n const objectUrls: string[] = [];\n const resourceUrls = new Map<string, string>();\n const itemByPath = new Map(\n [...packageData.readingOrder, ...packageData.resources].map((item) => [item.href, item]),\n );\n\n for (const path of archive.paths()) {\n const mediaType = itemByPath.get(path)?.mediaType ?? inferEpubMediaType(path);\n if (HTML_MEDIA_TYPES.has(mediaType) || mediaType === 'text/css') continue;\n const resource = await archive.read(path, signal);\n const resourceBuffer = resource.buffer.slice(\n resource.byteOffset,\n resource.byteOffset + resource.byteLength,\n ) as ArrayBuffer;\n const url = URL.createObjectURL(new Blob([resourceBuffer], { type: mediaType }));\n objectUrls.push(url);\n resourceUrls.set(path, url);\n }\n\n const overrides = new Map<string, Uint8Array>();\n for (const path of archive.paths()) {\n const mediaType = itemByPath.get(path)?.mediaType ?? inferEpubMediaType(path);\n if (mediaType !== 'text/css') continue;\n const css = sanitizeEpubStylesheet(await archive.readText(path, signal), path, (resourcePath) =>\n resourceUrls.get(resourcePath),\n );\n const url = URL.createObjectURL(new Blob([css], { type: mediaType }));\n objectUrls.push(url);\n resourceUrls.set(path, url);\n overrides.set(path, new TextEncoder().encode(css));\n }\n for (const [path, item] of itemByPath) {\n if (!HTML_MEDIA_TYPES.has(item.mediaType)) continue;\n const source = sanitizeEpubDocument(\n await archive.readText(path, signal),\n path,\n (resourcePath) => resourceUrls.get(resourcePath),\n );\n overrides.set(path, new TextEncoder().encode(source));\n }\n\n const manifest = Manifest.deserialize({\n metadata: {\n conformsTo: ['https://readium.org/webpub-manifest/profiles/epub'],\n language: packageData.language,\n layout: packageData.fixedLayout ? 'fixed' : 'reflowable',\n readingProgression: packageData.direction,\n title: packageData.title,\n },\n links: [\n {\n href: 'https://reader.invalid/manifest.json',\n rel: 'self',\n type: 'application/webpub+json',\n },\n ],\n readingOrder: packageData.readingOrder.map(toManifestLink),\n resources: packageData.resources.map(toManifestLink),\n toc: packageData.tableOfContents.map((item) => ({\n href: item.href,\n title: item.title,\n type: 'application/xhtml+xml',\n })),\n });\n if (manifest === undefined) throw new Error('Could not construct the EPUB publication manifest.');\n const links = [...manifest.readingOrder.items, ...(manifest.resources?.items ?? [])];\n return {\n publication: new Publication({\n fetcher: new EpubArchiveFetcher(archive, links, overrides),\n manifest,\n }),\n objectUrls,\n };\n}\n\nfunction toManifestLink(item: ParsedEpubPackage['readingOrder'][number]) {\n return {\n href: item.href,\n type: item.mediaType,\n properties:\n item.properties.length === 0\n ? undefined\n : Object.fromEntries(item.properties.map((key) => [key, true])),\n };\n}\n\nfunction resolvePreferences(appearance: ReaderAppearance) {\n const dark = appearance.colorScheme === 'dark';\n const sepia = appearance.colorScheme === 'sepia';\n return {\n backgroundColor: dark ? '#111111' : sepia ? '#f4ecd8' : '#ffffff',\n fontSize: appearance.fontScale,\n lineHeight: appearance.lineHeight,\n textColor: dark ? '#f5f5f5' : sepia ? '#3f3527' : '#111111',\n };\n}\n\nfunction parseInitialLocator(value: string | undefined): Locator | undefined {\n if (!value) return undefined;\n try {\n return Locator.deserialize(JSON.parse(value));\n } catch {\n return undefined;\n }\n}\n\nfunction attachFrameLinkPolicy(window: Window, onExternalLink: (url: string) => void): void {\n window.document.addEventListener('click', (event) => {\n const target = event.target instanceof Element ? event.target.closest('a') : null;\n const externalUrl = target?.getAttribute('data-reader-external-href');\n if (!externalUrl) return;\n event.preventDefault();\n event.stopPropagation();\n onExternalLink(externalUrl);\n });\n}\n\nfunction navigate(start: (done: (ok: boolean) => void) => void): Promise<void> {\n return new Promise((resolve) => start(() => resolve()));\n}\n\nfunction shouldAnimateNavigation(): boolean {\n return !window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n}\n\nfunction noop(): void {\n // Readium requires a complete listener object; these events are outside the reader contract.\n}\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ReaderDriver, ReaderDriverState } from '../../../types/reader.js';
|
|
2
|
+
export declare class PdfReaderDriver implements ReaderDriver {
|
|
3
|
+
#private;
|
|
4
|
+
readonly direction: "ltr";
|
|
5
|
+
private constructor();
|
|
6
|
+
static create(args: {
|
|
7
|
+
bytes: Uint8Array;
|
|
8
|
+
container: HTMLElement;
|
|
9
|
+
initialLocation?: string;
|
|
10
|
+
onExternalLink: (url: string) => void;
|
|
11
|
+
onRenderError: (error: unknown) => void;
|
|
12
|
+
}): Promise<PdfReaderDriver>;
|
|
13
|
+
getState(): ReaderDriverState;
|
|
14
|
+
goNext(): Promise<void>;
|
|
15
|
+
goPrevious(): Promise<void>;
|
|
16
|
+
destroy(): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=PdfReaderDriver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PdfReaderDriver.d.ts","sourceRoot":"","sources":["../../../../src/features/reader/drivers/PdfReaderDriver.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAGhF,qBAAa,eAAgB,YAAW,YAAY;;IAClD,QAAQ,CAAC,SAAS,EAAG,KAAK,CAAU;IAapC,OAAO;WAkBM,MAAM,CAAC,IAAI,EAAE;QACxB,KAAK,EAAE,UAAU,CAAC;QAClB,SAAS,EAAE,WAAW,CAAC;QACvB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,cAAc,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;QACtC,aAAa,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;KACzC,GAAG,OAAO,CAAC,eAAe,CAAC;IAqC5B,QAAQ,IAAI,iBAAiB;IAYvB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAMvB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAM3B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAuE/B"}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { getDocument, InvalidPDFException, PasswordException, PDFWorker, RenderingCancelledException, TextLayer, } from 'pdfjs-dist';
|
|
2
|
+
import { ReaderDocumentError } from '../errors/ReaderDocumentError.js';
|
|
3
|
+
export class PdfReaderDriver {
|
|
4
|
+
direction = 'ltr';
|
|
5
|
+
#container;
|
|
6
|
+
#document;
|
|
7
|
+
#loadingTask;
|
|
8
|
+
#onExternalLink;
|
|
9
|
+
#onRenderError;
|
|
10
|
+
#worker;
|
|
11
|
+
#page;
|
|
12
|
+
#renderTask;
|
|
13
|
+
#resizeFrame;
|
|
14
|
+
#resizeObserver;
|
|
15
|
+
#textLayer;
|
|
16
|
+
constructor(args) {
|
|
17
|
+
this.#container = args.container;
|
|
18
|
+
this.#document = args.document;
|
|
19
|
+
this.#loadingTask = args.loadingTask;
|
|
20
|
+
this.#onExternalLink = args.onExternalLink;
|
|
21
|
+
this.#onRenderError = args.onRenderError;
|
|
22
|
+
this.#worker = args.worker;
|
|
23
|
+
this.#page = args.initialPage;
|
|
24
|
+
}
|
|
25
|
+
static async create(args) {
|
|
26
|
+
const worker = createPdfWorker();
|
|
27
|
+
const loadingTask = getDocument({
|
|
28
|
+
data: args.bytes,
|
|
29
|
+
stopAtErrors: true,
|
|
30
|
+
worker,
|
|
31
|
+
});
|
|
32
|
+
try {
|
|
33
|
+
const document = await loadingTask.promise;
|
|
34
|
+
const driver = new PdfReaderDriver({
|
|
35
|
+
container: args.container,
|
|
36
|
+
document,
|
|
37
|
+
initialPage: resolveInitialPage(args.initialLocation, document.numPages),
|
|
38
|
+
loadingTask,
|
|
39
|
+
onExternalLink: args.onExternalLink,
|
|
40
|
+
onRenderError: args.onRenderError,
|
|
41
|
+
worker,
|
|
42
|
+
});
|
|
43
|
+
await driver.#renderPage();
|
|
44
|
+
driver.#observeResize();
|
|
45
|
+
return driver;
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
await loadingTask.destroy();
|
|
49
|
+
worker.destroy();
|
|
50
|
+
if (error instanceof PasswordException) {
|
|
51
|
+
throw new ReaderDocumentError('protected-document', 'This PDF is password protected and cannot be opened.');
|
|
52
|
+
}
|
|
53
|
+
if (error instanceof InvalidPDFException) {
|
|
54
|
+
throw new ReaderDocumentError('invalid-document', 'The PDF document is invalid.');
|
|
55
|
+
}
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
getState() {
|
|
60
|
+
return {
|
|
61
|
+
canGoNext: this.#page < this.#document.numPages,
|
|
62
|
+
canGoPrevious: this.#page > 1,
|
|
63
|
+
locator: `page=${this.#page}`,
|
|
64
|
+
page: this.#page,
|
|
65
|
+
pageCount: this.#document.numPages,
|
|
66
|
+
progression: this.#document.numPages <= 1 ? 1 : (this.#page - 1) / (this.#document.numPages - 1),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
async goNext() {
|
|
70
|
+
if (this.#page >= this.#document.numPages)
|
|
71
|
+
return;
|
|
72
|
+
this.#page += 1;
|
|
73
|
+
await this.#renderPage();
|
|
74
|
+
}
|
|
75
|
+
async goPrevious() {
|
|
76
|
+
if (this.#page <= 1)
|
|
77
|
+
return;
|
|
78
|
+
this.#page -= 1;
|
|
79
|
+
await this.#renderPage();
|
|
80
|
+
}
|
|
81
|
+
async destroy() {
|
|
82
|
+
this.#renderTask?.cancel();
|
|
83
|
+
this.#resizeObserver?.disconnect();
|
|
84
|
+
if (this.#resizeFrame !== undefined)
|
|
85
|
+
cancelAnimationFrame(this.#resizeFrame);
|
|
86
|
+
this.#textLayer?.cancel();
|
|
87
|
+
TextLayer.cleanup();
|
|
88
|
+
await this.#loadingTask.destroy();
|
|
89
|
+
this.#worker.destroy();
|
|
90
|
+
this.#container.replaceChildren();
|
|
91
|
+
}
|
|
92
|
+
async #renderPage() {
|
|
93
|
+
this.#renderTask?.cancel();
|
|
94
|
+
this.#textLayer?.cancel();
|
|
95
|
+
const page = await this.#document.getPage(this.#page);
|
|
96
|
+
const baseViewport = page.getViewport({ scale: 1 });
|
|
97
|
+
const availableWidth = Math.max(1, this.#container.clientWidth);
|
|
98
|
+
const viewport = page.getViewport({ scale: availableWidth / baseViewport.width });
|
|
99
|
+
const pageElement = document.createElement('div');
|
|
100
|
+
const canvas = document.createElement('canvas');
|
|
101
|
+
const textContainer = document.createElement('div');
|
|
102
|
+
const linkContainer = document.createElement('div');
|
|
103
|
+
const outputScale = window.devicePixelRatio || 1;
|
|
104
|
+
pageElement.className = 'reader-pdf-page';
|
|
105
|
+
pageElement.style.setProperty('--scale-factor', String(viewport.scale));
|
|
106
|
+
pageElement.style.width = `${viewport.width}px`;
|
|
107
|
+
pageElement.style.height = `${viewport.height}px`;
|
|
108
|
+
canvas.width = Math.floor(viewport.width * outputScale);
|
|
109
|
+
canvas.height = Math.floor(viewport.height * outputScale);
|
|
110
|
+
canvas.style.width = `${viewport.width}px`;
|
|
111
|
+
canvas.style.height = `${viewport.height}px`;
|
|
112
|
+
textContainer.className = 'reader-pdf-text-layer';
|
|
113
|
+
textContainer.style.width = `${viewport.width}px`;
|
|
114
|
+
textContainer.style.height = `${viewport.height}px`;
|
|
115
|
+
linkContainer.className = 'reader-pdf-link-layer';
|
|
116
|
+
pageElement.append(canvas, textContainer, linkContainer);
|
|
117
|
+
this.#container.replaceChildren(pageElement);
|
|
118
|
+
const context = canvas.getContext('2d');
|
|
119
|
+
if (context === null)
|
|
120
|
+
throw new Error('The PDF canvas context is unavailable.');
|
|
121
|
+
this.#renderTask = page.render({
|
|
122
|
+
canvas,
|
|
123
|
+
canvasContext: context,
|
|
124
|
+
transform: outputScale === 1 ? undefined : [outputScale, 0, 0, outputScale, 0, 0],
|
|
125
|
+
viewport,
|
|
126
|
+
});
|
|
127
|
+
this.#textLayer = new TextLayer({
|
|
128
|
+
container: textContainer,
|
|
129
|
+
textContentSource: page.streamTextContent(),
|
|
130
|
+
viewport,
|
|
131
|
+
});
|
|
132
|
+
await Promise.all([this.#renderTask.promise, this.#textLayer.render()]);
|
|
133
|
+
await renderExternalLinks(page, viewport, linkContainer, this.#onExternalLink);
|
|
134
|
+
}
|
|
135
|
+
#observeResize() {
|
|
136
|
+
let previousWidth = this.#container.clientWidth;
|
|
137
|
+
this.#resizeObserver = new ResizeObserver(() => {
|
|
138
|
+
const width = this.#container.clientWidth;
|
|
139
|
+
if (width === previousWidth)
|
|
140
|
+
return;
|
|
141
|
+
previousWidth = width;
|
|
142
|
+
if (this.#resizeFrame !== undefined)
|
|
143
|
+
cancelAnimationFrame(this.#resizeFrame);
|
|
144
|
+
this.#resizeFrame = requestAnimationFrame(() => {
|
|
145
|
+
void this.#renderPage().catch((error) => {
|
|
146
|
+
if (!(error instanceof RenderingCancelledException))
|
|
147
|
+
this.#onRenderError(error);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
this.#resizeObserver.observe(this.#container);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function createPdfWorker() {
|
|
155
|
+
const port = new Worker(new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url), {
|
|
156
|
+
type: 'module',
|
|
157
|
+
});
|
|
158
|
+
return PDFWorker.create({ port });
|
|
159
|
+
}
|
|
160
|
+
async function renderExternalLinks(page, viewport, container, onExternalLink) {
|
|
161
|
+
const annotations = await page.getAnnotations({ intent: 'display' });
|
|
162
|
+
if (!Array.isArray(annotations))
|
|
163
|
+
return;
|
|
164
|
+
for (const annotation of annotations) {
|
|
165
|
+
if (!isExternalPdfLink(annotation))
|
|
166
|
+
continue;
|
|
167
|
+
const [left, top] = viewport.convertToViewportPoint(annotation.rect[0], annotation.rect[1]);
|
|
168
|
+
const [right, bottom] = viewport.convertToViewportPoint(annotation.rect[2], annotation.rect[3]);
|
|
169
|
+
const link = document.createElement('a');
|
|
170
|
+
link.setAttribute('aria-label', annotation.titleObj?.str ?? annotation.url);
|
|
171
|
+
link.href = '#';
|
|
172
|
+
link.style.inset = `${Math.min(bottom, top)}px auto auto ${Math.min(left, right)}px`;
|
|
173
|
+
link.style.width = `${Math.abs(right - left)}px`;
|
|
174
|
+
link.style.height = `${Math.abs(top - bottom)}px`;
|
|
175
|
+
link.addEventListener('click', (event) => {
|
|
176
|
+
event.preventDefault();
|
|
177
|
+
onExternalLink(annotation.url);
|
|
178
|
+
});
|
|
179
|
+
container.append(link);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
function isExternalPdfLink(annotation) {
|
|
183
|
+
return (typeof annotation === 'object' &&
|
|
184
|
+
annotation !== null &&
|
|
185
|
+
'url' in annotation &&
|
|
186
|
+
'rect' in annotation &&
|
|
187
|
+
typeof annotation.url === 'string' &&
|
|
188
|
+
/^(?:https?:|mailto:|tel:)/i.test(annotation.url) &&
|
|
189
|
+
Array.isArray(annotation.rect) &&
|
|
190
|
+
annotation.rect.length === 4 &&
|
|
191
|
+
annotation.rect.every((value) => typeof value === 'number'));
|
|
192
|
+
}
|
|
193
|
+
function resolveInitialPage(location, pageCount) {
|
|
194
|
+
const match = /^page=(\d+)$/.exec(location ?? '');
|
|
195
|
+
const page = Number(match?.[1] ?? 1);
|
|
196
|
+
return Math.max(1, Math.min(pageCount, Number.isSafeInteger(page) ? page : 1));
|
|
197
|
+
}
|
|
198
|
+
//# sourceMappingURL=PdfReaderDriver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PdfReaderDriver.js","sourceRoot":"","sources":["../../../../src/features/reader/drivers/PdfReaderDriver.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,iBAAiB,EAGjB,SAAS,EACT,2BAA2B,EAE3B,SAAS,GACV,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAEvE,MAAM,OAAO,eAAe;IACjB,SAAS,GAAG,KAAc,CAAC;IAC3B,UAAU,CAAc;IACxB,SAAS,CAAmB;IAC5B,YAAY,CAAyB;IACrC,eAAe,CAAwB;IACvC,cAAc,CAA2B;IACzC,OAAO,CAAY;IAC5B,KAAK,CAAS;IACd,WAAW,CAAc;IACzB,YAAY,CAAU;IACtB,eAAe,CAAkB;IACjC,UAAU,CAAa;IAEvB,YAAoB,IAQnB;QACC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC;QACjC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC/B,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,WAAW,CAAC;QACrC,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,cAAc,CAAC;QAC3C,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,aAAa,CAAC;QACzC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC;QAC3B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC;IAChC,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,IAMnB;QACC,MAAM,MAAM,GAAG,eAAe,EAAE,CAAC;QACjC,MAAM,WAAW,GAAG,WAAW,CAAC;YAC9B,IAAI,EAAE,IAAI,CAAC,KAAK;YAChB,YAAY,EAAE,IAAI;YAClB,MAAM;SACP,CAAC,CAAC;QACH,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC;YAC3C,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC;gBACjC,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,QAAQ;gBACR,WAAW,EAAE,kBAAkB,CAAC,IAAI,CAAC,eAAe,EAAE,QAAQ,CAAC,QAAQ,CAAC;gBACxE,WAAW;gBACX,cAAc,EAAE,IAAI,CAAC,cAAc;gBACnC,aAAa,EAAE,IAAI,CAAC,aAAa;gBACjC,MAAM;aACP,CAAC,CAAC;YACH,MAAM,MAAM,CAAC,WAAW,EAAE,CAAC;YAC3B,MAAM,CAAC,cAAc,EAAE,CAAC;YACxB,OAAO,MAAM,CAAC;QAChB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,WAAW,CAAC,OAAO,EAAE,CAAC;YAC5B,MAAM,CAAC,OAAO,EAAE,CAAC;YACjB,IAAI,KAAK,YAAY,iBAAiB,EAAE,CAAC;gBACvC,MAAM,IAAI,mBAAmB,CAC3B,oBAAoB,EACpB,sDAAsD,CACvD,CAAC;YACJ,CAAC;YACD,IAAI,KAAK,YAAY,mBAAmB,EAAE,CAAC;gBACzC,MAAM,IAAI,mBAAmB,CAAC,kBAAkB,EAAE,8BAA8B,CAAC,CAAC;YACpF,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED,QAAQ;QACN,OAAO;YACL,SAAS,EAAE,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ;YAC/C,aAAa,EAAE,IAAI,CAAC,KAAK,GAAG,CAAC;YAC7B,OAAO,EAAE,QAAQ,IAAI,CAAC,KAAK,EAAE;YAC7B,IAAI,EAAE,IAAI,CAAC,KAAK;YAChB,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ;YAClC,WAAW,EACT,IAAI,CAAC,SAAS,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,GAAG,CAAC,CAAC;SACtF,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,MAAM;QACV,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,SAAS,CAAC,QAAQ;YAAE,OAAO;QAClD,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC;QAChB,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC;IAC3B,CAAC;IAED,KAAK,CAAC,UAAU;QACd,IAAI,IAAI,CAAC,KAAK,IAAI,CAAC;YAAE,OAAO;QAC5B,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC;QAChB,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC;IAC3B,CAAC;IAED,KAAK,CAAC,OAAO;QACX,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,CAAC;QAC3B,IAAI,CAAC,eAAe,EAAE,UAAU,EAAE,CAAC;QACnC,IAAI,IAAI,CAAC,YAAY,KAAK,SAAS;YAAE,oBAAoB,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC7E,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC;QAC1B,SAAS,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,CAAC;QAClC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;QACvB,IAAI,CAAC,UAAU,CAAC,eAAe,EAAE,CAAC;IACpC,CAAC;IAED,KAAK,CAAC,WAAW;QACf,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,CAAC;QAC3B,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC;QAC1B,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACtD,MAAM,YAAY,GAAG,IAAI,CAAC,WAAW,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;QACpD,MAAM,cAAc,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;QAChE,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,CAAC,EAAE,KAAK,EAAE,cAAc,GAAG,YAAY,CAAC,KAAK,EAAE,CAAC,CAAC;QAClF,MAAM,WAAW,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QAClD,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAChD,MAAM,aAAa,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QACpD,MAAM,aAAa,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QACpD,MAAM,WAAW,GAAG,MAAM,CAAC,gBAAgB,IAAI,CAAC,CAAC;QAEjD,WAAW,CAAC,SAAS,GAAG,iBAAiB,CAAC;QAC1C,WAAW,CAAC,KAAK,CAAC,WAAW,CAAC,gBAAgB,EAAE,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;QACxE,WAAW,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,QAAQ,CAAC,KAAK,IAAI,CAAC;QAChD,WAAW,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC;QAClD,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,GAAG,WAAW,CAAC,CAAC;QACxD,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,GAAG,WAAW,CAAC,CAAC;QAC1D,MAAM,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,QAAQ,CAAC,KAAK,IAAI,CAAC;QAC3C,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC;QAC7C,aAAa,CAAC,SAAS,GAAG,uBAAuB,CAAC;QAClD,aAAa,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,QAAQ,CAAC,KAAK,IAAI,CAAC;QAClD,aAAa,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC;QACpD,aAAa,CAAC,SAAS,GAAG,uBAAuB,CAAC;QAClD,WAAW,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,EAAE,aAAa,CAAC,CAAC;QACzD,IAAI,CAAC,UAAU,CAAC,eAAe,CAAC,WAAW,CAAC,CAAC;QAE7C,MAAM,OAAO,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,OAAO,KAAK,IAAI;YAAE,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;QAChF,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,MAAM,CAAC;YAC7B,MAAM;YACN,aAAa,EAAE,OAAO;YACtB,SAAS,EAAE,WAAW,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC,CAAC;YACjF,QAAQ;SACT,CAAC,CAAC;QACH,IAAI,CAAC,UAAU,GAAG,IAAI,SAAS,CAAC;YAC9B,SAAS,EAAE,aAAa;YACxB,iBAAiB,EAAE,IAAI,CAAC,iBAAiB,EAAE;YAC3C,QAAQ;SACT,CAAC,CAAC;QACH,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QACxE,MAAM,mBAAmB,CAAC,IAAI,EAAE,QAAQ,EAAE,aAAa,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC;IACjF,CAAC;IAED,cAAc;QACZ,IAAI,aAAa,GAAG,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC;QAChD,IAAI,CAAC,eAAe,GAAG,IAAI,cAAc,CAAC,GAAG,EAAE;YAC7C,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC;YAC1C,IAAI,KAAK,KAAK,aAAa;gBAAE,OAAO;YACpC,aAAa,GAAG,KAAK,CAAC;YACtB,IAAI,IAAI,CAAC,YAAY,KAAK,SAAS;gBAAE,oBAAoB,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YAC7E,IAAI,CAAC,YAAY,GAAG,qBAAqB,CAAC,GAAG,EAAE;gBAC7C,KAAK,IAAI,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;oBAC/C,IAAI,CAAC,CAAC,KAAK,YAAY,2BAA2B,CAAC;wBAAE,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;gBAClF,CAAC,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAChD,CAAC;CACF;AAED,SAAS,eAAe;IACtB,MAAM,IAAI,GAAG,IAAI,MAAM,CAAC,IAAI,GAAG,CAAC,qCAAqC,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE;QACvF,IAAI,EAAE,QAAQ;KACf,CAAC,CAAC;IACH,OAAO,SAAS,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;AACpC,CAAC;AAED,KAAK,UAAU,mBAAmB,CAChC,IAAsD,EACtD,QAAqB,EACrB,SAAsB,EACtB,cAAqC;IAErC,MAAM,WAAW,GAAY,MAAM,IAAI,CAAC,cAAc,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;IAC9E,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC;QAAE,OAAO;IACxC,KAAK,MAAM,UAAU,IAAI,WAAwB,EAAE,CAAC;QAClD,IAAI,CAAC,iBAAiB,CAAC,UAAU,CAAC;YAAE,SAAS;QAC7C,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,GAAG,QAAQ,CAAC,sBAAsB,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAGzF,CAAC;QACF,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAC,sBAAsB,CACrD,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAClB,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CACC,CAAC;QACtB,MAAM,IAAI,GAAG,QAAQ,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;QACzC,IAAI,CAAC,YAAY,CAAC,YAAY,EAAE,UAAU,CAAC,QAAQ,EAAE,GAAG,IAAI,UAAU,CAAC,GAAG,CAAC,CAAC;QAC5E,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC;QAChB,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,gBAAgB,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC;QACrF,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC;QACjD,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC;QAClD,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;YACvC,KAAK,CAAC,cAAc,EAAE,CAAC;YACvB,cAAc,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;QACjC,CAAC,CAAC,CAAC;QACH,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;AACH,CAAC;AAMD,SAAS,iBAAiB,CAAC,UAAmB;IAK5C,OAAO,CACL,OAAO,UAAU,KAAK,QAAQ;QAC9B,UAAU,KAAK,IAAI;QACnB,KAAK,IAAI,UAAU;QACnB,MAAM,IAAI,UAAU;QACpB,OAAO,UAAU,CAAC,GAAG,KAAK,QAAQ;QAClC,4BAA4B,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QACjD,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC;QAC9B,UAAU,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC;QAC5B,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAC5D,CAAC;AACJ,CAAC;AAED,SAAS,kBAAkB,CAAC,QAA4B,EAAE,SAAiB;IACzE,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC;IAClD,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACrC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACjF,CAAC","sourcesContent":["import {\n getDocument,\n InvalidPDFException,\n PasswordException,\n type PDFDocumentLoadingTask,\n type PDFDocumentProxy,\n PDFWorker,\n RenderingCancelledException,\n type RenderTask,\n TextLayer,\n} from 'pdfjs-dist';\n\nimport type { ReaderDriver, ReaderDriverState } from '../../../types/reader.js';\nimport { ReaderDocumentError } from '../errors/ReaderDocumentError.js';\n\nexport class PdfReaderDriver implements ReaderDriver {\n readonly direction = 'ltr' as const;\n readonly #container: HTMLElement;\n readonly #document: PDFDocumentProxy;\n readonly #loadingTask: PDFDocumentLoadingTask;\n readonly #onExternalLink: (url: string) => void;\n readonly #onRenderError: (error: unknown) => void;\n readonly #worker: PDFWorker;\n #page: number;\n #renderTask?: RenderTask;\n #resizeFrame?: number;\n #resizeObserver?: ResizeObserver;\n #textLayer?: TextLayer;\n\n private constructor(args: {\n container: HTMLElement;\n document: PDFDocumentProxy;\n initialPage: number;\n loadingTask: PDFDocumentLoadingTask;\n onExternalLink: (url: string) => void;\n onRenderError: (error: unknown) => void;\n worker: PDFWorker;\n }) {\n this.#container = args.container;\n this.#document = args.document;\n this.#loadingTask = args.loadingTask;\n this.#onExternalLink = args.onExternalLink;\n this.#onRenderError = args.onRenderError;\n this.#worker = args.worker;\n this.#page = args.initialPage;\n }\n\n static async create(args: {\n bytes: Uint8Array;\n container: HTMLElement;\n initialLocation?: string;\n onExternalLink: (url: string) => void;\n onRenderError: (error: unknown) => void;\n }): Promise<PdfReaderDriver> {\n const worker = createPdfWorker();\n const loadingTask = getDocument({\n data: args.bytes,\n stopAtErrors: true,\n worker,\n });\n try {\n const document = await loadingTask.promise;\n const driver = new PdfReaderDriver({\n container: args.container,\n document,\n initialPage: resolveInitialPage(args.initialLocation, document.numPages),\n loadingTask,\n onExternalLink: args.onExternalLink,\n onRenderError: args.onRenderError,\n worker,\n });\n await driver.#renderPage();\n driver.#observeResize();\n return driver;\n } catch (error) {\n await loadingTask.destroy();\n worker.destroy();\n if (error instanceof PasswordException) {\n throw new ReaderDocumentError(\n 'protected-document',\n 'This PDF is password protected and cannot be opened.',\n );\n }\n if (error instanceof InvalidPDFException) {\n throw new ReaderDocumentError('invalid-document', 'The PDF document is invalid.');\n }\n throw error;\n }\n }\n\n getState(): ReaderDriverState {\n return {\n canGoNext: this.#page < this.#document.numPages,\n canGoPrevious: this.#page > 1,\n locator: `page=${this.#page}`,\n page: this.#page,\n pageCount: this.#document.numPages,\n progression:\n this.#document.numPages <= 1 ? 1 : (this.#page - 1) / (this.#document.numPages - 1),\n };\n }\n\n async goNext(): Promise<void> {\n if (this.#page >= this.#document.numPages) return;\n this.#page += 1;\n await this.#renderPage();\n }\n\n async goPrevious(): Promise<void> {\n if (this.#page <= 1) return;\n this.#page -= 1;\n await this.#renderPage();\n }\n\n async destroy(): Promise<void> {\n this.#renderTask?.cancel();\n this.#resizeObserver?.disconnect();\n if (this.#resizeFrame !== undefined) cancelAnimationFrame(this.#resizeFrame);\n this.#textLayer?.cancel();\n TextLayer.cleanup();\n await this.#loadingTask.destroy();\n this.#worker.destroy();\n this.#container.replaceChildren();\n }\n\n async #renderPage(): Promise<void> {\n this.#renderTask?.cancel();\n this.#textLayer?.cancel();\n const page = await this.#document.getPage(this.#page);\n const baseViewport = page.getViewport({ scale: 1 });\n const availableWidth = Math.max(1, this.#container.clientWidth);\n const viewport = page.getViewport({ scale: availableWidth / baseViewport.width });\n const pageElement = document.createElement('div');\n const canvas = document.createElement('canvas');\n const textContainer = document.createElement('div');\n const linkContainer = document.createElement('div');\n const outputScale = window.devicePixelRatio || 1;\n\n pageElement.className = 'reader-pdf-page';\n pageElement.style.setProperty('--scale-factor', String(viewport.scale));\n pageElement.style.width = `${viewport.width}px`;\n pageElement.style.height = `${viewport.height}px`;\n canvas.width = Math.floor(viewport.width * outputScale);\n canvas.height = Math.floor(viewport.height * outputScale);\n canvas.style.width = `${viewport.width}px`;\n canvas.style.height = `${viewport.height}px`;\n textContainer.className = 'reader-pdf-text-layer';\n textContainer.style.width = `${viewport.width}px`;\n textContainer.style.height = `${viewport.height}px`;\n linkContainer.className = 'reader-pdf-link-layer';\n pageElement.append(canvas, textContainer, linkContainer);\n this.#container.replaceChildren(pageElement);\n\n const context = canvas.getContext('2d');\n if (context === null) throw new Error('The PDF canvas context is unavailable.');\n this.#renderTask = page.render({\n canvas,\n canvasContext: context,\n transform: outputScale === 1 ? undefined : [outputScale, 0, 0, outputScale, 0, 0],\n viewport,\n });\n this.#textLayer = new TextLayer({\n container: textContainer,\n textContentSource: page.streamTextContent(),\n viewport,\n });\n await Promise.all([this.#renderTask.promise, this.#textLayer.render()]);\n await renderExternalLinks(page, viewport, linkContainer, this.#onExternalLink);\n }\n\n #observeResize(): void {\n let previousWidth = this.#container.clientWidth;\n this.#resizeObserver = new ResizeObserver(() => {\n const width = this.#container.clientWidth;\n if (width === previousWidth) return;\n previousWidth = width;\n if (this.#resizeFrame !== undefined) cancelAnimationFrame(this.#resizeFrame);\n this.#resizeFrame = requestAnimationFrame(() => {\n void this.#renderPage().catch((error: unknown) => {\n if (!(error instanceof RenderingCancelledException)) this.#onRenderError(error);\n });\n });\n });\n this.#resizeObserver.observe(this.#container);\n }\n}\n\nfunction createPdfWorker(): PDFWorker {\n const port = new Worker(new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url), {\n type: 'module',\n });\n return PDFWorker.create({ port });\n}\n\nasync function renderExternalLinks(\n page: Awaited<ReturnType<PDFDocumentProxy['getPage']>>,\n viewport: PdfViewport,\n container: HTMLElement,\n onExternalLink: (url: string) => void,\n): Promise<void> {\n const annotations: unknown = await page.getAnnotations({ intent: 'display' });\n if (!Array.isArray(annotations)) return;\n for (const annotation of annotations as unknown[]) {\n if (!isExternalPdfLink(annotation)) continue;\n const [left, top] = viewport.convertToViewportPoint(annotation.rect[0], annotation.rect[1]) as [\n number,\n number,\n ];\n const [right, bottom] = viewport.convertToViewportPoint(\n annotation.rect[2],\n annotation.rect[3],\n ) as [number, number];\n const link = document.createElement('a');\n link.setAttribute('aria-label', annotation.titleObj?.str ?? annotation.url);\n link.href = '#';\n link.style.inset = `${Math.min(bottom, top)}px auto auto ${Math.min(left, right)}px`;\n link.style.width = `${Math.abs(right - left)}px`;\n link.style.height = `${Math.abs(top - bottom)}px`;\n link.addEventListener('click', (event) => {\n event.preventDefault();\n onExternalLink(annotation.url);\n });\n container.append(link);\n }\n}\n\ninterface PdfViewport {\n convertToViewportPoint(x: number, y: number): number[];\n}\n\nfunction isExternalPdfLink(annotation: unknown): annotation is Record<string, unknown> & {\n rect: [number, number, number, number];\n titleObj?: { str?: string };\n url: string;\n} {\n return (\n typeof annotation === 'object' &&\n annotation !== null &&\n 'url' in annotation &&\n 'rect' in annotation &&\n typeof annotation.url === 'string' &&\n /^(?:https?:|mailto:|tel:)/i.test(annotation.url) &&\n Array.isArray(annotation.rect) &&\n annotation.rect.length === 4 &&\n annotation.rect.every((value) => typeof value === 'number')\n );\n}\n\nfunction resolveInitialPage(location: string | undefined, pageCount: number): number {\n const match = /^page=(\\d+)$/.exec(location ?? '');\n const page = Number(match?.[1] ?? 1);\n return Math.max(1, Math.min(pageCount, Number.isSafeInteger(page) ? page : 1));\n}\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare class EpubArchive {
|
|
2
|
+
#private;
|
|
3
|
+
private constructor();
|
|
4
|
+
static open(bytes: Uint8Array, signal: AbortSignal): Promise<EpubArchive>;
|
|
5
|
+
paths(): readonly string[];
|
|
6
|
+
has(path: string): boolean;
|
|
7
|
+
read(path: string, signal?: AbortSignal): Promise<Uint8Array>;
|
|
8
|
+
readText(path: string, signal?: AbortSignal): Promise<string>;
|
|
9
|
+
close(): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=EpubArchive.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EpubArchive.d.ts","sourceRoot":"","sources":["../../../../src/features/reader/epub/EpubArchive.ts"],"names":[],"mappings":"AAYA,qBAAa,WAAW;;IAItB,OAAO;WAKM,IAAI,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;IAkB/E,KAAK,IAAI,SAAS,MAAM,EAAE;IAI1B,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAIpB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;IAY7D,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC;IAI7D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Uint8ArrayReader, Uint8ArrayWriter, ZipReader, } from '@zip.js/zip.js';
|
|
2
|
+
import { ReaderDocumentError } from '../errors/ReaderDocumentError.js';
|
|
3
|
+
import { normalizeArchivePath } from './path.js';
|
|
4
|
+
import { validateEpubArchiveEntriesAsync } from './validateEpubArchiveEntriesAsync.js';
|
|
5
|
+
export class EpubArchive {
|
|
6
|
+
#entries;
|
|
7
|
+
#reader;
|
|
8
|
+
constructor(reader, entries) {
|
|
9
|
+
this.#reader = reader;
|
|
10
|
+
this.#entries = createEntryMap(entries);
|
|
11
|
+
}
|
|
12
|
+
static async open(bytes, signal) {
|
|
13
|
+
const reader = new ZipReader(new Uint8ArrayReader(bytes), { strictness: 'strict' });
|
|
14
|
+
try {
|
|
15
|
+
const entries = await reader.getEntries({ strictness: 'strict' });
|
|
16
|
+
await validateEpubArchiveEntriesAsync(entries, signal);
|
|
17
|
+
if (entries.some((entry) => !entry.directory && entry.encrypted)) {
|
|
18
|
+
throw new ReaderDocumentError('protected-document', 'This EPUB uses content protection and cannot be opened.');
|
|
19
|
+
}
|
|
20
|
+
return new EpubArchive(reader, entries);
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
await reader.close();
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
paths() {
|
|
28
|
+
return [...this.#entries.keys()];
|
|
29
|
+
}
|
|
30
|
+
has(path) {
|
|
31
|
+
return this.#entries.has(normalizeArchivePath(path));
|
|
32
|
+
}
|
|
33
|
+
async read(path, signal) {
|
|
34
|
+
const normalizedPath = normalizeArchivePath(path);
|
|
35
|
+
const entry = this.#entries.get(normalizedPath);
|
|
36
|
+
if (entry === undefined)
|
|
37
|
+
throw new Error(`Missing EPUB resource: ${normalizedPath}`);
|
|
38
|
+
return entry.getData(new Uint8ArrayWriter(), {
|
|
39
|
+
checkCrc32: true,
|
|
40
|
+
checkOverlappingEntry: true,
|
|
41
|
+
...(signal === undefined ? {} : { signal }),
|
|
42
|
+
strictness: 'strict',
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
async readText(path, signal) {
|
|
46
|
+
return new TextDecoder().decode(await this.read(path, signal));
|
|
47
|
+
}
|
|
48
|
+
async close() {
|
|
49
|
+
await this.#reader.close();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function createEntryMap(entries) {
|
|
53
|
+
const files = new Map();
|
|
54
|
+
for (const entry of entries) {
|
|
55
|
+
if (entry.directory)
|
|
56
|
+
continue;
|
|
57
|
+
const normalizedPath = normalizeArchivePath(entry.filename);
|
|
58
|
+
if (files.has(normalizedPath)) {
|
|
59
|
+
throw new ReaderDocumentError('invalid-document', `The EPUB contains duplicate resource path "${normalizedPath}".`);
|
|
60
|
+
}
|
|
61
|
+
files.set(normalizedPath, entry);
|
|
62
|
+
}
|
|
63
|
+
return files;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=EpubArchive.js.map
|