@internetarchive/bookreader 5.0.0-11 → 5.0.0-111

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 (381) hide show
  1. package/BookReader/474.js +2 -0
  2. package/BookReader/474.js.map +1 -0
  3. package/BookReader/BookReader.css +649 -1225
  4. package/BookReader/BookReader.js +1 -1
  5. package/BookReader/BookReader.js.LICENSE.txt +20 -20
  6. package/BookReader/BookReader.js.map +1 -1
  7. package/BookReader/bergamot-translator-worker.js +2966 -0
  8. package/BookReader/bergamot-translator-worker.wasm +0 -0
  9. package/BookReader/hypothesis/LICENSE +50 -0
  10. package/BookReader/hypothesis/README.md +55 -0
  11. package/BookReader/hypothesis/build/boot.js +1 -0
  12. package/BookReader/hypothesis/build/manifest.json +20 -0
  13. package/BookReader/hypothesis/build/scripts/annotator.bundle.js +184 -0
  14. package/BookReader/hypothesis/build/scripts/annotator.bundle.js.map +1 -0
  15. package/BookReader/hypothesis/build/scripts/sidebar.bundle.js +798 -0
  16. package/BookReader/hypothesis/build/scripts/sidebar.bundle.js.map +1 -0
  17. package/BookReader/hypothesis/build/scripts/ui-playground.bundle.js +711 -0
  18. package/BookReader/hypothesis/build/scripts/ui-playground.bundle.js.map +1 -0
  19. package/BookReader/hypothesis/build/styles/annotator.css +2235 -0
  20. package/BookReader/hypothesis/build/styles/annotator.css.map +1 -0
  21. package/BookReader/hypothesis/build/styles/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  22. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  23. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  24. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  25. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  26. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Main-Bold.woff2 +0 -0
  27. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  28. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Main-Italic.woff2 +0 -0
  29. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Main-Regular.woff2 +0 -0
  30. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  31. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Math-Italic.woff2 +0 -0
  32. package/BookReader/hypothesis/build/styles/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  33. package/BookReader/hypothesis/build/styles/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  34. package/BookReader/hypothesis/build/styles/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  35. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Script-Regular.woff2 +0 -0
  36. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  37. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  38. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  39. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  40. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  41. package/BookReader/hypothesis/build/styles/highlights.css +2 -0
  42. package/BookReader/hypothesis/build/styles/highlights.css.map +1 -0
  43. package/BookReader/hypothesis/build/styles/katex.min.css +2 -0
  44. package/BookReader/hypothesis/build/styles/katex.min.css.map +1 -0
  45. package/BookReader/hypothesis/build/styles/pdfjs-overrides.css +2 -0
  46. package/BookReader/hypothesis/build/styles/pdfjs-overrides.css.map +1 -0
  47. package/BookReader/hypothesis/build/styles/sidebar.css +2731 -0
  48. package/BookReader/hypothesis/build/styles/sidebar.css.map +1 -0
  49. package/BookReader/hypothesis/build/styles/ui-playground.css +2659 -0
  50. package/BookReader/hypothesis/build/styles/ui-playground.css.map +1 -0
  51. package/BookReader/hypothesis/package.json +126 -0
  52. package/BookReader/ia-bookreader-bundle.js +1904 -0
  53. package/BookReader/ia-bookreader-bundle.js.LICENSE.txt +19 -0
  54. package/BookReader/ia-bookreader-bundle.js.map +1 -0
  55. package/BookReader/icons/1up.svg +1 -1
  56. package/BookReader/icons/2up.svg +1 -1
  57. package/BookReader/icons/advance.svg +1 -1
  58. package/BookReader/icons/chevron-right.svg +1 -1
  59. package/BookReader/icons/close-circle-dark.svg +1 -1
  60. package/BookReader/icons/close-circle.svg +1 -1
  61. package/BookReader/icons/fullscreen.svg +1 -1
  62. package/BookReader/icons/fullscreen_exit.svg +1 -1
  63. package/BookReader/icons/hamburger.svg +1 -1
  64. package/BookReader/icons/left-arrow.svg +1 -1
  65. package/BookReader/icons/magnify-minus.svg +1 -1
  66. package/BookReader/icons/magnify-plus.svg +1 -1
  67. package/BookReader/icons/magnify.svg +1 -1
  68. package/BookReader/icons/pause.svg +1 -1
  69. package/BookReader/icons/play.svg +1 -1
  70. package/BookReader/icons/playback-speed.svg +1 -1
  71. package/BookReader/icons/read-aloud.svg +1 -1
  72. package/BookReader/icons/review.svg +1 -1
  73. package/BookReader/icons/slider-toggle.svg +1 -0
  74. package/BookReader/icons/thumbnails.svg +1 -1
  75. package/BookReader/icons/voice.svg +1 -0
  76. package/BookReader/icons/volume-full.svg +1 -1
  77. package/BookReader/images/BRicons.svg +3 -3
  78. package/BookReader/images/books_graphic.svg +1 -1
  79. package/BookReader/images/hypothesis.ico +0 -0
  80. package/BookReader/images/icon_book.svg +1 -1
  81. package/BookReader/images/icon_bookmark.svg +1 -1
  82. package/BookReader/images/icon_experiment.svg +1 -0
  83. package/BookReader/images/icon_gear.svg +1 -1
  84. package/BookReader/images/icon_hamburger.svg +1 -1
  85. package/BookReader/images/icon_home.svg +1 -1
  86. package/BookReader/images/icon_info.svg +1 -1
  87. package/BookReader/images/icon_one_page.svg +1 -1
  88. package/BookReader/images/icon_pause.svg +1 -1
  89. package/BookReader/images/icon_play.svg +1 -1
  90. package/BookReader/images/icon_playback-rate.svg +1 -1
  91. package/BookReader/images/icon_search_button.svg +1 -1
  92. package/BookReader/images/icon_share.svg +1 -1
  93. package/BookReader/images/icon_skip-ahead.svg +1 -1
  94. package/BookReader/images/icon_skip-back.svg +1 -1
  95. package/BookReader/images/icon_speaker.svg +1 -1
  96. package/BookReader/images/icon_speaker_open.svg +1 -1
  97. package/BookReader/images/icon_thumbnails.svg +1 -1
  98. package/BookReader/images/icon_toc.svg +1 -1
  99. package/BookReader/images/icon_two_pages.svg +1 -1
  100. package/BookReader/images/marker_chap-off.svg +1 -1
  101. package/BookReader/images/marker_chap-on.svg +1 -1
  102. package/BookReader/images/marker_srch-on.svg +1 -1
  103. package/BookReader/images/translate.svg +1 -0
  104. package/BookReader/images/unviewable_page.png +0 -0
  105. package/BookReader/jquery-3.js +2 -0
  106. package/BookReader/jquery-3.js.LICENSE.txt +24 -0
  107. package/BookReader/plugins/plugin.archive_analytics.js +1 -1
  108. package/BookReader/plugins/plugin.archive_analytics.js.map +1 -1
  109. package/BookReader/plugins/plugin.autoplay.js +1 -1
  110. package/BookReader/plugins/plugin.autoplay.js.map +1 -1
  111. package/BookReader/plugins/plugin.chapters.js +22 -1
  112. package/BookReader/plugins/plugin.chapters.js.LICENSE.txt +1 -0
  113. package/BookReader/plugins/plugin.chapters.js.map +1 -1
  114. package/BookReader/plugins/plugin.experiments.js +3 -0
  115. package/BookReader/plugins/plugin.experiments.js.LICENSE.txt +1 -0
  116. package/BookReader/plugins/plugin.experiments.js.map +1 -0
  117. package/BookReader/plugins/plugin.iframe.js +1 -1
  118. package/BookReader/plugins/plugin.iframe.js.map +1 -1
  119. package/BookReader/plugins/plugin.iiif.js +2 -0
  120. package/BookReader/plugins/plugin.iiif.js.map +1 -0
  121. package/BookReader/plugins/plugin.resume.js +1 -1
  122. package/BookReader/plugins/plugin.resume.js.map +1 -1
  123. package/BookReader/plugins/plugin.search.js +2 -1
  124. package/BookReader/plugins/plugin.search.js.LICENSE.txt +1 -0
  125. package/BookReader/plugins/plugin.search.js.map +1 -1
  126. package/BookReader/plugins/plugin.text_selection.js +24 -1
  127. package/BookReader/plugins/plugin.text_selection.js.LICENSE.txt +1 -0
  128. package/BookReader/plugins/plugin.text_selection.js.map +1 -1
  129. package/BookReader/plugins/plugin.translate.js +159 -0
  130. package/BookReader/plugins/plugin.translate.js.LICENSE.txt +1 -0
  131. package/BookReader/plugins/plugin.translate.js.map +1 -0
  132. package/BookReader/plugins/plugin.tts.js +1 -1
  133. package/BookReader/plugins/plugin.tts.js.LICENSE.txt +2 -0
  134. package/BookReader/plugins/plugin.tts.js.map +1 -1
  135. package/BookReader/plugins/plugin.url.js +2 -1
  136. package/BookReader/plugins/plugin.url.js.LICENSE.txt +1 -0
  137. package/BookReader/plugins/plugin.url.js.map +1 -1
  138. package/BookReader/plugins/plugin.vendor-fullscreen.js +1 -1
  139. package/BookReader/plugins/plugin.vendor-fullscreen.js.map +1 -1
  140. package/BookReader/plugins/translator-worker.js +2 -0
  141. package/BookReader/plugins/translator-worker.js.map +1 -0
  142. package/BookReader/silence.mp3 +0 -0
  143. package/BookReader/translator-worker.js +475 -0
  144. package/BookReader/webcomponents-bundle.js +3 -0
  145. package/BookReader/webcomponents-bundle.js.LICENSE.txt +9 -0
  146. package/BookReader/webcomponents-bundle.js.map +1 -0
  147. package/README.md +14 -3
  148. package/jsconfig.json +19 -0
  149. package/package.json +93 -70
  150. package/src/BookReader/BookModel.js +92 -46
  151. package/src/BookReader/DragScrollable.js +233 -0
  152. package/src/BookReader/ImageCache.js +49 -16
  153. package/src/BookReader/Mode1Up.js +66 -365
  154. package/src/BookReader/Mode1UpLit.js +392 -0
  155. package/src/BookReader/Mode2Up.js +87 -1315
  156. package/src/BookReader/Mode2UpLit.js +786 -0
  157. package/src/BookReader/ModeAbstract.js +43 -0
  158. package/src/BookReader/ModeCoordinateSpace.js +29 -0
  159. package/src/BookReader/ModeSmoothZoom.js +312 -0
  160. package/src/BookReader/ModeThumb.js +29 -15
  161. package/src/BookReader/Navbar/Navbar.js +201 -76
  162. package/src/BookReader/PageContainer.js +120 -23
  163. package/src/BookReader/ReduceSet.js +2 -2
  164. package/src/BookReader/Toolbar/Toolbar.js +18 -40
  165. package/src/BookReader/events.js +3 -3
  166. package/src/BookReader/options.js +94 -17
  167. package/src/BookReader/utils/HTMLDimensionsCacher.js +44 -0
  168. package/src/BookReader/utils/ScrollClassAdder.js +31 -0
  169. package/src/BookReader/utils/SelectionObserver.js +53 -0
  170. package/src/BookReader/utils/classes.js +1 -1
  171. package/src/BookReader/utils.js +136 -12
  172. package/src/BookReader.js +695 -1228
  173. package/src/BookReaderPlugin.js +52 -0
  174. package/src/assets/icons/magnify-minus.svg +3 -7
  175. package/src/assets/icons/magnify-plus.svg +3 -7
  176. package/src/assets/icons/slider-toggle.svg +1 -0
  177. package/src/assets/icons/voice.svg +1 -0
  178. package/src/assets/images/hypothesis.ico +0 -0
  179. package/src/assets/images/icon_experiment.svg +1 -0
  180. package/src/assets/images/translate.svg +1 -0
  181. package/src/assets/images/unviewable_page.png +0 -0
  182. package/src/assets/silence.mp3 +0 -0
  183. package/src/css/BookReader.scss +1 -5
  184. package/src/css/_BRBookmarks.scss +1 -1
  185. package/src/css/_BRComponent.scss +1 -1
  186. package/src/css/_BRicon.scss +8 -2
  187. package/src/css/_BRmain.scss +16 -3
  188. package/src/css/_BRnav.scss +74 -70
  189. package/src/css/_BRpages.scss +171 -42
  190. package/src/css/_BRsearch.scss +69 -30
  191. package/src/css/_BRtoolbar.scss +5 -5
  192. package/src/css/_TextSelection.scss +188 -24
  193. package/src/css/_colorbox.scss +2 -2
  194. package/src/css/_controls.scss +24 -7
  195. package/src/css/_icons.scss +8 -1
  196. package/src/{BookNavigator/assets → css}/button-base.js +2 -2
  197. package/src/css/icon_checkmark.js +9 -0
  198. package/src/css/sharedStyles.js +15 -0
  199. package/src/ia-bookreader/downloads/downloads-provider.js +81 -0
  200. package/src/{BookNavigator → ia-bookreader}/downloads/downloads.js +25 -5
  201. package/src/ia-bookreader/ia-bookreader.js +666 -0
  202. package/src/ia-bookreader/sharing.js +27 -0
  203. package/src/ia-bookreader/viewable-files.js +98 -0
  204. package/src/{BookNavigator → ia-bookreader}/visual-adjustments/visual-adjustments-provider.js +17 -17
  205. package/src/{BookNavigator → ia-bookreader}/visual-adjustments/visual-adjustments.js +75 -67
  206. package/src/{BookNavigator → plugins}/bookmarks/bookmark-button.js +4 -3
  207. package/src/{BookNavigator/assets → plugins/bookmarks}/bookmark-colors.js +1 -1
  208. package/src/{BookNavigator → plugins}/bookmarks/bookmark-edit.js +43 -31
  209. package/src/{BookNavigator → plugins}/bookmarks/bookmarks-list.js +48 -49
  210. package/src/{BookNavigator → plugins}/bookmarks/bookmarks-loginCTA.js +3 -3
  211. package/src/plugins/bookmarks/bookmarks-provider.js +63 -0
  212. package/src/{BookNavigator → plugins/bookmarks}/delete-modal-actions.js +1 -1
  213. package/src/{BookNavigator → plugins}/bookmarks/ia-bookmarks.js +117 -68
  214. package/src/plugins/plugin.archive_analytics.js +84 -78
  215. package/src/plugins/plugin.autoplay.js +99 -104
  216. package/src/plugins/plugin.chapters.js +319 -205
  217. package/src/plugins/plugin.experiments.js +339 -0
  218. package/src/plugins/plugin.iframe.js +1 -1
  219. package/src/plugins/plugin.iiif.js +141 -0
  220. package/src/plugins/plugin.resume.js +54 -51
  221. package/src/plugins/plugin.text_selection.js +539 -219
  222. package/src/plugins/plugin.vendor-fullscreen.js +5 -5
  223. package/src/plugins/search/plugin.search.js +374 -392
  224. package/src/{BookNavigator → plugins}/search/search-provider.js +59 -27
  225. package/src/{BookNavigator → plugins}/search/search-results.js +105 -76
  226. package/src/plugins/search/utils.js +50 -0
  227. package/src/plugins/search/view.js +50 -68
  228. package/src/plugins/translate/TranslationManager.js +164 -0
  229. package/src/plugins/translate/plugin.translate.js +512 -0
  230. package/src/plugins/tts/AbstractTTSEngine.js +78 -49
  231. package/src/plugins/tts/FestivalTTSEngine.js +20 -30
  232. package/src/plugins/tts/PageChunk.js +33 -21
  233. package/src/plugins/tts/PageChunkIterator.js +11 -17
  234. package/src/plugins/tts/WebTTSEngine.js +131 -91
  235. package/src/plugins/tts/plugin.tts.js +345 -350
  236. package/src/plugins/tts/utils.js +77 -49
  237. package/src/plugins/url/UrlPlugin.js +211 -0
  238. package/src/plugins/{plugin.url.js → url/plugin.url.js} +105 -20
  239. package/src/util/TextSelectionManager.js +532 -0
  240. package/src/util/browserSniffing.js +33 -1
  241. package/src/util/cache.js +20 -0
  242. package/src/util/docCookies.js +21 -2
  243. package/src/util/lit.js +15 -0
  244. package/src/util/strings.js +1 -0
  245. package/.babelrc +0 -12
  246. package/.dependabot/config.yml +0 -6
  247. package/.eslintrc.js +0 -50
  248. package/.gitattributes +0 -2
  249. package/.github/ISSUE_TEMPLATE/bug.md +0 -32
  250. package/.github/ISSUE_TEMPLATE/feature-request.md +0 -30
  251. package/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md +0 -15
  252. package/.github/workflows/node.js.yml +0 -37
  253. package/.github/workflows/npm-publish.yml +0 -47
  254. package/.testcaferc.json +0 -5
  255. package/BookReader/bookreader-component-bundle.js +0 -1450
  256. package/BookReader/bookreader-component-bundle.js.LICENSE.txt +0 -38
  257. package/BookReader/bookreader-component-bundle.js.map +0 -1
  258. package/BookReader/jquery-1.10.1.js +0 -2
  259. package/BookReader/jquery-1.10.1.js.LICENSE.txt +0 -24
  260. package/BookReader/plugins/plugin.menu_toggle.js +0 -2
  261. package/BookReader/plugins/plugin.menu_toggle.js.map +0 -1
  262. package/BookReader/plugins/plugin.mobile_nav.js +0 -2
  263. package/BookReader/plugins/plugin.mobile_nav.js.map +0 -1
  264. package/BookReaderDemo/BookReaderDemo.css +0 -41
  265. package/BookReaderDemo/BookReaderJSAdvanced.js +0 -115
  266. package/BookReaderDemo/BookReaderJSAutoplay.js +0 -56
  267. package/BookReaderDemo/BookReaderJSSimple.js +0 -55
  268. package/BookReaderDemo/IIIFBookReader.js +0 -207
  269. package/BookReaderDemo/assets/v5/Bookreader-logo-cool-grad.svg +0 -1
  270. package/BookReaderDemo/assets/v5/Bookreader-logo-flat.svg +0 -1
  271. package/BookReaderDemo/assets/v5/Bookreader-logo-hex-cool-grad.png +0 -0
  272. package/BookReaderDemo/assets/v5/Bookreader-logo-hex-flat.png +0 -0
  273. package/BookReaderDemo/assets/v5/Bookreader-logo-lines.png +0 -0
  274. package/BookReaderDemo/assets/v5/Bookreader-logo-lines.svg +0 -1
  275. package/BookReaderDemo/assets/v5/Bookreader-logo-warm.svg +0 -1
  276. package/BookReaderDemo/assets/v5/bookreader-logo-renders@1x.png +0 -0
  277. package/BookReaderDemo/assets/v5/bookreader-logo-renders@2x.png +0 -0
  278. package/BookReaderDemo/assets/v5/bookreader-v5-screenshot.png +0 -0
  279. package/BookReaderDemo/bookreader-template-bundle.js +0 -7178
  280. package/BookReaderDemo/demo-advanced.html +0 -33
  281. package/BookReaderDemo/demo-autoplay.html +0 -38
  282. package/BookReaderDemo/demo-embed-iframe-src.html +0 -84
  283. package/BookReaderDemo/demo-embed.html +0 -26
  284. package/BookReaderDemo/demo-fullscreen-mobile.html +0 -36
  285. package/BookReaderDemo/demo-fullscreen.html +0 -33
  286. package/BookReaderDemo/demo-iiif.html +0 -34
  287. package/BookReaderDemo/demo-iiif.js +0 -26
  288. package/BookReaderDemo/demo-internetarchive.html +0 -74
  289. package/BookReaderDemo/demo-multiple.html +0 -43
  290. package/BookReaderDemo/demo-plugin-menu-toggle.html +0 -34
  291. package/BookReaderDemo/demo-preview-pages.html +0 -1092
  292. package/BookReaderDemo/demo-simple.html +0 -34
  293. package/BookReaderDemo/demo-vendor-fullscreen.html +0 -36
  294. package/BookReaderDemo/immersion-1up.html +0 -64
  295. package/BookReaderDemo/immersion-mode.html +0 -35
  296. package/BookReaderDemo/toggle_controls.html +0 -53
  297. package/BookReaderDemo/view_mode.html +0 -39
  298. package/BookReaderDemo/viewmode-cycle.html +0 -41
  299. package/CHANGELOG.md +0 -495
  300. package/CONTRIBUTING.md +0 -7
  301. package/codecov.yml +0 -17
  302. package/index.html +0 -31
  303. package/karma.conf.js +0 -23
  304. package/screenshot.png +0 -0
  305. package/scripts/postversion.js +0 -10
  306. package/scripts/preversion.js +0 -14
  307. package/scripts/version.js +0 -26
  308. package/src/BookNavigator/BookModel.js +0 -14
  309. package/src/BookNavigator/BookNavigator.js +0 -446
  310. package/src/BookNavigator/assets/book-loader.js +0 -27
  311. package/src/BookNavigator/assets/icon_checkmark.js +0 -6
  312. package/src/BookNavigator/assets/icon_close.js +0 -3
  313. package/src/BookNavigator/bookmarks/bookmarks-provider.js +0 -53
  314. package/src/BookNavigator/br-fullscreen-mgr.js +0 -83
  315. package/src/BookNavigator/downloads/downloads-provider.js +0 -66
  316. package/src/BookNavigator/search/a-search-result.js +0 -55
  317. package/src/BookReader/DebugConsole.js +0 -54
  318. package/src/BookReaderComponent/BookReaderComponent.js +0 -112
  319. package/src/ItemNavigator/ItemNavigator.js +0 -376
  320. package/src/ItemNavigator/providers/sharing.js +0 -29
  321. package/src/css/_MobileNav.scss +0 -194
  322. package/src/dragscrollable-br.js +0 -261
  323. package/src/plugins/menu_toggle/plugin.menu_toggle.js +0 -324
  324. package/src/plugins/plugin.mobile_nav.js +0 -287
  325. package/tests/BookReader/BookModel.test.js +0 -312
  326. package/tests/BookReader/BookReaderPublicFunctions.test.js +0 -171
  327. package/tests/BookReader/DebugConsole.test.js +0 -25
  328. package/tests/BookReader/ImageCache.test.js +0 -150
  329. package/tests/BookReader/Mode1Up.test.js +0 -164
  330. package/tests/BookReader/Mode2Up.test.js +0 -247
  331. package/tests/BookReader/Navbar/Navbar.test.js +0 -169
  332. package/tests/BookReader/PageContainer.test.js +0 -115
  333. package/tests/BookReader/ReduceSet.test.js +0 -38
  334. package/tests/BookReader/Toolbar/Toolbar.test.js +0 -26
  335. package/tests/BookReader/utils/classes.test.js +0 -88
  336. package/tests/BookReader/utils.test.js +0 -109
  337. package/tests/BookReader.options.test.js +0 -39
  338. package/tests/BookReader.test.js +0 -301
  339. package/tests/e2e/README.md +0 -75
  340. package/tests/e2e/autoplay.test.js +0 -13
  341. package/tests/e2e/base.test.js +0 -35
  342. package/tests/e2e/helpers/base.js +0 -263
  343. package/tests/e2e/helpers/debug.js +0 -13
  344. package/tests/e2e/helpers/desktopSearch.js +0 -72
  345. package/tests/e2e/helpers/mobileSearch.js +0 -85
  346. package/tests/e2e/helpers/mockSearch.js +0 -93
  347. package/tests/e2e/helpers/rightToLeft.js +0 -29
  348. package/tests/e2e/ia-production/ia-prod-base.js +0 -17
  349. package/tests/e2e/models/BookReader.js +0 -11
  350. package/tests/e2e/models/Navigation.js +0 -56
  351. package/tests/e2e/rightToLeft.test.js +0 -20
  352. package/tests/e2e/viewmode.test.js +0 -37
  353. package/tests/karma/BookNavigator/book-navigator.test.js +0 -132
  354. package/tests/karma/BookNavigator/bookmarks/bookmark-edit.test.js +0 -133
  355. package/tests/karma/BookNavigator/bookmarks/bookmarks-list.test.js +0 -222
  356. package/tests/karma/BookNavigator/search/search-provider.test.js +0 -23
  357. package/tests/karma/BookNavigator/search/search-results.test.js +0 -240
  358. package/tests/karma/BookNavigator/visual-adjustments.test.js +0 -201
  359. package/tests/plugins/menu_toggle/plugin.menu_toggle.test.js +0 -68
  360. package/tests/plugins/plugin.archive_analytics.test.js +0 -23
  361. package/tests/plugins/plugin.autoplay.test.js +0 -52
  362. package/tests/plugins/plugin.chapters.test.js +0 -130
  363. package/tests/plugins/plugin.iframe.test.js +0 -42
  364. package/tests/plugins/plugin.mobile_nav.test.js +0 -66
  365. package/tests/plugins/plugin.resume.test.js +0 -98
  366. package/tests/plugins/plugin.text_selection.test.js +0 -203
  367. package/tests/plugins/plugin.url.test.js +0 -129
  368. package/tests/plugins/plugin.vendor-fullscreen.test.js +0 -65
  369. package/tests/plugins/search/plugin.search.test.js +0 -173
  370. package/tests/plugins/search/plugin.search.view.test.js +0 -106
  371. package/tests/plugins/tts/AbstractTTSEngine.test.js +0 -153
  372. package/tests/plugins/tts/FestivalTTSEngine.test.js +0 -59
  373. package/tests/plugins/tts/PageChunk.test.js +0 -57
  374. package/tests/plugins/tts/PageChunkIterator.test.js +0 -179
  375. package/tests/plugins/tts/WebTTSEngine.test.js +0 -126
  376. package/tests/plugins/tts/utils.test.js +0 -133
  377. package/tests/util/browserSniffing.test.js +0 -56
  378. package/tests/util/docCookies.test.js +0 -15
  379. package/tests/util/strings.test.js +0 -63
  380. package/tests/utils.js +0 -42
  381. package/webpack.config.js +0 -86
