@ankhorage/expo-runtime 3.1.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/README.md +2 -1
- 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/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.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/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/navigation/resolveReaderKeyboardIntent.d.ts +9 -0
- package/dist/reader/navigation/resolveReaderKeyboardIntent.d.ts.map +1 -0
- package/dist/reader/navigation/resolveReaderKeyboardIntent.js +19 -0
- package/dist/reader/navigation/resolveReaderKeyboardIntent.js.map +1 -0
- package/dist/reader/navigation/resolveReaderNavigationIntent.d.ts +11 -0
- package/dist/reader/navigation/resolveReaderNavigationIntent.d.ts.map +1 -0
- package/dist/reader/navigation/resolveReaderNavigationIntent.js +16 -0
- package/dist/reader/navigation/resolveReaderNavigationIntent.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 +27 -5
- package/src/createExpoRuntimeRegistry.test.tsx +9 -1
- package/src/createExpoRuntimeRegistry.tsx +7 -1
- package/src/expoRuntimePlanningMetadata.ts +9 -1
- package/src/index.ts +1 -0
- 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/loadReaderSourceAsync.test.ts +52 -0
- package/src/reader/loadReaderSourceAsync.ts +59 -0
- package/src/reader/navigation/resolveReaderKeyboardIntent.test.ts +38 -0
- package/src/reader/navigation/resolveReaderKeyboardIntent.ts +20 -0
- package/src/reader/navigation/resolveReaderNavigationIntent.test.ts +48 -0
- package/src/reader/navigation/resolveReaderNavigationIntent.ts +23 -0
- package/src/reader/readerViewportPresentation.ts +28 -0
- package/src/reader/rendererPackages.test.ts +24 -0
- 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,11 @@
|
|
|
1
|
+
export declare function resolveReaderNavigationIntent(input: {
|
|
2
|
+
canGoNext: boolean;
|
|
3
|
+
canGoPrevious: boolean;
|
|
4
|
+
deltaX: number;
|
|
5
|
+
deltaY: number;
|
|
6
|
+
direction: 'ltr' | 'rtl';
|
|
7
|
+
hasSelection?: boolean;
|
|
8
|
+
isInteractiveTarget?: boolean;
|
|
9
|
+
viewportWidth: number;
|
|
10
|
+
}): 'next' | 'previous' | null;
|
|
11
|
+
//# sourceMappingURL=resolveReaderNavigationIntent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolveReaderNavigationIntent.d.ts","sourceRoot":"","sources":["../../../src/reader/navigation/resolveReaderNavigationIntent.ts"],"names":[],"mappings":"AAAA,wBAAgB,6BAA6B,CAAC,KAAK,EAAE;IACnD,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,OAAO,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,KAAK,GAAG,KAAK,CAAC;IACzB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,aAAa,EAAE,MAAM,CAAC;CACvB,GAAG,MAAM,GAAG,UAAU,GAAG,IAAI,CAa7B"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function resolveReaderNavigationIntent(input) {
|
|
2
|
+
if (input.hasSelection || input.isInteractiveTarget)
|
|
3
|
+
return null;
|
|
4
|
+
const horizontalDistance = Math.abs(input.deltaX);
|
|
5
|
+
const verticalDistance = Math.abs(input.deltaY);
|
|
6
|
+
const distanceThreshold = Math.max(48, input.viewportWidth * 0.15);
|
|
7
|
+
if (horizontalDistance <= verticalDistance * 1.25)
|
|
8
|
+
return null;
|
|
9
|
+
if (horizontalDistance <= distanceThreshold)
|
|
10
|
+
return null;
|
|
11
|
+
const movesForward = input.direction === 'ltr' ? input.deltaX < 0 : input.deltaX > 0;
|
|
12
|
+
if (movesForward)
|
|
13
|
+
return input.canGoNext ? 'next' : null;
|
|
14
|
+
return input.canGoPrevious ? 'previous' : null;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=resolveReaderNavigationIntent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolveReaderNavigationIntent.js","sourceRoot":"","sources":["../../../src/reader/navigation/resolveReaderNavigationIntent.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,6BAA6B,CAAC,KAS7C;IACC,IAAI,KAAK,CAAC,YAAY,IAAI,KAAK,CAAC,mBAAmB;QAAE,OAAO,IAAI,CAAC;IAEjE,MAAM,kBAAkB,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAClD,MAAM,gBAAgB,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAChD,MAAM,iBAAiB,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;IAEnE,IAAI,kBAAkB,IAAI,gBAAgB,GAAG,IAAI;QAAE,OAAO,IAAI,CAAC;IAC/D,IAAI,kBAAkB,IAAI,iBAAiB;QAAE,OAAO,IAAI,CAAC;IAEzD,MAAM,YAAY,GAAG,KAAK,CAAC,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;IACrF,IAAI,YAAY;QAAE,OAAO,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;IACzD,OAAO,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC;AACjD,CAAC","sourcesContent":["export function resolveReaderNavigationIntent(input: {\n canGoNext: boolean;\n canGoPrevious: boolean;\n deltaX: number;\n deltaY: number;\n direction: 'ltr' | 'rtl';\n hasSelection?: boolean;\n isInteractiveTarget?: boolean;\n viewportWidth: number;\n}): 'next' | 'previous' | null {\n if (input.hasSelection || input.isInteractiveTarget) return null;\n\n const horizontalDistance = Math.abs(input.deltaX);\n const verticalDistance = Math.abs(input.deltaY);\n const distanceThreshold = Math.max(48, input.viewportWidth * 0.15);\n\n if (horizontalDistance <= verticalDistance * 1.25) return null;\n if (horizontalDistance <= distanceThreshold) return null;\n\n const movesForward = input.direction === 'ltr' ? input.deltaX < 0 : input.deltaX > 0;\n if (movesForward) return input.canGoNext ? 'next' : null;\n return input.canGoPrevious ? 'previous' : null;\n}\n"]}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ReaderViewportState } from './types';
|
|
2
|
+
export declare const LOADING_STATE: ReaderViewportState;
|
|
3
|
+
export declare const READER_STYLES = "\n :root, body, #root { height: 100%; margin: 0; overflow: hidden; }\n * { box-sizing: border-box; }\n .reader-root { background: #fff; color: #111; height: 100%; outline: none; overflow: hidden; touch-action: pan-y; width: 100%; }\n .reader-dark { background: #111; color: #f5f5f5; }\n .reader-sepia { background: #f4ecd8; color: #3f3527; }\n .reader-content { height: 100%; overflow: auto; position: relative; width: 100%; }\n .reader-content > div:not(.reader-pdf-page) { height: 100%; width: 100%; }\n .reader-pdf-page { --user-unit: 1; --total-scale-factor: calc(var(--scale-factor) * var(--user-unit)); margin: 0 auto; position: relative; }\n .reader-pdf-page canvas { display: block; position: absolute; inset: 0; }\n .reader-pdf-text-layer { --min-font-size: 1; --text-scale-factor: calc(var(--total-scale-factor) * var(--min-font-size)); --min-font-size-inv: calc(1 / var(--min-font-size)); inset: 0; line-height: 1; opacity: 1; overflow: hidden; position: absolute; text-align: initial; text-size-adjust: none; transform-origin: 0 0; }\n .reader-pdf-text-layer span, .reader-pdf-text-layer br { color: transparent; cursor: text; position: absolute; transform-origin: 0 0; user-select: text; white-space: pre; }\n .reader-pdf-text-layer > :not(.markedContent), .reader-pdf-text-layer .markedContent span:not(.markedContent) { --font-height: 0; --scale-x: 1; --rotate: 0deg; font-size: calc(var(--text-scale-factor) * var(--font-height)); transform: rotate(var(--rotate)) scaleX(var(--scale-x)) scale(var(--min-font-size-inv)); }\n .reader-pdf-text-layer .markedContent { display: contents; }\n .reader-pdf-text-layer ::selection { background: rgba(0, 100, 255, 0.35); }\n .reader-pdf-link-layer { inset: 0; pointer-events: none; position: absolute; }\n .reader-pdf-link-layer a { outline-offset: 2px; pointer-events: auto; position: absolute; }\n";
|
|
4
|
+
//# sourceMappingURL=readerViewportPresentation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readerViewportPresentation.d.ts","sourceRoot":"","sources":["../../src/reader/readerViewportPresentation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAEnD,eAAO,MAAM,aAAa,EAAE,mBAM3B,CAAC;AAEF,eAAO,MAAM,aAAa,o1DAiBzB,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const LOADING_STATE = {
|
|
2
|
+
canGoNext: false,
|
|
3
|
+
canGoPrevious: false,
|
|
4
|
+
page: 1,
|
|
5
|
+
progress: 0,
|
|
6
|
+
status: 'loading',
|
|
7
|
+
};
|
|
8
|
+
export const READER_STYLES = `
|
|
9
|
+
:root, body, #root { height: 100%; margin: 0; overflow: hidden; }
|
|
10
|
+
* { box-sizing: border-box; }
|
|
11
|
+
.reader-root { background: #fff; color: #111; height: 100%; outline: none; overflow: hidden; touch-action: pan-y; width: 100%; }
|
|
12
|
+
.reader-dark { background: #111; color: #f5f5f5; }
|
|
13
|
+
.reader-sepia { background: #f4ecd8; color: #3f3527; }
|
|
14
|
+
.reader-content { height: 100%; overflow: auto; position: relative; width: 100%; }
|
|
15
|
+
.reader-content > div:not(.reader-pdf-page) { height: 100%; width: 100%; }
|
|
16
|
+
.reader-pdf-page { --user-unit: 1; --total-scale-factor: calc(var(--scale-factor) * var(--user-unit)); margin: 0 auto; position: relative; }
|
|
17
|
+
.reader-pdf-page canvas { display: block; position: absolute; inset: 0; }
|
|
18
|
+
.reader-pdf-text-layer { --min-font-size: 1; --text-scale-factor: calc(var(--total-scale-factor) * var(--min-font-size)); --min-font-size-inv: calc(1 / var(--min-font-size)); inset: 0; line-height: 1; opacity: 1; overflow: hidden; position: absolute; text-align: initial; text-size-adjust: none; transform-origin: 0 0; }
|
|
19
|
+
.reader-pdf-text-layer span, .reader-pdf-text-layer br { color: transparent; cursor: text; position: absolute; transform-origin: 0 0; user-select: text; white-space: pre; }
|
|
20
|
+
.reader-pdf-text-layer > :not(.markedContent), .reader-pdf-text-layer .markedContent span:not(.markedContent) { --font-height: 0; --scale-x: 1; --rotate: 0deg; font-size: calc(var(--text-scale-factor) * var(--font-height)); transform: rotate(var(--rotate)) scaleX(var(--scale-x)) scale(var(--min-font-size-inv)); }
|
|
21
|
+
.reader-pdf-text-layer .markedContent { display: contents; }
|
|
22
|
+
.reader-pdf-text-layer ::selection { background: rgba(0, 100, 255, 0.35); }
|
|
23
|
+
.reader-pdf-link-layer { inset: 0; pointer-events: none; position: absolute; }
|
|
24
|
+
.reader-pdf-link-layer a { outline-offset: 2px; pointer-events: auto; position: absolute; }
|
|
25
|
+
`;
|
|
26
|
+
//# sourceMappingURL=readerViewportPresentation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readerViewportPresentation.js","sourceRoot":"","sources":["../../src/reader/readerViewportPresentation.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,aAAa,GAAwB;IAChD,SAAS,EAAE,KAAK;IAChB,aAAa,EAAE,KAAK;IACpB,IAAI,EAAE,CAAC;IACP,QAAQ,EAAE,CAAC;IACX,MAAM,EAAE,SAAS;CAClB,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;CAiB5B,CAAC","sourcesContent":["import type { ReaderViewportState } from './types';\n\nexport const LOADING_STATE: ReaderViewportState = {\n canGoNext: false,\n canGoPrevious: false,\n page: 1,\n progress: 0,\n status: 'loading',\n};\n\nexport const READER_STYLES = `\n :root, body, #root { height: 100%; margin: 0; overflow: hidden; }\n * { box-sizing: border-box; }\n .reader-root { background: #fff; color: #111; height: 100%; outline: none; overflow: hidden; touch-action: pan-y; width: 100%; }\n .reader-dark { background: #111; color: #f5f5f5; }\n .reader-sepia { background: #f4ecd8; color: #3f3527; }\n .reader-content { height: 100%; overflow: auto; position: relative; width: 100%; }\n .reader-content > div:not(.reader-pdf-page) { height: 100%; width: 100%; }\n .reader-pdf-page { --user-unit: 1; --total-scale-factor: calc(var(--scale-factor) * var(--user-unit)); margin: 0 auto; position: relative; }\n .reader-pdf-page canvas { display: block; position: absolute; inset: 0; }\n .reader-pdf-text-layer { --min-font-size: 1; --text-scale-factor: calc(var(--total-scale-factor) * var(--min-font-size)); --min-font-size-inv: calc(1 / var(--min-font-size)); inset: 0; line-height: 1; opacity: 1; overflow: hidden; position: absolute; text-align: initial; text-size-adjust: none; transform-origin: 0 0; }\n .reader-pdf-text-layer span, .reader-pdf-text-layer br { color: transparent; cursor: text; position: absolute; transform-origin: 0 0; user-select: text; white-space: pre; }\n .reader-pdf-text-layer > :not(.markedContent), .reader-pdf-text-layer .markedContent span:not(.markedContent) { --font-height: 0; --scale-x: 1; --rotate: 0deg; font-size: calc(var(--text-scale-factor) * var(--font-height)); transform: rotate(var(--rotate)) scaleX(var(--scale-x)) scale(var(--min-font-size-inv)); }\n .reader-pdf-text-layer .markedContent { display: contents; }\n .reader-pdf-text-layer ::selection { background: rgba(0, 100, 255, 0.35); }\n .reader-pdf-link-layer { inset: 0; pointer-events: none; position: absolute; }\n .reader-pdf-link-layer a { outline-offset: 2px; pointer-events: auto; position: absolute; }\n`;\n"]}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { ReaderErrorEvent, ReaderLocationChangeEvent } from '@ankhorage/zora';
|
|
2
|
+
import type { DOMProps } from 'expo/dom';
|
|
3
|
+
export interface ReaderAppearance {
|
|
4
|
+
readonly colorScheme: 'dark' | 'light' | 'sepia' | 'system';
|
|
5
|
+
readonly fontScale: number;
|
|
6
|
+
readonly lineHeight: number;
|
|
7
|
+
}
|
|
8
|
+
export interface ReaderCommand {
|
|
9
|
+
readonly id: number;
|
|
10
|
+
readonly sourceUri: string;
|
|
11
|
+
readonly type: 'next' | 'previous';
|
|
12
|
+
}
|
|
13
|
+
export interface ReaderViewportState {
|
|
14
|
+
readonly canGoNext: boolean;
|
|
15
|
+
readonly canGoPrevious: boolean;
|
|
16
|
+
readonly location?: ReaderLocationChangeEvent;
|
|
17
|
+
readonly page: number;
|
|
18
|
+
readonly pageCount?: number;
|
|
19
|
+
readonly progress: number;
|
|
20
|
+
readonly status: 'loading' | 'ready';
|
|
21
|
+
}
|
|
22
|
+
export interface ReaderViewportProps {
|
|
23
|
+
readonly appearance: ReaderAppearance;
|
|
24
|
+
readonly command?: ReaderCommand;
|
|
25
|
+
readonly format: 'epub' | 'pdf';
|
|
26
|
+
readonly initialLocation?: string;
|
|
27
|
+
readonly onError: (event: ReaderErrorEvent) => Promise<void>;
|
|
28
|
+
readonly onOpenExternalLink: (event: {
|
|
29
|
+
readonly url: string;
|
|
30
|
+
}) => Promise<void>;
|
|
31
|
+
readonly onStateChange: (state: ReaderViewportState) => Promise<void>;
|
|
32
|
+
readonly sourceUri: string;
|
|
33
|
+
readonly dom?: DOMProps;
|
|
34
|
+
}
|
|
35
|
+
export interface ReaderDriverState {
|
|
36
|
+
readonly canGoNext: boolean;
|
|
37
|
+
readonly canGoPrevious: boolean;
|
|
38
|
+
readonly chapterId?: string;
|
|
39
|
+
readonly chapterTitle?: string;
|
|
40
|
+
readonly locator: string;
|
|
41
|
+
readonly page: number;
|
|
42
|
+
readonly pageCount?: number;
|
|
43
|
+
readonly progression: number;
|
|
44
|
+
}
|
|
45
|
+
export interface ReaderDriver {
|
|
46
|
+
readonly direction: 'ltr' | 'rtl';
|
|
47
|
+
destroy(): Promise<void>;
|
|
48
|
+
getState(): ReaderDriverState;
|
|
49
|
+
goNext(trigger: ReaderLocationChangeEvent['trigger']): Promise<void>;
|
|
50
|
+
goPrevious(trigger: ReaderLocationChangeEvent['trigger']): Promise<void>;
|
|
51
|
+
}
|
|
52
|
+
export interface ReaderTouchStart {
|
|
53
|
+
readonly interactive: boolean;
|
|
54
|
+
readonly x: number;
|
|
55
|
+
readonly y: number;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/reader/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/reader/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { ReaderErrorEvent, ReaderLocationChangeEvent } from '@ankhorage/zora';\nimport type { DOMProps } from 'expo/dom';\n\nexport interface ReaderAppearance {\n readonly colorScheme: 'dark' | 'light' | 'sepia' | 'system';\n readonly fontScale: number;\n readonly lineHeight: number;\n}\n\nexport interface ReaderCommand {\n readonly id: number;\n readonly sourceUri: string;\n readonly type: 'next' | 'previous';\n}\n\nexport interface ReaderViewportState {\n readonly canGoNext: boolean;\n readonly canGoPrevious: boolean;\n readonly location?: ReaderLocationChangeEvent;\n readonly page: number;\n readonly pageCount?: number;\n readonly progress: number;\n readonly status: 'loading' | 'ready';\n}\n\nexport interface ReaderViewportProps {\n readonly appearance: ReaderAppearance;\n readonly command?: ReaderCommand;\n readonly format: 'epub' | 'pdf';\n readonly initialLocation?: string;\n readonly onError: (event: ReaderErrorEvent) => Promise<void>;\n readonly onOpenExternalLink: (event: { readonly url: string }) => Promise<void>;\n readonly onStateChange: (state: ReaderViewportState) => Promise<void>;\n readonly sourceUri: string;\n readonly dom?: DOMProps;\n}\n\nexport interface ReaderDriverState {\n readonly canGoNext: boolean;\n readonly canGoPrevious: boolean;\n readonly chapterId?: string;\n readonly chapterTitle?: string;\n readonly locator: string;\n readonly page: number;\n readonly pageCount?: number;\n readonly progression: number;\n}\n\nexport interface ReaderDriver {\n readonly direction: 'ltr' | 'rtl';\n destroy(): Promise<void>;\n getState(): ReaderDriverState;\n goNext(trigger: ReaderLocationChangeEvent['trigger']): Promise<void>;\n goPrevious(trigger: ReaderLocationChangeEvent['trigger']): Promise<void>;\n}\n\nexport interface ReaderTouchStart {\n readonly interactive: boolean;\n readonly x: number;\n readonly y: number;\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validateEpubArchiveEntriesAsync.d.ts","sourceRoot":"","sources":["../../src/reader/validateEpubArchiveEntriesAsync.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAQ5C,wBAAsB,+BAA+B,CACnD,OAAO,EAAE,SAAS,KAAK,EAAE,EACzB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC,CAkDf"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Uint8ArrayWriter } from '@zip.js/zip.js';
|
|
2
|
+
const MAX_COMPRESSION_RATIO = 100;
|
|
3
|
+
const MAX_ENTRY_COUNT = 10_000;
|
|
4
|
+
const MAX_ENTRY_UNCOMPRESSED_BYTES = 64 * 1024 * 1024;
|
|
5
|
+
const MAX_TOTAL_UNCOMPRESSED_BYTES = 512 * 1024 * 1024;
|
|
6
|
+
export async function validateEpubArchiveEntriesAsync(entries, signal) {
|
|
7
|
+
if (entries.length > MAX_ENTRY_COUNT) {
|
|
8
|
+
throw new Error(`EPUB archive exceeds the ${MAX_ENTRY_COUNT}-entry limit.`);
|
|
9
|
+
}
|
|
10
|
+
let totalUncompressedBytes = 0;
|
|
11
|
+
for (const entry of entries) {
|
|
12
|
+
validateDeclaredSizes(entry);
|
|
13
|
+
if (entry.directory) {
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
if (entry.uncompressedSize > MAX_ENTRY_UNCOMPRESSED_BYTES) {
|
|
17
|
+
throw new Error(`EPUB archive entry "${entry.filename}" exceeds the ${MAX_ENTRY_UNCOMPRESSED_BYTES}-byte uncompressed limit.`);
|
|
18
|
+
}
|
|
19
|
+
if (entry.uncompressedSize > 0 &&
|
|
20
|
+
(entry.compressedSize === 0 ||
|
|
21
|
+
entry.uncompressedSize > entry.compressedSize * MAX_COMPRESSION_RATIO)) {
|
|
22
|
+
throw new Error(`EPUB archive entry "${entry.filename}" exceeds the ${MAX_COMPRESSION_RATIO}:1 compression-ratio limit.`);
|
|
23
|
+
}
|
|
24
|
+
if (totalUncompressedBytes > MAX_TOTAL_UNCOMPRESSED_BYTES - entry.uncompressedSize) {
|
|
25
|
+
throw new Error(`EPUB archive exceeds the ${MAX_TOTAL_UNCOMPRESSED_BYTES}-byte aggregate uncompressed limit.`);
|
|
26
|
+
}
|
|
27
|
+
totalUncompressedBytes += entry.uncompressedSize;
|
|
28
|
+
}
|
|
29
|
+
for (const entry of entries) {
|
|
30
|
+
if (entry.directory) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
await entry.getData(new Uint8ArrayWriter(), {
|
|
34
|
+
checkOverlappingEntryOnly: true,
|
|
35
|
+
signal,
|
|
36
|
+
strictness: 'strict',
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function validateDeclaredSizes(entry) {
|
|
41
|
+
if (!Number.isSafeInteger(entry.compressedSize) ||
|
|
42
|
+
entry.compressedSize < 0 ||
|
|
43
|
+
!Number.isSafeInteger(entry.uncompressedSize) ||
|
|
44
|
+
entry.uncompressedSize < 0) {
|
|
45
|
+
throw new Error(`EPUB archive entry "${entry.filename}" declares invalid sizes.`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=validateEpubArchiveEntriesAsync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validateEpubArchiveEntriesAsync.js","sourceRoot":"","sources":["../../src/reader/validateEpubArchiveEntriesAsync.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAElD,MAAM,qBAAqB,GAAG,GAAG,CAAC;AAClC,MAAM,eAAe,GAAG,MAAM,CAAC;AAC/B,MAAM,4BAA4B,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AACtD,MAAM,4BAA4B,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC;AAEvD,MAAM,CAAC,KAAK,UAAU,+BAA+B,CACnD,OAAyB,EACzB,MAAoB;IAEpB,IAAI,OAAO,CAAC,MAAM,GAAG,eAAe,EAAE,CAAC;QACrC,MAAM,IAAI,KAAK,CAAC,4BAA4B,eAAe,eAAe,CAAC,CAAC;IAC9E,CAAC;IAED,IAAI,sBAAsB,GAAG,CAAC,CAAC;IAE/B,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,qBAAqB,CAAC,KAAK,CAAC,CAAC;QAE7B,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;YACpB,SAAS;QACX,CAAC;QAED,IAAI,KAAK,CAAC,gBAAgB,GAAG,4BAA4B,EAAE,CAAC;YAC1D,MAAM,IAAI,KAAK,CACb,uBAAuB,KAAK,CAAC,QAAQ,iBAAiB,4BAA4B,2BAA2B,CAC9G,CAAC;QACJ,CAAC;QAED,IACE,KAAK,CAAC,gBAAgB,GAAG,CAAC;YAC1B,CAAC,KAAK,CAAC,cAAc,KAAK,CAAC;gBACzB,KAAK,CAAC,gBAAgB,GAAG,KAAK,CAAC,cAAc,GAAG,qBAAqB,CAAC,EACxE,CAAC;YACD,MAAM,IAAI,KAAK,CACb,uBAAuB,KAAK,CAAC,QAAQ,iBAAiB,qBAAqB,6BAA6B,CACzG,CAAC;QACJ,CAAC;QAED,IAAI,sBAAsB,GAAG,4BAA4B,GAAG,KAAK,CAAC,gBAAgB,EAAE,CAAC;YACnF,MAAM,IAAI,KAAK,CACb,4BAA4B,4BAA4B,qCAAqC,CAC9F,CAAC;QACJ,CAAC;QAED,sBAAsB,IAAI,KAAK,CAAC,gBAAgB,CAAC;IACnD,CAAC;IAED,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;YACpB,SAAS;QACX,CAAC;QAED,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,gBAAgB,EAAE,EAAE;YAC1C,yBAAyB,EAAE,IAAI;YAC/B,MAAM;YACN,UAAU,EAAE,QAAQ;SACrB,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAED,SAAS,qBAAqB,CAAC,KAAY;IACzC,IACE,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,cAAc,CAAC;QAC3C,KAAK,CAAC,cAAc,GAAG,CAAC;QACxB,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,gBAAgB,CAAC;QAC7C,KAAK,CAAC,gBAAgB,GAAG,CAAC,EAC1B,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,uBAAuB,KAAK,CAAC,QAAQ,2BAA2B,CAAC,CAAC;IACpF,CAAC;AACH,CAAC","sourcesContent":["import type { Entry } from '@zip.js/zip.js';\nimport { Uint8ArrayWriter } from '@zip.js/zip.js';\n\nconst MAX_COMPRESSION_RATIO = 100;\nconst MAX_ENTRY_COUNT = 10_000;\nconst MAX_ENTRY_UNCOMPRESSED_BYTES = 64 * 1024 * 1024;\nconst MAX_TOTAL_UNCOMPRESSED_BYTES = 512 * 1024 * 1024;\n\nexport async function validateEpubArchiveEntriesAsync(\n entries: readonly Entry[],\n signal?: AbortSignal,\n): Promise<void> {\n if (entries.length > MAX_ENTRY_COUNT) {\n throw new Error(`EPUB archive exceeds the ${MAX_ENTRY_COUNT}-entry limit.`);\n }\n\n let totalUncompressedBytes = 0;\n\n for (const entry of entries) {\n validateDeclaredSizes(entry);\n\n if (entry.directory) {\n continue;\n }\n\n if (entry.uncompressedSize > MAX_ENTRY_UNCOMPRESSED_BYTES) {\n throw new Error(\n `EPUB archive entry \"${entry.filename}\" exceeds the ${MAX_ENTRY_UNCOMPRESSED_BYTES}-byte uncompressed limit.`,\n );\n }\n\n if (\n entry.uncompressedSize > 0 &&\n (entry.compressedSize === 0 ||\n entry.uncompressedSize > entry.compressedSize * MAX_COMPRESSION_RATIO)\n ) {\n throw new Error(\n `EPUB archive entry \"${entry.filename}\" exceeds the ${MAX_COMPRESSION_RATIO}:1 compression-ratio limit.`,\n );\n }\n\n if (totalUncompressedBytes > MAX_TOTAL_UNCOMPRESSED_BYTES - entry.uncompressedSize) {\n throw new Error(\n `EPUB archive exceeds the ${MAX_TOTAL_UNCOMPRESSED_BYTES}-byte aggregate uncompressed limit.`,\n );\n }\n\n totalUncompressedBytes += entry.uncompressedSize;\n }\n\n for (const entry of entries) {\n if (entry.directory) {\n continue;\n }\n\n await entry.getData(new Uint8ArrayWriter(), {\n checkOverlappingEntryOnly: true,\n signal,\n strictness: 'strict',\n });\n }\n}\n\nfunction validateDeclaredSizes(entry: Entry): void {\n if (\n !Number.isSafeInteger(entry.compressedSize) ||\n entry.compressedSize < 0 ||\n !Number.isSafeInteger(entry.uncompressedSize) ||\n entry.uncompressedSize < 0\n ) {\n throw new Error(`EPUB archive entry \"${entry.filename}\" declares invalid sizes.`);\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ankhorage/expo-runtime",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.2.0",
|
|
5
5
|
"description": "Declarative runtime integration for Expo apps: maps app capabilities to permissions, packages, config plugins, providers, and adapters",
|
|
6
6
|
"homepage": "https://github.com/ankhorage/expo-runtime#readme",
|
|
7
7
|
"bugs": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"runtime"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@ankhorage/contracts": "^8.0.
|
|
28
|
+
"@ankhorage/contracts": "^8.0.1"
|
|
29
29
|
},
|
|
30
30
|
"files": [
|
|
31
31
|
"dist",
|
|
@@ -121,17 +121,21 @@
|
|
|
121
121
|
"validate:expo-platform": "bun scripts/validateExpoPlatform.ts",
|
|
122
122
|
"validate:headless-platform": "bun scripts/headlessPlatformAcceptance.ts",
|
|
123
123
|
"validate:packed-runtime": "bun scripts/packedRuntimeAcceptance.ts",
|
|
124
|
+
"validate:reader-dom": "bun scripts/readerDomAcceptance.ts",
|
|
124
125
|
"validate:web-barcode": "bun scripts/webBarcodeAcceptance.ts",
|
|
125
126
|
"version-packages": "ankhorage-changeset version"
|
|
126
127
|
},
|
|
127
128
|
"peerDependencies": {
|
|
128
129
|
"@ankhorage/permissions": "^0.2.3",
|
|
129
|
-
"@ankhorage/zora": "^3.0
|
|
130
|
+
"@ankhorage/zora": "^3.1.0",
|
|
131
|
+
"@readium/navigator": "2.8.2",
|
|
132
|
+
"@readium/shared": "2.4.0",
|
|
130
133
|
"@react-native-vector-icons/fontawesome": "^13.1.3",
|
|
131
134
|
"@react-native-vector-icons/fontawesome5": "^13.1.3",
|
|
132
135
|
"@react-native-vector-icons/fontawesome6": "^13.1.3",
|
|
133
136
|
"@react-native-vector-icons/ionicons": "^13.1.3",
|
|
134
137
|
"@react-native-vector-icons/material-design-icons": "^13.1.3",
|
|
138
|
+
"@zip.js/zip.js": "2.9.0",
|
|
135
139
|
"expo": "57.0.18",
|
|
136
140
|
"expo-camera": "~57.0.4",
|
|
137
141
|
"expo-constants": "~57.0.16",
|
|
@@ -139,6 +143,7 @@
|
|
|
139
143
|
"expo-file-system": "~57.0.6",
|
|
140
144
|
"expo-font": "~57.0.2",
|
|
141
145
|
"expo-image-picker": "~57.0.14",
|
|
146
|
+
"pdfjs-dist": "6.3.289",
|
|
142
147
|
"react": "19.2.3",
|
|
143
148
|
"react-native": "0.86.3"
|
|
144
149
|
},
|
|
@@ -149,6 +154,12 @@
|
|
|
149
154
|
"@ankhorage/zora": {
|
|
150
155
|
"optional": true
|
|
151
156
|
},
|
|
157
|
+
"@readium/navigator": {
|
|
158
|
+
"optional": true
|
|
159
|
+
},
|
|
160
|
+
"@readium/shared": {
|
|
161
|
+
"optional": true
|
|
162
|
+
},
|
|
152
163
|
"@react-native-vector-icons/fontawesome": {
|
|
153
164
|
"optional": true
|
|
154
165
|
},
|
|
@@ -164,6 +175,9 @@
|
|
|
164
175
|
"@react-native-vector-icons/material-design-icons": {
|
|
165
176
|
"optional": true
|
|
166
177
|
},
|
|
178
|
+
"@zip.js/zip.js": {
|
|
179
|
+
"optional": true
|
|
180
|
+
},
|
|
167
181
|
"expo": {
|
|
168
182
|
"optional": true
|
|
169
183
|
},
|
|
@@ -185,6 +199,9 @@
|
|
|
185
199
|
"expo-image-picker": {
|
|
186
200
|
"optional": true
|
|
187
201
|
},
|
|
202
|
+
"pdfjs-dist": {
|
|
203
|
+
"optional": true
|
|
204
|
+
},
|
|
188
205
|
"react": {
|
|
189
206
|
"optional": true
|
|
190
207
|
},
|
|
@@ -193,20 +210,24 @@
|
|
|
193
210
|
}
|
|
194
211
|
},
|
|
195
212
|
"devDependencies": {
|
|
196
|
-
"@ankhorage/devtools": "^1.8.
|
|
213
|
+
"@ankhorage/devtools": "^1.8.3",
|
|
197
214
|
"@ankhorage/paradox": "^0.1.24",
|
|
198
215
|
"@ankhorage/permissions": "^0.2.3",
|
|
199
|
-
"@ankhorage/zora": "^3.0
|
|
216
|
+
"@ankhorage/zora": "^3.1.0",
|
|
217
|
+
"@readium/navigator": "2.8.2",
|
|
218
|
+
"@readium/shared": "2.4.0",
|
|
200
219
|
"@react-native-picker/picker": "2.11.4",
|
|
201
220
|
"@react-native-vector-icons/fontawesome": "^13.1.3",
|
|
202
221
|
"@react-native-vector-icons/fontawesome5": "^13.1.3",
|
|
203
222
|
"@react-native-vector-icons/fontawesome6": "^13.1.3",
|
|
204
223
|
"@react-native-vector-icons/ionicons": "^13.1.3",
|
|
205
224
|
"@react-native-vector-icons/material-design-icons": "^13.1.3",
|
|
225
|
+
"@zip.js/zip.js": "2.9.0",
|
|
206
226
|
"@types/bun": "^1.4.0",
|
|
207
227
|
"@types/node": "^24.13.3",
|
|
208
228
|
"@types/pngjs": "^6.0.5",
|
|
209
229
|
"@types/react": "^19.2.18",
|
|
230
|
+
"@xmldom/xmldom": "0.9.10",
|
|
210
231
|
"bwip-js": "^4.11.4",
|
|
211
232
|
"expo": "57.0.18",
|
|
212
233
|
"expo-camera": "~57.0.4",
|
|
@@ -220,6 +241,7 @@
|
|
|
220
241
|
"react-native": "0.86.3",
|
|
221
242
|
"react-native-safe-area-context": "~5.7.0",
|
|
222
243
|
"react-native-web": "~0.21.0",
|
|
244
|
+
"pdfjs-dist": "6.3.289",
|
|
223
245
|
"playwright-core": "^1.62.1",
|
|
224
246
|
"pngjs": "^7.0.0",
|
|
225
247
|
"typescript": "~6.0.3"
|
|
@@ -4,20 +4,26 @@ import React from 'react';
|
|
|
4
4
|
const BaseBarcodeScannerView = (): React.ReactElement => React.createElement(React.Fragment);
|
|
5
5
|
const BaseText = (): React.ReactElement => React.createElement(React.Fragment);
|
|
6
6
|
const MockExpoBarcodeScannerAdapter = (): React.ReactElement => React.createElement(React.Fragment);
|
|
7
|
+
const BaseReaderSurface = (): React.ReactElement => React.createElement(React.Fragment);
|
|
8
|
+
const MockExpoReaderSurfaceAdapter = (): React.ReactElement => React.createElement(React.Fragment);
|
|
7
9
|
|
|
8
10
|
describe('createExpoRuntimeRegistry', () => {
|
|
9
11
|
afterEach(() => {
|
|
10
12
|
mock.restore();
|
|
11
13
|
});
|
|
12
14
|
|
|
13
|
-
it('overrides
|
|
15
|
+
it('overrides platform-owned views while preserving unrelated registry entries', async () => {
|
|
14
16
|
void mock.module('./ExpoBarcodeScannerAdapter', () => ({
|
|
15
17
|
ExpoBarcodeScannerAdapter: MockExpoBarcodeScannerAdapter,
|
|
16
18
|
}));
|
|
19
|
+
void mock.module('./reader/ExpoReaderSurfaceAdapter', () => ({
|
|
20
|
+
ExpoReaderSurfaceAdapter: MockExpoReaderSurfaceAdapter,
|
|
21
|
+
}));
|
|
17
22
|
|
|
18
23
|
const { createExpoRuntimeRegistry } = await import('./createExpoRuntimeRegistry');
|
|
19
24
|
const baseRegistry = {
|
|
20
25
|
BarcodeScannerView: BaseBarcodeScannerView,
|
|
26
|
+
ReaderSurface: BaseReaderSurface,
|
|
21
27
|
Text: BaseText,
|
|
22
28
|
};
|
|
23
29
|
|
|
@@ -27,5 +33,7 @@ describe('createExpoRuntimeRegistry', () => {
|
|
|
27
33
|
expect(registry.Text).toBe(BaseText);
|
|
28
34
|
expect(registry.BarcodeScannerView).not.toBe(BaseBarcodeScannerView);
|
|
29
35
|
expect(typeof registry.BarcodeScannerView).toBe('function');
|
|
36
|
+
expect(registry.ReaderSurface).not.toBe(BaseReaderSurface);
|
|
37
|
+
expect(typeof registry.ReaderSurface).toBe('function');
|
|
30
38
|
});
|
|
31
39
|
});
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { type BarcodeScannerViewProps } from '@ankhorage/zora';
|
|
1
|
+
import { type BarcodeScannerViewProps, type ReaderSurfaceProps } from '@ankhorage/zora';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
|
|
4
4
|
import { type ComponentRegistry, createComponentRegistry } from './componentRegistry';
|
|
5
5
|
import { ExpoBarcodeScannerAdapter } from './ExpoBarcodeScannerAdapter';
|
|
6
|
+
import { ExpoReaderSurfaceAdapter } from './reader/ExpoReaderSurfaceAdapter';
|
|
6
7
|
|
|
7
8
|
const EXPO_RUNTIME_OVERRIDES: ComponentRegistry = {
|
|
8
9
|
BarcodeScannerView: ExpoBarcodeScannerView,
|
|
10
|
+
ReaderSurface: ExpoReaderSurface,
|
|
9
11
|
};
|
|
10
12
|
|
|
11
13
|
export function createExpoRuntimeRegistry(baseRegistry: ComponentRegistry): ComponentRegistry {
|
|
@@ -15,3 +17,7 @@ export function createExpoRuntimeRegistry(baseRegistry: ComponentRegistry): Comp
|
|
|
15
17
|
function ExpoBarcodeScannerView(props: BarcodeScannerViewProps) {
|
|
16
18
|
return <ExpoBarcodeScannerAdapter {...props} />;
|
|
17
19
|
}
|
|
20
|
+
|
|
21
|
+
function ExpoReaderSurface(props: ReaderSurfaceProps) {
|
|
22
|
+
return <ExpoReaderSurfaceAdapter {...props} />;
|
|
23
|
+
}
|
|
@@ -13,7 +13,7 @@ export interface ExpoRuntimeConfigPlugin {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export type ExpoRuntimeProviderId = 'permissions';
|
|
16
|
-
export type ExpoRuntimeAdapterId = 'ExpoBarcodeScannerAdapter';
|
|
16
|
+
export type ExpoRuntimeAdapterId = 'ExpoBarcodeScannerAdapter' | 'ExpoReaderSurfaceAdapter';
|
|
17
17
|
|
|
18
18
|
export interface ExpoRuntimeDiagnostic {
|
|
19
19
|
readonly severity: 'error' | 'warning';
|
|
@@ -40,6 +40,10 @@ export interface ExpoRuntimeHintMetadata {
|
|
|
40
40
|
const GENERATED_ANKHORAGE_DEPENDENCY_VERSIONS = {
|
|
41
41
|
'@ankhorage/permissions': '^0.2.3',
|
|
42
42
|
'@ankhorage/expo-runtime': '^3.0.0',
|
|
43
|
+
'@readium/navigator': '2.8.2',
|
|
44
|
+
'@readium/shared': '2.4.0',
|
|
45
|
+
'@zip.js/zip.js': '2.9.0',
|
|
46
|
+
'pdfjs-dist': '6.3.289',
|
|
43
47
|
} as const satisfies Record<string, string>;
|
|
44
48
|
|
|
45
49
|
const GENERATED_EXPO_DEPENDENCY_VERSIONS = Object.fromEntries(
|
|
@@ -148,6 +152,10 @@ export const EXPO_CAPABILITY_RUNTIME_REGISTRY = {
|
|
|
148
152
|
},
|
|
149
153
|
],
|
|
150
154
|
},
|
|
155
|
+
ebookReader: {
|
|
156
|
+
requiredPackages: ['@readium/navigator', '@readium/shared', '@zip.js/zip.js', 'pdfjs-dist'],
|
|
157
|
+
runtimeAdapters: ['ExpoReaderSurfaceAdapter'],
|
|
158
|
+
},
|
|
151
159
|
} as const satisfies Readonly<
|
|
152
160
|
Partial<Record<AnkhorageCapabilityName, ExpoRuntimeCapabilityMetadata>>
|
|
153
161
|
>;
|
package/src/index.ts
CHANGED
|
@@ -53,6 +53,7 @@ export {
|
|
|
53
53
|
type ExpoRuntimeLayoutIntegrationPlan,
|
|
54
54
|
resolveExpoRuntimeLayoutIntegration,
|
|
55
55
|
} from './layoutIntegrationPlanning';
|
|
56
|
+
export { ExpoReaderSurfaceAdapter } from './reader/ExpoReaderSurfaceAdapter';
|
|
56
57
|
export type {
|
|
57
58
|
ExpoRuntimeAdapterId,
|
|
58
59
|
ExpoRuntimePlan,
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { type ReaderErrorEvent, ReaderSurface, type ReaderSurfaceProps } from '@ankhorage/zora';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Image, StyleSheet, View } from 'react-native';
|
|
4
|
+
|
|
5
|
+
import ReaderViewport from './ReaderViewport';
|
|
6
|
+
import type { ReaderAppearance, ReaderCommand, ReaderViewportState } from './types';
|
|
7
|
+
|
|
8
|
+
const INITIAL_STATE: ReaderViewportState = {
|
|
9
|
+
canGoNext: false,
|
|
10
|
+
canGoPrevious: false,
|
|
11
|
+
page: 1,
|
|
12
|
+
progress: 0,
|
|
13
|
+
status: 'loading',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function ExpoReaderSurfaceAdapter(props: ReaderSurfaceProps) {
|
|
17
|
+
const sourceUri = resolveReaderSourceUri(props.source);
|
|
18
|
+
const controller = useReaderController(props, sourceUri);
|
|
19
|
+
const appearance = useReaderAppearance(props);
|
|
20
|
+
const viewport = sourceUri ? (
|
|
21
|
+
<ReaderAdapterViewport
|
|
22
|
+
appearance={appearance}
|
|
23
|
+
command={controller.command}
|
|
24
|
+
format={props.format}
|
|
25
|
+
initialLocation={props.location}
|
|
26
|
+
onError={controller.handleError}
|
|
27
|
+
onOpenExternalLink={controller.handleOpenExternalLink}
|
|
28
|
+
onStateChange={controller.handleStateChange}
|
|
29
|
+
sourceUri={sourceUri}
|
|
30
|
+
/>
|
|
31
|
+
) : undefined;
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<ReaderSurface
|
|
35
|
+
{...props}
|
|
36
|
+
canGoNext={controller.state.canGoNext}
|
|
37
|
+
canGoPrevious={controller.state.canGoPrevious}
|
|
38
|
+
chapterLabel={controller.state.location?.chapterTitle ?? props.chapterLabel}
|
|
39
|
+
onNextPage={controller.handleNextPage}
|
|
40
|
+
onPreviousPage={controller.handlePreviousPage}
|
|
41
|
+
page={controller.state.page}
|
|
42
|
+
pageCount={controller.state.pageCount}
|
|
43
|
+
progress={controller.state.progress}
|
|
44
|
+
status={controller.error ? 'error' : sourceUri ? controller.state.status : 'idle'}
|
|
45
|
+
viewport={viewport}
|
|
46
|
+
/>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function ReaderAdapterViewport({
|
|
51
|
+
appearance,
|
|
52
|
+
command,
|
|
53
|
+
format,
|
|
54
|
+
initialLocation,
|
|
55
|
+
onError,
|
|
56
|
+
onOpenExternalLink,
|
|
57
|
+
onStateChange,
|
|
58
|
+
sourceUri,
|
|
59
|
+
}: React.ComponentProps<typeof ReaderViewport>) {
|
|
60
|
+
return (
|
|
61
|
+
<View style={styles.viewport}>
|
|
62
|
+
<ReaderViewport
|
|
63
|
+
appearance={appearance}
|
|
64
|
+
command={command}
|
|
65
|
+
dom={DOM_CONFIGURATION}
|
|
66
|
+
format={format}
|
|
67
|
+
initialLocation={initialLocation}
|
|
68
|
+
key={`${format}:${sourceUri}`}
|
|
69
|
+
onError={onError}
|
|
70
|
+
onOpenExternalLink={onOpenExternalLink}
|
|
71
|
+
onStateChange={onStateChange}
|
|
72
|
+
sourceUri={sourceUri}
|
|
73
|
+
/>
|
|
74
|
+
</View>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function useReaderController(props: ReaderSurfaceProps, sourceUri: string | undefined) {
|
|
79
|
+
const { onLocationChange, onNextPage, onOpenExternalLink, onPreviousPage, onReaderError } = props;
|
|
80
|
+
const [state, setState] = React.useState<ReaderViewportState>(INITIAL_STATE);
|
|
81
|
+
const [error, setError] = React.useState<ReaderErrorEvent>();
|
|
82
|
+
const { command, issueCommand } = useReaderCommandState(sourceUri);
|
|
83
|
+
const handlePreviousPage = React.useCallback(() => {
|
|
84
|
+
issueCommand('previous');
|
|
85
|
+
void onPreviousPage?.();
|
|
86
|
+
}, [issueCommand, onPreviousPage]);
|
|
87
|
+
const handleNextPage = React.useCallback(() => {
|
|
88
|
+
issueCommand('next');
|
|
89
|
+
void onNextPage?.();
|
|
90
|
+
}, [issueCommand, onNextPage]);
|
|
91
|
+
const handleStateChange = React.useCallback(
|
|
92
|
+
async (nextState: ReaderViewportState) => {
|
|
93
|
+
setState(nextState);
|
|
94
|
+
setError(undefined);
|
|
95
|
+
if (nextState.location) await onLocationChange?.(nextState.location);
|
|
96
|
+
},
|
|
97
|
+
[onLocationChange],
|
|
98
|
+
);
|
|
99
|
+
const handleError = React.useCallback(
|
|
100
|
+
async (event: ReaderErrorEvent) => {
|
|
101
|
+
setError(event);
|
|
102
|
+
await onReaderError?.(event);
|
|
103
|
+
},
|
|
104
|
+
[onReaderError],
|
|
105
|
+
);
|
|
106
|
+
const handleOpenExternalLink = React.useCallback(
|
|
107
|
+
async (event: { readonly url: string }) => onOpenExternalLink?.(event),
|
|
108
|
+
[onOpenExternalLink],
|
|
109
|
+
);
|
|
110
|
+
return {
|
|
111
|
+
command,
|
|
112
|
+
error,
|
|
113
|
+
handleError,
|
|
114
|
+
handleNextPage,
|
|
115
|
+
handleOpenExternalLink,
|
|
116
|
+
handlePreviousPage,
|
|
117
|
+
handleStateChange,
|
|
118
|
+
state,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function useReaderCommandState(sourceUri: string | undefined) {
|
|
123
|
+
const [command, setCommand] = React.useState<ReaderCommand>();
|
|
124
|
+
const commandIdRef = React.useRef(0);
|
|
125
|
+
const issueCommand = React.useCallback(
|
|
126
|
+
(type: ReaderCommand['type']) => {
|
|
127
|
+
if (!sourceUri) return;
|
|
128
|
+
commandIdRef.current += 1;
|
|
129
|
+
setCommand({ id: commandIdRef.current, sourceUri, type });
|
|
130
|
+
},
|
|
131
|
+
[sourceUri],
|
|
132
|
+
);
|
|
133
|
+
return { command, issueCommand };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function useReaderAppearance(props: ReaderSurfaceProps): ReaderAppearance {
|
|
137
|
+
const colorScheme = props.readerColorScheme ?? 'system';
|
|
138
|
+
const fontScale = props.fontScale ?? 1;
|
|
139
|
+
const lineHeight = resolveLineHeight(props.lineHeight ?? 'normal');
|
|
140
|
+
return React.useMemo(
|
|
141
|
+
() => ({ colorScheme, fontScale, lineHeight }),
|
|
142
|
+
[colorScheme, fontScale, lineHeight],
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function resolveReaderSourceUri(source: ReaderSurfaceProps['source']): string | undefined {
|
|
147
|
+
if (typeof source === 'string') return source;
|
|
148
|
+
if (typeof source === 'number') return Image.resolveAssetSource(source).uri;
|
|
149
|
+
return source ? source.uri : undefined;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function resolveLineHeight(lineHeight: NonNullable<ReaderSurfaceProps['lineHeight']>): number {
|
|
153
|
+
if (lineHeight === 'compact') return 1.25;
|
|
154
|
+
if (lineHeight === 'relaxed') return 1.75;
|
|
155
|
+
return 1.5;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const styles = StyleSheet.create({
|
|
159
|
+
viewport: { flex: 1, minHeight: 360 },
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const DOM_CONFIGURATION = {
|
|
163
|
+
allowsInlineMediaPlayback: false,
|
|
164
|
+
javaScriptCanOpenWindowsAutomatically: false,
|
|
165
|
+
mediaPlaybackRequiresUserAction: true,
|
|
166
|
+
originWhitelist: ['about:blank', 'blob:*'],
|
|
167
|
+
scrollEnabled: false,
|
|
168
|
+
style: styles.viewport,
|
|
169
|
+
unstable_useExpoModulesBridge: false,
|
|
170
|
+
};
|