@internetarchive/bookreader 5.0.0-28-remove-url-defaults → 5.0.0-30-b
Sign up to get free protection for your applications and to get access to all the features.
- package/.husky/_/husky.sh +30 -0
- package/BookReader/BookReader.css +1 -1
- package/BookReader/BookReader.js +1 -1
- package/BookReader/BookReader.js.map +1 -1
- package/BookReader/ia-bookreader-bundle.js +1458 -0
- package/BookReader/{bookreader-component-bundle.js.LICENSE.txt → ia-bookreader-bundle.js.LICENSE.txt} +12 -0
- package/BookReader/ia-bookreader-bundle.js.map +1 -0
- package/BookReader/plugins/plugin.search.js +1 -1
- package/BookReader/plugins/plugin.search.js.map +1 -1
- package/BookReaderDemo/BookReaderDemo.css +14 -1
- package/BookReaderDemo/IADemoBr.js +107 -0
- package/BookReaderDemo/demo-internetarchive.html +64 -99
- package/CHANGELOG.md +4 -0
- package/package.json +9 -6
- package/src/BookNavigator/assets/ia-logo.js +17 -0
- package/src/BookNavigator/book-navigator.js +528 -0
- package/src/BookNavigator/bookmarks/bookmark-button.js +2 -1
- package/src/BookNavigator/bookmarks/bookmarks-provider.js +20 -8
- package/src/BookNavigator/bookmarks/ia-bookmarks.js +84 -51
- package/src/BookNavigator/downloads/downloads-provider.js +5 -9
- package/src/BookNavigator/downloads/downloads.js +1 -0
- package/src/BookNavigator/search/search-provider.js +15 -8
- package/src/BookNavigator/sharing.js +27 -0
- package/src/BookNavigator/visual-adjustments/visual-adjustments-provider.js +9 -8
- package/src/BookNavigator/volumes/volumes-provider.js +3 -4
- package/src/BookReader/options.js +6 -0
- package/src/BookReader.js +20 -8
- package/src/css/_BRComponent.scss +1 -1
- package/src/ia-bookreader/ia-bookreader.js +209 -0
- package/src/plugins/search/plugin.search.js +9 -9
- package/{src → stat}/BookNavigator/BookModel.js +0 -0
- package/{src → stat}/BookNavigator/BookNavigator.js +109 -102
- package/stat/BookNavigator/assets/bookmark-colors.js +15 -0
- package/stat/BookNavigator/assets/button-base.js +61 -0
- package/stat/BookNavigator/assets/ia-logo.js +17 -0
- package/stat/BookNavigator/assets/icon_checkmark.js +6 -0
- package/stat/BookNavigator/assets/icon_close.js +3 -0
- package/stat/BookNavigator/assets/icon_sort_asc.js +5 -0
- package/stat/BookNavigator/assets/icon_sort_desc.js +5 -0
- package/stat/BookNavigator/assets/icon_sort_neutral.js +5 -0
- package/stat/BookNavigator/assets/icon_volumes.js +11 -0
- package/stat/BookNavigator/bookmarks/bookmark-button.js +64 -0
- package/stat/BookNavigator/bookmarks/bookmark-edit.js +215 -0
- package/stat/BookNavigator/bookmarks/bookmarks-list.js +285 -0
- package/stat/BookNavigator/bookmarks/bookmarks-loginCTA.js +28 -0
- package/stat/BookNavigator/bookmarks/bookmarks-provider.js +56 -0
- package/stat/BookNavigator/bookmarks/ia-bookmarks.js +523 -0
- package/{src → stat}/BookNavigator/br-fullscreen-mgr.js +1 -2
- package/stat/BookNavigator/delete-modal-actions.js +49 -0
- package/stat/BookNavigator/downloads/downloads-provider.js +72 -0
- package/stat/BookNavigator/downloads/downloads.js +139 -0
- package/stat/BookNavigator/provider-config.js +0 -0
- package/stat/BookNavigator/search/a-search-result.js +55 -0
- package/stat/BookNavigator/search/search-provider.js +180 -0
- package/stat/BookNavigator/search/search-results.js +360 -0
- package/{src/ItemNavigator/providers → stat/BookNavigator}/sharing.js +3 -5
- package/stat/BookNavigator/visual-adjustments/visual-adjustments-provider.js +94 -0
- package/stat/BookNavigator/visual-adjustments/visual-adjustments.js +280 -0
- package/stat/BookNavigator/volumes/volumes-provider.js +83 -0
- package/stat/BookNavigator/volumes/volumes.js +178 -0
- package/stat/BookReader/BookModel.js +518 -0
- package/stat/BookReader/DebugConsole.js +54 -0
- package/stat/BookReader/DragScrollable.js +233 -0
- package/stat/BookReader/ImageCache.js +116 -0
- package/stat/BookReader/Mode1Up.js +102 -0
- package/stat/BookReader/Mode1UpLit.js +434 -0
- package/stat/BookReader/Mode2Up.js +1372 -0
- package/stat/BookReader/ModeSmoothZoom.js +177 -0
- package/stat/BookReader/ModeThumb.js +344 -0
- package/stat/BookReader/Navbar/Navbar.js +310 -0
- package/stat/BookReader/PageContainer.js +120 -0
- package/stat/BookReader/ReduceSet.js +26 -0
- package/stat/BookReader/Toolbar/Toolbar.js +384 -0
- package/stat/BookReader/events.js +20 -0
- package/stat/BookReader/options.js +324 -0
- package/stat/BookReader/utils/HTMLDimensionsCacher.js +44 -0
- package/stat/BookReader/utils/classes.js +36 -0
- package/stat/BookReader/utils.js +240 -0
- package/stat/BookReader.js +2550 -0
- package/{src → stat}/BookReaderComponent/BookReaderComponent.js +15 -10
- package/stat/assets/icons/1up.svg +12 -0
- package/stat/assets/icons/2up.svg +15 -0
- package/stat/assets/icons/advance.svg +26 -0
- package/stat/assets/icons/chevron-right.svg +1 -0
- package/stat/assets/icons/close-circle-dark.svg +1 -0
- package/stat/assets/icons/close-circle.svg +1 -0
- package/stat/assets/icons/fullscreen.svg +17 -0
- package/stat/assets/icons/fullscreen_exit.svg +17 -0
- package/stat/assets/icons/hamburger.svg +15 -0
- package/stat/assets/icons/left-arrow.svg +12 -0
- package/stat/assets/icons/magnify-minus.svg +16 -0
- package/stat/assets/icons/magnify-plus.svg +17 -0
- package/stat/assets/icons/magnify.svg +15 -0
- package/stat/assets/icons/pause.svg +23 -0
- package/stat/assets/icons/play.svg +22 -0
- package/stat/assets/icons/playback-speed.svg +34 -0
- package/stat/assets/icons/read-aloud.svg +22 -0
- package/stat/assets/icons/review.svg +22 -0
- package/stat/assets/icons/thumbnails.svg +17 -0
- package/stat/assets/icons/voice.svg +1 -0
- package/stat/assets/icons/volume-full.svg +22 -0
- package/stat/assets/images/BRicons.png +0 -0
- package/stat/assets/images/BRicons.svg +94 -0
- package/stat/assets/images/BRicons_ia.png +0 -0
- package/stat/assets/images/back_pages.png +0 -0
- package/stat/assets/images/book_bottom_icon.png +0 -0
- package/stat/assets/images/book_down_icon.png +0 -0
- package/stat/assets/images/book_left_icon.png +0 -0
- package/stat/assets/images/book_leftmost_icon.png +0 -0
- package/stat/assets/images/book_right_icon.png +0 -0
- package/stat/assets/images/book_rightmost_icon.png +0 -0
- package/stat/assets/images/book_top_icon.png +0 -0
- package/stat/assets/images/book_up_icon.png +0 -0
- package/stat/assets/images/books_graphic.svg +177 -0
- package/stat/assets/images/booksplit.png +0 -0
- package/stat/assets/images/control_pause_icon.png +0 -0
- package/stat/assets/images/control_play_icon.png +0 -0
- package/stat/assets/images/embed_icon.png +0 -0
- package/stat/assets/images/icon-home-ia.png +0 -0
- package/stat/assets/images/icon_OL-logo-xs.png +0 -0
- package/stat/assets/images/icon_alert-xs.png +0 -0
- package/stat/assets/images/icon_book.svg +12 -0
- package/stat/assets/images/icon_bookmark.svg +12 -0
- package/stat/assets/images/icon_close-pop.png +0 -0
- package/stat/assets/images/icon_download.png +0 -0
- package/stat/assets/images/icon_gear.svg +14 -0
- package/stat/assets/images/icon_hamburger.svg +20 -0
- package/stat/assets/images/icon_home.png +0 -0
- package/stat/assets/images/icon_home.svg +21 -0
- package/stat/assets/images/icon_home_ia.png +0 -0
- package/stat/assets/images/icon_indicator.png +0 -0
- package/stat/assets/images/icon_info.svg +11 -0
- package/stat/assets/images/icon_one_page.svg +8 -0
- package/stat/assets/images/icon_pause.svg +1 -0
- package/stat/assets/images/icon_play.svg +1 -0
- package/stat/assets/images/icon_playback-rate.svg +15 -0
- package/stat/assets/images/icon_return.png +0 -0
- package/stat/assets/images/icon_search_button.svg +8 -0
- package/stat/assets/images/icon_share.svg +9 -0
- package/stat/assets/images/icon_skip-ahead.svg +6 -0
- package/stat/assets/images/icon_skip-back.svg +13 -0
- package/stat/assets/images/icon_speaker.svg +18 -0
- package/stat/assets/images/icon_speaker_open.svg +10 -0
- package/stat/assets/images/icon_thumbnails.svg +12 -0
- package/stat/assets/images/icon_toc.svg +5 -0
- package/stat/assets/images/icon_two_pages.svg +9 -0
- package/stat/assets/images/icon_zoomer.png +0 -0
- package/stat/assets/images/loading.gif +0 -0
- package/stat/assets/images/logo_icon.png +0 -0
- package/stat/assets/images/marker_chap-off.png +0 -0
- package/stat/assets/images/marker_chap-off.svg +11 -0
- package/stat/assets/images/marker_chap-off_ia.png +0 -0
- package/stat/assets/images/marker_chap-on.png +0 -0
- package/stat/assets/images/marker_chap-on.svg +11 -0
- package/stat/assets/images/marker_srch-on.svg +11 -0
- package/stat/assets/images/marker_srchchap-off.png +0 -0
- package/stat/assets/images/marker_srchchap-on.png +0 -0
- package/stat/assets/images/nav_control-dn.png +0 -0
- package/stat/assets/images/nav_control-dn_ia.png +0 -0
- package/stat/assets/images/nav_control-up.png +0 -0
- package/stat/assets/images/nav_control-up_ia.png +0 -0
- package/stat/assets/images/nav_control.png +0 -0
- package/stat/assets/images/one_page_mode_icon.png +0 -0
- package/stat/assets/images/paper-badge.png +0 -0
- package/stat/assets/images/print_icon.png +0 -0
- package/stat/assets/images/progressbar.gif +0 -0
- package/stat/assets/images/right_edges.png +0 -0
- package/stat/assets/images/slider.png +0 -0
- package/stat/assets/images/slider_ia.png +0 -0
- package/stat/assets/images/thumbnail_mode_icon.png +0 -0
- package/stat/assets/images/transparent.png +0 -0
- package/stat/assets/images/two_page_mode_icon.png +0 -0
- package/stat/assets/images/zoom_in_icon.png +0 -0
- package/stat/assets/images/zoom_out_icon.png +0 -0
- package/stat/css/BookReader.scss +89 -0
- package/stat/css/_BRBookmarks.scss +29 -0
- package/stat/css/_BRComponent.scss +13 -0
- package/stat/css/_BRfloat.scss +197 -0
- package/stat/css/_BRicon.scss +48 -0
- package/stat/css/_BRmain.scss +251 -0
- package/stat/css/_BRnav.scss +359 -0
- package/stat/css/_BRpages.scss +139 -0
- package/stat/css/_BRsearch.scss +226 -0
- package/stat/css/_BRtoolbar.scss +84 -0
- package/stat/css/_BRvendor.scss +5 -0
- package/stat/css/_MobileNav.scss +194 -0
- package/stat/css/_TextSelection.scss +32 -0
- package/stat/css/_colorbox.scss +52 -0
- package/stat/css/_controls.scss +253 -0
- package/stat/css/_icons.scss +121 -0
- package/stat/jquery-wrapper.js +4 -0
- package/stat/plugins/plugin.archive_analytics.js +86 -0
- package/stat/plugins/plugin.autoplay.js +129 -0
- package/stat/plugins/plugin.chapters.js +248 -0
- package/stat/plugins/plugin.iframe.js +48 -0
- package/stat/plugins/plugin.mobile_nav.js +288 -0
- package/stat/plugins/plugin.resume.js +68 -0
- package/stat/plugins/plugin.text_selection.js +291 -0
- package/stat/plugins/plugin.url.js +198 -0
- package/stat/plugins/plugin.vendor-fullscreen.js +247 -0
- package/stat/plugins/search/plugin.search.js +439 -0
- package/stat/plugins/search/view.js +439 -0
- package/stat/plugins/tts/AbstractTTSEngine.js +249 -0
- package/stat/plugins/tts/FestivalTTSEngine.js +169 -0
- package/stat/plugins/tts/PageChunk.js +107 -0
- package/stat/plugins/tts/PageChunkIterator.js +163 -0
- package/stat/plugins/tts/WebTTSEngine.js +357 -0
- package/stat/plugins/tts/plugin.tts.js +357 -0
- package/stat/plugins/tts/tooltip_dict.js +15 -0
- package/stat/plugins/tts/utils.js +91 -0
- package/stat/util/browserSniffing.js +30 -0
- package/stat/util/debouncer.js +26 -0
- package/stat/util/docCookies.js +67 -0
- package/stat/util/strings.js +34 -0
- package/tests/e2e/viewmode.test.js +30 -30
- package/tests/jest/BookReader/BookReaderPublicFunctions.test.js +64 -52
- package/tests/karma/BookNavigator/book-navigator.test.js +413 -108
- package/tests/karma/BookNavigator/bookmarks/bookmark-button.test.js +44 -0
- package/tests/karma/BookNavigator/downloads/downloads-provider.test.js +6 -3
- package/tests/karma/BookNavigator/search/search-provider.test.js +106 -6
- package/tests/karma/BookNavigator/search/search-results.test.js +0 -2
- package/tests/karma/BookNavigator/sharing/sharing-provider.test.js +29 -20
- package/tests/karma/BookNavigator/volumes/volumes-provider.test.js +41 -17
- package/webpack.config.js +1 -1
- package/.nvmrc +0 -1
- package/BookReader/bookreader-component-bundle.js +0 -1436
- package/BookReader/bookreader-component-bundle.js.map +0 -1
- package/src/BookNavigator/assets/book-loader.js +0 -27
- package/src/ItemNavigator/ItemNavigator.js +0 -377
@@ -0,0 +1,169 @@
|
|
1
|
+
import AbstractTTSEngine from './AbstractTTSEngine.js';
|
2
|
+
import { sleep } from './utils.js';
|
3
|
+
/* global soundManager */
|
4
|
+
import 'soundmanager2';
|
5
|
+
import 'jquery.browser';
|
6
|
+
|
7
|
+
/** @typedef {import("./AbstractTTSEngine.js").TTSEngineOptions} TTSEngineOptions */
|
8
|
+
/** @typedef {import("./AbstractTTSEngine.js").AbstractTTSSound} AbstractTTSSound */
|
9
|
+
|
10
|
+
/**
|
11
|
+
* @extends AbstractTTSEngine
|
12
|
+
* TTS using Festival endpoint
|
13
|
+
**/
|
14
|
+
export default class FestivalTTSEngine extends AbstractTTSEngine {
|
15
|
+
/** @override */
|
16
|
+
static isSupported() {
|
17
|
+
return typeof(soundManager) !== 'undefined' && soundManager.supported();
|
18
|
+
}
|
19
|
+
|
20
|
+
/** @param {TTSEngineOptions} options */
|
21
|
+
constructor(options) {
|
22
|
+
super(options);
|
23
|
+
// $.browsers is sometimes undefined on some Android browsers :/
|
24
|
+
// Likely related to when $.browser was moved to npm
|
25
|
+
/** @type {'mp3' | 'ogg'} format of audio to get */
|
26
|
+
this.audioFormat = $.browser?.mozilla ? 'ogg' : 'mp3';
|
27
|
+
}
|
28
|
+
|
29
|
+
/** @override */
|
30
|
+
getVoices() {
|
31
|
+
return [
|
32
|
+
{ default: true, lang: "en-US", localService: false, name: "Festival - English (US)", voiceURI: null }
|
33
|
+
];
|
34
|
+
}
|
35
|
+
|
36
|
+
/** @override */
|
37
|
+
init() {
|
38
|
+
// setup sound manager
|
39
|
+
soundManager.setup({
|
40
|
+
debugMode: false,
|
41
|
+
// Note, there's a bug in Chrome regarding range requests.
|
42
|
+
// Flash is used as a workaround.
|
43
|
+
// See https://bugs.chromium.org/p/chromium/issues/detail?id=505707
|
44
|
+
preferFlash: true,
|
45
|
+
url: '/bookreader/BookReader/soundmanager/swf',
|
46
|
+
useHTML5Audio: true,
|
47
|
+
//flash 8 version of swf is buggy when calling play() on a sound that is still loading
|
48
|
+
flashVersion: 9
|
49
|
+
});
|
50
|
+
}
|
51
|
+
|
52
|
+
/**
|
53
|
+
* @override
|
54
|
+
* @param {number} leafIndex
|
55
|
+
* @param {number} numLeafs total number of leafs in the current book
|
56
|
+
*/
|
57
|
+
start(leafIndex, numLeafs) {
|
58
|
+
let promise = null;
|
59
|
+
|
60
|
+
// Hack for iOS
|
61
|
+
if (navigator.userAgent.match(/mobile/i)) {
|
62
|
+
promise = this.iOSCaptureUserIntentHack();
|
63
|
+
}
|
64
|
+
|
65
|
+
promise = promise || Promise.resolve();
|
66
|
+
promise.then(() => super.start(leafIndex, numLeafs));
|
67
|
+
}
|
68
|
+
|
69
|
+
/** @override */
|
70
|
+
createSound(chunk) {
|
71
|
+
return new FestivalTTSSound(this.getSoundUrl(chunk.text));
|
72
|
+
}
|
73
|
+
|
74
|
+
/**
|
75
|
+
* @private
|
76
|
+
* Get URL for audio that says this text
|
77
|
+
* @param {String} dataString the thing to say
|
78
|
+
* @return {String} url
|
79
|
+
*/
|
80
|
+
getSoundUrl(dataString) {
|
81
|
+
return 'https://' + this.opts.server + '/BookReader/BookReaderGetTTS.php?string='
|
82
|
+
+ encodeURIComponent(dataString)
|
83
|
+
+ '&format=.' + this.audioFormat;
|
84
|
+
}
|
85
|
+
|
86
|
+
/**
|
87
|
+
* @private
|
88
|
+
* Security restrictions require playback to be triggered
|
89
|
+
* by a user click/touch. This intention gets lost in the async calls
|
90
|
+
* on iOS, but, for some reason, if we start the audio here, it works.
|
91
|
+
* See https://stackoverflow.com/questions/12206631/html5-audio-cant-play-through-javascript-unless-triggered-manually-once
|
92
|
+
* @return {PromiseLike}
|
93
|
+
*/
|
94
|
+
iOSCaptureUserIntentHack() {
|
95
|
+
const sound = soundManager.createSound({ url: SILENCE_1MS[this.audioFormat] });
|
96
|
+
return new Promise(res => sound.play({onfinish: res}))
|
97
|
+
.then(() => sound.destruct());
|
98
|
+
}
|
99
|
+
}
|
100
|
+
|
101
|
+
/** @extends AbstractTTSSound */
|
102
|
+
class FestivalTTSSound {
|
103
|
+
/** @param {string} soundUrl **/
|
104
|
+
constructor(soundUrl) {
|
105
|
+
this.soundUrl = soundUrl;
|
106
|
+
/** @type {SMSound} */
|
107
|
+
this.sound = null;
|
108
|
+
this.rate = 1;
|
109
|
+
/** @type {function} calling this resolves the "play" promise */
|
110
|
+
this._finishResolver = null;
|
111
|
+
}
|
112
|
+
|
113
|
+
get loaded() {
|
114
|
+
return this.sound && this.sound.loaded;
|
115
|
+
}
|
116
|
+
|
117
|
+
load(onload) {
|
118
|
+
this.sound = soundManager.createSound({
|
119
|
+
url: this.soundUrl,
|
120
|
+
// API recommended, but only fires once play started on safari
|
121
|
+
onload: () => {
|
122
|
+
if (this.rate != 1) this.sound.setPlaybackRate(this.rate);
|
123
|
+
onload();
|
124
|
+
},
|
125
|
+
onresume: () => {
|
126
|
+
sleep(25).then(() => {
|
127
|
+
if (this.rate != 1) this.sound.setPlaybackRate(this.rate);
|
128
|
+
});
|
129
|
+
}
|
130
|
+
});
|
131
|
+
return this.sound.load();
|
132
|
+
}
|
133
|
+
|
134
|
+
play() {
|
135
|
+
return new Promise(res => {
|
136
|
+
this._finishResolver = res;
|
137
|
+
this.sound.play({ onfinish: res });
|
138
|
+
})
|
139
|
+
.then(() => this.sound.destruct());
|
140
|
+
}
|
141
|
+
|
142
|
+
/** @override */
|
143
|
+
stop() {
|
144
|
+
this.sound.stop();
|
145
|
+
return Promise.resolve();
|
146
|
+
}
|
147
|
+
|
148
|
+
/** @override */
|
149
|
+
pause() { this.sound.pause(); }
|
150
|
+
/** @override */
|
151
|
+
resume() { this.sound.resume(); }
|
152
|
+
/** @override */
|
153
|
+
setPlaybackRate(rate) {
|
154
|
+
this.rate = rate;
|
155
|
+
this.sound.setPlaybackRate(rate);
|
156
|
+
}
|
157
|
+
|
158
|
+
/** @override */
|
159
|
+
finish() {
|
160
|
+
this.sound.stop();
|
161
|
+
this._finishResolver();
|
162
|
+
}
|
163
|
+
}
|
164
|
+
|
165
|
+
/** Needed to capture the audio context for iOS hack. Generated using Audacity. */
|
166
|
+
const SILENCE_1MS = {
|
167
|
+
mp3: 'data:audio/mp3;base64,//uQxAAAAAAAAAAAAAAAAAAAAAAAWGluZwAAAA8AAAACAAACcQCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA//////////////////////////////////////////////////////////////////8AAAAeTEFNRTMuOTlyBJwAAAAAAAAAADUgJAaUQQABrgAAAnHIf8sZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//sQxAADwlwBKGAAACAAAD/AAAAEAAAAH///////////////+UBAMExBTUUzLjk5LjOqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqr/+xDEIAPAAAGkAAAAIAAANIAAAASqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqg==',
|
168
|
+
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=',
|
169
|
+
};
|
@@ -0,0 +1,107 @@
|
|
1
|
+
/**
|
2
|
+
* Class to manage a 'chunk' (approximately a paragraph) of text on a page.
|
3
|
+
*/
|
4
|
+
export default class PageChunk {
|
5
|
+
/**
|
6
|
+
* @param {number} leafIndex
|
7
|
+
* @param {number} chunkIndex
|
8
|
+
* @param {string} text
|
9
|
+
* @param {DJVURect[]} lineRects
|
10
|
+
*/
|
11
|
+
constructor(leafIndex, chunkIndex, text, lineRects) {
|
12
|
+
this.leafIndex = leafIndex;
|
13
|
+
this.chunkIndex = chunkIndex;
|
14
|
+
this.text = text;
|
15
|
+
this.lineRects = lineRects;
|
16
|
+
}
|
17
|
+
|
18
|
+
/**
|
19
|
+
* @param {string} server
|
20
|
+
* @param {string} bookPath
|
21
|
+
* @param {number} leafIndex
|
22
|
+
* @return {Promise<PageChunk[]>}
|
23
|
+
*/
|
24
|
+
static fetch(server, bookPath, leafIndex) {
|
25
|
+
// jquery's ajax "PromiseLike" implementation is inconsistent with
|
26
|
+
// modern Promises, so convert it to a full promise (it doesn't forward
|
27
|
+
// a returned promise to the next handler in the chain, which kind of
|
28
|
+
// defeats the entire point of using promises to avoid "callback hell")
|
29
|
+
return new Promise((res, rej) => {
|
30
|
+
$.ajax({
|
31
|
+
type: 'GET',
|
32
|
+
url: `https://${server}/BookReader/BookReaderGetTextWrapper.php`,
|
33
|
+
dataType:'jsonp',
|
34
|
+
cache: true,
|
35
|
+
data: {
|
36
|
+
path: `${bookPath}_djvu.xml`,
|
37
|
+
page: leafIndex
|
38
|
+
},
|
39
|
+
error: rej,
|
40
|
+
})
|
41
|
+
.then(chunks => {
|
42
|
+
res(PageChunk._fromTextWrapperResponse(leafIndex, chunks));
|
43
|
+
});
|
44
|
+
});
|
45
|
+
}
|
46
|
+
|
47
|
+
/**
|
48
|
+
* Convert the response from BookReaderGetTextWrapper.php into a {@link PageChunk} instance
|
49
|
+
* @param {number} leafIndex
|
50
|
+
* @param {Array<[String, ...DJVURect[]]>} chunksResponse
|
51
|
+
* @return {PageChunk[]}
|
52
|
+
*/
|
53
|
+
static _fromTextWrapperResponse(leafIndex, chunksResponse) {
|
54
|
+
return chunksResponse.map((c, i) => {
|
55
|
+
const correctedLineRects = PageChunk._fixChunkRects(c.slice(1));
|
56
|
+
const correctedText = PageChunk._removeDanglingHyphens(c[0]);
|
57
|
+
return new PageChunk(leafIndex, i, correctedText, correctedLineRects);
|
58
|
+
});
|
59
|
+
}
|
60
|
+
|
61
|
+
/**
|
62
|
+
* @private
|
63
|
+
* Sometimes the first rectangle will be ridiculously wide/tall. Find those and fix them
|
64
|
+
* *NOTE*: Modifies the original array and returns it.
|
65
|
+
* *NOTE*: This should probably be fixed on the petabox side, and then removed here
|
66
|
+
* Has 2 problems:
|
67
|
+
* - If the rect is the last rect on the page (and hence the only rect in the array),
|
68
|
+
* the rect's size isn't fixed
|
69
|
+
* - Because this relies on the second rect, there's a chance it won't be the right
|
70
|
+
* width
|
71
|
+
* @param {DJVURect[]} rects
|
72
|
+
* @return {DJVURect[]}
|
73
|
+
*/
|
74
|
+
static _fixChunkRects(rects) {
|
75
|
+
if (rects.length < 2) return rects;
|
76
|
+
|
77
|
+
const [firstRect, secondRect] = rects;
|
78
|
+
const [left, bottom, right] = firstRect;
|
79
|
+
const width = right - left;
|
80
|
+
const secondHeight = secondRect[1] - secondRect[3];
|
81
|
+
const secondWidth = secondRect[2] - secondRect[0];
|
82
|
+
const secondRight = secondRect[2];
|
83
|
+
|
84
|
+
if (width > secondWidth * 30) {
|
85
|
+
// Set the end to be the same
|
86
|
+
firstRect[2] = secondRight;
|
87
|
+
// And the top to be the same height
|
88
|
+
firstRect[3] = bottom - secondHeight;
|
89
|
+
}
|
90
|
+
|
91
|
+
return rects;
|
92
|
+
}
|
93
|
+
|
94
|
+
/**
|
95
|
+
* Remove "dangling" hyphens from read aloud text to avoid TTS stuttering
|
96
|
+
* @param {string} text
|
97
|
+
* @return {string}
|
98
|
+
*/
|
99
|
+
static _removeDanglingHyphens(text) {
|
100
|
+
return text.replace(/-\s+/g, '');
|
101
|
+
}
|
102
|
+
}
|
103
|
+
|
104
|
+
/**
|
105
|
+
* @typedef {[number, number, number, number]} DJVURect
|
106
|
+
* coords are in l,b,r,t order
|
107
|
+
*/
|
@@ -0,0 +1,163 @@
|
|
1
|
+
import PageChunk from './PageChunk.js';
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Class that iterates over the page chunks of a book; caching/buffering
|
5
|
+
* as much as possible to try to ensure a smooth experience.
|
6
|
+
*/
|
7
|
+
export default class PageChunkIterator {
|
8
|
+
/**
|
9
|
+
* @param {number} pageCount total number of pages
|
10
|
+
* @param {number} start page to start on
|
11
|
+
* @param {PageChunkIteratorOptions} opts
|
12
|
+
*/
|
13
|
+
constructor(pageCount, start, opts) {
|
14
|
+
this.pageCount = pageCount;
|
15
|
+
this.opts = Object.assign({}, DEFAULT_OPTS, opts);
|
16
|
+
/** Position in the chunk sequence */
|
17
|
+
this._cursor = { page: start, chunk: 0 };
|
18
|
+
/** @type {Object<number, PageChunk[]>} leaf index -> chunks*/
|
19
|
+
this._bufferedPages = {};
|
20
|
+
/** @type {Object<number, PromiseLike<PageChunk[]>} leaf index -> chunks*/
|
21
|
+
this._bufferingPages = {};
|
22
|
+
/**
|
23
|
+
* @type {Promise} promise that manages cursor modifications so that they
|
24
|
+
* happen in order triggered as opposed to order the server responds
|
25
|
+
**/
|
26
|
+
this._cursorLock = Promise.resolve();
|
27
|
+
}
|
28
|
+
|
29
|
+
/**
|
30
|
+
* Get the next chunk
|
31
|
+
* @return {PromiseLike<"__PageChunkIterator.AT_END__" | PageChunk>}
|
32
|
+
*/
|
33
|
+
next() {
|
34
|
+
return this._cursorLock = this._cursorLock
|
35
|
+
.then(() => this._nextUncontrolled());
|
36
|
+
}
|
37
|
+
|
38
|
+
/**
|
39
|
+
* Sends the cursor back 1
|
40
|
+
* @return {Promise}
|
41
|
+
**/
|
42
|
+
decrement() {
|
43
|
+
return this._cursorLock = this._cursorLock
|
44
|
+
.then(() => this._decrementUncontrolled());
|
45
|
+
}
|
46
|
+
|
47
|
+
/**
|
48
|
+
* Gets without ensuring synchronization. Since this iterator has a lot of async
|
49
|
+
* code, calling e.g. "next" twice (before the first call to next has finished)
|
50
|
+
* would cause the system to be in a weird state. To avoid that, we make sure calls
|
51
|
+
* to next and decrement (functions that modify the cursor) are synchronized,
|
52
|
+
* so that regardless how long it takes for one to respond, they'll always be executed
|
53
|
+
* in the correct order.
|
54
|
+
* @return {PromiseLike<"__PageChunkIterator.AT_END__" | PageChunk>}
|
55
|
+
*/
|
56
|
+
_nextUncontrolled() {
|
57
|
+
if (this._cursor.page == this.pageCount) {
|
58
|
+
return Promise.resolve(PageChunkIterator.AT_END);
|
59
|
+
}
|
60
|
+
|
61
|
+
this._recenterBuffer(this._cursor.page);
|
62
|
+
|
63
|
+
return this._fetchPageChunks(this._cursor.page)
|
64
|
+
.then(chunks => {
|
65
|
+
if (this._cursor.chunk == chunks.length) {
|
66
|
+
this._cursor.page++;
|
67
|
+
this._cursor.chunk = 0;
|
68
|
+
return this._nextUncontrolled();
|
69
|
+
}
|
70
|
+
return chunks[this._cursor.chunk++];
|
71
|
+
});
|
72
|
+
}
|
73
|
+
|
74
|
+
/**
|
75
|
+
* Decrements without ensuring synchronization. (See {@link PageChunkIterator._nextUncontrolled});
|
76
|
+
* @return {Promise}
|
77
|
+
*/
|
78
|
+
_decrementUncontrolled() {
|
79
|
+
let cursorChangePromise = Promise.resolve();
|
80
|
+
|
81
|
+
if (this._cursor.chunk > 0) {
|
82
|
+
this._cursor.chunk--;
|
83
|
+
} else if (this._cursor.page > 0) {
|
84
|
+
this._cursor.page--;
|
85
|
+
// Go back possibly multiple pages, because pages can be blank
|
86
|
+
cursorChangePromise = this._fetchPageChunks(this._cursor.page)
|
87
|
+
.then(prevPageChunks => {
|
88
|
+
if (prevPageChunks.length == 0) return this._decrementUncontrolled();
|
89
|
+
else this._cursor.chunk = prevPageChunks.length - 1;
|
90
|
+
});
|
91
|
+
}
|
92
|
+
|
93
|
+
return cursorChangePromise
|
94
|
+
.then(() => this._fetchPageChunks(this._cursor.page));
|
95
|
+
}
|
96
|
+
|
97
|
+
/**
|
98
|
+
* Recenter the buffer around the provided page index
|
99
|
+
* @param {number} index
|
100
|
+
*/
|
101
|
+
_recenterBuffer(index) {
|
102
|
+
const start = Math.max(0, index - this.opts.pageBufferSize);
|
103
|
+
const end = Math.min(this.pageCount, index + this.opts.pageBufferSize + 1);
|
104
|
+
for (let i = start; i < end; i++) {
|
105
|
+
this._fetchPageChunks(i);
|
106
|
+
}
|
107
|
+
|
108
|
+
this._removePageFromBuffer(start - 1);
|
109
|
+
this._removePageFromBuffer(end + 1);
|
110
|
+
}
|
111
|
+
|
112
|
+
/**
|
113
|
+
* @param {number} index
|
114
|
+
*/
|
115
|
+
_removePageFromBuffer(index) {
|
116
|
+
delete this._bufferingPages[index];
|
117
|
+
delete this._bufferedPages[index];
|
118
|
+
}
|
119
|
+
|
120
|
+
/**
|
121
|
+
* Fetches the chunks on a page; checks the buffer, so it won't make unnecessary
|
122
|
+
* requests if it's called multiple times for the same index.
|
123
|
+
* @param {number} index
|
124
|
+
* @return {Promise<PageChunk[]>}
|
125
|
+
*/
|
126
|
+
_fetchPageChunks(index) {
|
127
|
+
if (index in this._bufferingPages) return this._bufferingPages[index];
|
128
|
+
if (index in this._bufferedPages) return Promise.resolve(this._bufferedPages[index]);
|
129
|
+
|
130
|
+
this._bufferingPages[index] = this._fetchPageChunksDirect(index)
|
131
|
+
.then(chunks => {
|
132
|
+
delete this._bufferingPages[index];
|
133
|
+
this._bufferedPages[index] = chunks;
|
134
|
+
return chunks;
|
135
|
+
});
|
136
|
+
|
137
|
+
return this._bufferingPages[index];
|
138
|
+
}
|
139
|
+
|
140
|
+
/**
|
141
|
+
* Fetches a page without checking buffer
|
142
|
+
* @param {number} index
|
143
|
+
*/
|
144
|
+
_fetchPageChunksDirect(index) {
|
145
|
+
return PageChunk.fetch(this.opts.server, this.opts.bookPath, index);
|
146
|
+
}
|
147
|
+
}
|
148
|
+
|
149
|
+
PageChunkIterator.AT_END = "__PageChunkIterator.AT_END__";
|
150
|
+
|
151
|
+
/** @type {PageChunkIteratorOptions} */
|
152
|
+
const DEFAULT_OPTS = {
|
153
|
+
server: null,
|
154
|
+
bookPath: null,
|
155
|
+
pageBufferSize: 2,
|
156
|
+
};
|
157
|
+
|
158
|
+
/**
|
159
|
+
* @typedef {Object} PageChunkIteratorOptions
|
160
|
+
* @property {string} server
|
161
|
+
* @property {string} bookPath
|
162
|
+
* @property {number} [pageBufferSize] number of pages to buffer before/after the current page
|
163
|
+
*/
|