@internetarchive/bookreader 5.0.0-10-alpha-3 → 5.0.0-101

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