@ankhorage/expo-runtime 3.1.1 → 3.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/README.md +2 -1
- package/dist/barcodeScanner.d.ts +2 -0
- package/dist/barcodeScanner.d.ts.map +1 -0
- package/dist/barcodeScanner.js +2 -0
- package/dist/barcodeScanner.js.map +1 -0
- package/dist/createExpoRuntimeRegistry.d.ts.map +1 -1
- package/dist/createExpoRuntimeRegistry.js +5 -0
- package/dist/createExpoRuntimeRegistry.js.map +1 -1
- package/dist/expoRuntimePlanningMetadata.d.ts +9 -1
- package/dist/expoRuntimePlanningMetadata.d.ts.map +1 -1
- package/dist/expoRuntimePlanningMetadata.js +8 -0
- package/dist/expoRuntimePlanningMetadata.js.map +1 -1
- package/dist/generatedSources.d.ts +1 -0
- package/dist/generatedSources.d.ts.map +1 -1
- package/dist/generatedSources.js +9 -1
- package/dist/generatedSources.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/planning.d.ts +1 -1
- package/dist/planning.d.ts.map +1 -1
- package/dist/planning.js +1 -1
- package/dist/planning.js.map +1 -1
- package/dist/reader/ExpoReaderSurfaceAdapter.d.ts +4 -0
- package/dist/reader/ExpoReaderSurfaceAdapter.d.ts.map +1 -0
- package/dist/reader/ExpoReaderSurfaceAdapter.js +102 -0
- package/dist/reader/ExpoReaderSurfaceAdapter.js.map +1 -0
- package/dist/reader/ReaderDocumentError.d.ts +6 -0
- package/dist/reader/ReaderDocumentError.d.ts.map +1 -0
- package/dist/reader/ReaderDocumentError.js +9 -0
- package/dist/reader/ReaderDocumentError.js.map +1 -0
- package/dist/reader/ReaderViewport.d.ts +4 -0
- package/dist/reader/ReaderViewport.d.ts.map +1 -0
- package/dist/reader/ReaderViewport.js +212 -0
- package/dist/reader/ReaderViewport.js.map +1 -0
- package/dist/reader/drivers/EpubReaderDriver.d.ts +20 -0
- package/dist/reader/drivers/EpubReaderDriver.d.ts.map +1 -0
- package/dist/reader/drivers/EpubReaderDriver.js +259 -0
- package/dist/reader/drivers/EpubReaderDriver.js.map +1 -0
- package/dist/reader/drivers/PdfReaderDriver.d.ts +18 -0
- package/dist/reader/drivers/PdfReaderDriver.d.ts.map +1 -0
- package/dist/reader/drivers/PdfReaderDriver.js +198 -0
- package/dist/reader/drivers/PdfReaderDriver.js.map +1 -0
- package/dist/reader/epub/EpubArchive.d.ts +11 -0
- package/dist/reader/epub/EpubArchive.d.ts.map +1 -0
- package/dist/reader/epub/EpubArchive.js +65 -0
- package/dist/reader/epub/EpubArchive.js.map +1 -0
- package/dist/reader/epub/EpubArchiveFetcher.d.ts +11 -0
- package/dist/reader/epub/EpubArchiveFetcher.d.ts.map +1 -0
- package/dist/reader/epub/EpubArchiveFetcher.js +44 -0
- package/dist/reader/epub/EpubArchiveFetcher.js.map +1 -0
- package/dist/reader/epub/mediaTypes.d.ts +3 -0
- package/dist/reader/epub/mediaTypes.d.ts.map +1 -0
- package/dist/reader/epub/mediaTypes.js +26 -0
- package/dist/reader/epub/mediaTypes.js.map +1 -0
- package/dist/reader/epub/parseEpubPackage.d.ts +22 -0
- package/dist/reader/epub/parseEpubPackage.d.ts.map +1 -0
- package/dist/reader/epub/parseEpubPackage.js +98 -0
- package/dist/reader/epub/parseEpubPackage.js.map +1 -0
- package/dist/reader/epub/path.d.ts +4 -0
- package/dist/reader/epub/path.d.ts.map +1 -0
- package/dist/reader/epub/path.js +28 -0
- package/dist/reader/epub/path.js.map +1 -0
- package/dist/reader/epub/sanitizeEpubDocument.d.ts +3 -0
- package/dist/reader/epub/sanitizeEpubDocument.d.ts.map +1 -0
- package/dist/reader/epub/sanitizeEpubDocument.js +90 -0
- package/dist/reader/epub/sanitizeEpubDocument.js.map +1 -0
- package/dist/reader/index.d.ts +2 -0
- package/dist/reader/index.d.ts.map +1 -0
- package/dist/reader/index.js +2 -0
- package/dist/reader/index.js.map +1 -0
- package/dist/reader/loadReaderSourceAsync.d.ts +2 -0
- package/dist/reader/loadReaderSourceAsync.d.ts.map +1 -0
- package/dist/reader/loadReaderSourceAsync.js +49 -0
- package/dist/reader/loadReaderSourceAsync.js.map +1 -0
- package/dist/reader/readerViewportPresentation.d.ts +4 -0
- package/dist/reader/readerViewportPresentation.d.ts.map +1 -0
- package/dist/reader/readerViewportPresentation.js +26 -0
- package/dist/reader/readerViewportPresentation.js.map +1 -0
- package/dist/reader/types.d.ts +57 -0
- package/dist/reader/types.d.ts.map +1 -0
- package/dist/reader/types.js +2 -0
- package/dist/reader/types.js.map +1 -0
- package/dist/reader/validateEpubArchiveEntriesAsync.d.ts +3 -0
- package/dist/reader/validateEpubArchiveEntriesAsync.d.ts.map +1 -0
- package/dist/reader/validateEpubArchiveEntriesAsync.js +48 -0
- package/dist/reader/validateEpubArchiveEntriesAsync.js.map +1 -0
- package/package.json +25 -4
- package/src/adapterEntrypoints.test.ts +48 -0
- package/src/barcodeScanner.ts +1 -0
- package/src/createExpoRuntimeRegistry.test.tsx +9 -1
- package/src/createExpoRuntimeRegistry.tsx +7 -1
- package/src/expoRuntimePlanningMetadata.ts +9 -1
- package/src/generatedSources.test.ts +11 -2
- package/src/generatedSources.ts +11 -1
- package/src/index.ts +5 -1
- package/src/planning.ts +4 -1
- package/src/reader/ExpoReaderSurfaceAdapter.tsx +170 -0
- package/src/reader/ReaderDocumentError.ts +11 -0
- package/src/reader/ReaderViewport.tsx +319 -0
- package/src/reader/drivers/EpubReaderDriver.ts +309 -0
- package/src/reader/drivers/PdfReaderDriver.ts +253 -0
- package/src/reader/epub/EpubArchive.ts +83 -0
- package/src/reader/epub/EpubArchiveFetcher.ts +58 -0
- package/src/reader/epub/mediaTypes.ts +16 -0
- package/src/reader/epub/parseEpubPackage.test.ts +116 -0
- package/src/reader/epub/parseEpubPackage.ts +152 -0
- package/src/reader/epub/path.test.ts +22 -0
- package/src/reader/epub/path.ts +26 -0
- package/src/reader/epub/sanitizeEpubDocument.ts +121 -0
- package/src/reader/index.ts +1 -0
- package/src/reader/loadReaderSourceAsync.test.ts +52 -0
- package/src/reader/loadReaderSourceAsync.ts +59 -0
- package/src/reader/readerViewportPresentation.ts +28 -0
- package/src/reader/rendererPackages.test.ts +3 -1
- package/src/reader/types.ts +61 -0
- package/src/reader/validateEpubArchiveEntriesAsync.test.ts +142 -0
- package/src/reader/validateEpubArchiveEntriesAsync.ts +73 -0
- package/src/resolveExpoRuntimePlan.test.ts +36 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { getDocument, InvalidPDFException, PasswordException, PDFWorker, RenderingCancelledException, TextLayer, } from 'pdfjs-dist';
|
|
2
|
+
import { ReaderDocumentError } from '../ReaderDocumentError';
|
|
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/reader/drivers/PdfReaderDriver.ts"],"names":[],"mappings":"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","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 { ReaderDocumentError } from '../ReaderDocumentError';\nimport type { ReaderDriver, ReaderDriverState } from '../types';\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/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 '../ReaderDocumentError';
|
|
3
|
+
import { validateEpubArchiveEntriesAsync } from '../validateEpubArchiveEntriesAsync';
|
|
4
|
+
import { normalizeArchivePath } from './path';
|
|
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,
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EpubArchive.js","sourceRoot":"","sources":["../../../src/reader/epub/EpubArchive.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type Entry,\n type FileEntry,\n Uint8ArrayReader,\n Uint8ArrayWriter,\n ZipReader,\n} from '@zip.js/zip.js';\n\nimport { ReaderDocumentError } from '../ReaderDocumentError';\nimport { validateEpubArchiveEntriesAsync } from '../validateEpubArchiveEntriesAsync';\nimport { normalizeArchivePath } from './path';\n\nexport class EpubArchive {\n readonly #entries: Map<string, FileEntry>;\n readonly #reader: ZipReader<Uint8Array>;\n\n private constructor(reader: ZipReader<Uint8Array>, entries: readonly Entry[]) {\n this.#reader = reader;\n this.#entries = createEntryMap(entries);\n }\n\n static async open(bytes: Uint8Array, signal: AbortSignal): Promise<EpubArchive> {\n const reader = new ZipReader(new Uint8ArrayReader(bytes), { strictness: 'strict' });\n try {\n const entries = await reader.getEntries({ strictness: 'strict' });\n await validateEpubArchiveEntriesAsync(entries, signal);\n if (entries.some((entry) => !entry.directory && entry.encrypted)) {\n throw new ReaderDocumentError(\n 'protected-document',\n 'This EPUB uses content protection and cannot be opened.',\n );\n }\n return new EpubArchive(reader, entries);\n } catch (error) {\n await reader.close();\n throw error;\n }\n }\n\n paths(): readonly string[] {\n return [...this.#entries.keys()];\n }\n\n has(path: string): boolean {\n return this.#entries.has(normalizeArchivePath(path));\n }\n\n async read(path: string, signal?: AbortSignal): Promise<Uint8Array> {\n const normalizedPath = normalizeArchivePath(path);\n const entry = this.#entries.get(normalizedPath);\n if (entry === undefined) throw new Error(`Missing EPUB resource: ${normalizedPath}`);\n return entry.getData(new Uint8ArrayWriter(), {\n checkCrc32: true,\n checkOverlappingEntry: true,\n signal,\n strictness: 'strict',\n });\n }\n\n async readText(path: string, signal?: AbortSignal): Promise<string> {\n return new TextDecoder().decode(await this.read(path, signal));\n }\n\n async close(): Promise<void> {\n await this.#reader.close();\n }\n}\n\nfunction createEntryMap(entries: readonly Entry[]): Map<string, FileEntry> {\n const files = new Map<string, FileEntry>();\n for (const entry of entries) {\n if (entry.directory) continue;\n const normalizedPath = normalizeArchivePath(entry.filename);\n if (files.has(normalizedPath)) {\n throw new ReaderDocumentError(\n 'invalid-document',\n `The EPUB contains duplicate resource path \"${normalizedPath}\".`,\n );\n }\n files.set(normalizedPath, entry);\n }\n return files;\n}\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Link } from '@readium/shared';
|
|
2
|
+
import { type Fetcher, Resource } from '@readium/shared';
|
|
3
|
+
import type { EpubArchive } from './EpubArchive';
|
|
4
|
+
export declare class EpubArchiveFetcher implements Fetcher {
|
|
5
|
+
#private;
|
|
6
|
+
constructor(archive: EpubArchive, links: readonly Link[], overrides: ReadonlyMap<string, Uint8Array>);
|
|
7
|
+
links(): Link[];
|
|
8
|
+
get(link: Link): Resource;
|
|
9
|
+
close(): void;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=EpubArchiveFetcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EpubArchiveFetcher.d.ts","sourceRoot":"","sources":["../../../src/reader/epub/EpubArchiveFetcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,KAAK,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAEzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD,qBAAa,kBAAmB,YAAW,OAAO;;gBAM9C,OAAO,EAAE,WAAW,EACpB,KAAK,EAAE,SAAS,IAAI,EAAE,EACtB,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC;IAO5C,KAAK,IAAI,IAAI,EAAE;IAIf,GAAG,CAAC,IAAI,EAAE,IAAI,GAAG,QAAQ;IAIzB,KAAK,IAAI,IAAI;CAGd"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Resource } from '@readium/shared';
|
|
2
|
+
export class EpubArchiveFetcher {
|
|
3
|
+
#archive;
|
|
4
|
+
#links;
|
|
5
|
+
#overrides;
|
|
6
|
+
constructor(archive, links, overrides) {
|
|
7
|
+
this.#archive = archive;
|
|
8
|
+
this.#links = [...links];
|
|
9
|
+
this.#overrides = overrides;
|
|
10
|
+
}
|
|
11
|
+
links() {
|
|
12
|
+
return [...this.#links];
|
|
13
|
+
}
|
|
14
|
+
get(link) {
|
|
15
|
+
return new EpubArchiveResource(this.#archive, link, this.#overrides.get(link.href));
|
|
16
|
+
}
|
|
17
|
+
close() {
|
|
18
|
+
void this.#archive.close();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
class EpubArchiveResource extends Resource {
|
|
22
|
+
archive;
|
|
23
|
+
resourceLink;
|
|
24
|
+
override;
|
|
25
|
+
constructor(archive, resourceLink, override) {
|
|
26
|
+
super();
|
|
27
|
+
this.archive = archive;
|
|
28
|
+
this.resourceLink = resourceLink;
|
|
29
|
+
this.override = override;
|
|
30
|
+
}
|
|
31
|
+
link() {
|
|
32
|
+
return Promise.resolve(this.resourceLink);
|
|
33
|
+
}
|
|
34
|
+
async length() {
|
|
35
|
+
return (this.override ?? (await this.archive.read(this.resourceLink.href))).byteLength;
|
|
36
|
+
}
|
|
37
|
+
async read() {
|
|
38
|
+
return this.override ?? this.archive.read(this.resourceLink.href);
|
|
39
|
+
}
|
|
40
|
+
close() {
|
|
41
|
+
// Archive lifetime is owned by the driver.
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=EpubArchiveFetcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EpubArchiveFetcher.js","sourceRoot":"","sources":["../../../src/reader/epub/EpubArchiveFetcher.ts"],"names":[],"mappings":"AACA,OAAO,EAAgB,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAIzD,MAAM,OAAO,kBAAkB;IACpB,QAAQ,CAAc;IACtB,MAAM,CAAS;IACf,UAAU,CAAkC;IAErD,YACE,OAAoB,EACpB,KAAsB,EACtB,SAA0C;QAE1C,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;QACxB,IAAI,CAAC,MAAM,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC;QACzB,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;IAC9B,CAAC;IAED,KAAK;QACH,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;IAC1B,CAAC;IAED,GAAG,CAAC,IAAU;QACZ,OAAO,IAAI,mBAAmB,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACtF,CAAC;IAED,KAAK;QACH,KAAK,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;IAC7B,CAAC;CACF;AAED,MAAM,mBAAoB,SAAQ,QAAQ;IAE7B;IACA;IACA;IAHX,YACW,OAAoB,EACpB,YAAkB,EAClB,QAAgC;QAEzC,KAAK,EAAE,CAAC;QAJC,YAAO,GAAP,OAAO,CAAa;QACpB,iBAAY,GAAZ,YAAY,CAAM;QAClB,aAAQ,GAAR,QAAQ,CAAwB;IAG3C,CAAC;IAED,IAAI;QACF,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAC5C,CAAC;IAED,KAAK,CAAC,MAAM;QACV,OAAO,CAAC,IAAI,CAAC,QAAQ,IAAI,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;IACzF,CAAC;IAED,KAAK,CAAC,IAAI;QACR,OAAO,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IACpE,CAAC;IAED,KAAK;QACH,2CAA2C;IAC7C,CAAC;CACF","sourcesContent":["import type { Link } from '@readium/shared';\nimport { type Fetcher, Resource } from '@readium/shared';\n\nimport type { EpubArchive } from './EpubArchive';\n\nexport class EpubArchiveFetcher implements Fetcher {\n readonly #archive: EpubArchive;\n readonly #links: Link[];\n readonly #overrides: ReadonlyMap<string, Uint8Array>;\n\n constructor(\n archive: EpubArchive,\n links: readonly Link[],\n overrides: ReadonlyMap<string, Uint8Array>,\n ) {\n this.#archive = archive;\n this.#links = [...links];\n this.#overrides = overrides;\n }\n\n links(): Link[] {\n return [...this.#links];\n }\n\n get(link: Link): Resource {\n return new EpubArchiveResource(this.#archive, link, this.#overrides.get(link.href));\n }\n\n close(): void {\n void this.#archive.close();\n }\n}\n\nclass EpubArchiveResource extends Resource {\n constructor(\n readonly archive: EpubArchive,\n readonly resourceLink: Link,\n readonly override: Uint8Array | undefined,\n ) {\n super();\n }\n\n link(): Promise<Link> {\n return Promise.resolve(this.resourceLink);\n }\n\n async length(): Promise<number | undefined> {\n return (this.override ?? (await this.archive.read(this.resourceLink.href))).byteLength;\n }\n\n async read(): Promise<Uint8Array | undefined> {\n return this.override ?? this.archive.read(this.resourceLink.href);\n }\n\n close(): void {\n // Archive lifetime is owned by the driver.\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mediaTypes.d.ts","sourceRoot":"","sources":["../../../src/reader/epub/mediaTypes.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,gBAAgB,aAAkD,CAAC;AAEhF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAavD"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const HTML_MEDIA_TYPES = new Set(['application/xhtml+xml', 'text/html']);
|
|
2
|
+
export function inferEpubMediaType(path) {
|
|
3
|
+
const extension = path.split('.').pop()?.toLowerCase() ?? '';
|
|
4
|
+
if (extension === 'css')
|
|
5
|
+
return 'text/css';
|
|
6
|
+
if (extension === 'gif')
|
|
7
|
+
return 'image/gif';
|
|
8
|
+
if (extension === 'html')
|
|
9
|
+
return 'text/html';
|
|
10
|
+
if (extension === 'jpeg' || extension === 'jpg')
|
|
11
|
+
return 'image/jpeg';
|
|
12
|
+
if (extension === 'png')
|
|
13
|
+
return 'image/png';
|
|
14
|
+
if (extension === 'svg')
|
|
15
|
+
return 'image/svg+xml';
|
|
16
|
+
if (extension === 'ttf')
|
|
17
|
+
return 'font/ttf';
|
|
18
|
+
if (extension === 'woff')
|
|
19
|
+
return 'font/woff';
|
|
20
|
+
if (extension === 'woff2')
|
|
21
|
+
return 'font/woff2';
|
|
22
|
+
if (extension === 'xhtml')
|
|
23
|
+
return 'application/xhtml+xml';
|
|
24
|
+
return 'application/octet-stream';
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=mediaTypes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mediaTypes.js","sourceRoot":"","sources":["../../../src/reader/epub/mediaTypes.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,CAAC,uBAAuB,EAAE,WAAW,CAAC,CAAC,CAAC;AAEhF,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;IAC7D,IAAI,SAAS,KAAK,KAAK;QAAE,OAAO,UAAU,CAAC;IAC3C,IAAI,SAAS,KAAK,KAAK;QAAE,OAAO,WAAW,CAAC;IAC5C,IAAI,SAAS,KAAK,MAAM;QAAE,OAAO,WAAW,CAAC;IAC7C,IAAI,SAAS,KAAK,MAAM,IAAI,SAAS,KAAK,KAAK;QAAE,OAAO,YAAY,CAAC;IACrE,IAAI,SAAS,KAAK,KAAK;QAAE,OAAO,WAAW,CAAC;IAC5C,IAAI,SAAS,KAAK,KAAK;QAAE,OAAO,eAAe,CAAC;IAChD,IAAI,SAAS,KAAK,KAAK;QAAE,OAAO,UAAU,CAAC;IAC3C,IAAI,SAAS,KAAK,MAAM;QAAE,OAAO,WAAW,CAAC;IAC7C,IAAI,SAAS,KAAK,OAAO;QAAE,OAAO,YAAY,CAAC;IAC/C,IAAI,SAAS,KAAK,OAAO;QAAE,OAAO,uBAAuB,CAAC;IAC1D,OAAO,0BAA0B,CAAC;AACpC,CAAC","sourcesContent":["export const HTML_MEDIA_TYPES = new Set(['application/xhtml+xml', 'text/html']);\n\nexport function inferEpubMediaType(path: string): string {\n const extension = path.split('.').pop()?.toLowerCase() ?? '';\n if (extension === 'css') return 'text/css';\n if (extension === 'gif') return 'image/gif';\n if (extension === 'html') return 'text/html';\n if (extension === 'jpeg' || extension === 'jpg') return 'image/jpeg';\n if (extension === 'png') return 'image/png';\n if (extension === 'svg') return 'image/svg+xml';\n if (extension === 'ttf') return 'font/ttf';\n if (extension === 'woff') return 'font/woff';\n if (extension === 'woff2') return 'font/woff2';\n if (extension === 'xhtml') return 'application/xhtml+xml';\n return 'application/octet-stream';\n}\n"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { EpubArchive } from './EpubArchive';
|
|
2
|
+
interface EpubPackageItem {
|
|
3
|
+
readonly href: string;
|
|
4
|
+
readonly mediaType: string;
|
|
5
|
+
readonly properties: readonly string[];
|
|
6
|
+
}
|
|
7
|
+
export interface ParsedEpubPackage {
|
|
8
|
+
readonly direction: 'ltr' | 'rtl';
|
|
9
|
+
readonly fixedLayout: boolean;
|
|
10
|
+
readonly language?: string;
|
|
11
|
+
readonly packagePath: string;
|
|
12
|
+
readonly readingOrder: readonly EpubPackageItem[];
|
|
13
|
+
readonly resources: readonly EpubPackageItem[];
|
|
14
|
+
readonly tableOfContents: readonly {
|
|
15
|
+
readonly href: string;
|
|
16
|
+
readonly title: string;
|
|
17
|
+
}[];
|
|
18
|
+
readonly title: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function parseEpubPackage(archive: EpubArchive, signal: AbortSignal): Promise<ParsedEpubPackage>;
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=parseEpubPackage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parseEpubPackage.d.ts","sourceRoot":"","sources":["../../../src/reader/epub/parseEpubPackage.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAGjD,UAAU,eAAe;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,SAAS,EAAE,KAAK,GAAG,KAAK,CAAC;IAClC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,SAAS,eAAe,EAAE,CAAC;IAClD,QAAQ,CAAC,SAAS,EAAE,SAAS,eAAe,EAAE,CAAC;IAC/C,QAAQ,CAAC,eAAe,EAAE,SAAS;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACvF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,iBAAiB,CAAC,CAmF5B"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/* eslint-disable max-lines-per-function, complexity -- Package parsing validates one transactional document graph. */
|
|
2
|
+
import { ReaderDocumentError } from '../ReaderDocumentError';
|
|
3
|
+
import { resolveArchivePath } from './path';
|
|
4
|
+
export async function parseEpubPackage(archive, signal) {
|
|
5
|
+
const mimetype = (await archive.readText('mimetype', signal)).trim();
|
|
6
|
+
if (mimetype !== 'application/epub+zip') {
|
|
7
|
+
throw new ReaderDocumentError('invalid-document', 'The archive is not an EPUB document.');
|
|
8
|
+
}
|
|
9
|
+
if (archive.has('META-INF/encryption.xml')) {
|
|
10
|
+
const encryption = await archive.readText('META-INF/encryption.xml', signal);
|
|
11
|
+
if (elementsByLocalName(parseXml(encryption, 'EPUB encryption metadata'), 'EncryptedData')
|
|
12
|
+
.length > 0) {
|
|
13
|
+
throw new ReaderDocumentError('protected-document', 'This EPUB uses content protection and cannot be opened.');
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
const container = parseXml(await archive.readText('META-INF/container.xml', signal), 'EPUB container');
|
|
17
|
+
const [rootfile] = elementsByLocalName(container, 'rootfile');
|
|
18
|
+
const packagePathValue = rootfile?.getAttribute('full-path');
|
|
19
|
+
if (!packagePathValue) {
|
|
20
|
+
throw new ReaderDocumentError('invalid-document', 'The EPUB container has no package document.');
|
|
21
|
+
}
|
|
22
|
+
const packagePath = resolveArchivePath('', packagePathValue);
|
|
23
|
+
const packageDocument = parseXml(await archive.readText(packagePath, signal), 'EPUB package');
|
|
24
|
+
const manifestItems = new Map();
|
|
25
|
+
for (const item of elementsByLocalName(packageDocument, 'item')) {
|
|
26
|
+
const id = item.getAttribute('id');
|
|
27
|
+
const href = item.getAttribute('href');
|
|
28
|
+
const mediaType = item.getAttribute('media-type');
|
|
29
|
+
if (!id || !href || !mediaType)
|
|
30
|
+
continue;
|
|
31
|
+
manifestItems.set(id, {
|
|
32
|
+
href: resolveArchivePath(packagePath, href),
|
|
33
|
+
mediaType,
|
|
34
|
+
properties: (item.getAttribute('properties') ?? '').split(/\s+/).filter(Boolean),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
const [spine] = elementsByLocalName(packageDocument, 'spine');
|
|
38
|
+
const readingOrder = elementsByLocalName(spine ?? packageDocument, 'itemref')
|
|
39
|
+
.map((itemref) => manifestItems.get(itemref.getAttribute('idref') ?? ''))
|
|
40
|
+
.filter((item) => item !== undefined);
|
|
41
|
+
if (readingOrder.length === 0) {
|
|
42
|
+
throw new ReaderDocumentError('invalid-document', 'The EPUB package has no readable spine.');
|
|
43
|
+
}
|
|
44
|
+
const metadata = elementsByLocalName(packageDocument, 'metadata')[0] ?? packageDocument.documentElement;
|
|
45
|
+
const title = elementText(metadata, 'title') ?? 'Untitled publication';
|
|
46
|
+
const language = elementText(metadata, 'language');
|
|
47
|
+
const fixedLayout = elementsByLocalName(metadata, 'meta').some((element) => element.getAttribute('property') === 'rendition:layout' &&
|
|
48
|
+
element.textContent.trim() === 'pre-paginated') || readingOrder.every((item) => item.properties.includes('rendition:layout-pre-paginated'));
|
|
49
|
+
const direction = spine?.getAttribute('page-progression-direction') === 'rtl' ? 'rtl' : 'ltr';
|
|
50
|
+
const readingPaths = new Set(readingOrder.map((item) => item.href));
|
|
51
|
+
const navigationItem = [...manifestItems.values()].find((item) => item.properties.includes('nav'));
|
|
52
|
+
const tableOfContents = navigationItem
|
|
53
|
+
? await parseNavigationDocument(archive, navigationItem.href, signal)
|
|
54
|
+
: [];
|
|
55
|
+
return {
|
|
56
|
+
direction,
|
|
57
|
+
fixedLayout,
|
|
58
|
+
language,
|
|
59
|
+
packagePath,
|
|
60
|
+
readingOrder,
|
|
61
|
+
resources: [...manifestItems.values()].filter((item) => !readingPaths.has(item.href)),
|
|
62
|
+
tableOfContents,
|
|
63
|
+
title,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
async function parseNavigationDocument(archive, navigationPath, signal) {
|
|
67
|
+
const document = parseXml(await archive.readText(navigationPath, signal), 'EPUB navigation');
|
|
68
|
+
const navigation = elementsByLocalName(document, 'nav').find((element) => (element.getAttribute('epub:type') ?? element.getAttribute('type') ?? '')
|
|
69
|
+
.split(/\s+/)
|
|
70
|
+
.includes('toc')) ?? elementsByLocalName(document, 'nav')[0];
|
|
71
|
+
if (!navigation)
|
|
72
|
+
return [];
|
|
73
|
+
return elementsByLocalName(navigation, 'a').flatMap((anchor) => {
|
|
74
|
+
const href = anchor.getAttribute('href');
|
|
75
|
+
const title = anchor.textContent.trim();
|
|
76
|
+
if (!href || !title)
|
|
77
|
+
return [];
|
|
78
|
+
const [resourceReference, fragment] = href.split('#', 2);
|
|
79
|
+
const resourcePath = resolveArchivePath(navigationPath, resourceReference ?? '');
|
|
80
|
+
return [{ href: fragment ? `${resourcePath}#${fragment}` : resourcePath, title }];
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function parseXml(source, label) {
|
|
84
|
+
const document = new DOMParser().parseFromString(source, 'application/xml');
|
|
85
|
+
if (elementsByLocalName(document, 'parsererror').length > 0) {
|
|
86
|
+
throw new ReaderDocumentError('invalid-document', `Invalid ${label}.`);
|
|
87
|
+
}
|
|
88
|
+
return document;
|
|
89
|
+
}
|
|
90
|
+
function elementsByLocalName(root, localName) {
|
|
91
|
+
return Array.from(root.getElementsByTagName('*')).filter((element) => element.localName === localName);
|
|
92
|
+
}
|
|
93
|
+
function elementText(root, localName) {
|
|
94
|
+
const [element] = elementsByLocalName(root, localName);
|
|
95
|
+
const value = element?.textContent.trim();
|
|
96
|
+
return value === '' ? undefined : value;
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=parseEpubPackage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parseEpubPackage.js","sourceRoot":"","sources":["../../../src/reader/epub/parseEpubPackage.ts"],"names":[],"mappings":"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","sourcesContent":["/* eslint-disable max-lines-per-function, complexity -- Package parsing validates one transactional document graph. */\nimport { ReaderDocumentError } from '../ReaderDocumentError';\nimport type { EpubArchive } from './EpubArchive';\nimport { resolveArchivePath } from './path';\n\ninterface EpubPackageItem {\n readonly href: string;\n readonly mediaType: string;\n readonly properties: readonly string[];\n}\n\nexport interface ParsedEpubPackage {\n readonly direction: 'ltr' | 'rtl';\n readonly fixedLayout: boolean;\n readonly language?: string;\n readonly packagePath: string;\n readonly readingOrder: readonly EpubPackageItem[];\n readonly resources: readonly EpubPackageItem[];\n readonly tableOfContents: readonly { readonly href: string; readonly title: string }[];\n readonly title: string;\n}\n\nexport async function parseEpubPackage(\n archive: EpubArchive,\n signal: AbortSignal,\n): Promise<ParsedEpubPackage> {\n const mimetype = (await archive.readText('mimetype', signal)).trim();\n if (mimetype !== 'application/epub+zip') {\n throw new ReaderDocumentError('invalid-document', 'The archive is not an EPUB document.');\n }\n if (archive.has('META-INF/encryption.xml')) {\n const encryption = await archive.readText('META-INF/encryption.xml', signal);\n if (\n elementsByLocalName(parseXml(encryption, 'EPUB encryption metadata'), 'EncryptedData')\n .length > 0\n ) {\n throw new ReaderDocumentError(\n 'protected-document',\n 'This EPUB uses content protection and cannot be opened.',\n );\n }\n }\n\n const container = parseXml(\n await archive.readText('META-INF/container.xml', signal),\n 'EPUB container',\n );\n const [rootfile] = elementsByLocalName(container, 'rootfile');\n const packagePathValue = rootfile?.getAttribute('full-path');\n if (!packagePathValue) {\n throw new ReaderDocumentError(\n 'invalid-document',\n 'The EPUB container has no package document.',\n );\n }\n const packagePath = resolveArchivePath('', packagePathValue);\n const packageDocument = parseXml(await archive.readText(packagePath, signal), 'EPUB package');\n\n const manifestItems = new Map<string, EpubPackageItem>();\n for (const item of elementsByLocalName(packageDocument, 'item')) {\n const id = item.getAttribute('id');\n const href = item.getAttribute('href');\n const mediaType = item.getAttribute('media-type');\n if (!id || !href || !mediaType) continue;\n manifestItems.set(id, {\n href: resolveArchivePath(packagePath, href),\n mediaType,\n properties: (item.getAttribute('properties') ?? '').split(/\\s+/).filter(Boolean),\n });\n }\n\n const [spine] = elementsByLocalName(packageDocument, 'spine');\n const readingOrder = elementsByLocalName(spine ?? packageDocument, 'itemref')\n .map((itemref) => manifestItems.get(itemref.getAttribute('idref') ?? ''))\n .filter((item): item is EpubPackageItem => item !== undefined);\n if (readingOrder.length === 0) {\n throw new ReaderDocumentError('invalid-document', 'The EPUB package has no readable spine.');\n }\n\n const metadata =\n elementsByLocalName(packageDocument, 'metadata')[0] ?? packageDocument.documentElement;\n const title = elementText(metadata, 'title') ?? 'Untitled publication';\n const language = elementText(metadata, 'language');\n const fixedLayout =\n elementsByLocalName(metadata, 'meta').some(\n (element) =>\n element.getAttribute('property') === 'rendition:layout' &&\n element.textContent.trim() === 'pre-paginated',\n ) || readingOrder.every((item) => item.properties.includes('rendition:layout-pre-paginated'));\n const direction = spine?.getAttribute('page-progression-direction') === 'rtl' ? 'rtl' : 'ltr';\n const readingPaths = new Set(readingOrder.map((item) => item.href));\n const navigationItem = [...manifestItems.values()].find((item) =>\n item.properties.includes('nav'),\n );\n const tableOfContents = navigationItem\n ? await parseNavigationDocument(archive, navigationItem.href, signal)\n : [];\n\n return {\n direction,\n fixedLayout,\n language,\n packagePath,\n readingOrder,\n resources: [...manifestItems.values()].filter((item) => !readingPaths.has(item.href)),\n tableOfContents,\n title,\n };\n}\n\nasync function parseNavigationDocument(\n archive: EpubArchive,\n navigationPath: string,\n signal: AbortSignal,\n): Promise<{ href: string; title: string }[]> {\n const document = parseXml(await archive.readText(navigationPath, signal), 'EPUB navigation');\n const navigation =\n elementsByLocalName(document, 'nav').find((element) =>\n (element.getAttribute('epub:type') ?? element.getAttribute('type') ?? '')\n .split(/\\s+/)\n .includes('toc'),\n ) ?? elementsByLocalName(document, 'nav')[0];\n if (!navigation) return [];\n return elementsByLocalName(navigation, 'a').flatMap((anchor) => {\n const href = anchor.getAttribute('href');\n const title = anchor.textContent.trim();\n if (!href || !title) return [];\n const [resourceReference, fragment] = href.split('#', 2);\n const resourcePath = resolveArchivePath(navigationPath, resourceReference ?? '');\n return [{ href: fragment ? `${resourcePath}#${fragment}` : resourcePath, title }];\n });\n}\n\nfunction parseXml(source: string, label: string): XMLDocument {\n const document = new DOMParser().parseFromString(source, 'application/xml');\n if (elementsByLocalName(document, 'parsererror').length > 0) {\n throw new ReaderDocumentError('invalid-document', `Invalid ${label}.`);\n }\n return document;\n}\n\nfunction elementsByLocalName(root: Document | Element, localName: string): Element[] {\n return Array.from(root.getElementsByTagName('*')).filter(\n (element) => element.localName === localName,\n );\n}\n\nfunction elementText(root: Document | Element, localName: string): string | undefined {\n const [element] = elementsByLocalName(root, localName);\n const value = element?.textContent.trim();\n return value === '' ? undefined : value;\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../../src/reader/epub/path.ts"],"names":[],"mappings":"AAAA,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAYzD;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAO9E;AAED,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAE9D"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export function normalizeArchivePath(path) {
|
|
2
|
+
const segments = [];
|
|
3
|
+
for (const segment of decodeURIComponent(path).replaceAll('\\', '/').split('/')) {
|
|
4
|
+
if (segment === '' || segment === '.')
|
|
5
|
+
continue;
|
|
6
|
+
if (segment === '..') {
|
|
7
|
+
if (segments.length === 0)
|
|
8
|
+
throw new Error(`EPUB resource escapes the archive: ${path}`);
|
|
9
|
+
segments.pop();
|
|
10
|
+
continue;
|
|
11
|
+
}
|
|
12
|
+
segments.push(segment);
|
|
13
|
+
}
|
|
14
|
+
return segments.join('/');
|
|
15
|
+
}
|
|
16
|
+
export function resolveArchivePath(basePath, reference) {
|
|
17
|
+
const cleanReference = reference.split('#', 1)[0]?.split('?', 1)[0] ?? '';
|
|
18
|
+
if (cleanReference.startsWith('/'))
|
|
19
|
+
return normalizeArchivePath(cleanReference);
|
|
20
|
+
const baseDirectory = basePath.includes('/')
|
|
21
|
+
? basePath.slice(0, basePath.lastIndexOf('/') + 1)
|
|
22
|
+
: '';
|
|
23
|
+
return normalizeArchivePath(`${baseDirectory}${cleanReference}`);
|
|
24
|
+
}
|
|
25
|
+
export function isExternalReference(reference) {
|
|
26
|
+
return /^(?:https?:|mailto:|tel:|data:|javascript:|\/\/)/i.test(reference.trim());
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=path.js.map
|