@readium/navigator 2.11.1 → 2.12.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.
Files changed (52) hide show
  1. package/dist/divina/DivinaNavigator.js +1 -1
  2. package/dist/epub/EpubNavigator.js +1 -1
  3. package/dist/epub/frame/FramePoolManager.js +1 -1
  4. package/dist/index.js +1 -1
  5. package/dist/preferences/guards.js +1 -1
  6. package/dist/readaloud/GuidedNavigationProvider.js +1 -0
  7. package/dist/readaloud/ReadAloudNavigator.js +1 -0
  8. package/dist/readaloud/ReadingUnit.js +1 -0
  9. package/dist/readaloud/SpeechProgress.js +1 -0
  10. package/dist/readaloud/preferences/ReadAloudDefaults.js +1 -0
  11. package/dist/readaloud/preferences/ReadAloudPreferences.js +1 -0
  12. package/dist/readaloud/preferences/ReadAloudPreferencesEditor.js +1 -0
  13. package/dist/readaloud/preferences/ReadAloudSettings.js +1 -0
  14. package/dist/webpub/WebPubFrameManager.js +1 -1
  15. package/dist/webpub/WebPubFramePoolManager.js +1 -1
  16. package/dist/webpub/WebPubNavigator.js +1 -1
  17. package/package.json +8 -6
  18. package/src/divina/DivinaNavigator.ts +1 -0
  19. package/src/epub/EpubNavigator.ts +127 -21
  20. package/src/epub/frame/FramePoolManager.ts +5 -0
  21. package/src/index.ts +1 -0
  22. package/src/preferences/guards.ts +11 -0
  23. package/src/readaloud/GuidedNavigationProvider.ts +87 -0
  24. package/src/readaloud/ReadAloudNavigator.ts +949 -0
  25. package/src/readaloud/ReadingUnit.ts +154 -0
  26. package/src/readaloud/SpeechProgress.ts +60 -0
  27. package/src/readaloud/index.ts +17 -0
  28. package/src/readaloud/preferences/ReadAloudDefaults.ts +24 -0
  29. package/src/readaloud/preferences/ReadAloudPreferences.ts +104 -0
  30. package/src/readaloud/preferences/ReadAloudPreferencesEditor.ts +115 -0
  31. package/src/readaloud/preferences/ReadAloudSettings.ts +44 -0
  32. package/src/readaloud/preferences/index.ts +4 -0
  33. package/src/webpub/WebPubFrameManager.ts +4 -0
  34. package/src/webpub/WebPubFramePoolManager.ts +5 -0
  35. package/src/webpub/WebPubNavigator.ts +114 -10
  36. package/types/src/epub/EpubNavigator.d.ts +26 -1
  37. package/types/src/epub/frame/FramePoolManager.d.ts +2 -0
  38. package/types/src/index.d.ts +1 -0
  39. package/types/src/preferences/guards.d.ts +1 -0
  40. package/types/src/readaloud/GuidedNavigationProvider.d.ts +28 -0
  41. package/types/src/readaloud/ReadAloudNavigator.d.ts +158 -0
  42. package/types/src/readaloud/ReadingUnit.d.ts +58 -0
  43. package/types/src/readaloud/SpeechProgress.d.ts +19 -0
  44. package/types/src/readaloud/index.d.ts +6 -0
  45. package/types/src/readaloud/preferences/ReadAloudDefaults.d.ts +11 -0
  46. package/types/src/readaloud/preferences/ReadAloudPreferences.d.ts +58 -0
  47. package/types/src/readaloud/preferences/ReadAloudPreferencesEditor.d.ts +37 -0
  48. package/types/src/readaloud/preferences/ReadAloudSettings.d.ts +26 -0
  49. package/types/src/readaloud/preferences/index.d.ts +4 -0
  50. package/types/src/webpub/WebPubFrameManager.d.ts +1 -0
  51. package/types/src/webpub/WebPubFramePoolManager.d.ts +2 -0
  52. package/types/src/webpub/WebPubNavigator.d.ts +25 -2