@@ -1,79 +1,99 @@
1
1
  //@ts-check
2
- import { isFirefox, isSafari } from '../util/browserSniffing.js';
2
+ import { createDIVPageLayer } from '../BookReader/PageContainer.js';
3
+ import { BookReaderPlugin } from '../BookReaderPlugin.js';
3
4
  import { applyVariables } from '../util/strings.js';
5
+ import { Cache } from '../util/cache.js';
6
+ import { toISO6391 } from './tts/utils.js';
7
+ import { TextSelectionManager } from '../util/TextSelectionManager.js';
4
8
  /** @typedef {import('../util/strings.js').StringWithVars} StringWithVars */
9
+ /** @typedef {import('../BookReader/PageContainer.js').PageContainer} PageContainer */
5
10
 
6
11
  const BookReader = /** @type {typeof import('../BookReader').default} */(window.BookReader);
7
12
 
8
- export const DEFAULT_OPTIONS = {
9
- enabled: true,
10
- /** @type {StringWithVars} The URL to fetch the entire DJVU xml. Supports options.vars */
11
- fullDjvuXmlUrl: null,
12
- /** @type {StringWithVars} The URL to fetch a single page of the DJVU xml. Supports options.vars. Also has {{pageIndex}} */
13
- singlePageDjvuXmlUrl: null,
14
- };
15
- /** @typedef {typeof DEFAULT_OPTIONS} TextSelectionPluginOptions */
16
13
 
