@readium/navigator 2.6.1 → 2.7.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +28 -0
- package/dist/index.js +4795 -3156
- package/dist/index.umd.cjs +42 -40
- package/package.json +10 -10
- package/src/divina/DivinaNavigator.ts +662 -0
- package/src/divina/DivinaPageManager.ts +322 -0
- package/src/divina/DivinaPagedPresenter.ts +508 -0
- package/src/divina/DivinaPeripherals.ts +630 -0
- package/src/divina/DivinaScrolledPresenter.ts +516 -0
- package/src/divina/DivinaSpreader.ts +202 -0
- package/src/divina/DivinaVariantSelector.ts +77 -0
- package/src/divina/index.ts +8 -0
- package/src/divina/preferences/DivinaDefaults.ts +37 -0
- package/src/divina/preferences/DivinaPreferences.ts +77 -0
- package/src/divina/preferences/DivinaPreferencesEditor.ts +89 -0
- package/src/divina/preferences/DivinaSettings.ts +38 -0
- package/src/divina/preferences/index.ts +4 -0
- package/src/divina/protection/DivinaNavigatorProtector.ts +44 -0
- package/src/epub/EpubNavigator.ts +59 -3
- package/src/epub/frame/FrameBlobBuilder.ts +85 -32
- package/src/epub/frame/FrameManager.ts +3 -3
- package/src/epub/frame/FramePoolManager.ts +78 -34
- package/src/epub/fxl/FXLFrameManager.ts +69 -26
- package/src/epub/fxl/FXLFramePoolManager.ts +88 -50
- package/src/epub/fxl/FXLPeripherals.ts +22 -1
- package/src/index.ts +1 -0
- package/src/peripherals/KeyboardConditionBridge.ts +15 -5
- package/src/peripherals/KeyboardPeripherals.ts +13 -7
- package/src/webpub/WebPubFrameManager.ts +3 -3
- package/types/src/divina/DivinaNavigator.d.ts +113 -0
- package/types/src/divina/DivinaPageManager.d.ts +66 -0
- package/types/src/divina/DivinaPagedPresenter.d.ts +122 -0
- package/types/src/divina/DivinaPeripherals.d.ts +121 -0
- package/types/src/divina/DivinaScrolledPresenter.d.ts +125 -0
- package/types/src/divina/DivinaSpreader.d.ts +65 -0
- package/types/src/divina/DivinaVariantSelector.d.ts +30 -0
- package/types/src/divina/index.d.ts +8 -0
- package/types/src/divina/preferences/DivinaDefaults.d.ts +18 -0
- package/types/src/divina/preferences/DivinaPreferences.d.ts +33 -0
- package/types/src/divina/preferences/DivinaPreferencesEditor.d.ts +20 -0
- package/types/src/divina/preferences/DivinaSettings.d.ts +26 -0
- package/types/src/divina/preferences/index.d.ts +4 -0
- package/types/src/divina/protection/DivinaNavigatorProtector.d.ts +14 -0
- package/types/src/epub/EpubNavigator.d.ts +3 -1
- package/types/src/epub/frame/FrameBlobBuilder.d.ts +6 -3
- package/types/src/epub/frame/FramePoolManager.d.ts +1 -0
- package/types/src/epub/fxl/FXLFrameManager.d.ts +2 -0
- package/types/src/epub/fxl/FXLFramePoolManager.d.ts +6 -2
- package/types/src/epub/fxl/FXLPeripherals.d.ts +3 -0
- package/types/src/index.d.ts +1 -0
- package/types/src/peripherals/KeyboardPeripherals.d.ts +1 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { Layout, Link, Locator, Publication, ReadingProgression } from "@readium/shared";
|
|
2
|
+
import { ContextMenuEvent, FrameClickEvent, SuspiciousActivityEvent } from "@readium/navigator-html-injectables";
|
|
3
|
+
import { IContentProtectionConfig, IKeyboardPeripheralsConfig, KeyboardPeripheralEventData, VisualNavigator, VisualNavigatorViewport } from "../Navigator.ts";
|
|
4
|
+
import { Configurable, ConfigurableSettings } from "../preferences/Configurable.ts";
|
|
5
|
+
import { IDivinaDefaults } from "./preferences/DivinaDefaults.ts";
|
|
6
|
+
import { DivinaPreferences, IDivinaPreferences } from "./preferences/DivinaPreferences.ts";
|
|
7
|
+
import { DivinaSettings } from "./preferences/DivinaSettings.ts";
|
|
8
|
+
import { DivinaPreferencesEditor } from "./preferences/DivinaPreferencesEditor.ts";
|
|
9
|
+
export interface DivinaNavigatorConfiguration {
|
|
10
|
+
preferences: IDivinaPreferences;
|
|
11
|
+
defaults: IDivinaDefaults;
|
|
12
|
+
contentProtection?: IContentProtectionConfig;
|
|
13
|
+
keyboardPeripherals?: IKeyboardPeripheralsConfig;
|
|
14
|
+
}
|
|
15
|
+
export interface DivinaNavigatorListeners {
|
|
16
|
+
positionChanged: (locator: Locator) => void;
|
|
17
|
+
tap: (e: FrameClickEvent) => boolean;
|
|
18
|
+
click: (e: FrameClickEvent) => boolean;
|
|
19
|
+
zoom: (scale: number) => void;
|
|
20
|
+
miscPointer: (amount: number) => void;
|
|
21
|
+
scroll: (delta: number) => void;
|
|
22
|
+
customEvent: (key: string, data: unknown) => void;
|
|
23
|
+
handleLocator: (locator: Locator) => boolean;
|
|
24
|
+
contentProtection: (type: string, data: SuspiciousActivityEvent) => void;
|
|
25
|
+
contextMenu: (data: ContextMenuEvent) => void;
|
|
26
|
+
peripheral: (data: KeyboardPeripheralEventData) => void;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Navigator for Divina (visual narrative) publications: bitmap pages rendered
|
|
30
|
+
* as <img> elements with blob sources, in either a horizontal paged view with
|
|
31
|
+
* synthetic spreads (comics/manga) or a continuous vertical scroll (webtoons).
|
|
32
|
+
*/
|
|
33
|
+
export declare class DivinaNavigator extends VisualNavigator implements Configurable<ConfigurableSettings, DivinaPreferences> {
|
|
34
|
+
private readonly pub;
|
|
35
|
+
private readonly container;
|
|
36
|
+
private readonly listeners;
|
|
37
|
+
private positions;
|
|
38
|
+
private currentLocation;
|
|
39
|
+
private _destroyed;
|
|
40
|
+
private _isNavigating;
|
|
41
|
+
private _preferences;
|
|
42
|
+
private _defaults;
|
|
43
|
+
private _settings;
|
|
44
|
+
private _preferencesEditor;
|
|
45
|
+
private readonly spreader;
|
|
46
|
+
private readonly pool;
|
|
47
|
+
private pagedPresenter;
|
|
48
|
+
private scrolledPresenter;
|
|
49
|
+
private readonly _contentProtection;
|
|
50
|
+
private readonly _keyboardPeripherals;
|
|
51
|
+
private readonly _navigatorProtector;
|
|
52
|
+
private _keyboardPeripheralsManager;
|
|
53
|
+
private readonly _keyboardConditionBridge;
|
|
54
|
+
private readonly _suspiciousActivityListener;
|
|
55
|
+
private readonly _keyboardPeripheralListener;
|
|
56
|
+
private resizeObserver;
|
|
57
|
+
constructor(container: HTMLElement, pub: Publication, listeners: Partial<DivinaNavigatorListeners>, positions?: Locator[], initialPosition?: Locator | undefined, configuration?: DivinaNavigatorConfiguration);
|
|
58
|
+
private get manifestScrolled();
|
|
59
|
+
get ownerWindow(): Window;
|
|
60
|
+
get publication(): Publication;
|
|
61
|
+
get layout(): Layout;
|
|
62
|
+
get readingProgression(): ReadingProgression;
|
|
63
|
+
private get presenter();
|
|
64
|
+
load(): Promise<boolean | undefined>;
|
|
65
|
+
private synthesizePositions;
|
|
66
|
+
private createPresenter;
|
|
67
|
+
private destroyPresenter;
|
|
68
|
+
private handlePointer;
|
|
69
|
+
/** Paged mode: move by one spread and report the new position */
|
|
70
|
+
private changeSpread;
|
|
71
|
+
private setLocationToIndex;
|
|
72
|
+
/** Scrolled mode: debounced position reports from the scroll handler */
|
|
73
|
+
private updateLocationFromScroll;
|
|
74
|
+
private applyBackground;
|
|
75
|
+
/** Whether a CSS color is dark, via computed-style normalization; null if unparseable */
|
|
76
|
+
private isDarkColor;
|
|
77
|
+
resizeHandler(): void;
|
|
78
|
+
private completeLocator;
|
|
79
|
+
/**
|
|
80
|
+
* The reading order index for a href, tolerating representation
|
|
81
|
+
* mismatches: a TOC can use absolute URLs while the reading order is
|
|
82
|
+
* relative (or vice versa), and it can reference a page through one of
|
|
83
|
+
* its alternate variants rather than the main href. Both sides are
|
|
84
|
+
* resolved against the publication base, and each page matches on its
|
|
85
|
+
* own href or any of its alternates.
|
|
86
|
+
*/
|
|
87
|
+
private findReadingOrderIndex;
|
|
88
|
+
go(locator: Locator, animated: boolean, cb: (ok: boolean) => void): void;
|
|
89
|
+
goLink(link: Link, animated: boolean, cb: (ok: boolean) => void): void;
|
|
90
|
+
goForward(animated: boolean, cb: (ok: boolean) => void): void;
|
|
91
|
+
goBackward(animated: boolean, cb: (ok: boolean) => void): void;
|
|
92
|
+
/**
|
|
93
|
+
* Scrolled mode: scroll by a fixed pixel amount (e.g. a line step for
|
|
94
|
+
* arrow keys) instead of the viewport-sized go* step. Falls back to a
|
|
95
|
+
* spread turn in paged mode.
|
|
96
|
+
*/
|
|
97
|
+
scrollBy(px: number, animated: boolean, cb: (ok: boolean) => void): void;
|
|
98
|
+
zoomIn(): void;
|
|
99
|
+
zoomOut(): void;
|
|
100
|
+
zoomReset(): void;
|
|
101
|
+
get currentLocator(): Locator;
|
|
102
|
+
get viewport(): VisualNavigatorViewport;
|
|
103
|
+
get isScrollStart(): boolean;
|
|
104
|
+
get isScrollEnd(): boolean;
|
|
105
|
+
get canGoBackward(): boolean;
|
|
106
|
+
get canGoForward(): boolean;
|
|
107
|
+
get settings(): Readonly<DivinaSettings>;
|
|
108
|
+
get preferencesEditor(): DivinaPreferencesEditor;
|
|
109
|
+
private _applying;
|
|
110
|
+
submitPreferences(preferences: DivinaPreferences): Promise<void>;
|
|
111
|
+
private applyPreferences;
|
|
112
|
+
destroy(): Promise<void>;
|
|
113
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Link, Page } from "@readium/shared";
|
|
2
|
+
import { DivinaPage } from "./DivinaSpreader.ts";
|
|
3
|
+
import { DivinaQuality } from "./DivinaVariantSelector.ts";
|
|
4
|
+
export declare function placeholderSVG(width: number, height: number, message: string, detail?: string): string;
|
|
5
|
+
export type DivinaPageMode = "paged" | "scrolled";
|
|
6
|
+
/**
|
|
7
|
+
* Manages the lifecycle of a single Divina page: an <img> element inside a
|
|
8
|
+
* wrapper <div>, with blob-based loading, an intrinsic-size SVG placeholder,
|
|
9
|
+
* and per-mode layout. The image-based equivalent of FXLFrameManager.
|
|
10
|
+
*/
|
|
11
|
+
export declare class DivinaPageManager {
|
|
12
|
+
readonly page: DivinaPage;
|
|
13
|
+
readonly wrapper: HTMLDivElement;
|
|
14
|
+
readonly img: HTMLImageElement;
|
|
15
|
+
private readonly fetchBlob;
|
|
16
|
+
private readonly getQuality;
|
|
17
|
+
private readonly placeholder;
|
|
18
|
+
private objectURL;
|
|
19
|
+
private loadPromise;
|
|
20
|
+
private abortController;
|
|
21
|
+
private _loaded;
|
|
22
|
+
private destroyed;
|
|
23
|
+
private generation;
|
|
24
|
+
private mode;
|
|
25
|
+
constructor(page: DivinaPage, fetchBlob: (link: Link, signal?: AbortSignal) => Promise<Blob>, getQuality?: () => DivinaQuality);
|
|
26
|
+
get intrinsicWidth(): number;
|
|
27
|
+
get intrinsicHeight(): number;
|
|
28
|
+
get loaded(): boolean;
|
|
29
|
+
get element(): HTMLDivElement;
|
|
30
|
+
/**
|
|
31
|
+
* Configure the wrapper/image styles for the given presentation mode.
|
|
32
|
+
* Paged: the image is fitted (letterboxed) inside the slot-sized wrapper.
|
|
33
|
+
* Scrolled: the image dictates the height via its aspect ratio.
|
|
34
|
+
*/
|
|
35
|
+
setMode(mode: DivinaPageMode): void;
|
|
36
|
+
/**
|
|
37
|
+
* Position the fitted image within its wrapper (paged mode only).
|
|
38
|
+
*
|
|
39
|
+
* @param position In a two-page spread, the side of the gutter this page
|
|
40
|
+
* sits on (pages butt against the gutter). With `half`, the half of a
|
|
41
|
+
* full-spread wrapper the page occupies.
|
|
42
|
+
* @param half The page is alone in a double-width (full-spread) slot but
|
|
43
|
+
* carries a left/right position hint — e.g. a shifted cover or the
|
|
44
|
+
* orphaned component of a spread — so it is fitted to and centered
|
|
45
|
+
* within its half of the spread.
|
|
46
|
+
*/
|
|
47
|
+
fit(position: Page, half?: boolean): void;
|
|
48
|
+
/** Scrolled mode: constrain the display width of this page in the strip */
|
|
49
|
+
applyStripWidth(displayWidth: number): void;
|
|
50
|
+
/**
|
|
51
|
+
* Fetch the image as a blob and swap it in for the placeholder.
|
|
52
|
+
* Idempotent; concurrent calls await the same promise.
|
|
53
|
+
*/
|
|
54
|
+
/**
|
|
55
|
+
* The most appropriate variant (main link or alternate) for the page's
|
|
56
|
+
* current display box and the user's quality preference.
|
|
57
|
+
*/
|
|
58
|
+
private pickVariant;
|
|
59
|
+
load(): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Release the blob and go back to the placeholder (memory reclamation),
|
|
62
|
+
* aborting any in-flight fetch.
|
|
63
|
+
*/
|
|
64
|
+
unload(): void;
|
|
65
|
+
destroy(): void;
|
|
66
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { Publication } from "@readium/shared";
|
|
2
|
+
import { VisualNavigatorViewport } from "../Navigator.ts";
|
|
3
|
+
import { DivinaPageManager } from "./DivinaPageManager.ts";
|
|
4
|
+
import { DivinaManagerEventKey, DivinaPagedManager, DivinaPeripherals } from "./DivinaPeripherals.ts";
|
|
5
|
+
import { DivinaPage, DivinaSpreader } from "./DivinaSpreader.ts";
|
|
6
|
+
export type DivinaPresenterListener = (key: DivinaManagerEventKey, data: unknown) => void;
|
|
7
|
+
/**
|
|
8
|
+
* Horizontal paged presenter for Divina publications: a sliding spine of
|
|
9
|
+
* page slots with synthetic spreads, ported from the FXLFramePoolManager
|
|
10
|
+
* slider core but rendering <img> pages instead of iframes.
|
|
11
|
+
*
|
|
12
|
+
* Slot model: in double-page mode every spread occupies exactly two slots
|
|
13
|
+
* (single pages get a double-width slot), so slide indices are always even
|
|
14
|
+
* and always aligned with spread boundaries.
|
|
15
|
+
*/
|
|
16
|
+
export declare class DivinaPagedPresenter implements DivinaPagedManager {
|
|
17
|
+
private readonly container;
|
|
18
|
+
private readonly pub;
|
|
19
|
+
private readonly spreader;
|
|
20
|
+
private readonly pool;
|
|
21
|
+
readonly bookElement: HTMLDivElement;
|
|
22
|
+
readonly spineElement: HTMLDivElement;
|
|
23
|
+
readonly peripherals: DivinaPeripherals;
|
|
24
|
+
width: number;
|
|
25
|
+
height: number;
|
|
26
|
+
currentSlide: number;
|
|
27
|
+
private transform;
|
|
28
|
+
private spread;
|
|
29
|
+
private orientationInternal;
|
|
30
|
+
private containerHeightCached;
|
|
31
|
+
private destroyed;
|
|
32
|
+
private loadTimers;
|
|
33
|
+
listener: DivinaPresenterListener;
|
|
34
|
+
constructor(container: HTMLElement, pub: Publication, spreader: DivinaSpreader, pool: Map<number, DivinaPageManager>, spreads: boolean);
|
|
35
|
+
get rtl(): boolean;
|
|
36
|
+
private get single();
|
|
37
|
+
get perPage(): 1 | 2;
|
|
38
|
+
get threshold(): number;
|
|
39
|
+
get portrait(): boolean;
|
|
40
|
+
get slength(): number;
|
|
41
|
+
/** Total slot count in the current mode */
|
|
42
|
+
get length(): number;
|
|
43
|
+
/**
|
|
44
|
+
* The x position of a slot boundary within the spine, snapped to device
|
|
45
|
+
* pixels. Fractional boundaries leave hairline gaps at the spread gutter
|
|
46
|
+
* (the background shows through between the two page images), varying
|
|
47
|
+
* with the window size.
|
|
48
|
+
*/
|
|
49
|
+
private slotEdge;
|
|
50
|
+
private get offset();
|
|
51
|
+
get doNotDisturb(): boolean;
|
|
52
|
+
/** The reading-order index of the first page of the current spread */
|
|
53
|
+
get currentIndex(): number;
|
|
54
|
+
/** The pages currently (meant to be) visible */
|
|
55
|
+
get currentSpread(): DivinaPage[];
|
|
56
|
+
private updateDimensions;
|
|
57
|
+
private bookScale;
|
|
58
|
+
private bookTranslate;
|
|
59
|
+
private bookTransform;
|
|
60
|
+
updateBookStyle(initial?: boolean): void;
|
|
61
|
+
updateSpineStyle(animate: boolean, fast?: boolean): void;
|
|
62
|
+
/**
|
|
63
|
+
* Recompute every slot's width and height. Widths are derived from
|
|
64
|
+
* device-pixel-snapped slot boundaries so adjacent pages of a spread
|
|
65
|
+
* meet exactly at the gutter without subpixel seams.
|
|
66
|
+
*/
|
|
67
|
+
private updateSlotSizes;
|
|
68
|
+
reAlign(index?: number): number;
|
|
69
|
+
/**
|
|
70
|
+
* Moves the spine to the position of the currently active slide.
|
|
71
|
+
* Always commits: the spine transform may have been written directly by
|
|
72
|
+
* the peripherals during a drag (snap-back relies on this), and reading
|
|
73
|
+
* style.transform back for comparison is unreliable under CSS Typed OM.
|
|
74
|
+
*/
|
|
75
|
+
slideToCurrent(enableTransition?: boolean, fast?: boolean): void;
|
|
76
|
+
bounce(rtl?: boolean): void;
|
|
77
|
+
private onChange;
|
|
78
|
+
/**
|
|
79
|
+
* Go to the next spread/page.
|
|
80
|
+
* @returns whether moving forward was possible
|
|
81
|
+
*/
|
|
82
|
+
next(): boolean;
|
|
83
|
+
/**
|
|
84
|
+
* Go to the previous spread/page.
|
|
85
|
+
* @returns whether moving backward was possible
|
|
86
|
+
*/
|
|
87
|
+
prev(): boolean;
|
|
88
|
+
/**
|
|
89
|
+
* Go to the slide showing the given reading order item.
|
|
90
|
+
* @returns whether the slide changed
|
|
91
|
+
*/
|
|
92
|
+
/**
|
|
93
|
+
* The slide showing the given item in the current mode: the item index
|
|
94
|
+
* itself in single mode, or the start slot of the item's spread in
|
|
95
|
+
* double-page mode (the item may occupy the spread's second slot).
|
|
96
|
+
*/
|
|
97
|
+
private slideForItem;
|
|
98
|
+
goToItem(itemIndex: number, animated?: boolean): boolean;
|
|
99
|
+
/**
|
|
100
|
+
* Loads pages within the load window around the current position,
|
|
101
|
+
* unloads distant ones, and positions the current spread.
|
|
102
|
+
*/
|
|
103
|
+
update(): Promise<void>;
|
|
104
|
+
/**
|
|
105
|
+
* Fit a page of the current spread within its slot. A lone portrait page
|
|
106
|
+
* with a left/right hint (shifted cover, orphaned spread half) is fitted
|
|
107
|
+
* into its half of the full-spread slot; everything else is fitted to the
|
|
108
|
+
* whole slot and anchored against the gutter (or centered).
|
|
109
|
+
*/
|
|
110
|
+
private placePage;
|
|
111
|
+
/** Union of the current spread's image bounds, used for zoom/pan clamping */
|
|
112
|
+
get currentBounds(): DOMRect;
|
|
113
|
+
get viewport(): VisualNavigatorViewport;
|
|
114
|
+
/**
|
|
115
|
+
* When the container resizes, resize slider components as well
|
|
116
|
+
*/
|
|
117
|
+
resizeHandler(slide?: boolean, fast?: boolean): void;
|
|
118
|
+
/** Toggle double-page spreads (from preferences) */
|
|
119
|
+
setSpreads(spreads: boolean): void;
|
|
120
|
+
deselect(): void;
|
|
121
|
+
destroy(): Promise<void>;
|
|
122
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { PanTracker } from "../epub/fxl/FXLPeripherals.ts";
|
|
2
|
+
export type DivinaManagerEventKey = "no_more" | "no_less" | "zoom" | "tap" | "click";
|
|
3
|
+
export interface DivinaPointerEvent {
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
clientX: number;
|
|
7
|
+
clientY: number;
|
|
8
|
+
doNotDisturb: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The narrow surface of DivinaPagedPresenter that the peripherals interact with.
|
|
12
|
+
* Mirrors what FXLPeripherals uses from FXLFramePoolManager.
|
|
13
|
+
*/
|
|
14
|
+
export interface DivinaPagedManager {
|
|
15
|
+
readonly spineElement: HTMLDivElement;
|
|
16
|
+
readonly bookElement: HTMLDivElement;
|
|
17
|
+
readonly width: number;
|
|
18
|
+
readonly height: number;
|
|
19
|
+
readonly perPage: number;
|
|
20
|
+
readonly rtl: boolean;
|
|
21
|
+
readonly threshold: number;
|
|
22
|
+
readonly length: number;
|
|
23
|
+
readonly currentSlide: number;
|
|
24
|
+
readonly currentBounds: DOMRect;
|
|
25
|
+
listener(key: DivinaManagerEventKey, data: unknown): void;
|
|
26
|
+
updateBookStyle(initial?: boolean): void;
|
|
27
|
+
updateSpineStyle(animate: boolean, fast?: boolean): void;
|
|
28
|
+
slideToCurrent(enableTransition?: boolean, fast?: boolean): void;
|
|
29
|
+
deselect(): void;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Input handling for the paged Divina presenter. Ported from FXLPeripherals
|
|
33
|
+
* (touch pan/pinch-zoom/swipe state machines) with additions for content that
|
|
34
|
+
* lives in the host DOM instead of iframes: click/tap synthesis, mouse wheel
|
|
35
|
+
* page turning (one page per wheel burst, xbreader/bibi-like), ctrl+wheel and
|
|
36
|
+
* double-click zoom.
|
|
37
|
+
*/
|
|
38
|
+
export declare class DivinaPeripherals {
|
|
39
|
+
private readonly manager;
|
|
40
|
+
private readonly coordinator;
|
|
41
|
+
dragState: number;
|
|
42
|
+
private minimumMoved;
|
|
43
|
+
pan: PanTracker;
|
|
44
|
+
private pinch;
|
|
45
|
+
private _scale;
|
|
46
|
+
get scale(): number;
|
|
47
|
+
private scaleDebouncer;
|
|
48
|
+
set scale(value: number);
|
|
49
|
+
private frameBounds;
|
|
50
|
+
private destroyed;
|
|
51
|
+
private mouseDown;
|
|
52
|
+
private clickTimer;
|
|
53
|
+
private lastTouchEnd;
|
|
54
|
+
private tapTimer;
|
|
55
|
+
private lastTapTime;
|
|
56
|
+
private lastTapX;
|
|
57
|
+
private lastTapY;
|
|
58
|
+
private wheelHot;
|
|
59
|
+
private wheelLastDir;
|
|
60
|
+
private wheelLastMag;
|
|
61
|
+
private wheelDecelCount;
|
|
62
|
+
private wheelLastEvent;
|
|
63
|
+
private wheelCooldownTimer;
|
|
64
|
+
constructor(manager: DivinaPagedManager);
|
|
65
|
+
private readonly btouchstartHandler;
|
|
66
|
+
private readonly btouchendHandler;
|
|
67
|
+
private readonly btouchmoveHandler;
|
|
68
|
+
private readonly bdblclickHandler;
|
|
69
|
+
private readonly bmousedownHandler;
|
|
70
|
+
private readonly bmouseupHandler;
|
|
71
|
+
private readonly bmousemoveHandler;
|
|
72
|
+
private readonly bwheelHandler;
|
|
73
|
+
observe(item: EventTarget): void;
|
|
74
|
+
unobserve(item: EventTarget): void;
|
|
75
|
+
destroy(): void;
|
|
76
|
+
private clearPan;
|
|
77
|
+
clearPinch(): void;
|
|
78
|
+
get isScaled(): boolean;
|
|
79
|
+
resetZoom(): void;
|
|
80
|
+
private emitPointer;
|
|
81
|
+
/**
|
|
82
|
+
* touchstart event handler (ported from FXLPeripherals)
|
|
83
|
+
*/
|
|
84
|
+
touchstartHandler(e: TouchEvent): void;
|
|
85
|
+
private startTouch;
|
|
86
|
+
/**
|
|
87
|
+
* touchend event handler (ported from FXLPeripherals, adds tap synthesis)
|
|
88
|
+
*/
|
|
89
|
+
touchendHandler(e: TouchEvent): void;
|
|
90
|
+
private moveFrame;
|
|
91
|
+
private dragTranslate;
|
|
92
|
+
private dragTransform;
|
|
93
|
+
/**
|
|
94
|
+
* touchmove event handler (ported from FXLPeripherals)
|
|
95
|
+
*/
|
|
96
|
+
touchmoveHandler(e: TouchEvent): void;
|
|
97
|
+
/**
|
|
98
|
+
* Double click/tap: toggle zoom between 1x and 2x, centered on the point.
|
|
99
|
+
*/
|
|
100
|
+
toggleZoom(clientX: number, clientY: number): void;
|
|
101
|
+
/** Zoom in/out by a keyboard step, clamped to [1, MAX_SCALE] */
|
|
102
|
+
zoomBy(delta: number): void;
|
|
103
|
+
zoomIn(): void;
|
|
104
|
+
zoomOut(): void;
|
|
105
|
+
dblclickHandler(e: MouseEvent): void;
|
|
106
|
+
private addTouch;
|
|
107
|
+
mousedownHandler(e: MouseEvent): void;
|
|
108
|
+
mouseupHandler(e: MouseEvent): void;
|
|
109
|
+
mousemoveHandler(e: MouseEvent): void;
|
|
110
|
+
/**
|
|
111
|
+
* Wheel handler: ctrl+wheel zooms (trackpad pinch on Chrome), otherwise a
|
|
112
|
+
* wheel burst turns exactly one page. A new page turn requires either
|
|
113
|
+
* 200ms of quiet (fresh gesture), a direction reversal, or a re-acceleration
|
|
114
|
+
* after a decelerating tail (deliberate consecutive flicks).
|
|
115
|
+
*/
|
|
116
|
+
wheelHandler(e: WheelEvent): void;
|
|
117
|
+
/**
|
|
118
|
+
* Recalculate drag/swipe event and reposition the frame of the slider
|
|
119
|
+
*/
|
|
120
|
+
private updateAfterDrag;
|
|
121
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { VisualNavigatorViewport } from "../Navigator.ts";
|
|
2
|
+
import { DivinaPageManager } from "./DivinaPageManager.ts";
|
|
3
|
+
import { DivinaSpreader } from "./DivinaSpreader.ts";
|
|
4
|
+
export type DivinaScrolledEventKey = "scroll" | "position" | "tap" | "click";
|
|
5
|
+
export type DivinaScrolledListener = (key: DivinaScrolledEventKey, data: unknown) => void;
|
|
6
|
+
/**
|
|
7
|
+
* Vertical scrolled ("webtoon") presenter for Divina publications: all pages
|
|
8
|
+
* stacked in a native scroll container with no gaps, constrained to a
|
|
9
|
+
* configurable strip width, based on xbreader but improved
|
|
10
|
+
*
|
|
11
|
+
* Page offsets are cached and looked up with binary search so per-frame
|
|
12
|
+
* scroll work stays constant regardless of publication size.
|
|
13
|
+
*/
|
|
14
|
+
export declare class DivinaScrolledPresenter {
|
|
15
|
+
private readonly container;
|
|
16
|
+
private readonly spreader;
|
|
17
|
+
private readonly pool;
|
|
18
|
+
readonly scrollerElement: HTMLDivElement;
|
|
19
|
+
private stripWidth;
|
|
20
|
+
private destroyed;
|
|
21
|
+
private scrollRAF;
|
|
22
|
+
private restoreRAF;
|
|
23
|
+
private positionTimer;
|
|
24
|
+
private lastScrollTop;
|
|
25
|
+
/** Position within the strip as a fraction of total strip height (resize retention) */
|
|
26
|
+
private fraction;
|
|
27
|
+
private pageTops;
|
|
28
|
+
private pageHeights;
|
|
29
|
+
private stripHeightCached;
|
|
30
|
+
private viewportHeightCached;
|
|
31
|
+
private loadedIndices;
|
|
32
|
+
private lastWindowIndex;
|
|
33
|
+
private programmaticScroll;
|
|
34
|
+
private programmaticTimer;
|
|
35
|
+
private scrollTarget;
|
|
36
|
+
private animating;
|
|
37
|
+
private animRAF;
|
|
38
|
+
private animLastTime;
|
|
39
|
+
listener: DivinaScrolledListener;
|
|
40
|
+
constructor(container: HTMLElement, spreader: DivinaSpreader, pool: Map<number, DivinaPageManager>, stripWidth: number);
|
|
41
|
+
private readonly bscrollHandler;
|
|
42
|
+
private readonly bscrollendHandler;
|
|
43
|
+
private readonly banimStep;
|
|
44
|
+
private readonly buserTakeoverHandler;
|
|
45
|
+
private readonly bmousedownHandler;
|
|
46
|
+
private readonly bclickHandler;
|
|
47
|
+
private readonly btouchendHandler;
|
|
48
|
+
private readonly btouchstartHandler;
|
|
49
|
+
/** Display width of each page in the strip */
|
|
50
|
+
private get displayWidth();
|
|
51
|
+
/**
|
|
52
|
+
* Recomputes the cached page offsets and viewport metrics.
|
|
53
|
+
* Cheap relative to the per-frame layout reads it replaces, but still a
|
|
54
|
+
* forced layout: only call after actual layout changes.
|
|
55
|
+
*/
|
|
56
|
+
private refreshLayoutCache;
|
|
57
|
+
/** The index of the last page whose top is at or above the given strip offset */
|
|
58
|
+
private indexAtOffset;
|
|
59
|
+
applyStripWidth(stripWidth: number): void;
|
|
60
|
+
private restoreFraction;
|
|
61
|
+
private updateFraction;
|
|
62
|
+
/** Top offset in pixels of the given page within the strip */
|
|
63
|
+
private pageTop;
|
|
64
|
+
private pageHeight;
|
|
65
|
+
/**
|
|
66
|
+
* The reading-order index of the current page: the last page whose top
|
|
67
|
+
* is above the middle of the viewport.
|
|
68
|
+
*/
|
|
69
|
+
get currentIndex(): number;
|
|
70
|
+
/** Progression within the current page (0..1) */
|
|
71
|
+
get currentPageProgression(): number;
|
|
72
|
+
/**
|
|
73
|
+
* Marks the start of a programmatic scroll: "scroll" events are not
|
|
74
|
+
* reported to the host until the scroll settles (scrollend or timeout),
|
|
75
|
+
* so navigation doesn't masquerade as user scrolling.
|
|
76
|
+
*/
|
|
77
|
+
private beginProgrammaticScroll;
|
|
78
|
+
private scrollendHandler;
|
|
79
|
+
/** Load the window around wherever the scroll settled */
|
|
80
|
+
private settleWindow;
|
|
81
|
+
/** Direct user input takes over any in-flight programmatic scroll */
|
|
82
|
+
private userTakeoverHandler;
|
|
83
|
+
private scrollHandler;
|
|
84
|
+
/** Load nearby pages, unload distant ones */
|
|
85
|
+
private updateWindow;
|
|
86
|
+
private touchStartY;
|
|
87
|
+
private lastTouchEnd;
|
|
88
|
+
private touchstartHandler;
|
|
89
|
+
private touchendHandler;
|
|
90
|
+
private mouseDownPos;
|
|
91
|
+
private mousedownHandler;
|
|
92
|
+
private clickHandler;
|
|
93
|
+
private emitPointer;
|
|
94
|
+
/**
|
|
95
|
+
* Scroll forward/backward by a fraction of the viewport.
|
|
96
|
+
* Repeated calls chain from the pending target, so rapid key presses
|
|
97
|
+
* (including autorepeat from a held key) extend one animation that keeps
|
|
98
|
+
* cruising instead of restarting; the target is clamped to stay within
|
|
99
|
+
* reach of the position so releasing the key stops promptly rather than
|
|
100
|
+
* replaying the queued distance.
|
|
101
|
+
* @returns whether scrolling was possible
|
|
102
|
+
*/
|
|
103
|
+
next(animated?: boolean, stepPx?: number): boolean;
|
|
104
|
+
prev(animated?: boolean, stepPx?: number): boolean;
|
|
105
|
+
private scrollToTarget;
|
|
106
|
+
/**
|
|
107
|
+
* One frame of the owned scroll animation: exponential approach toward
|
|
108
|
+
* the (re)targetable scrollTarget, capped at a cruise speed. Under a held
|
|
109
|
+
* key the cap dominates and the scroll advances at constant speed; once
|
|
110
|
+
* input stops the exponential term eases it out into the target.
|
|
111
|
+
*/
|
|
112
|
+
private animStep;
|
|
113
|
+
private stopAnimation;
|
|
114
|
+
/**
|
|
115
|
+
* Go to the top of a page, optionally at a progression within it.
|
|
116
|
+
* @returns whether the position changed
|
|
117
|
+
*/
|
|
118
|
+
goToItem(itemIndex: number, animated?: boolean, progression?: number): boolean;
|
|
119
|
+
update(): Promise<void>;
|
|
120
|
+
get atStart(): boolean;
|
|
121
|
+
get atEnd(): boolean;
|
|
122
|
+
get viewport(): VisualNavigatorViewport;
|
|
123
|
+
resizeHandler(): void;
|
|
124
|
+
destroy(): Promise<void>;
|
|
125
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Link, Page, Publication } from "@readium/shared";
|
|
2
|
+
/**
|
|
3
|
+
* A single page of a Divina publication, with its resolved layout properties.
|
|
4
|
+
* Unlike FXLSpreader, this model never mutates the publication's links.
|
|
5
|
+
*/
|
|
6
|
+
export declare class DivinaPage {
|
|
7
|
+
readonly link: Link;
|
|
8
|
+
readonly index: number;
|
|
9
|
+
page: Page;
|
|
10
|
+
isLandscape: boolean;
|
|
11
|
+
addBlank: boolean;
|
|
12
|
+
constructor(link: Link, index: number);
|
|
13
|
+
/** 1-based page number */
|
|
14
|
+
get number(): number;
|
|
15
|
+
get authoredPage(): Page | undefined;
|
|
16
|
+
/** Whether this page occupies both slots of a spread in double-page mode */
|
|
17
|
+
get double(): boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Computes synthetic spreads for a Divina publication from the `page`
|
|
21
|
+
* (left/right/center) link properties, falling back to computed alternation.
|
|
22
|
+
*
|
|
23
|
+
* Slot model (used by the paged presenter): in double-page mode every spread
|
|
24
|
+
* occupies exactly two slots — a pair is 1+1, and a lone page (center,
|
|
25
|
+
* landscape or orphaned/addBlank) takes a double-width slot. This keeps
|
|
26
|
+
* slide indices (always even) aligned with spread boundaries.
|
|
27
|
+
*/
|
|
28
|
+
export declare class DivinaSpreader {
|
|
29
|
+
readonly rtl: boolean;
|
|
30
|
+
shift: boolean;
|
|
31
|
+
pages: DivinaPage[];
|
|
32
|
+
spreads: DivinaPage[][];
|
|
33
|
+
private itemToSlotArr;
|
|
34
|
+
private slotToItemArr;
|
|
35
|
+
private spreadOfItemArr;
|
|
36
|
+
constructor(publication: Publication);
|
|
37
|
+
private index;
|
|
38
|
+
private testShift;
|
|
39
|
+
private buildSpreads;
|
|
40
|
+
/**
|
|
41
|
+
* Builds the item <-> slot mappings for double-page mode.
|
|
42
|
+
* Slots are consumed in spread order; every spread takes exactly two slots.
|
|
43
|
+
*/
|
|
44
|
+
private buildSlots;
|
|
45
|
+
/** Total number of slots (spread mode). Always even. */
|
|
46
|
+
get slotCount(): number;
|
|
47
|
+
/** Count of pages that take a double-width slot in spread mode */
|
|
48
|
+
get nDouble(): number;
|
|
49
|
+
slotOfItem(itemIndex: number): number;
|
|
50
|
+
itemOfSlot(slot: number): number;
|
|
51
|
+
spreadIndexOfItem(itemIndex: number): number;
|
|
52
|
+
spreadOfItem(itemIndex: number): DivinaPage[];
|
|
53
|
+
/**
|
|
54
|
+
* Whether the page occupies a full spread on its own in double-page mode
|
|
55
|
+
* (and therefore takes a double-width slot). This mirrors buildSlots()
|
|
56
|
+
* exactly: any page alone in its spread is double.
|
|
57
|
+
*/
|
|
58
|
+
isDouble(itemIndex: number): boolean;
|
|
59
|
+
findByHref(href: string): DivinaPage | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* The position of a page within a spread, viewport-wise:
|
|
62
|
+
* left/right of the gutter, or centered when displayed alone.
|
|
63
|
+
*/
|
|
64
|
+
spreadPosition(spread: DivinaPage[], target: DivinaPage, perPage: number): Page;
|
|
65
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Link } from "@readium/shared";
|
|
2
|
+
/**
|
|
3
|
+
* User-selectable image quality for publications that provide alternate
|
|
4
|
+
* resolutions of their pages (RWPM `alternate` links).
|
|
5
|
+
*/
|
|
6
|
+
export declare enum DivinaQuality {
|
|
7
|
+
auto = "auto",// Match the display resolution
|
|
8
|
+
low = "low",// Smallest available variant
|
|
9
|
+
high = "high",// One step above the display resolution
|
|
10
|
+
max = "max"
|
|
11
|
+
}
|
|
12
|
+
export declare const MOBILE_MAX_HEIGHT = 2560;
|
|
13
|
+
export declare const MOBILE_MAX_WIDTH = 1800;
|
|
14
|
+
/** The link itself and its (bitmap) alternates, if any */
|
|
15
|
+
export declare function gatherVariants(item: Link): Link[];
|
|
16
|
+
export interface VariantSelectionOptions {
|
|
17
|
+
/** Display size of the page in device pixels (CSS box × devicePixelRatio) */
|
|
18
|
+
targetWidth: number;
|
|
19
|
+
targetHeight: number;
|
|
20
|
+
/** The axis that constrains the display: height in paged mode, width in scrolled mode */
|
|
21
|
+
axis: "width" | "height";
|
|
22
|
+
quality: DivinaQuality;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Selects the most appropriate variant of a page image for the current
|
|
26
|
+
* display, based purely on the choices the manifest offers: the smallest
|
|
27
|
+
* variant that covers the display resolution (in device pixels), biased by
|
|
28
|
+
* the quality preference, and capped on mobile devices.
|
|
29
|
+
*/
|
|
30
|
+
export declare function selectVariant(item: Link, opts: VariantSelectionOptions): Link;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from "./DivinaNavigator.ts";
|
|
2
|
+
export * from "./DivinaSpreader.ts";
|
|
3
|
+
export * from "./DivinaPageManager.ts";
|
|
4
|
+
export * from "./DivinaPagedPresenter.ts";
|
|
5
|
+
export * from "./DivinaScrolledPresenter.ts";
|
|
6
|
+
export * from "./DivinaPeripherals.ts";
|
|
7
|
+
export * from "./DivinaVariantSelector.ts";
|
|
8
|
+
export * from "./preferences/index.ts";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { DivinaQuality } from "../DivinaVariantSelector.ts";
|
|
2
|
+
export interface IDivinaDefaults {
|
|
3
|
+
backgroundColor?: string | null;
|
|
4
|
+
constraint?: number | null;
|
|
5
|
+
quality?: DivinaQuality | null;
|
|
6
|
+
scrolled?: boolean | null;
|
|
7
|
+
spreads?: boolean | null;
|
|
8
|
+
stripWidth?: number | null;
|
|
9
|
+
}
|
|
10
|
+
export declare class DivinaDefaults {
|
|
11
|
+
backgroundColor: string | null;
|
|
12
|
+
constraint: number;
|
|
13
|
+
quality: DivinaQuality;
|
|
14
|
+
scrolled: boolean | null;
|
|
15
|
+
spreads: boolean;
|
|
16
|
+
stripWidth: number;
|
|
17
|
+
constructor(defaults: IDivinaDefaults);
|
|
18
|
+
}
|