@@ -2,7 +2,7 @@ import { Feature, Link, Locator, LocatorText, Publication, ReadingProgression, L
2
2
  import { VisualNavigator, VisualNavigatorViewport, ProgressionRange, KeyboardPeripheralEventData } from "../Navigator.ts";
3
3
  import { Configurable } from "../preferences/Configurable.ts";
4
4
  import { WebPubFramePoolManager } from "./WebPubFramePoolManager.ts";
5
- import { BasicTextSelection, CommsEventKey, ContextMenuEvent, DecorationActivatedEvent, DecorationPointerEnterData, DecorationPointerLeaveData, FrameClickEvent, KeyboardPeripheralEvent, ModuleName, SuspiciousActivityEvent, WebPubModules } from "@readium/navigator-html-injectables";
5
+ import { BasicTextSelection, CommsEventKey, ContextMenuEvent, DecorationActivatedEvent, DecorationPointerEnterData, DecorationPointerLeaveData, FrameClickEvent, KeyboardPeripheralEvent, ModuleName, SuspiciousActivityEvent, TextLayout, PointedPieceRequest, PointedPieceResponse, WebPubModules } from "@readium/navigator-html-injectables";
6
6
  import * as path from "path-browserify";
7
7
  import { WebPubFrameManager } from "./WebPubFrameManager.ts";
8
8
  import { Decoration, DecorableNavigator, OnDecorationActivatedEvent, OnDecorationPointerEnterEvent, OnDecorationPointerLeaveEvent, DecorationObserver, DecoratorConfig, decorationsEqual, resolveDecorationForWire, supportsDecorationStyle as canRenderDecorationStyle, DecorationStyleType } from "../decorations/index.ts";
@@ -46,6 +46,9 @@ export interface WebPubNavigatorListeners {
46
46
  peripheral: (data: KeyboardPeripheralEventData) => void;
47
47
  }
48
48
 
49
+ // Expired comms callbacks are dropped, never called.
50
+ const POINTED_PIECE_TIMEOUT = 1000;
51
+
49
52
  const defaultListeners = (listeners: WebPubNavigatorListeners): WebPubNavigatorListeners => ({
50
53
  frameLoaded: listeners.frameLoaded || (() => {}),
51
54
  positionChanged: listeners.positionChanged || (() => {}),
@@ -102,6 +105,11 @@ export class WebPubNavigator extends VisualNavigator implements Configurable<Web
102
105
  private _decorationHoverState: Map<string, boolean> = new Map();
103
106
  private _decorationActivationConsumed = false;
104
107
  private _decorationResizeSelectors: Set<string>;
108
+ private _textLayoutWatch?: { href: string; pieces: unknown[]; cb: (layout: TextLayout) => void };
109
+ private _textLayoutFrame?: WebPubFrameManager;
110
+ private _navigationLocked = false;
111
+ private readonly _pointedPieceRequests = new Map<string, (index: number) => void>();
112
+ private _pointedPieceRequestId = 0;
105
113
 
106
114
  private webViewport: VisualNavigatorViewport = {
107
115
  readingOrder: [],
@@ -283,14 +291,25 @@ export class WebPubNavigator extends VisualNavigator implements Configurable<Web
283
291
  ) ?? false;
284
292
  }
285
293
 
286
- public eventListener(key: CommsEventKey | ManagerEventKey, data: unknown) {
294
+ public eventListener(key: CommsEventKey | ManagerEventKey, data: unknown, sourceFrame?: WebPubFrameManager) {
287
295
  switch (key) {
288
296
  case "_pong":
289
297
  this.listeners.frameLoaded(this.framePool.currentFrames[0]!.iframe.contentWindow!);
290
298
  this.listeners.positionChanged(this.currentLocation);
291
299
  this._notifyTimelineChange(this.currentLocation);
292
300
  this._reapplyDecorationsToCurrentFrame();
301
+ this._watchTextLayoutInCurrentFrame(true);
302
+ this._sendNavigationLock();
303
+ break;
304
+ case "text_layout":
305
+ // A frame being hidden after moving to another resource lays out again, collapsed.
306
+ if (sourceFrame && sourceFrame === this._textLayoutFrame && this.currentLocation.href.split("#")[0] === this._textLayoutWatch?.href) this._textLayoutWatch.cb(data as TextLayout);
293
307
  break;
308
+ case "pointed_piece": {
309
+ const { id, index } = data as PointedPieceResponse;
310
+ this._pointedPieceRequests.get(id)?.(index);
311
+ break;
312
+ }
294
313
  case "first_visible_locator":
295
314
  const loc = Locator.deserialize(data as string);
296
315
  if(!loc) break;
@@ -340,7 +359,7 @@ export class WebPubNavigator extends VisualNavigator implements Configurable<Web
340
359
  ) {
341
360
  const origHref = element.attributes.getNamedItem("href")?.value!;
342
361
  if (origHref.startsWith("#")) {
343
- this.go(this.currentLocation.copyWithLocations({
362
+ if (!this._navigationLocked) this.go(this.currentLocation.copyWithLocations({
344
363
  fragments: [origHref.substring(1)]
345
364
  }), false, () => { });
346
365
  } else if(
@@ -374,7 +393,7 @@ export class WebPubNavigator extends VisualNavigator implements Configurable<Web
374
393
 
375
394
  const link = this.pub.readingOrder.findWithHref(hrefToCheck);
376
395
  if (link) {
377
- this.goLink(link, false, () => { });
396
+ if (!this._navigationLocked) this.goLink(link, false, () => { });
378
397
  } else {
379
398
  console.warn(`Internal link not found in readingOrder: ${hrefToCheck}`);
380
399
  this.listeners.handleLocator(new Link({
@@ -400,9 +419,13 @@ export class WebPubNavigator extends VisualNavigator implements Configurable<Web
400
419
  case "zoom":
401
420
  this.listeners.zoom(data as number);
402
421
  break;
403
- case "progress":
422
+ case "progress": {
423
+ // The previous resource's frame can still report once moving to another, which would take its progress.
424
+ const frame = sourceFrame && this.framePool._frameFor(this.currentLocation.href.split("#")[0]);
425
+ if (sourceFrame && sourceFrame !== frame) break;
404
426
  this.syncLocation(data as ProgressionRange);
405
427
  break;
428
+ }
406
429
  case "content_protection":
407
430
  const activity = data as SuspiciousActivityEvent;
408
431
  this.listeners.contentProtection(activity.type, activity);
@@ -442,12 +465,15 @@ export class WebPubNavigator extends VisualNavigator implements Configurable<Web
442
465
  }
443
466
 
444
467
  private attachListener() {
445
- if (this.framePool.currentFrames[0]?.msg) {
446
- this.framePool.currentFrames[0].msg.listener = (key: CommsEventKey | ManagerEventKey, value: unknown) => {
447
- this.eventListener(key, value);
448
- };
449
- }
468
+ const vframes = this.framePool.currentFrames.filter(f => !!f) as WebPubFrameManager[];
469
+ vframes.forEach(f => {
470
+ if(f.msg) f.msg.listener = (key: CommsEventKey | ManagerEventKey, value: unknown) => {
471
+ this.eventListener(key, value, f);
472
+ }
473
+ })
450
474
  this._reapplyDecorationsToCurrentFrame();
475
+ this._watchTextLayoutInCurrentFrame();
476
+ this._sendNavigationLock();
451
477
  }
452
478
 
453
479
  private async apply() {
@@ -619,6 +645,84 @@ export class WebPubNavigator extends VisualNavigator implements Configurable<Web
619
645
  }
620
646
  }
621
647
 
648
+ /**
649
+ * Reports where the text of `locators`, all in one resource, starts each line along the scroll axis,
650
+ * with the viewport. Reported again whenever that resource is scrolled or laid out again.
651
+ * Replaces any previous watch, and returns a function that stops this one.
652
+ */
653
+ public watchTextLayout(locators: Locator[], cb: (layout: TextLayout) => void): () => void {
654
+ const watch = {
655
+ href: locators[0]?.href.split("#")[0] ?? "",
656
+ pieces: locators.map(locator => [locator.text?.serialize(), getCssSelector(locator.locations)]),
657
+ cb,
658
+ };
659
+ this._textLayoutWatch = watch;
660
+ this._watchTextLayoutInCurrentFrame(true);
661
+ return () => {
662
+ if (this._textLayoutWatch !== watch) return;
663
+ this._textLayoutWatch = undefined;
664
+ this._watchTextLayoutInCurrentFrame();
665
+ };
666
+ }
667
+
668
+ /**
669
+ * Locks or unlocks navigating in the content: scrolling and following its links.
670
+ * Calls to the navigator still move it.
671
+ */
672
+ public lockNavigation(locked: boolean): void {
673
+ this._navigationLocked = locked;
674
+ this._sendNavigationLock();
675
+ }
676
+
677
+ private _sendNavigationLock(): void {
678
+ this.framePool?.currentFrames[0]?.msg?.send("lock_navigation", this._navigationLocked);
679
+ }
680
+
681
+ /**
682
+ * Returns the first of `pieces` under the point of `event`, else the first containing the element it targets,
683
+ * in the resource of the frame it comes from. Undefined when none does.
684
+ */
685
+ public async findPointedPiece(event: FrameClickEvent, pieces: Locator[]): Promise<Locator | undefined> {
686
+ const frames = this.framePool.currentFrames.filter(f => !!f) as WebPubFrameManager[];
687
+ const i = frames.findIndex(frame => frame.source === event.targetFrameSrc);
688
+ const href = i >= 0 ? this.viewport.readingOrder[i] : undefined;
689
+ if (!href || !event.cssSelector) return undefined;
690
+ const candidates = pieces.filter(piece => piece.href.split("#")[0] === href);
691
+ if (candidates.length === 0) return undefined;
692
+
693
+ const id = `${++this._pointedPieceRequestId}`;
694
+ let timeout: ReturnType<typeof setTimeout> | undefined;
695
+ const index = await new Promise<number>(resolve => {
696
+ this._pointedPieceRequests.set(id, resolve);
697
+ timeout = setTimeout(() => resolve(-1), POINTED_PIECE_TIMEOUT);
698
+ const request: PointedPieceRequest = {
699
+ id,
700
+ cssSelector: event.cssSelector!,
701
+ x: event.x,
702
+ y: event.y,
703
+ pieces: candidates.map(piece => [piece.text?.serialize(), getCssSelector(piece.locations)])
704
+ };
705
+ // The reply is posted before the ack, so a pending request on ack got no reply.
706
+ const sent = frames[i].msg?.send("pointed_piece", request, () => resolve(-1));
707
+ if (!sent) resolve(-1);
708
+ });
709
+ clearTimeout(timeout);
710
+ this._pointedPieceRequests.delete(id);
711
+ return candidates[index];
712
+ }
713
+
714
+ // The previous frame stops watching, so only the displayed resource reports its layout.
715
+ private _watchTextLayoutInCurrentFrame(resend = false): void {
716
+ const frame = this.framePool?.currentFrames[0];
717
+ const watch = this._textLayoutWatch;
718
+ const target = watch && frame && this.currentLocation?.href.split("#")[0] === watch.href ? frame : undefined;
719
+ if (target === this._textLayoutFrame && !resend) return;
720
+ const previous = this._textLayoutFrame;
721
+ if (previous && previous !== target && !previous.isDestroyed) previous.msg?.send("watch_text_layout", []);
722
+ this._textLayoutFrame = target;
723
+ if (target && watch) target.msg?.send("watch_text_layout", watch.pieces);
724
+ }
725
+
622
726
  private _handleDecorationActivated(data: DecorationActivatedEvent): boolean {
623
727
  const observers = this._decorationObservers.get(data.group);
624
728
  if (!observers || observers.size === 0) return false;
@@ -2,7 +2,7 @@ import { Layout, Link, Locator, Publication, ReadingProgression, Timeline, Timel
2
2
  import { Configurable, ConfigurableSettings, VisualNavigator, VisualNavigatorViewport } from "../index.ts";
3
3
  import { FramePoolManager } from "./frame/FramePoolManager.ts";
4
4
  import { FXLFramePoolManager } from "./fxl/FXLFramePoolManager.ts";
5
- import { CommsEventKey, ContextMenuEvent, BasicTextSelection, FrameClickEvent, SuspiciousActivityEvent } from "@readium/navigator-html-injectables";
5
+ import { CommsEventKey, ContextMenuEvent, BasicTextSelection, FrameClickEvent, SuspiciousActivityEvent, TextLayout } from "@readium/navigator-html-injectables";
6
6
  import { Decoration, DecorationObserver, DecorableNavigator, DecoratorConfig, DecorationStyleType } from "../decorations/index.ts";
7
7
  import { FXLFrameManager } from "./fxl/FXLFrameManager.ts";
8
8
  import { FrameManager } from "./frame/FrameManager.ts";
@@ -81,6 +81,11 @@ export declare class EpubNavigator extends VisualNavigator implements Configurab
81
81
  private _decorationHoverState;
82
82
  private _decorationActivationConsumed;
83
83
  private _decorationResizeSelectors;
84
+ private _textLayoutWatch?;
85
+ private _textLayoutFrame?;
86
+ private _navigationLocked;
87
+ private readonly _pointedPieceRequests;
88
+ private _pointedPieceRequestId;
84
89
  private reflowViewport;
85
90
  constructor(container: HTMLElement, pub: Publication, listeners: EpubNavigatorListeners, positions?: Locator[], initialPosition?: Locator | undefined, configuration?: EpubNavigatorConfiguration);
86
91
  static determineLayout(pub: Publication, scroll?: boolean): Layout;
@@ -126,6 +131,26 @@ export declare class EpubNavigator extends VisualNavigator implements Configurab
126
131
  private _sendDecorationOps;
127
132
  private _reapplyDecorationsToFrame;
128
133
  private _reapplyDecorationsToCurrentFrames;
134
+ /**
135
+ * Reports where the text of `locators`, all in one reflowable resource, starts each line or column
136
+ * along the scroll axis, with the viewport. Reported again whenever that resource is scrolled or laid out again.
137
+ * Replaces any previous watch, and returns a function that stops this one.
138
+ */
139
+ watchTextLayout(locators: Locator[], cb: (layout: TextLayout) => void): () => void;
140
+ /**
141
+ * Locks or unlocks navigating in the content: scrolling, swiping, tapping its edges and following its links.
142
+ * Calls to the navigator still move it.
143
+ */
144
+ lockNavigation(locked: boolean): void;
145
+ private _sendNavigationLock;
146
+ /** The left page's width when `targetFrameSrc` is the right page (screen-wise) of a fixed-layout spread, else 0. */
147
+ private _spreadOffsetX;
148
+ /**
149
+ * Returns the first of `pieces` under the point of `event`, else the first containing the element it targets,
150
+ * in the resource of the frame it comes from. Undefined when none does.
151
+ */
152
+ findPointedPiece(event: FrameClickEvent, pieces: Locator[]): Promise<Locator | undefined>;
153
+ private _watchTextLayoutInCurrentFrame;
129
154
  private _handleDecorationActivated;
130
155
  private _handleDecorationPointerEnter;
131
156
  private _handleDecorationPointerLeave;
@@ -33,5 +33,7 @@ export declare class FramePoolManager {
33
33
  [key: string]: string;
34
34
  }): void;
35
35
  get currentFrames(): (FrameManager | undefined)[];
36
+ /** The pooled frame of `href`, whether or not it's shown. */
37
+ _frameFor(href: string): FrameManager | undefined;
36
38
  get currentBounds(): DOMRect;
37
39
  }
@@ -8,3 +8,4 @@ export * from './helpers/index.ts';
8
8
  export * from './preferences/index.ts';
9
9
  export * from './css/index.ts';
10
10
  export * from './injection/index.ts';
11
+ export * from './readaloud/index.ts';
@@ -7,5 +7,6 @@ export declare function ensureEnumValue<T extends string>(value: T | null | unde
7
7
  export declare function ensureFilter(filter: boolean | number | null | undefined): boolean | number | null | undefined;
8
8
  export declare function ensureNonNegative(value: number | null | undefined): number | null | undefined;
9
9
  export declare function ensureValueInRange(value: number | null | undefined, range: [number, number]): number | null | undefined;
10
+ export declare function ensureDecorationStyle<T extends object>(value: T | false | null | undefined): T | false | null | undefined;
10
11
  export declare function withFallback<T>(value: T | null | undefined, defaultValue: T | null): T | null;
11
12
  export declare function ensureExperiment(experimentsInput: ExperimentKey[] | null | undefined): ExperimentKey[] | null | undefined;
@@ -0,0 +1,28 @@
1
+ import { GuidedNavigationObject, Link, Publication } from "@readium/shared";
2
+ export interface GuidedNavigationProvider {
3
+ /**
4
+ * Guided Navigation objects of a resource, with every textref qualified by the resource's href.
5
+ * Resolves to undefined when the resource has nothing to read.
6
+ */
7
+ guideFor(link: Link): Promise<GuidedNavigationObject[] | undefined>;
8
+ }
9
+ export interface PublicationGuidedNavigationProviderOptions {
10
+ /** Generates from the resource's markup without using the publication's Guided Navigation document. */
11
+ generateFromMarkup?: boolean;
12
+ }
13
+ /**
14
+ * Uses the publication's Guided Navigation document when its text can be located in the resource,
15
+ * and generates one from the resource's markup otherwise, or always with `generateFromMarkup`.
16
+ * Only the objects of the resource are kept from a document covering the whole publication.
17
+ */
18
+ export declare class PublicationGuidedNavigationProvider implements GuidedNavigationProvider {
19
+ private readonly publication;
20
+ private readonly options;
21
+ constructor(publication: Publication, options?: PublicationGuidedNavigationProviderOptions);
22
+ guideFor(link: Link): Promise<GuidedNavigationObject[] | undefined>;
23
+ private generate;
24
+ private objectsIn;
25
+ private resourceOf;
26
+ private imageOf;
27
+ private isLocatable;
28
+ }
@@ -0,0 +1,158 @@
1
+ import { Layout, Locator } from "@readium/shared";
2
+ import { FrameClickEvent, TextLayout } from "@readium/navigator-html-injectables";
3
+ import { ReadiumSpeechPlaybackEngine, ReadiumSpeechPlaybackState, ReadiumSpeechVoice } from "@readium/speech";
4
+ import { Navigator, VisualNavigatorViewport } from "../Navigator.ts";
5
+ import { DecorableNavigator } from "../decorations/index.ts";
6
+ import { GuidedNavigationProvider } from "./GuidedNavigationProvider.ts";
7
+ import { IReadAloudDefaults, IReadAloudPreferences, ReadAloudPreferences, ReadAloudPreferencesEditor, ReadAloudSettings } from "./preferences/index.ts";
8
+ export type ReadAloudState = ReadiumSpeechPlaybackState;
9
+ export interface ReadAloudVoicesOptions {
10
+ /** BCP 47 tags, matched by base language. */
11
+ languages?: string[];
12
+ }
13
+ export interface ReadAloudUtterance {
14
+ text: string;
15
+ /** One locator per piece of the utterance, each in its own resource. */
16
+ locators: Locator[];
17
+ }
18
+ export interface ReadAloudListeners {
19
+ stateChanged?: (state: ReadAloudState) => void;
20
+ utteranceChanged?: (utterance: ReadAloudUtterance) => void;
21
+ wordChanged?: (locator: Locator, word: string) => void;
22
+ error?: (error: unknown) => void;
23
+ }
24
+ export interface ReadAloudConfiguration {
25
+ /** Defaults to a `WebSpeechEngine`. */
26
+ engine?: ReadiumSpeechPlaybackEngine;
27
+ provider?: GuidedNavigationProvider;
28
+ preferences?: IReadAloudPreferences;
29
+ defaults?: IReadAloudDefaults;
30
+ /**
31
+ * Whether the reader can move away while reading aloud, which then stops following until back.
32
+ * Otherwise navigating in the content is locked while playing. Defaults to true.
33
+ */
34
+ detachable?: boolean;
35
+ }
36
+ /**
37
+ * Reads a publication aloud with a speech engine, alongside the navigator displaying it.
38
+ * The spoken utterance and word are highlighted, and the navigator follows them.
39
+ */
40
+ export declare class ReadAloudNavigator {
41
+ private readonly navigator;
42
+ private readonly listeners;
43
+ private readonly speech;
44
+ private readonly pool;
45
+ private readonly units;
46
+ private readonly unsubscribers;
47
+ private readonly _defaults;
48
+ private _preferences;
49
+ private _settings;
50
+ private _preferencesEditor;
51
+ private utteranceLocators;
52
+ private wordLocator?;
53
+ private pausedBefore?;
54
+ private turning?;
55
+ private followedHref?;
56
+ private following?;
57
+ private detached;
58
+ private navigationLocked;
59
+ private shownHref?;
60
+ private watchedPieces;
61
+ private unwatchLayout?;
62
+ private lines;
63
+ private viewport?;
64
+ private readonly progress;
65
+ private breakTimer?;
66
+ private unit?;
67
+ private loading;
68
+ private loadPaused;
69
+ private loadToken;
70
+ private settleReady?;
71
+ private lastState;
72
+ constructor(navigator: Navigator & Partial<DecorableNavigator> & {
73
+ readonly viewport?: VisualNavigatorViewport;
74
+ readonly layout?: Layout;
75
+ watchTextLayout?(locators: Locator[], cb: (layout: TextLayout) => void): () => void;
76
+ findPointedPiece?(event: FrameClickEvent, pieces: Locator[]): Promise<Locator | undefined>;
77
+ lockNavigation?(locked: boolean): void;
78
+ }, listeners?: ReadAloudListeners, configuration?: ReadAloudConfiguration);
79
+ /** Whether the reader can move away while reading aloud. Otherwise the app keeps its own navigation from moving while playing. */
80
+ readonly detachable: boolean;
81
+ get state(): ReadAloudState;
82
+ /**
83
+ * Starts reading from `from`, or resumes when paused.
84
+ * Without `from`, an idle reader starts from the navigator's current position.
85
+ */
86
+ play(from?: Locator): Promise<void>;
87
+ /**
88
+ * Starts reading from the utterance under a click or tap in a displayed resource, loading that resource when needed.
89
+ * Resolves false when the press isn't on content read aloud, or is on an interactive element, so the caller handles it.
90
+ */
91
+ readFromPointer(event: FrameClickEvent): Promise<boolean>;
92
+ pause(): void;
93
+ stop(): void;
94
+ /** Moves to the next utterance, continuing into the next reading unit at the end of this one. */
95
+ next(): Promise<boolean>;
96
+ /** Moves to the previous utterance, continuing into the previous reading unit at the start of this one. */
97
+ previous(): Promise<boolean>;
98
+ /**
99
+ * The available voices, only those of `languages` when given,
100
+ * ranked the way the default voice is picked: preferred region first, then quality.
101
+ */
102
+ getVoices(options?: ReadAloudVoicesOptions): Promise<ReadiumSpeechVoice[]>;
103
+ setVoice(voice: ReadiumSpeechVoice | string): void;
104
+ getCurrentVoice(): ReadiumSpeechVoice | null;
105
+ get settings(): Readonly<ReadAloudSettings>;
106
+ get preferencesEditor(): ReadAloudPreferencesEditor;
107
+ submitPreferences(preferences: ReadAloudPreferences): Promise<void>;
108
+ destroy(): Promise<void>;
109
+ private listen;
110
+ private start;
111
+ private resumePaused;
112
+ private speechReady;
113
+ private untilReady;
114
+ private movePaused;
115
+ private loadUnit;
116
+ private startIndex;
117
+ private indexAfterRebuild;
118
+ private pointedIndex;
119
+ private piecesOf;
120
+ private speakFrom;
121
+ private pausesAtPages;
122
+ private turnTo;
123
+ private staysPaused;
124
+ private pauseTurning;
125
+ private pausesBeforeUnit;
126
+ private pausesBetween;
127
+ private hrefsOf;
128
+ private notifyUtterance;
129
+ private pageLocator;
130
+ private decorate;
131
+ private applyDecorations;
132
+ private clearHighlights;
133
+ private follow;
134
+ private attach;
135
+ private isDetached;
136
+ private watchLayout;
137
+ private laidOut;
138
+ private isHidden;
139
+ private lineStart;
140
+ private spokenPoint;
141
+ private utteranceStart;
142
+ private wordPoint;
143
+ private pointAt;
144
+ private pointLocator;
145
+ private armBreak;
146
+ private clearBreak;
147
+ private sendsBoundaries;
148
+ private voiceName;
149
+ private speed;
150
+ private static collapsed;
151
+ private static rawOffset;
152
+ private locatorFor;
153
+ private layout;
154
+ private static speechPreferences;
155
+ private setLoading;
156
+ private lockNavigation;
157
+ private notifyState;
158
+ }
@@ -0,0 +1,58 @@
1
+ import { GuidedNavigationObject, Link, Publication } from "@readium/shared";
2
+ import { Navigator, VisualNavigatorViewport } from "../Navigator.ts";
3
+ import { GuidedNavigationProvider } from "./GuidedNavigationProvider.ts";
4
+ /** Reading order links whose Guided Navigation objects are read as one sequence. */
5
+ export interface ReadingUnit {
6
+ links: Link[];
7
+ }
8
+ /**
9
+ * Splits the publication into reading units: the displayed spread for fixed layouts, Divina excepted,
10
+ * and a single resource otherwise. Spreads are only known to the navigator, so it's moved to find them;
11
+ * FXL navigators update their viewport synchronously, so it's read without waiting for the callback.
12
+ */
13
+ export declare class ReadingUnits {
14
+ private readonly navigator;
15
+ private readonly spreads;
16
+ constructor(navigator: Navigator & {
17
+ readonly viewport?: VisualNavigatorViewport;
18
+ });
19
+ /** The unit containing `href`. */
20
+ around(href: string): ReadingUnit | undefined;
21
+ /** The first link of the unit following `unit`, without moving the navigator. */
22
+ linkAfter(unit: ReadingUnit): Link | undefined;
23
+ /** The last link of the unit preceding `unit`, without moving the navigator. */
24
+ linkBefore(unit: ReadingUnit): Link | undefined;
25
+ after(unit: ReadingUnit): ReadingUnit | undefined;
26
+ before(unit: ReadingUnit): ReadingUnit | undefined;
27
+ /** Whether `href` is a page of the displayed spread. */
28
+ displays(href: string): boolean;
29
+ private move;
30
+ private displayedFrom;
31
+ private indexOf;
32
+ }
33
+ export interface StitchedUnit {
34
+ /** The unit's Guided Navigation objects, concatenated in reading order. */
35
+ guided: GuidedNavigationObject[];
36
+ /** Links whose objects couldn't be obtained, left out of `guided`. */
37
+ failures: {
38
+ link: Link;
39
+ error: unknown;
40
+ }[];
41
+ }
42
+ /**
43
+ * Keeps the Guided Navigation objects of the resources around the unit being read,
44
+ * fetching those within LOWER_BOUNDARY reading order items and releasing those beyond UPPER_BOUNDARY.
45
+ */
46
+ export declare class GuidedNavigationPool {
47
+ private readonly publication;
48
+ private readonly provider;
49
+ private readonly guides;
50
+ constructor(publication: Publication, provider: GuidedNavigationProvider);
51
+ /**
52
+ * Concatenates the Guided Navigation objects of every link in the unit, in reading order,
53
+ * fetching at most `concurrency` resources at once, then fetches the unit's neighbours.
54
+ */
55
+ stitch(unit: ReadingUnit, concurrency?: number): Promise<StitchedUnit>;
56
+ private update;
57
+ private guideFor;
58
+ }
@@ -0,0 +1,19 @@
1
+ import { ReadiumSpeechUtterance } from "@readium/speech";
2
+ /** Times the utterance being spoken, and measures each voice's speed on the utterances it finishes. */
3
+ export declare class SpeechProgress {
4
+ private readonly speeds;
5
+ private utterance?;
6
+ private startedAt;
7
+ private pausedAt?;
8
+ start(utterance: ReadiumSpeechUtterance): void;
9
+ pause(): void;
10
+ resume(): void;
11
+ /** Measures the speed of `voice` once it has spoken `utterance` through. */
12
+ end(utterance: ReadiumSpeechUtterance | null, voice: string, rate: number): void;
13
+ reset(): void;
14
+ get speaking(): boolean;
15
+ /** Seconds spent speaking the current utterance. */
16
+ elapsed(): number;
17
+ /** Characters per second of `voice` at `rate`. */
18
+ speed(voice: string, rate: number): number;
19
+ }
@@ -0,0 +1,6 @@
1
+ export * from './GuidedNavigationProvider.ts';
2
+ export * from './ReadAloudNavigator.ts';
3
+ export * from './preferences/index.ts';
4
+ export { defaultContextualizations, skippableRoles, filterByLanguages, filterOutNoveltyVoices, filterOutVeryLowQualityVoices, getQualityValue, groupVoicesByLanguage, sortAlphabetically, sortByQuality, sortVoicesByRegions } from "@readium/speech";
5
+ export type { ExtractionFormat, LanguageMode, ReadiumSpeechPlaybackEngine, ReadiumSpeechVoice, Segmentation, VerbosityPreset } from "@readium/speech";
6
+ export type { GndRole } from "@readium/guided-navigation";
@@ -0,0 +1,11 @@
1
+ import { IReadAloudPreferences, ReadAloudAutoPause, ReadAloudDecorationStyle } from "./ReadAloudPreferences.ts";
2
+ export type IReadAloudDefaults = IReadAloudPreferences;
3
+ export declare class ReadAloudDefaults {
4
+ /** Defaults of the speech fields, validated by speech itself. */
5
+ speech: IReadAloudPreferences;
6
+ autoPause: ReadAloudAutoPause;
7
+ speakInContentLanguage: boolean;
8
+ utteranceStyle: ReadAloudDecorationStyle;
9
+ wordStyle: ReadAloudDecorationStyle;
10
+ constructor(defaults?: IReadAloudDefaults);
11
+ }
@@ -0,0 +1,58 @@
1
+ import type { GndRole } from "@readium/guided-navigation";
2
+ import { ExtractionFormat, LanguageMode, Segmentation, VerbosityPreset } from "@readium/speech";
3
+ import { ConfigurablePreferences } from "../../preferences/Configurable.ts";
4
+ import { BuiltinDecorationStyle, NamedDecorationStyle } from "../../decorations/index.ts";
5
+ /**
6
+ * When playback pauses on its own: never, after every utterance, before every block,
7
+ * or before the first utterance starting on another page or outside the displayed spread.
8
+ */
9
+ export declare const ReadAloudAutoPause: {
10
+ readonly none: "none";
11
+ readonly utterance: "utterance";
12
+ readonly block: "block";
13
+ readonly page: "page";
14
+ readonly spread: "spread";
15
+ };
16
+ export type ReadAloudAutoPause = typeof ReadAloudAutoPause[keyof typeof ReadAloudAutoPause];
17
+ /** A decoration style, or `false` to not decorate at all. */
18
+ export type ReadAloudDecorationStyle = BuiltinDecorationStyle | NamedDecorationStyle | false;
19
+ export interface IReadAloudPreferences {
20
+ format?: ExtractionFormat | null;
21
+ inlineContextualization?: boolean | null;
22
+ verbosity?: VerbosityPreset | null;
23
+ skip?: GndRole[] | null;
24
+ contextualize?: GndRole[] | null;
25
+ language?: LanguageMode | null;
26
+ segmentation?: Segmentation | null;
27
+ pauseDuration?: number | null;
28
+ autoPause?: ReadAloudAutoPause | null;
29
+ speakInContentLanguage?: boolean | null;
30
+ rate?: number | null;
31
+ pitch?: number | null;
32
+ volume?: number | null;
33
+ /** Style of the utterance being spoken */
34
+ utteranceStyle?: ReadAloudDecorationStyle | null;
35
+ /** Style of the word being spoken */
36
+ wordStyle?: ReadAloudDecorationStyle | null;
37
+ }
38
+ export declare class ReadAloudPreferences implements ConfigurablePreferences<ReadAloudPreferences> {
39
+ format?: ExtractionFormat | null;
40
+ inlineContextualization?: boolean | null;
41
+ verbosity?: VerbosityPreset | null;
42
+ skip?: GndRole[] | null;
43
+ contextualize?: GndRole[] | null;
44
+ language?: LanguageMode | null;
45
+ segmentation?: Segmentation | null;
46
+ pauseDuration?: number | null;
47
+ autoPause?: ReadAloudAutoPause | null;
48
+ speakInContentLanguage?: boolean | null;
49
+ rate?: number | null;
50
+ pitch?: number | null;
51
+ volume?: number | null;
52
+ utteranceStyle?: ReadAloudDecorationStyle | null;
53
+ wordStyle?: ReadAloudDecorationStyle | null;
54
+ constructor(preferences?: IReadAloudPreferences);
55
+ static serialize(preferences: ReadAloudPreferences): string;
56
+ static deserialize(preferences: string): ReadAloudPreferences | null;
57
+ merging(other: ReadAloudPreferences): ReadAloudPreferences;
58
+ }
@@ -0,0 +1,37 @@
1
+ import { Layout } from "@readium/shared";
2
+ import { SpeechSettings } from "@readium/speech";
3
+ import { IPreferencesEditor } from "../../preferences/PreferencesEditor.ts";
4
+ import { BooleanPreference, EnumPreference, Preference } from "../../preferences/Preference.ts";
5
+ import { ReadAloudAutoPause, ReadAloudDecorationStyle, ReadAloudPreferences } from "./ReadAloudPreferences.ts";
6
+ import { ReadAloudSettings } from "./ReadAloudSettings.ts";
7
+ /**
8
+ * Edits read-aloud preferences, speech's own fields through speech's editor.
9
+ */
10
+ export declare class ReadAloudPreferencesEditor implements IPreferencesEditor {
11
+ private speech;
12
+ private own;
13
+ private settings;
14
+ private layout;
15
+ /**
16
+ * @param layout How the publication is displayed: `Layout.reflowable` for columns, `Layout.scrolled` when scrolled.
17
+ */
18
+ constructor(initialPreferences: ReadAloudPreferences, settings: ReadAloudSettings, speechSettings: SpeechSettings, layout: Layout);
19
+ get preferences(): ReadAloudPreferences;
20
+ clear(): void;
21
+ private updatePreference;
22
+ get format(): import("@readium/speech").EnumPreference<import("@readium/speech").ExtractionFormat>;
23
+ get inlineContextualization(): import("@readium/speech").BooleanPreference;
24
+ get verbosity(): import("@readium/speech").EnumPreference<import("@readium/speech").VerbosityPreset>;
25
+ get skip(): import("@readium/speech").StringArrayPreference;
26
+ get contextualize(): import("@readium/speech").StringArrayPreference;
27
+ get language(): import("@readium/speech").EnumPreference<import("@readium/speech").LanguageMode>;
28
+ get segmentation(): import("@readium/speech").EnumPreference<import("@readium/speech").Segmentation>;
29
+ get pauseDuration(): import("@readium/speech").RangePreference<number>;
30
+ get rate(): import("@readium/speech").RangePreference<number>;
31
+ get pitch(): import("@readium/speech").RangePreference<number>;
32
+ get volume(): import("@readium/speech").RangePreference<number>;
33
+ get autoPause(): EnumPreference<ReadAloudAutoPause>;
34
+ get speakInContentLanguage(): BooleanPreference;
35
+ get utteranceStyle(): Preference<ReadAloudDecorationStyle>;
36
+ get wordStyle(): Preference<ReadAloudDecorationStyle>;
37
+ }