@internetarchive/bookreader 5.0.0-88-alpha.7 → 5.0.0-88-alpha.9
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/babel.config.js +30 -12
- package/dist/esm/BookNavigator/assets/bookmark-colors.js +4 -0
- package/dist/esm/BookNavigator/assets/button-base.js +4 -0
- package/dist/esm/BookNavigator/assets/ia-logo.js +4 -0
- package/dist/esm/BookNavigator/assets/icon_checkmark.js +8 -0
- package/dist/esm/BookNavigator/assets/icon_close.js +4 -0
- package/dist/esm/BookNavigator/book-navigator.js +612 -0
- package/dist/esm/BookNavigator/bookmarks/bookmark-button.js +35 -0
- package/dist/esm/BookNavigator/bookmarks/bookmark-edit.js +78 -0
- package/dist/esm/BookNavigator/bookmarks/bookmarks-list.js +160 -0
- package/dist/esm/BookNavigator/bookmarks/bookmarks-loginCTA.js +24 -0
- package/dist/esm/BookNavigator/bookmarks/bookmarks-provider.js +55 -0
- package/dist/esm/BookNavigator/bookmarks/ia-bookmarks.js +521 -0
- package/dist/esm/BookNavigator/delete-modal-actions.js +29 -0
- package/dist/esm/BookNavigator/downloads/downloads-provider.js +84 -0
- package/dist/esm/BookNavigator/downloads/downloads.js +69 -0
- package/dist/esm/BookNavigator/search/search-provider.js +238 -0
- package/dist/esm/BookNavigator/search/search-results.js +161 -0
- package/dist/esm/BookNavigator/sharing.js +26 -0
- package/dist/esm/BookNavigator/viewable-files.js +94 -0
- package/dist/esm/BookNavigator/visual-adjustments/visual-adjustments-provider.js +83 -0
- package/dist/esm/BookNavigator/visual-adjustments/visual-adjustments.js +131 -0
- package/dist/esm/BookReader/BookModel.js +575 -0
- package/dist/esm/BookReader/DragScrollable.js +224 -0
- package/dist/esm/BookReader/ImageCache.js +122 -0
- package/dist/esm/BookReader/Mode1Up.js +114 -0
- package/dist/esm/BookReader/Mode1UpLit.js +579 -0
- package/dist/esm/BookReader/Mode2Up.js +106 -0
- package/dist/esm/BookReader/Mode2UpLit.js +1020 -0
- package/dist/esm/BookReader/ModeCoordinateSpace.js +28 -0
- package/dist/esm/BookReader/ModeSmoothZoom.js +318 -0
- package/dist/esm/BookReader/ModeThumb.js +366 -0
- package/dist/esm/BookReader/Navbar/Navbar.js +253 -0
- package/dist/esm/BookReader/PageContainer.js +165 -0
- package/dist/esm/BookReader/ReduceSet.js +27 -0
- package/dist/esm/BookReader/Toolbar/Toolbar.js +242 -0
- package/dist/esm/BookReader/events.js +20 -0
- package/dist/esm/BookReader/options.js +331 -0
- package/dist/esm/BookReader/utils/HTMLDimensionsCacher.js +48 -0
- package/dist/esm/BookReader/utils/ScrollClassAdder.js +31 -0
- package/dist/esm/BookReader/utils/SelectionObserver.js +42 -0
- package/dist/esm/BookReader/utils/classes.js +37 -0
- package/dist/esm/BookReader/utils.js +315 -0
- package/dist/esm/BookReader.js +1827 -0
- package/dist/esm/assets/icons/1up.svg +12 -0
- package/dist/esm/assets/icons/2up.svg +15 -0
- package/dist/esm/assets/icons/advance.svg +26 -0
- package/dist/esm/assets/icons/chevron-right.svg +1 -0
- package/dist/esm/assets/icons/close-circle-dark.svg +1 -0
- package/dist/esm/assets/icons/close-circle.svg +1 -0
- package/dist/esm/assets/icons/fullscreen.svg +17 -0
- package/dist/esm/assets/icons/fullscreen_exit.svg +17 -0
- package/dist/esm/assets/icons/hamburger.svg +15 -0
- package/dist/esm/assets/icons/left-arrow.svg +12 -0
- package/dist/esm/assets/icons/magnify-minus.svg +12 -0
- package/dist/esm/assets/icons/magnify-plus.svg +13 -0
- package/dist/esm/assets/icons/magnify.svg +15 -0
- package/dist/esm/assets/icons/pause.svg +23 -0
- package/dist/esm/assets/icons/play.svg +22 -0
- package/dist/esm/assets/icons/playback-speed.svg +34 -0
- package/dist/esm/assets/icons/read-aloud.svg +22 -0
- package/dist/esm/assets/icons/review.svg +22 -0
- package/dist/esm/assets/icons/thumbnails.svg +17 -0
- package/dist/esm/assets/icons/voice.svg +1 -0
- package/dist/esm/assets/icons/volume-full.svg +22 -0
- package/dist/esm/assets/images/BRicons.png +0 -0
- package/dist/esm/assets/images/BRicons.svg +94 -0
- package/dist/esm/assets/images/BRicons_ia.png +0 -0
- package/dist/esm/assets/images/back_pages.png +0 -0
- package/dist/esm/assets/images/book_bottom_icon.png +0 -0
- package/dist/esm/assets/images/book_down_icon.png +0 -0
- package/dist/esm/assets/images/book_left_icon.png +0 -0
- package/dist/esm/assets/images/book_leftmost_icon.png +0 -0
- package/dist/esm/assets/images/book_right_icon.png +0 -0
- package/dist/esm/assets/images/book_rightmost_icon.png +0 -0
- package/dist/esm/assets/images/book_top_icon.png +0 -0
- package/dist/esm/assets/images/book_up_icon.png +0 -0
- package/dist/esm/assets/images/books_graphic.svg +177 -0
- package/dist/esm/assets/images/booksplit.png +0 -0
- package/dist/esm/assets/images/control_pause_icon.png +0 -0
- package/dist/esm/assets/images/control_play_icon.png +0 -0
- package/dist/esm/assets/images/embed_icon.png +0 -0
- package/dist/esm/assets/images/icon-home-ia.png +0 -0
- package/dist/esm/assets/images/icon_OL-logo-xs.png +0 -0
- package/dist/esm/assets/images/icon_alert-xs.png +0 -0
- package/dist/esm/assets/images/icon_book.svg +12 -0
- package/dist/esm/assets/images/icon_bookmark.svg +12 -0
- package/dist/esm/assets/images/icon_close-pop.png +0 -0
- package/dist/esm/assets/images/icon_download.png +0 -0
- package/dist/esm/assets/images/icon_gear.svg +14 -0
- package/dist/esm/assets/images/icon_hamburger.svg +20 -0
- package/dist/esm/assets/images/icon_home.png +0 -0
- package/dist/esm/assets/images/icon_home.svg +21 -0
- package/dist/esm/assets/images/icon_home_ia.png +0 -0
- package/dist/esm/assets/images/icon_indicator.png +0 -0
- package/dist/esm/assets/images/icon_info.svg +11 -0
- package/dist/esm/assets/images/icon_one_page.svg +8 -0
- package/dist/esm/assets/images/icon_pause.svg +1 -0
- package/dist/esm/assets/images/icon_play.svg +1 -0
- package/dist/esm/assets/images/icon_playback-rate.svg +15 -0
- package/dist/esm/assets/images/icon_return.png +0 -0
- package/dist/esm/assets/images/icon_search_button.svg +8 -0
- package/dist/esm/assets/images/icon_share.svg +9 -0
- package/dist/esm/assets/images/icon_skip-ahead.svg +6 -0
- package/dist/esm/assets/images/icon_skip-back.svg +13 -0
- package/dist/esm/assets/images/icon_speaker.svg +18 -0
- package/dist/esm/assets/images/icon_speaker_open.svg +10 -0
- package/dist/esm/assets/images/icon_thumbnails.svg +12 -0
- package/dist/esm/assets/images/icon_toc.svg +5 -0
- package/dist/esm/assets/images/icon_two_pages.svg +9 -0
- package/dist/esm/assets/images/icon_zoomer.png +0 -0
- package/dist/esm/assets/images/loading.gif +0 -0
- package/dist/esm/assets/images/logo_icon.png +0 -0
- package/dist/esm/assets/images/marker_chap-off.png +0 -0
- package/dist/esm/assets/images/marker_chap-off.svg +11 -0
- package/dist/esm/assets/images/marker_chap-off_ia.png +0 -0
- package/dist/esm/assets/images/marker_chap-on.png +0 -0
- package/dist/esm/assets/images/marker_chap-on.svg +11 -0
- package/dist/esm/assets/images/marker_srch-on.svg +11 -0
- package/dist/esm/assets/images/marker_srchchap-off.png +0 -0
- package/dist/esm/assets/images/marker_srchchap-on.png +0 -0
- package/dist/esm/assets/images/nav_control-dn.png +0 -0
- package/dist/esm/assets/images/nav_control-dn_ia.png +0 -0
- package/dist/esm/assets/images/nav_control-up.png +0 -0
- package/dist/esm/assets/images/nav_control-up_ia.png +0 -0
- package/dist/esm/assets/images/nav_control.png +0 -0
- package/dist/esm/assets/images/one_page_mode_icon.png +0 -0
- package/dist/esm/assets/images/paper-badge.png +0 -0
- package/dist/esm/assets/images/print_icon.png +0 -0
- package/dist/esm/assets/images/progressbar.gif +0 -0
- package/dist/esm/assets/images/right_edges.png +0 -0
- package/dist/esm/assets/images/slider.png +0 -0
- package/dist/esm/assets/images/slider_ia.png +0 -0
- package/dist/esm/assets/images/thumbnail_mode_icon.png +0 -0
- package/dist/esm/assets/images/transparent.png +0 -0
- package/dist/esm/assets/images/two_page_mode_icon.png +0 -0
- package/dist/esm/assets/images/unviewable_page.png +0 -0
- package/dist/esm/assets/images/zoom_in_icon.png +0 -0
- package/dist/esm/assets/images/zoom_out_icon.png +0 -0
- package/dist/esm/css/BookReader.scss +85 -0
- package/dist/esm/css/_BRBookmarks.scss +29 -0
- package/dist/esm/css/_BRComponent.scss +13 -0
- package/dist/esm/css/_BRfloat.scss +197 -0
- package/dist/esm/css/_BRicon.scss +54 -0
- package/dist/esm/css/_BRmain.scss +262 -0
- package/dist/esm/css/_BRnav.scss +354 -0
- package/dist/esm/css/_BRpages.scss +213 -0
- package/dist/esm/css/_BRsearch.scss +268 -0
- package/dist/esm/css/_BRtoolbar.scss +84 -0
- package/dist/esm/css/_BRvendor.scss +5 -0
- package/dist/esm/css/_TextSelection.scss +108 -0
- package/dist/esm/css/_colorbox.scss +52 -0
- package/dist/esm/css/_controls.scss +257 -0
- package/dist/esm/css/_icons.scss +121 -0
- package/dist/esm/ia-bookreader/ia-bookreader.js +141 -0
- package/dist/esm/jquery-wrapper.js +3 -0
- package/dist/esm/plugins/plugin.archive_analytics.js +72 -0
- package/dist/esm/plugins/plugin.autoplay.js +119 -0
- package/dist/esm/plugins/plugin.chapters.js +288 -0
- package/dist/esm/plugins/plugin.iframe.js +44 -0
- package/dist/esm/plugins/plugin.iiif.js +146 -0
- package/dist/esm/plugins/plugin.resume.js +66 -0
- package/dist/esm/plugins/plugin.text_selection.js +621 -0
- package/dist/esm/plugins/plugin.vendor-fullscreen.js +227 -0
- package/dist/esm/plugins/search/plugin.search.js +499 -0
- package/dist/esm/plugins/search/utils.js +42 -0
- package/dist/esm/plugins/search/view.js +360 -0
- package/dist/esm/plugins/tts/AbstractTTSEngine.js +282 -0
- package/dist/esm/plugins/tts/FestivalTTSEngine.js +192 -0
- package/dist/esm/plugins/tts/PageChunk.js +105 -0
- package/dist/esm/plugins/tts/PageChunkIterator.js +155 -0
- package/dist/esm/plugins/tts/WebTTSEngine.js +364 -0
- package/dist/esm/plugins/tts/plugin.tts.js +315 -0
- package/dist/esm/plugins/tts/tooltip_dict.js +14 -0
- package/dist/esm/plugins/tts/utils.js +79 -0
- package/dist/esm/plugins/url/UrlPlugin.js +197 -0
- package/dist/esm/plugins/url/plugin.url.js +212 -0
- package/dist/esm/util/browserSniffing.js +56 -0
- package/dist/esm/util/debouncer.js +25 -0
- package/dist/esm/util/docCookies.js +75 -0
- package/dist/esm/util/strings.js +34 -0
- package/jsconfig.json +1 -0
- package/package.json +13 -6
@@ -0,0 +1,192 @@
|
|
1
|
+
function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
|
2
|
+
function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
|
3
|
+
import AbstractTTSEngine from './AbstractTTSEngine.js';
|
4
|
+
import { sleep } from '../../BookReader/utils.js';
|
5
|
+
/* global soundManager */
|
6
|
+
import 'soundmanager2';
|
7
|
+
import 'jquery.browser';
|
8
|
+
|
9
|
+
/** @typedef {import("./AbstractTTSEngine.js").TTSEngineOptions} TTSEngineOptions */
|
10
|
+
/** @typedef {import("./AbstractTTSEngine.js").AbstractTTSSound} AbstractTTSSound */
|
11
|
+
|
12
|
+
/**
|
13
|
+
* @extends AbstractTTSEngine
|
14
|
+
* TTS using Festival endpoint
|
15
|
+
**/
|
16
|
+
export default class FestivalTTSEngine extends AbstractTTSEngine {
|
17
|
+
/** @override */
|
18
|
+
static isSupported() {
|
19
|
+
return typeof soundManager !== 'undefined' && soundManager.supported();
|
20
|
+
}
|
21
|
+
|
22
|
+
/** @param {TTSEngineOptions} options */
|
23
|
+
constructor(options) {
|
24
|
+
var _$$browser;
|
25
|
+
super(options);
|
26
|
+
// $.browsers is sometimes undefined on some Android browsers :/
|
27
|
+
// Likely related to when $.browser was moved to npm
|
28
|
+
/** @type {'mp3' | 'ogg'} format of audio to get */
|
29
|
+
this.audioFormat = (_$$browser = $.browser) !== null && _$$browser !== void 0 && _$$browser.mozilla ? 'ogg' : 'mp3'; //eslint-disable-line no-jquery/no-browser
|
30
|
+
}
|
31
|
+
|
32
|
+
/** @override */
|
33
|
+
getVoices() {
|
34
|
+
return [{
|
35
|
+
default: true,
|
36
|
+
lang: "en-US",
|
37
|
+
localService: false,
|
38
|
+
name: "Festival - English (US)",
|
39
|
+
voiceURI: null
|
40
|
+
}];
|
41
|
+
}
|
42
|
+
|
43
|
+
/** @override */
|
44
|
+
init() {
|
45
|
+
// setup sound manager
|
46
|
+
soundManager.setup({
|
47
|
+
debugMode: false,
|
48
|
+
// Note, there's a bug in Chrome regarding range requests.
|
49
|
+
// Flash is used as a workaround.
|
50
|
+
// See https://bugs.chromium.org/p/chromium/issues/detail?id=505707
|
51
|
+
preferFlash: true,
|
52
|
+
url: '/bookreader/BookReader/soundmanager/swf',
|
53
|
+
useHTML5Audio: true,
|
54
|
+
//flash 8 version of swf is buggy when calling play() on a sound that is still loading
|
55
|
+
flashVersion: 9
|
56
|
+
});
|
57
|
+
}
|
58
|
+
|
59
|
+
/**
|
60
|
+
* @override
|
61
|
+
* @param {number} leafIndex
|
62
|
+
* @param {number} numLeafs total number of leafs in the current book
|
63
|
+
*/
|
64
|
+
start(leafIndex, numLeafs) {
|
65
|
+
var promise = null;
|
66
|
+
|
67
|
+
// Hack for iOS
|
68
|
+
if (navigator.userAgent.match(/mobile/i)) {
|
69
|
+
promise = this.iOSCaptureUserIntentHack();
|
70
|
+
}
|
71
|
+
promise = promise || Promise.resolve();
|
72
|
+
promise.then(() => super.start(leafIndex, numLeafs));
|
73
|
+
}
|
74
|
+
|
75
|
+
/** @override */
|
76
|
+
createSound(chunk) {
|
77
|
+
return new FestivalTTSSound(this.getSoundUrl(chunk.text));
|
78
|
+
}
|
79
|
+
|
80
|
+
/**
|
81
|
+
* @private
|
82
|
+
* Get URL for audio that says this text
|
83
|
+
* @param {String} dataString the thing to say
|
84
|
+
* @return {String} url
|
85
|
+
*/
|
86
|
+
getSoundUrl(dataString) {
|
87
|
+
return 'https://' + this.opts.server + '/BookReader/BookReaderGetTTS.php?string=' + encodeURIComponent(dataString) + '&format=.' + this.audioFormat;
|
88
|
+
}
|
89
|
+
|
90
|
+
/**
|
91
|
+
* @private
|
92
|
+
* Security restrictions require playback to be triggered
|
93
|
+
* by a user click/touch. This intention gets lost in the async calls
|
94
|
+
* on iOS, but, for some reason, if we start the audio here, it works.
|
95
|
+
* See https://stackoverflow.com/questions/12206631/html5-audio-cant-play-through-javascript-unless-triggered-manually-once
|
96
|
+
* @return {PromiseLike}
|
97
|
+
*/
|
98
|
+
iOSCaptureUserIntentHack() {
|
99
|
+
var _this = this;
|
100
|
+
return _asyncToGenerator(function* () {
|
101
|
+
var sound = soundManager.createSound({
|
102
|
+
url: SILENCE_1MS[_this.audioFormat]
|
103
|
+
});
|
104
|
+
yield new Promise(res => sound.play({
|
105
|
+
onfinish: res
|
106
|
+
}));
|
107
|
+
sound.destruct();
|
108
|
+
})();
|
109
|
+
}
|
110
|
+
}
|
111
|
+
|
112
|
+
/** @extends AbstractTTSSound */
|
113
|
+
class FestivalTTSSound {
|
114
|
+
/** @param {string} soundUrl **/
|
115
|
+
constructor(soundUrl) {
|
116
|
+
this.soundUrl = soundUrl;
|
117
|
+
/** @type {SMSound} */
|
118
|
+
this.sound = null;
|
119
|
+
this.rate = 1;
|
120
|
+
/** @type {function} calling this resolves the "play" promise */
|
121
|
+
this._finishResolver = null;
|
122
|
+
}
|
123
|
+
get loaded() {
|
124
|
+
return this.sound && this.sound.loaded;
|
125
|
+
}
|
126
|
+
load(_onload) {
|
127
|
+
var _this2 = this;
|
128
|
+
this.sound = soundManager.createSound({
|
129
|
+
url: this.soundUrl,
|
130
|
+
// API recommended, but only fires once play started on safari
|
131
|
+
onload: () => {
|
132
|
+
if (this.rate != 1) this.sound.setPlaybackRate(this.rate);
|
133
|
+
_onload();
|
134
|
+
},
|
135
|
+
onresume: function () {
|
136
|
+
var _onresume = _asyncToGenerator(function* () {
|
137
|
+
yield sleep(25);
|
138
|
+
if (_this2.rate != 1) _this2.sound.setPlaybackRate(_this2.rate);
|
139
|
+
});
|
140
|
+
function onresume() {
|
141
|
+
return _onresume.apply(this, arguments);
|
142
|
+
}
|
143
|
+
return onresume;
|
144
|
+
}()
|
145
|
+
});
|
146
|
+
return this.sound.load();
|
147
|
+
}
|
148
|
+
play() {
|
149
|
+
var _this3 = this;
|
150
|
+
return _asyncToGenerator(function* () {
|
151
|
+
yield new Promise(res => {
|
152
|
+
_this3._finishResolver = res;
|
153
|
+
_this3.sound.play({
|
154
|
+
onfinish: res
|
155
|
+
});
|
156
|
+
});
|
157
|
+
_this3.sound.destruct();
|
158
|
+
})();
|
159
|
+
}
|
160
|
+
|
161
|
+
/** @override */
|
162
|
+
stop() {
|
163
|
+
this.sound.stop();
|
164
|
+
return Promise.resolve();
|
165
|
+
}
|
166
|
+
|
167
|
+
/** @override */
|
168
|
+
pause() {
|
169
|
+
this.sound.pause();
|
170
|
+
}
|
171
|
+
/** @override */
|
172
|
+
resume() {
|
173
|
+
this.sound.resume();
|
174
|
+
}
|
175
|
+
/** @override */
|
176
|
+
setPlaybackRate(rate) {
|
177
|
+
this.rate = rate;
|
178
|
+
this.sound.setPlaybackRate(rate);
|
179
|
+
}
|
180
|
+
|
181
|
+
/** @override */
|
182
|
+
finish() {
|
183
|
+
this.sound.stop();
|
184
|
+
this._finishResolver();
|
185
|
+
}
|
186
|
+
}
|
187
|
+
|
188
|
+
/** Needed to capture the audio context for iOS hack. Generated using Audacity. */
|
189
|
+
var SILENCE_1MS = {
|
190
|
+
mp3: 'data:audio/mp3;base64,//uQxAAAAAAAAAAAAAAAAAAAAAAAWGluZwAAAA8AAAACAAACcQCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA//////////////////////////////////////////////////////////////////8AAAAeTEFNRTMuOTlyBJwAAAAAAAAAADUgJAaUQQABrgAAAnHIf8sZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//sQxAADwlwBKGAAACAAAD/AAAAEAAAAH///////////////+UBAMExBTUUzLjk5LjOqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqr/+xDEIAPAAAGkAAAAIAAANIAAAASqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqg==',
|
191
|
+
ogg: 'data:audio/ogg;base64,T2dnUwACAAAAAAAAAAAVEgAAAAAAAADSDf4BHgF2b3JiaXMAAAAAAUSsAAAAAAAAAHcBAAAAAAC4AU9nZ1MAAAAAAAAAAAAAFRIAAAEAAAB4VKTpEDv//////////////////8kDdm9yYmlzKwAAAFhpcGguT3JnIGxpYlZvcmJpcyBJIDIwMTIwMjAzIChPbW5pcHJlc2VudCkAAAAAAQV2b3JiaXMpQkNWAQAIAAAAMUwgxYDQkFUAABAAAGAkKQ6TZkkppZShKHmYlEhJKaWUxTCJmJSJxRhjjDHGGGOMMcYYY4wgNGQVAAAEAIAoCY6j5klqzjlnGCeOcqA5aU44pyAHilHgOQnC9SZjbqa0pmtuziklCA1ZBQAAAgBASCGFFFJIIYUUYoghhhhiiCGHHHLIIaeccgoqqKCCCjLIIINMMumkk0466aijjjrqKLTQQgsttNJKTDHVVmOuvQZdfHPOOeecc84555xzzglCQ1YBACAAAARCBhlkEEIIIYUUUogppphyCjLIgNCQVQAAIACAAAAAAEeRFEmxFMuxHM3RJE/yLFETNdEzRVNUTVVVVVV1XVd2Zdd2ddd2fVmYhVu4fVm4hVvYhV33hWEYhmEYhmEYhmH4fd/3fd/3fSA0ZBUAIAEAoCM5luMpoiIaouI5ogOEhqwCAGQAAAQAIAmSIimSo0mmZmquaZu2aKu2bcuyLMuyDISGrAIAAAEABAAAAAAAoGmapmmapmmapmmapmmapmmapmmaZlmWZVmWZVmWZVmWZVmWZVmWZVmWZVmWZVmWZVmWZVmWZVmWZVlAaMgqAEACAEDHcRzHcSRFUiTHciwHCA1ZBQDIAAAIAEBSLMVyNEdzNMdzPMdzPEd0RMmUTM30TA8IDVkFAAACAAgAAAAAAEAxHMVxHMnRJE9SLdNyNVdzPddzTdd1XVdVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVgdCQVQAABAAAIZ1mlmqACDOQYSA0ZBUAgAAAABihCEMMCA1ZBQAABAAAiKHkIJrQmvPNOQ6a5aCpFJvTwYlUmye5qZibc84555xszhnjnHPOKcqZxaCZ0JpzzkkMmqWgmdCac855EpsHranSmnPOGeecDsYZYZxzzmnSmgep2Vibc85Z0JrmqLkUm3POiZSbJ7W5VJtzzjnnnHPOOeecc86pXpzOwTnhnHPOidqba7kJXZxzzvlknO7NCeGcc84555xzzjnnnHPOCUJDVgEAQAAABGHYGMadgiB9jgZiFCGmIZMedI8Ok6AxyCmkHo2ORkqpg1BSGSeldILQkFUAACAAAIQQUkghhRRSSCGFFFJIIYYYYoghp5xyCiqopJKKKsoos8wyyyyzzDLLrMPOOuuwwxBDDDG00kosNdVWY4215p5zrjlIa6W11lorpZRSSimlIDRkFQAAAgBAIGSQQQYZhRRSSCGGmHLKKaegggoIDVkFAAACAAgAAADwJM8RHdERHdERHdERHdERHc/xHFESJVESJdEyLVMzPVVUVVd2bVmXddu3hV3Ydd/Xfd/XjV8XhmVZlmVZlmVZlmVZlmVZlmUJQkNWAQAgAAAAQgghhBRSSCGFlGKMMcecg05CCYHQkFUAACAAgAAAAABHcRTHkRzJkSRLsiRN0izN8jRP8zTRE0VRNE1TFV3RFXXTFmVTNl3TNWXTVWXVdmXZtmVbt31Ztn3f933f933f933f933f13UgNGQVACABAKAjOZIiKZIiOY7jSJIEhIasAgBkAAAEAKAojuI4jiNJkiRZkiZ5lmeJmqmZnumpogqEhqwCAAABAAQAAAAAAKBoiqeYiqeIiueIjiiJlmmJmqq5omzKruu6ruu6ruu6ruu6ruu6ruu6ruu6ruu6ruu6ruu6ruu6ruu6QGjIKgBAAgBAR3IkR3IkRVIkRXIkBwgNWQUAyAAACADAMRxDUiTHsixN8zRP8zTREz3RMz1VdEUXCA1ZBQAAAgAIAAAAAADAkAxLsRzN0SRRUi3VUjXVUi1VVD1VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVXVNE3TNIHQkJUAABkAACNBBhmEEIpykEJuPVgIMeYkBaE5BqHEGISnEDMMOQ0idJBBJz24kjnDDPPgUigVREyDjSU3jiANwqZcSeU4CEJDVgQAUQAAgDHIMcQYcs5JyaBEzjEJnZTIOSelk9JJKS2WGDMpJaYSY+Oco9JJyaSUGEuKnaQSY4mtAACAAAcAgAALodCQFQFAFAAAYgxSCimFlFLOKeaQUsox5RxSSjmnnFPOOQgdhMoxBp2DECmlHFPOKccchMxB5ZyD0EEoAAAgwAEAIMBCKDRkRQAQJwDgcCTPkzRLFCVLE0XPFGXXE03XlTTNNDVRVFXLE1XVVFXbFk1VtiVNE01N9FRVE0VVFVXTlk1VtW3PNGXZVFXdFlXVtmXbFn5XlnXfM01ZFlXV1k1VtXXXln1f1m1dmDTNNDVRVFVNFFXVVFXbNlXXtjVRdFVRVWVZVFVZdmVZ91VX1n1LFFXVU03ZFVVVtlXZ9W1Vln3hdFVdV2XZ91VZFn5b14Xh9n3hGFXV1k3X1XVVln1h1mVht3XfKGmaaWqiqKqaKKqqqaq2baqurVui6KqiqsqyZ6qurMqyr6uubOuaKKquqKqyLKqqLKuyrPuqLOu2qKq6rcqysJuuq+u27wvDLOu6cKqurquy7PuqLOu6revGceu6MHymKcumq+q6qbq6buu6ccy2bRyjquq+KsvCsMqy7+u6L7R1IVFVdd2UXeNXZVn3bV93nlv3hbJtO7+t+8px67rS+DnPbxy5tm0cs24bv637xvMrP2E4jqVnmrZtqqqtm6qr67JuK8Os60JRVX1dlWXfN11ZF27fN45b142iquq6Ksu+sMqyMdzGbxy7MBxd2zaOW9edsq0LfWPI9wnPa9vGcfs64/Z1o68MCcePAACAAQcAgAATykChISsCgDgBAAYh5xRTECrFIHQQUuogpFQxBiFzTkrFHJRQSmohlNQqxiBUjknInJMSSmgplNJSB6GlUEproZTWUmuxptRi7SCkFkppLZTSWmqpxtRajBFjEDLnpGTOSQmltBZKaS1zTkrnoKQOQkqlpBRLSi1WzEnJoKPSQUippBJTSam1UEprpaQWS0oxthRbbjHWHEppLaQSW0kpxhRTbS3GmiPGIGTOScmckxJKaS2U0lrlmJQOQkqZg5JKSq2VklLMnJPSQUipg45KSSm2kkpMoZTWSkqxhVJabDHWnFJsNZTSWkkpxpJKbC3GWltMtXUQWgultBZKaa21VmtqrcZQSmslpRhLSrG1FmtuMeYaSmmtpBJbSanFFluOLcaaU2s1ptZqbjHmGlttPdaac0qt1tRSjS3GmmNtvdWae+8gpBZKaS2U0mJqLcbWYq2hlNZKKrGVklpsMebaWow5lNJiSanFklKMLcaaW2y5ppZqbDHmmlKLtebac2w19tRarC3GmlNLtdZac4+59VYAAMCAAwBAgAlloNCQlQBAFAAAQYhSzklpEHLMOSoJQsw5J6lyTEIpKVXMQQgltc45KSnF1jkIJaUWSyotxVZrKSm1FmstAACgwAEAIMAGTYnFAQoNWQkARAEAIMYgxBiEBhmlGIPQGKQUYxAipRhzTkqlFGPOSckYcw5CKhljzkEoKYRQSiophRBKSSWlAgAAChwAAAJs0JRYHKDQkBUBQBQAAGAMYgwxhiB0VDIqEYRMSiepgRBaC6111lJrpcXMWmqttNhACK2F1jJLJcbUWmatxJhaKwAA7MABAOzAQig0ZCUAkAcAQBijFGPOOWcQYsw56Bw0CDHmHIQOKsacgw5CCBVjzkEIIYTMOQghhBBC5hyEEEIIoYMQQgillNJBCCGEUkrpIIQQQimldBBCCKGUUgoAACpwAAAIsFFkc4KRoEJDVgIAeQAAgDFKOQehlEYpxiCUklKjFGMQSkmpcgxCKSnFVjkHoZSUWuwglNJabDV2EEppLcZaQ0qtxVhrriGl1mKsNdfUWoy15pprSi3GWmvNuQAA3AUHALADG0U2JxgJKjRkJQCQBwCAIKQUY4wxhhRiijHnnEMIKcWYc84pphhzzjnnlGKMOeecc4wx55xzzjnGmHPOOeccc84555xzjjnnnHPOOeecc84555xzzjnnnHPOCQAAKnAAAAiwUWRzgpGgQkNWAgCpAAAAEVZijDHGGBsIMcYYY4wxRhJijDHGGGNsMcYYY4wxxphijDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYW2uttdZaa6211lprrbXWWmutAEC/CgcA/wcbVkc4KRoLLDRkJQAQDgAAGMOYc445Bh2EhinopIQOQgihQ0o5KCWEUEopKXNOSkqlpJRaSplzUlIqJaWWUuogpNRaSi211loHJaXWUmqttdY6CKW01FprrbXYQUgppdZaiy3GUEpKrbXYYow1hlJSaq3F2GKsMaTSUmwtxhhjrKGU1lprMcYYay0ptdZijLXGWmtJqbXWYos11loLAOBucACASLBxhpWks8LR4EJDVgIAIQEABEKMOeeccxBCCCFSijHnoIMQQgghREox5hx0EEIIIYSMMeeggxBCCCGEkDHmHHQQQgghhBA65xyEEEIIoYRSSuccdBBCCCGUUELpIIQQQgihhFJKKR2EEEIooYRSSiklhBBCCaWUUkoppYQQQgihhBJKKaWUEEIIpZRSSimllBJCCCGUUkoppZRSQgihlFBKKaWUUkoIIYRSSimllFJKCSGEUEoppZRSSikhhBJKKaWUUkoppQAAgAMHAIAAI+gko8oibDThwgNQaMhKAIAMAABx2GrrKdbIIMWchJZLhJByEGIuEVKKOUexZUgZxRjVlDGlFFNSa+icYoxRT51jSjHDrJRWSiiRgtJyrLV2zAEAACAIADAQITOBQAEUGMgAgAOEBCkAoLDA0DFcBATkEjIKDArHhHPSaQMAEITIDJGIWAwSE6qBomI6AFhcYMgHgAyNjbSLC+gywAVd3HUghCAEIYjFARSQgIMTbnjiDU+4wQk6RaUOAgAAAAAAAQAeAACSDSAiIpo5jg6PD5AQkRGSEpMTlAAAAAAA4AGADwCAJAWIiIhmjqPD4wMkRGSEpMTkBCUAAAAAAAAAAAAICAgAAAAAAAQAAAAICE9nZ1MABCwAAAAAAAAAFRIAAAIAAAAPBTD1AgEBAAo='
|
192
|
+
};
|
@@ -0,0 +1,105 @@
|
|
1
|
+
function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
|
2
|
+
function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
|
3
|
+
/**
|
4
|
+
* Class to manage a 'chunk' (approximately a paragraph) of text on a page.
|
5
|
+
*/
|
6
|
+
export default class PageChunk {
|
7
|
+
/**
|
8
|
+
* @param {number} leafIndex
|
9
|
+
* @param {number} chunkIndex
|
10
|
+
* @param {string} text
|
11
|
+
* @param {DJVURect[]} lineRects
|
12
|
+
*/
|
13
|
+
constructor(leafIndex, chunkIndex, text, lineRects) {
|
14
|
+
this.leafIndex = leafIndex;
|
15
|
+
this.chunkIndex = chunkIndex;
|
16
|
+
this.text = text;
|
17
|
+
this.lineRects = lineRects;
|
18
|
+
}
|
19
|
+
|
20
|
+
/**
|
21
|
+
* @param {string} server
|
22
|
+
* @param {string} bookPath
|
23
|
+
* @param {number} leafIndex
|
24
|
+
* @return {Promise<PageChunk[]>}
|
25
|
+
*/
|
26
|
+
static fetch(server, bookPath, leafIndex) {
|
27
|
+
return _asyncToGenerator(function* () {
|
28
|
+
var chunks = yield $.ajax({
|
29
|
+
type: 'GET',
|
30
|
+
url: "https://".concat(server, "/BookReader/BookReaderGetTextWrapper.php"),
|
31
|
+
cache: true,
|
32
|
+
xhrFields: {
|
33
|
+
withCredentials: window.br.protected
|
34
|
+
},
|
35
|
+
data: {
|
36
|
+
path: "".concat(bookPath, "_djvu.xml"),
|
37
|
+
page: leafIndex,
|
38
|
+
callback: 'false'
|
39
|
+
}
|
40
|
+
});
|
41
|
+
return PageChunk._fromTextWrapperResponse(leafIndex, chunks);
|
42
|
+
})();
|
43
|
+
}
|
44
|
+
|
45
|
+
/**
|
46
|
+
* Convert the response from BookReaderGetTextWrapper.php into a {@link PageChunk} instance
|
47
|
+
* @param {number} leafIndex
|
48
|
+
* @param {Array<[String, ...DJVURect[]]>} chunksResponse
|
49
|
+
* @return {PageChunk[]}
|
50
|
+
*/
|
51
|
+
static _fromTextWrapperResponse(leafIndex, chunksResponse) {
|
52
|
+
return chunksResponse.map((c, i) => {
|
53
|
+
var correctedLineRects = PageChunk._fixChunkRects(c.slice(1));
|
54
|
+
var correctedText = PageChunk._removeDanglingHyphens(c[0]);
|
55
|
+
return new PageChunk(leafIndex, i, correctedText, correctedLineRects);
|
56
|
+
});
|
57
|
+
}
|
58
|
+
|
59
|
+
/**
|
60
|
+
* @private
|
61
|
+
* Sometimes the first rectangle will be ridiculously wide/tall. Find those and fix them
|
62
|
+
* *NOTE*: Modifies the original array and returns it.
|
63
|
+
* *NOTE*: This should probably be fixed on the petabox side, and then removed here
|
64
|
+
* Has 2 problems:
|
65
|
+
* - If the rect is the last rect on the page (and hence the only rect in the array),
|
66
|
+
* the rect's size isn't fixed
|
67
|
+
* - Because this relies on the second rect, there's a chance it won't be the right
|
68
|
+
* width
|
69
|
+
* @param {DJVURect[]} rects
|
70
|
+
* @return {DJVURect[]}
|
71
|
+
*/
|
72
|
+
static _fixChunkRects(rects) {
|
73
|
+
if (rects.length < 2) return rects;
|
74
|
+
var [firstRect, secondRect] = rects;
|
75
|
+
var [left, bottom, right] = firstRect;
|
76
|
+
var width = right - left;
|
77
|
+
var secondHeight = secondRect[1] - secondRect[3];
|
78
|
+
var secondWidth = secondRect[2] - secondRect[0];
|
79
|
+
var secondRight = secondRect[2];
|
80
|
+
if (width > secondWidth * 30) {
|
81
|
+
// Set the end to be the same
|
82
|
+
firstRect[2] = secondRight;
|
83
|
+
// And the top to be the same height
|
84
|
+
firstRect[3] = bottom - secondHeight;
|
85
|
+
}
|
86
|
+
return rects;
|
87
|
+
}
|
88
|
+
|
89
|
+
/**
|
90
|
+
* Remove "dangling" hyphens from read aloud text to avoid TTS stuttering
|
91
|
+
* @param {string} text
|
92
|
+
* @return {string}
|
93
|
+
*/
|
94
|
+
static _removeDanglingHyphens(text) {
|
95
|
+
// Some books mis-OCR a dangling hyphen as a ¬ (mathematical not sign) . Since in math
|
96
|
+
// the not sign should not appear followed by a space, we think we can safely assume
|
97
|
+
// this should be replaced.
|
98
|
+
return text.replace(/[-¬]\s+/g, '');
|
99
|
+
}
|
100
|
+
}
|
101
|
+
|
102
|
+
/**
|
103
|
+
* @typedef {[number, number, number, number]} DJVURect
|
104
|
+
* coords are in l,b,r,t order
|
105
|
+
*/
|
@@ -0,0 +1,155 @@
|
|
1
|
+
function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
|
2
|
+
function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
|
3
|
+
import PageChunk from './PageChunk.js';
|
4
|
+
|
5
|
+
/**
|
6
|
+
* Class that iterates over the page chunks of a book; caching/buffering
|
7
|
+
* as much as possible to try to ensure a smooth experience.
|
8
|
+
*/
|
9
|
+
export default class PageChunkIterator {
|
10
|
+
/**
|
11
|
+
* @param {number} pageCount total number of pages
|
12
|
+
* @param {number} start page to start on
|
13
|
+
* @param {PageChunkIteratorOptions} opts
|
14
|
+
*/
|
15
|
+
constructor(pageCount, start, opts) {
|
16
|
+
this.pageCount = pageCount;
|
17
|
+
this.opts = Object.assign({}, DEFAULT_OPTS, opts);
|
18
|
+
/** Position in the chunk sequence */
|
19
|
+
this._cursor = {
|
20
|
+
page: start,
|
21
|
+
chunk: 0
|
22
|
+
};
|
23
|
+
/** @type {Object<number, PageChunk[]>} leaf index -> chunks*/
|
24
|
+
this._bufferedPages = {};
|
25
|
+
/** @type {Object<number, PromiseLike<PageChunk[]>} leaf index -> chunks*/
|
26
|
+
this._bufferingPages = {};
|
27
|
+
/**
|
28
|
+
* @type {Promise} promise that manages cursor modifications so that they
|
29
|
+
* happen in order triggered as opposed to order the server responds
|
30
|
+
**/
|
31
|
+
this._cursorLock = Promise.resolve();
|
32
|
+
}
|
33
|
+
|
34
|
+
/**
|
35
|
+
* Get the next chunk
|
36
|
+
* @return {PromiseLike<"__PageChunkIterator.AT_END__" | PageChunk>}
|
37
|
+
*/
|
38
|
+
next() {
|
39
|
+
return this._cursorLock = this._cursorLock.then(() => this._nextUncontrolled());
|
40
|
+
}
|
41
|
+
|
42
|
+
/**
|
43
|
+
* Sends the cursor back 1
|
44
|
+
* @return {Promise}
|
45
|
+
**/
|
46
|
+
decrement() {
|
47
|
+
return this._cursorLock = this._cursorLock.then(() => this._decrementUncontrolled());
|
48
|
+
}
|
49
|
+
|
50
|
+
/**
|
51
|
+
* Gets without ensuring synchronization. Since this iterator has a lot of async
|
52
|
+
* code, calling e.g. "next" twice (before the first call to next has finished)
|
53
|
+
* would cause the system to be in a weird state. To avoid that, we make sure calls
|
54
|
+
* to next and decrement (functions that modify the cursor) are synchronized,
|
55
|
+
* so that regardless how long it takes for one to respond, they'll always be executed
|
56
|
+
* in the correct order.
|
57
|
+
* @return {PromiseLike<"__PageChunkIterator.AT_END__" | PageChunk>}
|
58
|
+
*/
|
59
|
+
_nextUncontrolled() {
|
60
|
+
var _this = this;
|
61
|
+
return _asyncToGenerator(function* () {
|
62
|
+
if (_this._cursor.page == _this.pageCount) {
|
63
|
+
return Promise.resolve(PageChunkIterator.AT_END);
|
64
|
+
}
|
65
|
+
_this._recenterBuffer(_this._cursor.page);
|
66
|
+
var chunks = yield _this._fetchPageChunks(_this._cursor.page);
|
67
|
+
if (_this._cursor.chunk == chunks.length) {
|
68
|
+
_this._cursor.page++;
|
69
|
+
_this._cursor.chunk = 0;
|
70
|
+
return _this._nextUncontrolled();
|
71
|
+
}
|
72
|
+
return chunks[_this._cursor.chunk++];
|
73
|
+
})();
|
74
|
+
}
|
75
|
+
|
76
|
+
/**
|
77
|
+
* Decrements without ensuring synchronization. (See {@link PageChunkIterator._nextUncontrolled});
|
78
|
+
* @return {Promise}
|
79
|
+
*/
|
80
|
+
_decrementUncontrolled() {
|
81
|
+
var cursorChangePromise = Promise.resolve();
|
82
|
+
if (this._cursor.chunk > 0) {
|
83
|
+
this._cursor.chunk--;
|
84
|
+
} else if (this._cursor.page > 0) {
|
85
|
+
this._cursor.page--;
|
86
|
+
// Go back possibly multiple pages, because pages can be blank
|
87
|
+
cursorChangePromise = this._fetchPageChunks(this._cursor.page).then(prevPageChunks => {
|
88
|
+
if (prevPageChunks.length == 0) return this._decrementUncontrolled();else this._cursor.chunk = prevPageChunks.length - 1;
|
89
|
+
});
|
90
|
+
}
|
91
|
+
return cursorChangePromise.then(() => this._fetchPageChunks(this._cursor.page));
|
92
|
+
}
|
93
|
+
|
94
|
+
/**
|
95
|
+
* Recenter the buffer around the provided page index
|
96
|
+
* @param {number} index
|
97
|
+
*/
|
98
|
+
_recenterBuffer(index) {
|
99
|
+
var start = Math.max(0, index - this.opts.pageBufferSize);
|
100
|
+
var end = Math.min(this.pageCount, index + this.opts.pageBufferSize + 1);
|
101
|
+
for (var i = start; i < end; i++) {
|
102
|
+
this._fetchPageChunks(i);
|
103
|
+
}
|
104
|
+
this._removePageFromBuffer(start - 1);
|
105
|
+
this._removePageFromBuffer(end + 1);
|
106
|
+
}
|
107
|
+
|
108
|
+
/**
|
109
|
+
* @param {number} index
|
110
|
+
*/
|
111
|
+
_removePageFromBuffer(index) {
|
112
|
+
delete this._bufferingPages[index];
|
113
|
+
delete this._bufferedPages[index];
|
114
|
+
}
|
115
|
+
|
116
|
+
/**
|
117
|
+
* Fetches the chunks on a page; checks the buffer, so it won't make unnecessary
|
118
|
+
* requests if it's called multiple times for the same index.
|
119
|
+
* @param {number} index
|
120
|
+
* @return {Promise<PageChunk[]>}
|
121
|
+
*/
|
122
|
+
_fetchPageChunks(index) {
|
123
|
+
if (index in this._bufferingPages) return this._bufferingPages[index];
|
124
|
+
if (index in this._bufferedPages) return Promise.resolve(this._bufferedPages[index]);
|
125
|
+
this._bufferingPages[index] = this._fetchPageChunksDirect(index).then(chunks => {
|
126
|
+
delete this._bufferingPages[index];
|
127
|
+
this._bufferedPages[index] = chunks;
|
128
|
+
return chunks;
|
129
|
+
});
|
130
|
+
return this._bufferingPages[index];
|
131
|
+
}
|
132
|
+
|
133
|
+
/**
|
134
|
+
* Fetches a page without checking buffer
|
135
|
+
* @param {number} index
|
136
|
+
*/
|
137
|
+
_fetchPageChunksDirect(index) {
|
138
|
+
return PageChunk.fetch(this.opts.server, this.opts.bookPath, index);
|
139
|
+
}
|
140
|
+
}
|
141
|
+
PageChunkIterator.AT_END = "__PageChunkIterator.AT_END__";
|
142
|
+
|
143
|
+
/** @type {PageChunkIteratorOptions} */
|
144
|
+
var DEFAULT_OPTS = {
|
145
|
+
server: null,
|
146
|
+
bookPath: null,
|
147
|
+
pageBufferSize: 2
|
148
|
+
};
|
149
|
+
|
150
|
+
/**
|
151
|
+
* @typedef {Object} PageChunkIteratorOptions
|
152
|
+
* @property {string} server
|
153
|
+
* @property {string} bookPath
|
154
|
+
* @property {number} [pageBufferSize] number of pages to buffer before/after the current page
|
155
|
+
*/
|