17
- /**
18
- * @template T
19
- */
20
- export class Cache {
21
- constructor(maxSize = 10) {
22
- this.maxSize = maxSize;
23
- /** @type {T[]} */
24
- this.entries = [];
14
+ export class TextSelectionPlugin extends BookReaderPlugin {
15
+ options = {
16
+ enabled: true,
17
+ /** @type {StringWithVars} The URL to fetch the entire DJVU xml. Supports options.vars */
18
+ fullDjvuXmlUrl: null,
19
+ /** @type {StringWithVars} The URL to fetch a single page of the DJVU xml. Supports options.vars. Also has {{pageIndex}} */
20
+ singlePageDjvuXmlUrl: null,
21
+ /** Whether to fetch the XML as a jsonp */
22
+ jsonp: false,
23
+ /** Mox words that can be selected when the text layer is protected */
24
+ maxProtectedWords: 200,
25
25
  }
26
26
 
27
+ /**@type {PromiseLike<JQuery<HTMLElement>|undefined>} */
28
+ djvuPagesPromise = null;
29
+
30
+ /** @type {Cache<{index: number, response: any}>} */
31
+ pageTextCache = new Cache();
32
+
27
33
  /**
28
- * @param {T} entry
34
+ * Sometimes there are too many words on a page, and the browser becomes near
35
+ * unusable. For now don't render text layer for pages with too many words.
29
36
  */
30
- add(entry) {
31
- if (this.entries.length >= this.maxSize) {
32
- this.entries.shift();
33
- }
34
- this.entries.push(entry);
37
+ maxWordRendered = 2500;
38
+
39
+ /**
40
+ * @param {import('../BookReader.js').default} br
41
+ */
42
+ constructor(br) {
43
+ super(br);
44
+ // In the future this should be in the ocr file
45
+ // since a book being right to left doesn't mean the ocr is right to left. But for
46
+ // now we do make that assumption.
47
+ /** Whether the book is right-to-left */
48
+ this.rtl = this.br.pageProgression === 'rl';
49
+ this.textSelectionManager = new TextSelectionManager('.BRtextLayer', this.br, {selectionElement: ['.BRwordElement', '.BRspace']}, this.options.maxProtectedWords);
35
50
  }
36
- }
37
51
 
38
- export class TextSelectionPlugin {
39
-
40
- constructor(options = DEFAULT_OPTIONS, optionVariables, avoidTspans = isFirefox(), pointerEventsOnParagraph = isSafari()) {
41
- this.options = options;
42
- this.optionVariables = optionVariables;
43
- /**@type {PromiseLike<JQuery<HTMLElement>|undefined>} */
44
- this.djvuPagesPromise = null;
45
- // Using text elements instead of tspans for words because Firefox does not allow svg tspan stretch.
46
- // Tspans are necessary on Chrome because they prevent newline character after every word when copying
47
- this.svgParagraphElement = "text";
48
- this.svgWordElement = "tspan";
49
- this.insertNewlines = avoidTspans;
50
- // Safari has a bug where `pointer-events` doesn't work on `<tspans>`. So
51
- // there we will set `pointer-events: all` on the paragraph element. We don't
52
- // do this everywhere, because it's a worse experience. Thanks Safari :/
53
- this.pointerEventsOnParagraph = pointerEventsOnParagraph;
54
- if (avoidTspans) {
55
- this.svgParagraphElement = "g";
56
- this.svgWordElement = "text";
57
- }
52
+ /** @override */
53
+ init() {
54
+ if (!this.options.enabled) return;
55
+
56
+ this.br.on('pageVisible', (_, {pageContainerEl}) => {
57
+ if (pageContainerEl.querySelector('.BRtextLayer')) {
58
+ this.br.trigger('textLayerVisible', {pageContainerEl});
59
+ }
60
+ });
58
61
 
59
- /** @type {Cache<{index: number, response: any}>} */
60
- this.pageTextCache = new Cache();
62
+ this.loadData();
63
+ this.textSelectionManager.init();
64
+ }
61
65
 
62
- /**
63
- * Sometimes there are too many words on a page, and the browser becomes near
64
- * unusable. For now don't render text layer for pages with too many words.
65
- */
66
- this.maxWordRendered = 2500;
66
+ enableSelectionMenu() {
67
+ this.textSelectionManager.selectionMenuEnabled = true;
68
+ this.textSelectionManager.renderSelectionMenu();
67
69
  }
68
70
 
69
- init() {
71
+ /**
72
+ * @override
73
+ * @param {PageContainer} pageContainer
74
+ * @returns {PageContainer}
75
+ */
76
+ _configurePageContainer(pageContainer) {
77
+ // Disable if thumb mode; it's too janky
78
+ // .page can be null for "pre-cover" region
79
+ if (this.br.mode !== this.br.constModeThumb && pageContainer.page) {
80
+ this.createTextLayer(pageContainer);
81
+ }
82
+ return pageContainer;
83
+ }
84
+
85
+ loadData() {
70
86
  // Only fetch the full djvu xml if the single page url isn't there
71
87
  if (this.options.singlePageDjvuXmlUrl) return;
72
88
  this.djvuPagesPromise = $.ajax({
73
89
  type: "GET",
74
- url: applyVariables(this.options.fullDjvuXmlUrl, this.optionVariables),
75
- dataType: "html",
76
- error: (e) => undefined
90
+ url: applyVariables(this.options.fullDjvuXmlUrl, this.br.options.vars),
91
+ dataType: this.options.jsonp ? "jsonp" : "html",
92
+ cache: true,
93
+ xhrFields: {
94
+ withCredentials: this.br.protected,
95
+ },
96
+ error: (e) => undefined,
77
97
  }).then((res) => {
78
98
  try {
79
99
  const xmlMap = $.parseXML(res);
@@ -94,21 +114,24 @@ export class TextSelectionPlugin {
94
114
  if (cachedEntry) {
95
115
  return cachedEntry.response;
96
116
  }
97
- return $.ajax({
117
+ const res = await $.ajax({
98
118
  type: "GET",
99
- url: applyVariables(this.options.singlePageDjvuXmlUrl, this.optionVariables, { pageIndex: index }),
100
- dataType: "html",
119
+ url: applyVariables(this.options.singlePageDjvuXmlUrl, this.br.options.vars, { pageIndex: index }),
120
+ dataType: this.options.jsonp ? "jsonp" : "html",
121
+ cache: true,
122
+ xhrFields: {
123
+ withCredentials: this.br.protected,
124
+ },
101
125
  error: (e) => undefined,
102
- }).then((res) => {
103
- try {
104
- const xmlDoc = $.parseXML(res);
105
- const result = xmlDoc && $(xmlDoc).find("OBJECT")[0];
106
- this.pageTextCache.add({ index, response: result });
107
- return result;
108
- } catch (e) {
109
- return undefined;
110
- }
111
126
  });
127
+ try {
128
+ const xmlDoc = $.parseXML(res);
129
+ const result = xmlDoc && $(xmlDoc).find("OBJECT")[0];
130
+ this.pageTextCache.add({ index, response: result });
131
+ return result;
132
+ } catch (e) {
133
+ return undefined;
134
+ }
112
135
  } else {
113
136
  const XMLpagesArr = await this.djvuPagesPromise;
114
137
  if (XMLpagesArr) return XMLpagesArr[index];
@@ -116,80 +139,25 @@ export class TextSelectionPlugin {
116
139
  }
117
140
 
118
141
  /**
119
- * Intercept copied text to remove any styling applied to it
120
- * @param {JQuery} $container
121
- */
122
- interceptCopy($container) {
123
- $container[0].addEventListener('copy', (event) => {
124
- const selection = document.getSelection();
125
- event.clipboardData.setData('text/plain', selection.toString());
126
- event.preventDefault();
127
- });
128
- }
129
-
130
- /**
131
- * Applies mouse events when in default mode
132
- * @param {SVGElement} svg
133
- */
134
- defaultMode(svg) {
135
- svg.classList.remove("selectingSVG");
136
- $(svg).on("mousedown.textSelectPluginHandler", (event) => {
137
- if (!$(event.target).is(".BRwordElement")) return;
138
- event.stopPropagation();
139
- svg.classList.add("selectingSVG");
140
- $(svg).one("mouseup.textSelectPluginHandler", (event) => {
141
- if (window.getSelection().toString() != "") {
142
- event.stopPropagation();
143
- $(svg).off(".textSelectPluginHandler");
144
- this.textSelectingMode(svg);
145
- }
146
- else svg.classList.remove("selectingSVG");
147
- });
148
- });
149
- }
150
-
151
- /**
152
- * Applies mouse events when in textSelecting mode
153
- * @param {SVGElement} svg
154
- */
155
- textSelectingMode(svg) {
156
- $(svg).on('mousedown.textSelectPluginHandler', (event) => {
157
- if (!$(event.target).is(".BRwordElement")) {
158
- if (window.getSelection().toString() != "") window.getSelection().removeAllRanges();
159
- }
160
- event.stopPropagation();
161
- });
162
- $(svg).on('mouseup.textSelectPluginHandler', (event) => {
163
- event.stopPropagation();
164
- if (window.getSelection().toString() == "") {
165
- $(svg).off(".textSelectPluginHandler");
166
- this.defaultMode(svg); }
167
- });
168
- }
169
-
170
- /**
171
- * Initializes text selection modes if there is an svg on the page
172
- * @param {JQuery} $container
142
+ * @param {PageContainer} pageContainer
173
143
  */
174
- stopPageFlip($container) {
175
- /** @type {JQuery<SVGElement>} */
176
- const $svg = $container.find('svg.textSelectionSVG');
177
- if (!$svg.length) return;
178
- $svg.each((i, s) => this.defaultMode(s));
179
- this.interceptCopy($container);
180
- }
181
-
182
- /**
183
- * @param {number} pageIndex
184
- * @param {JQuery} $container
185
- */
186
- async createTextLayer(pageIndex, $container) {
187
- const $svgLayers = $container.find('.textSelectionSVG');
188
- if ($svgLayers.length) return;
144
+ async createTextLayer(pageContainer) {
145
+ const pageIndex = pageContainer.page.index;
146
+ const $container = pageContainer.$container;
147
+ const $textLayers = $container.find('.BRtextLayer');
148
+ if ($textLayers.length) return;
189
149
  const XMLpage = await this.getPageText(pageIndex);
190
150
  if (!XMLpage) return;
191
- const XMLwidth = $(XMLpage).attr("width");
192
- const XMLheight = $(XMLpage).attr("height");
151
+ // Seeing some 0 left and 0 top coordinates in OCR, remove it entirely to prevent odd rendering
152
+ // eg https://archive.org/details/illustratedbooko00robe/page/n11/mode/2up
153
+ $(XMLpage).find("WORD").filter((_, ele) => {
154
+ const [left, , , top] = ele.getAttribute('coords').split(",").map(parseFloat);
155
+ if (left == 0 && top == 0) {
156
+ console.error("Found invalid ocr word coordinates");
157
+ return true;
158
+ }
159
+ }).remove();
160
+ recursivelyAddCoords(XMLpage);
193
161
 
194
162
  const totalWords = $(XMLpage).find("WORD").length;
195
163
  if (totalWords > this.maxWordRendered) {
@@ -197,107 +165,459 @@ export class TextSelectionPlugin {
197
165
  return;
198
166
  }
199
167
 
200
- const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
201
- svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
202
- svg.setAttribute("viewBox", `0 0 ${XMLwidth} ${XMLheight}`);
203
- $container.append(svg);
204
- svg.setAttribute('class', 'textSelectionSVG');
205
- svg.setAttribute('preserveAspectRatio', 'none');
206
- $(svg).css({
207
- "width": "100%",
208
- "position": "absolute",
209
- "height": "100%",
210
- "top": "0",
211
- "left": "0",
168
+ const textLayer = createDIVPageLayer(pageContainer.page, 'BRtextLayer');
169
+ // Have to wait to make sure the page container is actually rendered,
170
+ // otherwise width/height are unset after a mode change.
171
+ await Promise.resolve();
172
+ const ratioW = parseFloat(pageContainer.$container[0].style.width) / pageContainer.page.width;
173
+ const ratioH = parseFloat(pageContainer.$container[0].style.height) / pageContainer.page.height;
174
+ textLayer.style.transform = `scale(${ratioW}, ${ratioH})`;
175
+ const bookLangCode = toISO6391(this.br.options.bookLanguage);
176
+ if (bookLangCode) {
177
+ textLayer.setAttribute("lang", bookLangCode);
178
+ }
179
+ textLayer.setAttribute("dir", this.rtl ? "rtl" : "ltr");
180
+
181
+ const ocrParagraphs = $(XMLpage).find("PARAGRAPH[coords]").toArray();
182
+ const paragEls = ocrParagraphs.map(p => {
183
+ const el = this.renderParagraph(p);
184
+ textLayer.appendChild(el);
185
+ return el;
212
186
  });
213
187
 
214
- $(XMLpage).find("PARAGRAPH").each((i, paragraph) => {
215
- // Adding text element for each paragraph in the page
216
- const words = $(paragraph).find("WORD");
217
- if (!words.length) return;
218
- const paragSvg = document.createElementNS("http://www.w3.org/2000/svg", this.svgParagraphElement);
219
- paragSvg.setAttribute("class", "BRparagElement");
220
- if (this.pointerEventsOnParagraph) {
221
- paragSvg.style.pointerEvents = "all";
222
- }
188
+ // Fix up paragraph positions
189
+ const paragraphRects = determineRealRects(textLayer, '.BRparagraphElement');
190
+ let yAdded = 0;
191
+ for (const [ocrParagraph, paragEl] of zip(ocrParagraphs, paragEls)) {
192
+ const ocrParagBounds = $(ocrParagraph).attr("coords").split(",").map(parseFloat);
193
+ const realRect = paragraphRects.get(paragEl);
194
+ const [ocrLeft, , ocrRight, ocrTop] = ocrParagBounds;
195
+ const newStartMargin = this.rtl ? (realRect.right - ocrRight) : (ocrLeft - realRect.left);
196
+ const newTop = ocrTop - (realRect.top + yAdded);
197
+
198
+ paragEl.style[this.rtl ? 'marginRight' : 'marginLeft'] = `${newStartMargin}px`;
199
+ paragEl.style.marginTop = `${newTop}px`;
200
+ yAdded += newTop;
201
+ textLayer.appendChild(paragEl);
202
+ textLayer.appendChild(document.createTextNode('\n'));
203
+ }
204
+ $container.append(textLayer);
205
+ this.textSelectionManager.stopPageFlip($container);
206
+ this.br.trigger('textLayerRendered', {
207
+ pageIndex,
208
+ pageContainer,
209
+ });
210
+
211
+ // Check if page is visible
212
+ if ($container.hasClass('BRpage-visible')) {
213
+ this.br.trigger('textLayerVisible', {pageContainerEl: $container[0]});
214
+ }
215
+ }
216
+
217
+ /**
218
+ * @param {HTMLElement} ocrParagraph
219
+ * @returns {HTMLParagraphElement}
220
+ */
221
+ renderParagraph(ocrParagraph) {
222
+ const paragEl = document.createElement('p');
223
+ paragEl.classList.add('BRparagraphElement');
224
+ if (ocrParagraph.getAttribute("x-role")) {
225
+ paragEl.classList.add('ocr-role-header-footer');
226
+ paragEl.ariaHidden = "true";
227
+ }
228
+ const [paragLeft, paragBottom, paragRight, paragTop] = $(ocrParagraph).attr("coords").split(",").map(parseFloat);
229
+ const wordHeightArr = [];
230
+ const lines = $(ocrParagraph).find("LINE[coords]").toArray();
231
+ if (!lines.length) return paragEl;
223
232
 
224
- const wordHeightArr = [];
225
233
 
226
- for (let i = 0; i < words.length; i++) {
227
- // Adding tspan for each word in paragraph
228
- const currWord = words[i];
229
- // eslint-disable-next-line no-unused-vars
230
- const [left, bottom, right, top] = $(currWord).attr("coords").split(',').map(parseFloat);
234
+ for (const [prevLine, line, nextLine] of lookAroundWindow(genMap(lines, augmentLine))) {
235
+ const isLastLineOfParagraph = line.ocrElement == lines[lines.length - 1];
236
+ const lineEl = document.createElement('span');
237
+ lineEl.classList.add('BRlineElement');
238
+
239
+ for (const [wordIndex, currWord] of line.words.entries()) {
240
+ const [, bottom, right, top] = $(currWord).attr("coords").split(',').map(parseFloat);
231
241
  const wordHeight = bottom - top;
232
242
  wordHeightArr.push(wordHeight);
233
243
 
234
- const wordTspan = document.createElementNS("http://www.w3.org/2000/svg", this.svgWordElement);
235
- wordTspan.setAttribute("class", "BRwordElement");
236
- wordTspan.setAttribute("x", left.toString());
237
- wordTspan.setAttribute("y", bottom.toString());
238
- wordTspan.setAttribute("textLength", (right - left).toString());
239
- wordTspan.setAttribute("lengthAdjust", "spacingAndGlyphs");
240
- wordTspan.textContent = currWord.textContent;
241
- paragSvg.appendChild(wordTspan);
242
-
243
- // Adding spaces after words except at the end of the paragraph
244
- // TODO: assumes left-to-right text
245
- if (i < words.length - 1) {
246
- const nextWord = words[i + 1];
247
- // eslint-disable-next-line no-unused-vars
248
- const [leftNext, bottomNext, rightNext, topNext] = $(nextWord).attr("coords").split(',').map(parseFloat);
249
- const spaceTspan = document.createElementNS("http://www.w3.org/2000/svg", this.svgWordElement);
250
- spaceTspan.setAttribute("class", "BRwordElement");
251
- spaceTspan.setAttribute("x", right.toString());
252
- spaceTspan.setAttribute("y", bottom.toString());
253
- if ((leftNext - right) > 0) spaceTspan.setAttribute("textLength", (leftNext - right).toString());
254
- spaceTspan.setAttribute("lengthAdjust", "spacingAndGlyphs");
255
- spaceTspan.textContent = " ";
256
- paragSvg.appendChild(spaceTspan);
244
+ if (wordIndex == 0 && prevLine?.lastWord.textContent.trim().endsWith('-')) {
245
+ // ideally prefer the next line to determine the left position,
246
+ // since the previous line could be the first line of the paragraph
247
+ // and hence have an incorrectly indented first word.
248
+ // E.g. https://archive.org/details/driitaleofdaring00bachuoft/page/360/mode/2up
249
+ const [newLeft, , , ] = $((nextLine || prevLine).firstWord).attr("coords").split(',').map(parseFloat);
250
+ $(currWord).attr("coords", `${newLeft},${bottom},${right},${top}`);
257
251
  }
258
252
 
259
- // Adds newline at the end of paragraph on Firefox
260
- if ((i == words.length - 1 && (this.insertNewlines))) {
261
- paragSvg.appendChild(document.createTextNode("\n"));
253
+ const wordEl = document.createElement('span');
254
+ wordEl.setAttribute("class", "BRwordElement");
255
+ wordEl.textContent = currWord.textContent.trim();
256
+
257
+ if (wordIndex > 0) {
258
+ const space = document.createElement('span');
259
+ space.classList.add('BRspace');
260
+ space.textContent = ' ';
261
+ // Hack to make screen readers (eg NVDA) read spaces correctly;
262
+ // otherwise they ignore elements with just whitespace.
263
+ space.setAttribute('aria-label', '\u00A0');
264
+ lineEl.append(space);
265
+
266
+ // Edge ignores empty elements (like BRspace), so add another
267
+ // space to ensure Edge's ReadAloud works correctly.
268
+ lineEl.appendChild(document.createTextNode(' '));
262
269
  }
270
+
271
+ lineEl.appendChild(wordEl);
263
272
  }
264
273
 
265
- wordHeightArr.sort();
266
- const paragWordHeight = wordHeightArr[Math.floor(wordHeightArr.length * 0.85)];
267
- paragSvg.setAttribute("font-size", paragWordHeight.toString());
268
- svg.appendChild(paragSvg);
269
- });
270
- this.stopPageFlip($container);
274
+ const hasHyphen = line.lastWord.textContent.trim().endsWith('-');
275
+ const lastWordEl = lineEl.children[lineEl.children.length - 1];
276
+ if (hasHyphen && !isLastLineOfParagraph) {
277
+ lastWordEl.textContent = lastWordEl.textContent.trim().slice(0, -1);
278
+ lastWordEl.classList.add('BRwordElement--hyphen');
279
+ }
280
+
281
+ paragEl.appendChild(lineEl);
282
+ if (!isLastLineOfParagraph && !hasHyphen) {
283
+ // Edge does not correctly have spaces between the lines.
284
+ paragEl.appendChild(document.createTextNode(' '));
285
+ }
286
+ }
287
+
288
+ wordHeightArr.sort((a, b) => a - b);
289
+ const paragWordHeight = wordHeightArr[Math.floor(wordHeightArr.length * 0.85)] + 4;
290
+ paragEl.style.left = `${paragLeft}px`;
291
+ paragEl.style.top = `${paragTop}px`;
292
+ paragEl.style.width = `${paragRight - paragLeft}px`;
293
+ paragEl.style.height = `${paragBottom - paragTop}px`;
294
+ paragEl.style.fontSize = `${paragWordHeight}px`;
295
+
296
+ // Fix up sizes - stretch/crush words as necessary using letter spacing
297
+ let wordRects = determineRealRects(paragEl, '.BRwordElement');
298
+ const ocrWords = $(ocrParagraph).find("WORD").toArray();
299
+ const wordEls = paragEl.querySelectorAll('.BRwordElement');
300
+ for (const [ocrWord, wordEl] of zip(ocrWords, wordEls)) {
301
+ const realRect = wordRects.get(wordEl);
302
+ const [left, , right ] = $(ocrWord).attr("coords").split(',').map(parseFloat);
303
+ let ocrWidth = right - left;
304
+ // Some books (eg theworksofplato01platiala) have a space _inside_ the <WORD>
305
+ // element. That makes it impossible to determine the correct positining
306
+ // of everything, but to avoid the BRspace's being width 0, which makes selection
307
+ // janky on Chrome Android, assume the space is the same width as one of the
308
+ // letters.
309
+ if (ocrWord.textContent.endsWith(' ')) {
310
+ ocrWidth = ocrWidth * (ocrWord.textContent.length - 1) / ocrWord.textContent.length;
311
+ }
312
+ const diff = ocrWidth - realRect.width;
313
+ wordEl.style.letterSpacing = `${diff / (ocrWord.textContent.length - 1)}px`;
314
+ }
315
+
316
+ // Stretch/crush lines as necessary using line spacing
317
+ // Recompute rects after letter spacing
318
+ wordRects = determineRealRects(paragEl, '.BRwordElement');
319
+ const spaceRects = determineRealRects(paragEl, '.BRspace');
320
+
321
+ const ocrLines = $(ocrParagraph).find("LINE[coords]").toArray();
322
+ const lineEls = Array.from(paragEl.querySelectorAll('.BRlineElement'));
323
+
324
+ let ySoFar = paragTop;
325
+ for (const [ocrLine, lineEl] of zip(ocrLines, lineEls)) {
326
+ // shift words using marginLeft to align with the correct x position
327
+ const words = $(ocrLine).find("WORD").toArray();
328
+ // const ocrLineLeft = Math.min(...words.map(w => parseFloat($(w).attr("coords").split(',')[0])));
329
+ let xSoFar = this.rtl ? paragRight : paragLeft;
330
+ for (const [ocrWord, wordEl] of zip(words, lineEl.querySelectorAll('.BRwordElement'))) {
331
+ // start of line, need to compute the offset relative to the OCR words
332
+ const wordRect = wordRects.get(wordEl);
333
+ const [ocrLeft, , ocrRight ] = $(ocrWord).attr("coords").split(',').map(parseFloat);
334
+ const diff = (this.rtl ? -(ocrRight - xSoFar) : ocrLeft - xSoFar);
335
+
336
+ if (wordEl.previousElementSibling) {
337
+ const space = wordEl.previousElementSibling;
338
+ space.style.letterSpacing = `${diff - spaceRects.get(space).width}px`;
339
+ } else {
340
+ wordEl.style[this.rtl ? 'paddingRight' : 'paddingLeft'] = `${diff}px`;
341
+ }
342
+ if (this.rtl) xSoFar -= diff + wordRect.width;
343
+ else xSoFar += diff + wordRect.width;
344
+ }
345
+ // And also fix y position
346
+ const ocrLineTop = Math.min(...words.map(w => parseFloat($(w).attr("coords").split(',')[3])));
347
+ const diff = ocrLineTop - ySoFar;
348
+ if (lineEl.previousElementSibling) {
349
+ lineEl.previousElementSibling.style.lineHeight = `${diff}px`;
350
+ ySoFar += diff;
351
+ }
352
+ }
353
+
354
+ // The last line will have a line height subtracting from the paragraph height
355
+ lineEls[lineEls.length - 1].style.lineHeight = `${paragBottom - ySoFar}px`;
356
+
357
+ // Edge does not include a newline for some reason when copying/pasting the <p> els
358
+ paragEl.appendChild(document.createElement('br'));
359
+ return paragEl;
271
360
  }
272
361
  }
273
362
 
274
- export class BookreaderWithTextSelection extends BookReader {
275
- init() {
276
- const options = Object.assign({}, DEFAULT_OPTIONS, this.options.plugins.textSelection);
277
- if (options.enabled) {
278
- this.textSelectionPlugin = new TextSelectionPlugin(options, this.options.vars);
279
- // Write this back; this way the plugin is the source of truth, and BR just
280
- // contains a reference to it.
281
- this.options.plugins.textSelection = options;
282
- this.textSelectionPlugin.init();
363
+ BookReader?.registerPlugin('textSelection', TextSelectionPlugin);
364
+
365
+
366
+ /**
367
+ * @param {HTMLElement} parentEl
368
+ * @param {string} selector
369
+ * @returns {Map<Element, Rect>}
370
+ */
371
+ function determineRealRects(parentEl, selector) {
372
+ const initals = {
373
+ position: parentEl.style.position,
374
+ visibility: parentEl.style.visibility,
375
+ top: parentEl.style.top,
376
+ left: parentEl.style.left,
377
+ transform: parentEl.style.transform,
378
+ };
379
+ parentEl.style.position = 'absolute';
380
+ parentEl.style.visibility = 'hidden';
381
+ parentEl.style.top = '0';
382
+ parentEl.style.left = '0';
383
+ parentEl.style.transform = 'none';
384
+ document.body.appendChild(parentEl);
385
+ const rects = new Map(
386
+ Array.from(parentEl.querySelectorAll(selector))
387
+ .map(wordEl => {
388
+ const origRect = wordEl.getBoundingClientRect();
389
+ return [wordEl, new Rect(
390
+ origRect.left + window.scrollX,
391
+ origRect.top + window.scrollY,
392
+ origRect.width,
393
+ origRect.height,
394
+ )];
395
+ }),
396
+ );
397
+ document.body.removeChild(parentEl);
398
+ Object.assign(parentEl.style, initals);
399
+ return rects;
400
+ }
401
+
402
+ /**
403
+ * @param {HTMLElement} line
404
+ */
405
+ function augmentLine(line) {
406
+ const words = $(line).find("WORD").toArray();
407
+ return {
408
+ ocrElement: line,
409
+ words,
410
+ firstWord: words[0],
411
+ lastWord: words[words.length - 1],
412
+ };
413
+ }
414
+
415
+ /**
416
+ * @template T
417
+ * Get the i-th element of an iterable
418
+ * @param {Iterable<T>} iterable
419
+ * @param {number} index
420
+ */
421
+ export function genAt(iterable, index) {
422
+ let i = 0;
423
+ for (const x of iterable) {
424
+ if (i == index) return x;
425
+ i++;
426
+ }
427
+ return undefined;
428
+ }
429
+
430
+ /**
431
+ * @template T
432
+ * Generator version of filter
433
+ * @param {Iterable<T>} iterable
434
+ * @param {function(T): boolean} fn
435
+ */
436
+ export function* genFilter(iterable, fn) {
437
+ for (const x of iterable) {
438
+ if (fn(x)) yield x;
439
+ }
440
+ }
441
+
442
+ /**
443
+ * @template TFrom, TTo
444
+ * Generator version of map
445
+ * @param {Iterable<TFrom>} gen
446
+ * @param {function(TFrom): TTo} fn
447
+ * @returns {Iterable<TTo>}
448
+ */
449
+ export function* genMap(gen, fn) {
450
+ for (const x of gen) yield fn(x);
451
+ }
452
+
453
+ /**
454
+ * @template T
455
+ * Generator that provides a sliding window of 3 elements,
456
+ * prev, current, and next.
457
+ * @param {Iterable<T>} gen
458
+ * @returns {Iterable<[T | undefined, T, T | undefined]>}
459
+ */
460
+ export function* lookAroundWindow(gen) {
461
+ let prev = undefined;
462
+ let cur = undefined;
463
+ let next = undefined;
464
+ for (const x of gen) {
465
+ if (typeof cur !== 'undefined') {
466
+ next = x;
467
+ yield [prev, cur, next];
468
+ }
469
+ prev = cur;
470
+ cur = x;
471
+ next = undefined;
472
+ }
473
+
474
+ if (typeof cur !== 'undefined') {
475
+ yield [prev, cur, next];
476
+ }
477
+ }
478
+
479
+ /**
480
+ * @template T1, T2
481
+ * Lazy zip implementation to avoid importing lodash
482
+ * Expects iterators to be of the same length
483
+ * @param {Iterable<T1>} gen1
484
+ * @param {Iterable<T2>} gen2
485
+ * @returns {Iterable<[T1, T2]>}
486
+ */
487
+ export function* zip(gen1, gen2) {
488
+ const it1 = gen1[Symbol.iterator]();
489
+ const it2 = gen2[Symbol.iterator]();
490
+ while (true) {
491
+ const r1 = it1.next();
492
+ const r2 = it2.next();
493
+ if (r1.done && r2.done) {
494
+ return;
495
+ }
496
+ if (r1.done || r2.done) {
497
+ throw new Error('zip: one of the iterators is done');
283
498
  }
284
- super.init();
499
+ yield [r1.value, r2.value];
285
500
  }
501
+ }
286
502
 
503
+ /**
504
+ * [left, bottom, right, top]
505
+ * @param {Array<[number, number, number, number]>} bounds
506
+ * @returns {[number, number, number, number]}
507
+ */
508
+ function determineBounds(bounds) {
509
+ let leftMost = Infinity;
510
+ let bottomMost = -Infinity;
511
+ let rightMost = -Infinity;
512
+ let topMost = Infinity;
513
+
514
+ for (const [left, bottom, right, top] of bounds) {
515
+ leftMost = Math.min(leftMost, left);
516
+ bottomMost = Math.max(bottomMost, bottom);
517
+ rightMost = Math.max(rightMost, right);
518
+ topMost = Math.min(topMost, top);
519
+ }
520
+
521
+ return [leftMost, bottomMost, rightMost, topMost];
522
+ }
523
+
524
+ /**
525
+ * Recursively traverses the XML tree and adds coords
526
+ * which are the bounding box of all child coords
527
+ * @param {Element} xmlEl
528
+ */
529
+ function recursivelyAddCoords(xmlEl) {
530
+ if ($(xmlEl).attr('coords') || !xmlEl.children) {
531
+ return;
532
+ }
533
+
534
+ const children = $(xmlEl).children().toArray();
535
+ if (children.length === 0) {
536
+ return;
537
+ }
538
+
539
+ for (const child of children) {
540
+ recursivelyAddCoords(child);
541
+ }
542
+
543
+ const childCoords = [];
544
+
545
+ for (const child of children) {
546
+ if (!$(child).attr('coords')) continue;
547
+ childCoords.push($(child).attr('coords').split(',').map(parseFloat));
548
+ }
549
+
550
+ const boundingCoords = determineBounds(childCoords);
551
+ if (Math.abs(boundingCoords[0]) != Infinity) {
552
+ $(xmlEl).attr('coords', boundingCoords.join(','));
553
+ }
554
+ }
555
+
556
+ /**
557
+ * Basically a polyfill for the native DOMRect class
558
+ */
559
+ class Rect {
287
560
  /**
288
- * @param {number} index
561
+ * @param {number} x
562
+ * @param {number} y
563
+ * @param {number} width
564
+ * @param {number} height
289
565
  */
290
- _createPageContainer(index) {
291
- const pageContainer = super._createPageContainer(index);
292
- // Disable if thumb mode; it's too janky
293
- // index can be -1 for "pre-cover" region
294
- // Added checking of lastPageIndex to avoid loop around index value
295
- const lastPageIndex = this.getNumLeafs() - 1;
296
- if (this.mode !== this.constModeThumb && (index >= 0 && index <= lastPageIndex)) {
297
- this.textSelectionPlugin?.createTextLayer(index, pageContainer.$container);
566
+ constructor(x, y, width, height) {
567
+ this.x = x;
568
+ this.y = y;
569
+ this.width = width;
570
+ this.height = height;
571
+ }
572
+
573
+ get right() { return this.x + this.width; }
574
+ get bottom() { return this.y + this.height; }
575
+ get top() { return this.y; }
576
+ get left() { return this.x; }
577
+ }
578
+
579
+ /**
580
+ * Depth traverse the DOM tree starting at `start`, and ending at `end`.
581
+ * @param {Node} start
582
+ * @param {Node} end
583
+ * @returns {Generator<Node>}
584
+ */
585
+ export function* walkBetweenNodes(start, end) {
586
+ let done = false;
587
+
588
+ /**
589
+ * @param {Node} node
590
+ */
591
+ function* walk(node, {children = true, parents = true, siblings = true} = {}) {
592
+ if (node === end) {
593
+ done = true;
594
+ yield node;
595
+ return;
596
+ }
597
+
598
+ // yield self
599
+ yield node;
600
+
601
+ // First iterate children (depth-first traversal)
602
+ if (children && node.firstChild) {
603
+ yield* walk(node.firstChild, {children: true, parents: false, siblings: true});
604
+ if (done) return;
605
+ }
606
+
607
+ // Then iterate siblings
608
+ if (siblings) {
609
+ for (let sib = node.nextSibling; sib; sib = sib.nextSibling) {
610
+ yield* walk(sib, {children: true, parents: false, siblings: false});
611
+ if (done) return;
612
+ }
613
+ }
614
+
615
+ // Finally, move up the tree
616
+ if (parents && node.parentNode) {
617
+ yield* walk(node.parentNode, {children: false, parents: true, siblings: true});
618
+ if (done) return;
298
619
  }
299
- return pageContainer;
300
620
  }
621
+
622
+ yield* walk(start);
301
623
  }
302
- window.BookReader = BookreaderWithTextSelection;
303
- export default BookreaderWithTextSelection;