@internetarchive/bookreader 5.0.0-12-fixsorting-8 → 5.0.0-120

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 (420) hide show
  1. package/BookReader/474.js +2 -0
  2. package/BookReader/474.js.map +1 -0
  3. package/BookReader/BookReader.css +670 -1190
  4. package/BookReader/bergamot-translator-worker.js +2966 -0
  5. package/BookReader/bergamot-translator-worker.wasm +0 -0
  6. package/BookReader/hypothesis/LICENSE +50 -0
  7. package/BookReader/hypothesis/README.md +55 -0
  8. package/BookReader/hypothesis/build/boot-template.js +1 -0
  9. package/BookReader/hypothesis/build/boot.js +1 -0
  10. package/BookReader/hypothesis/build/manifest.json +20 -0
  11. package/BookReader/hypothesis/build/scripts/annotator.bundle.js +199 -0
  12. package/BookReader/hypothesis/build/scripts/annotator.bundle.js.map +1 -0
  13. package/BookReader/hypothesis/build/scripts/sidebar.bundle.js +811 -0
  14. package/BookReader/hypothesis/build/scripts/sidebar.bundle.js.map +1 -0
  15. package/BookReader/hypothesis/build/scripts/ui-playground.bundle.js +437 -0
  16. package/BookReader/hypothesis/build/scripts/ui-playground.bundle.js.map +1 -0
  17. package/BookReader/hypothesis/build/styles/annotator.css +4 -0
  18. package/BookReader/hypothesis/build/styles/annotator.css.map +1 -0
  19. package/BookReader/hypothesis/build/styles/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  20. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  21. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  22. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  23. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  24. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Main-Bold.woff2 +0 -0
  25. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  26. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Main-Italic.woff2 +0 -0
  27. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Main-Regular.woff2 +0 -0
  28. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  29. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Math-Italic.woff2 +0 -0
  30. package/BookReader/hypothesis/build/styles/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  31. package/BookReader/hypothesis/build/styles/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  32. package/BookReader/hypothesis/build/styles/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  33. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Script-Regular.woff2 +0 -0
  34. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  35. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  36. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  37. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  38. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  39. package/BookReader/hypothesis/build/styles/highlights.css +2 -0
  40. package/BookReader/hypothesis/build/styles/highlights.css.map +1 -0
  41. package/BookReader/hypothesis/build/styles/katex.min.css +3 -0
  42. package/BookReader/hypothesis/build/styles/katex.min.css.map +1 -0
  43. package/BookReader/hypothesis/build/styles/pdfjs-overrides.css +14 -0
  44. package/BookReader/hypothesis/build/styles/pdfjs-overrides.css.map +1 -0
  45. package/BookReader/hypothesis/build/styles/sidebar.css +4 -0
  46. package/BookReader/hypothesis/build/styles/sidebar.css.map +1 -0
  47. package/BookReader/hypothesis/build/styles/ui-playground.css +4 -0
  48. package/BookReader/hypothesis/build/styles/ui-playground.css.map +1 -0
  49. package/BookReader/hypothesis/package.json +126 -0
  50. package/BookReader/ia-bookreader.js +1886 -0
  51. package/BookReader/{BookReader.js.LICENSE.txt → ia-bookreader.js.LICENSE.txt} +26 -20
  52. package/BookReader/ia-bookreader.js.map +1 -0
  53. package/BookReader/icons/1up.svg +1 -12
  54. package/BookReader/icons/2up.svg +1 -15
  55. package/BookReader/icons/advance.svg +3 -26
  56. package/BookReader/icons/chevron-right.svg +1 -1
  57. package/BookReader/icons/close-circle-dark.svg +1 -1
  58. package/BookReader/icons/close-circle.svg +1 -1
  59. package/BookReader/icons/fullscreen.svg +1 -17
  60. package/BookReader/icons/fullscreen_exit.svg +1 -17
  61. package/BookReader/icons/hamburger.svg +1 -15
  62. package/BookReader/icons/left-arrow.svg +1 -12
  63. package/BookReader/icons/magnify-minus.svg +1 -16
  64. package/BookReader/icons/magnify-plus.svg +1 -17
  65. package/BookReader/icons/magnify.svg +1 -15
  66. package/BookReader/icons/pause.svg +1 -23
  67. package/BookReader/icons/play.svg +1 -22
  68. package/BookReader/icons/playback-speed.svg +1 -34
  69. package/BookReader/icons/read-aloud.svg +1 -22
  70. package/BookReader/icons/review.svg +3 -22
  71. package/BookReader/icons/slider-toggle.svg +1 -0
  72. package/BookReader/icons/thumbnails.svg +1 -17
  73. package/BookReader/icons/voice.svg +1 -0
  74. package/BookReader/icons/volume-full.svg +1 -22
  75. package/BookReader/images/BRicons.svg +5 -94
  76. package/BookReader/images/books_graphic.svg +1 -177
  77. package/BookReader/images/hypothesis.ico +0 -0
  78. package/BookReader/images/icon_book.svg +1 -12
  79. package/BookReader/images/icon_bookmark.svg +1 -12
  80. package/BookReader/images/icon_experiment.svg +1 -0
  81. package/BookReader/images/icon_gear.svg +1 -14
  82. package/BookReader/images/icon_hamburger.svg +1 -20
  83. package/BookReader/images/icon_home.svg +1 -21
  84. package/BookReader/images/icon_info.svg +1 -11
  85. package/BookReader/images/icon_one_page.svg +1 -8
  86. package/BookReader/images/icon_pause.svg +1 -1
  87. package/BookReader/images/icon_play.svg +1 -1
  88. package/BookReader/images/icon_playback-rate.svg +1 -15
  89. package/BookReader/images/icon_search_button.svg +1 -8
  90. package/BookReader/images/icon_share.svg +1 -9
  91. package/BookReader/images/icon_skip-ahead.svg +1 -6
  92. package/BookReader/images/icon_skip-back.svg +2 -13
  93. package/BookReader/images/icon_speaker.svg +1 -18
  94. package/BookReader/images/icon_speaker_open.svg +1 -10
  95. package/BookReader/images/icon_thumbnails.svg +1 -12
  96. package/BookReader/images/icon_toc.svg +1 -5
  97. package/BookReader/images/icon_two_pages.svg +1 -9
  98. package/BookReader/images/marker_chap-off.svg +1 -11
  99. package/BookReader/images/marker_chap-on.svg +1 -11
  100. package/BookReader/images/marker_srch-on.svg +1 -11
  101. package/BookReader/images/translate.svg +1 -0
  102. package/BookReader/images/unviewable_page.png +0 -0
  103. package/BookReader/jquery-3.js +2 -0
  104. package/BookReader/jquery-3.js.LICENSE.txt +24 -0
  105. package/BookReader/plugins/plugin.archive_analytics.js +1 -172
  106. package/BookReader/plugins/plugin.archive_analytics.js.map +1 -1
  107. package/BookReader/plugins/plugin.autoplay.js +1 -165
  108. package/BookReader/plugins/plugin.autoplay.js.map +1 -1
  109. package/BookReader/plugins/plugin.chapters.js +19 -301
  110. package/BookReader/plugins/plugin.chapters.js.LICENSE.txt +1 -0
  111. package/BookReader/plugins/plugin.chapters.js.map +1 -1
  112. package/BookReader/plugins/plugin.experiments.js +3 -0
  113. package/BookReader/plugins/plugin.experiments.js.LICENSE.txt +1 -0
  114. package/BookReader/plugins/plugin.experiments.js.map +1 -0
  115. package/BookReader/plugins/plugin.iframe.js +1 -74
  116. package/BookReader/plugins/plugin.iframe.js.map +1 -1
  117. package/BookReader/plugins/plugin.iiif.js +2 -0
  118. package/BookReader/plugins/plugin.iiif.js.map +1 -0
  119. package/BookReader/plugins/plugin.resume.js +1 -368
  120. package/BookReader/plugins/plugin.resume.js.map +1 -1
  121. package/BookReader/plugins/plugin.search.js +2 -1385
  122. package/BookReader/plugins/plugin.search.js.LICENSE.txt +1 -0
  123. package/BookReader/plugins/plugin.search.js.map +1 -1
  124. package/BookReader/plugins/plugin.text_selection.js +2 -1080
  125. package/BookReader/plugins/plugin.text_selection.js.LICENSE.txt +1 -0
  126. package/BookReader/plugins/plugin.text_selection.js.map +1 -1
  127. package/BookReader/plugins/plugin.translate.js +3 -0
  128. package/BookReader/plugins/plugin.translate.js.LICENSE.txt +1 -0
  129. package/BookReader/plugins/plugin.translate.js.map +1 -0
  130. package/BookReader/plugins/plugin.tts.js +2 -9193
  131. package/BookReader/plugins/plugin.tts.js.LICENSE.txt +2 -0
  132. package/BookReader/plugins/plugin.tts.js.map +1 -1
  133. package/BookReader/plugins/plugin.url.js +1 -269
  134. package/BookReader/plugins/plugin.url.js.map +1 -1
  135. package/BookReader/plugins/plugin.vendor-fullscreen.js +1 -379
  136. package/BookReader/plugins/plugin.vendor-fullscreen.js.map +1 -1
  137. package/BookReader/plugins/translator-worker.js +2 -0
  138. package/BookReader/plugins/translator-worker.js.map +1 -0
  139. package/BookReader/silence.mp3 +0 -0
  140. package/BookReader/translator-worker.js +475 -0
  141. package/BookReader/webcomponents-bundle.js +3 -0
  142. package/BookReader/webcomponents-bundle.js.LICENSE.txt +9 -0
  143. package/BookReader/webcomponents-bundle.js.map +1 -0
  144. package/README.md +17 -4
  145. package/dist/esm/ia-bookreader.js +3 -0
  146. package/dist/esm/ia-bookreader.js.LICENSE.txt +17 -0
  147. package/dist/esm/ia-bookreader.js.map +1 -0
  148. package/dist/esm/plugins/plugin.archive_analytics.js +2 -0
  149. package/dist/esm/plugins/plugin.archive_analytics.js.map +1 -0
  150. package/dist/esm/plugins/plugin.autoplay.js +2 -0
  151. package/dist/esm/plugins/plugin.autoplay.js.map +1 -0
  152. package/dist/esm/plugins/plugin.chapters.js +2 -0
  153. package/dist/esm/plugins/plugin.chapters.js.map +1 -0
  154. package/dist/esm/plugins/plugin.experiments.js +2 -0
  155. package/dist/esm/plugins/plugin.experiments.js.map +1 -0
  156. package/dist/esm/plugins/plugin.iframe.js +2 -0
  157. package/dist/esm/plugins/plugin.iframe.js.map +1 -0
  158. package/dist/esm/plugins/plugin.iiif.js +2 -0
  159. package/dist/esm/plugins/plugin.iiif.js.map +1 -0
  160. package/dist/esm/plugins/plugin.resume.js +2 -0
  161. package/dist/esm/plugins/plugin.resume.js.map +1 -0
  162. package/dist/esm/plugins/plugin.search.js +2 -0
  163. package/dist/esm/plugins/plugin.search.js.map +1 -0
  164. package/dist/esm/plugins/plugin.text_selection.js +2 -0
  165. package/dist/esm/plugins/plugin.text_selection.js.map +1 -0
  166. package/dist/esm/plugins/plugin.translate.js +2 -0
  167. package/dist/esm/plugins/plugin.translate.js.map +1 -0
  168. package/dist/esm/plugins/plugin.tts.js +2 -0
  169. package/dist/esm/plugins/plugin.tts.js.map +1 -0
  170. package/dist/esm/plugins/plugin.url.js +2 -0
  171. package/dist/esm/plugins/plugin.url.js.map +1 -0
  172. package/dist/esm/plugins/plugin.vendor-fullscreen.js +2 -0
  173. package/dist/esm/plugins/plugin.vendor-fullscreen.js.map +1 -0
  174. package/jsconfig.json +18 -0
  175. package/package.json +113 -71
  176. package/src/BookReader/BookModel.js +92 -46
  177. package/src/BookReader/DragScrollable.js +233 -0
  178. package/src/BookReader/ImageCache.js +49 -16
  179. package/src/BookReader/Mode1Up.js +66 -365
  180. package/src/BookReader/Mode1UpLit.js +392 -0
  181. package/src/BookReader/Mode2Up.js +87 -1315
  182. package/src/BookReader/Mode2UpLit.js +786 -0
  183. package/src/BookReader/ModeAbstract.js +43 -0
  184. package/src/BookReader/ModeCoordinateSpace.js +29 -0
  185. package/src/BookReader/ModeSmoothZoom.js +312 -0
  186. package/src/BookReader/ModeThumb.js +29 -15
  187. package/src/BookReader/Navbar/Navbar.js +204 -84
  188. package/src/BookReader/PageContainer.js +120 -23
  189. package/src/BookReader/ReduceSet.js +2 -2
  190. package/src/BookReader/Toolbar/Toolbar.js +18 -40
  191. package/src/BookReader/events.js +3 -3
  192. package/src/BookReader/options.js +105 -18
  193. package/src/BookReader/utils/HTMLDimensionsCacher.js +44 -0
  194. package/src/BookReader/utils/ScrollClassAdder.js +31 -0
  195. package/src/BookReader/utils/SelectionObserver.js +72 -0
  196. package/src/BookReader/utils/classes.js +1 -1
  197. package/src/BookReader/utils.js +136 -12
  198. package/src/BookReader.js +732 -1231
  199. package/src/BookReaderPlugin.js +52 -0
  200. package/src/assets/icons/magnify-minus.svg +3 -7
  201. package/src/assets/icons/magnify-plus.svg +3 -7
  202. package/src/assets/icons/slider-toggle.svg +1 -0
  203. package/src/assets/icons/voice.svg +1 -0
  204. package/src/assets/images/hypothesis.ico +0 -0
  205. package/src/assets/images/icon_experiment.svg +1 -0
  206. package/src/assets/images/translate.svg +1 -0
  207. package/src/assets/images/unviewable_page.png +0 -0
  208. package/src/assets/silence.mp3 +0 -0
  209. package/src/css/BookReader.scss +1 -5
  210. package/src/css/_BRBookmarks.scss +1 -1
  211. package/src/css/_BRComponent.scss +1 -1
  212. package/src/css/_BRicon.scss +8 -2
  213. package/src/css/_BRmain.scss +16 -3
  214. package/src/css/_BRnav.scss +74 -70
  215. package/src/css/_BRpages.scss +174 -42
  216. package/src/css/_BRsearch.scss +69 -30
  217. package/src/css/_BRtoolbar.scss +5 -5
  218. package/src/css/_TextSelection.scss +249 -25
  219. package/src/css/_colorbox.scss +2 -2
  220. package/src/css/_controls.scss +24 -7
  221. package/src/css/_icons.scss +8 -1
  222. package/src/{BookNavigator/assets → css}/button-base.js +2 -2
  223. package/src/css/icon_checkmark.js +9 -0
  224. package/src/css/sharedStyles.js +15 -0
  225. package/src/ia-bookreader/downloads/downloads-provider.js +81 -0
  226. package/src/{BookNavigator → ia-bookreader}/downloads/downloads.js +25 -5
  227. package/src/ia-bookreader/ia-bookreader.js +686 -0
  228. package/src/ia-bookreader/sharing.js +27 -0
  229. package/src/ia-bookreader/viewable-files.js +98 -0
  230. package/src/{BookNavigator → ia-bookreader}/visual-adjustments/visual-adjustments-provider.js +17 -17
  231. package/src/{BookNavigator → ia-bookreader}/visual-adjustments/visual-adjustments.js +75 -67
  232. package/src/{BookNavigator → plugins}/bookmarks/bookmark-button.js +4 -3
  233. package/src/{BookNavigator/assets → plugins/bookmarks}/bookmark-colors.js +1 -1
  234. package/src/{BookNavigator → plugins}/bookmarks/bookmark-edit.js +43 -31
  235. package/src/{BookNavigator → plugins}/bookmarks/bookmarks-list.js +48 -49
  236. package/src/{BookNavigator → plugins}/bookmarks/bookmarks-loginCTA.js +3 -3
  237. package/src/plugins/bookmarks/bookmarks-provider.js +63 -0
  238. package/src/{BookNavigator → plugins/bookmarks}/delete-modal-actions.js +1 -1
  239. package/src/{BookNavigator → plugins}/bookmarks/ia-bookmarks.js +117 -68
  240. package/src/plugins/plugin.archive_analytics.js +84 -78
  241. package/src/plugins/plugin.autoplay.js +99 -104
  242. package/src/plugins/plugin.chapters.js +323 -205
  243. package/src/plugins/plugin.experiments.js +375 -0
  244. package/src/plugins/plugin.iframe.js +36 -37
  245. package/src/plugins/plugin.iiif.js +147 -0
  246. package/src/plugins/plugin.resume.js +54 -51
  247. package/src/plugins/plugin.text_selection.js +464 -219
  248. package/src/plugins/plugin.vendor-fullscreen.js +5 -5
  249. package/src/plugins/search/plugin.search.js +347 -397
  250. package/src/{BookNavigator → plugins}/search/search-provider.js +59 -27
  251. package/src/{BookNavigator → plugins}/search/search-results.js +105 -76
  252. package/src/plugins/search/utils.js +50 -0
  253. package/src/plugins/search/view.js +51 -69
  254. package/src/plugins/translate/TranslationManager.js +164 -0
  255. package/src/plugins/translate/plugin.translate.js +559 -0
  256. package/src/plugins/tts/AbstractTTSEngine.js +78 -49
  257. package/src/plugins/tts/FestivalTTSEngine.js +20 -30
  258. package/src/plugins/tts/PageChunk.js +117 -23
  259. package/src/plugins/tts/PageChunkIterator.js +11 -17
  260. package/src/plugins/tts/WebTTSEngine.js +132 -92
  261. package/src/plugins/tts/plugin.tts.js +341 -351
  262. package/src/plugins/tts/utils.js +76 -67
  263. package/src/plugins/url/UrlPlugin.js +213 -0
  264. package/src/plugins/{plugin.url.js → url/plugin.url.js} +54 -18
  265. package/src/util/TextSelectionManager.js +1464 -0
  266. package/src/util/browserSniffing.js +42 -1
  267. package/src/util/cache.js +20 -0
  268. package/src/util/docCookies.js +21 -2
  269. package/src/util/dom.js +88 -0
  270. package/src/util/generators.js +89 -0
  271. package/src/util/lit.js +15 -0
  272. package/src/util/strings.js +11 -0
  273. package/.babelrc +0 -12
  274. package/.dependabot/config.yml +0 -6
  275. package/.eslintrc.js +0 -50
  276. package/.gitattributes +0 -2
  277. package/.github/ISSUE_TEMPLATE/bug.md +0 -32
  278. package/.github/ISSUE_TEMPLATE/feature-request.md +0 -30
  279. package/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md +0 -15
  280. package/.github/workflows/node.js.yml +0 -37
  281. package/.github/workflows/npm-publish.yml +0 -47
  282. package/.testcaferc.json +0 -5
  283. package/BookReader/BookReader.js +0 -21613
  284. package/BookReader/BookReader.js.map +0 -1
  285. package/BookReader/bookreader-component-bundle.js +0 -14497
  286. package/BookReader/bookreader-component-bundle.js.LICENSE.txt +0 -38
  287. package/BookReader/bookreader-component-bundle.js.map +0 -1
  288. package/BookReader/jquery-1.10.1.js +0 -108
  289. package/BookReader/jquery-1.10.1.js.LICENSE.txt +0 -24
  290. package/BookReader/plugins/plugin.menu_toggle.js +0 -369
  291. package/BookReader/plugins/plugin.menu_toggle.js.map +0 -1
  292. package/BookReader/plugins/plugin.mobile_nav.js +0 -335
  293. package/BookReader/plugins/plugin.mobile_nav.js.map +0 -1
  294. package/BookReaderDemo/BookReaderDemo.css +0 -41
  295. package/BookReaderDemo/BookReaderJSAdvanced.js +0 -115
  296. package/BookReaderDemo/BookReaderJSAutoplay.js +0 -56
  297. package/BookReaderDemo/BookReaderJSSimple.js +0 -55
  298. package/BookReaderDemo/IIIFBookReader.js +0 -207
  299. package/BookReaderDemo/assets/v5/Bookreader-logo-cool-grad.svg +0 -1
  300. package/BookReaderDemo/assets/v5/Bookreader-logo-flat.svg +0 -1
  301. package/BookReaderDemo/assets/v5/Bookreader-logo-hex-cool-grad.png +0 -0
  302. package/BookReaderDemo/assets/v5/Bookreader-logo-hex-flat.png +0 -0
  303. package/BookReaderDemo/assets/v5/Bookreader-logo-lines.png +0 -0
  304. package/BookReaderDemo/assets/v5/Bookreader-logo-lines.svg +0 -1
  305. package/BookReaderDemo/assets/v5/Bookreader-logo-warm.svg +0 -1
  306. package/BookReaderDemo/assets/v5/bookreader-logo-renders@1x.png +0 -0
  307. package/BookReaderDemo/assets/v5/bookreader-logo-renders@2x.png +0 -0
  308. package/BookReaderDemo/assets/v5/bookreader-v5-screenshot.png +0 -0
  309. package/BookReaderDemo/bookreader-template-bundle.js +0 -7178
  310. package/BookReaderDemo/demo-advanced.html +0 -33
  311. package/BookReaderDemo/demo-autoplay.html +0 -38
  312. package/BookReaderDemo/demo-embed-iframe-src.html +0 -84
  313. package/BookReaderDemo/demo-embed.html +0 -26
  314. package/BookReaderDemo/demo-fullscreen-mobile.html +0 -36
  315. package/BookReaderDemo/demo-fullscreen.html +0 -33
  316. package/BookReaderDemo/demo-iiif.html +0 -34
  317. package/BookReaderDemo/demo-iiif.js +0 -26
  318. package/BookReaderDemo/demo-internetarchive.html +0 -74
  319. package/BookReaderDemo/demo-multiple.html +0 -43
  320. package/BookReaderDemo/demo-plugin-menu-toggle.html +0 -34
  321. package/BookReaderDemo/demo-preview-pages.html +0 -1092
  322. package/BookReaderDemo/demo-simple.html +0 -34
  323. package/BookReaderDemo/demo-vendor-fullscreen.html +0 -36
  324. package/BookReaderDemo/immersion-1up.html +0 -64
  325. package/BookReaderDemo/immersion-mode.html +0 -35
  326. package/BookReaderDemo/toggle_controls.html +0 -53
  327. package/BookReaderDemo/view_mode.html +0 -39
  328. package/BookReaderDemo/viewmode-cycle.html +0 -41
  329. package/CHANGELOG.md +0 -499
  330. package/CONTRIBUTING.md +0 -7
  331. package/codecov.yml +0 -17
  332. package/index.html +0 -31
  333. package/karma.conf.js +0 -23
  334. package/screenshot.png +0 -0
  335. package/scripts/postversion.js +0 -10
  336. package/scripts/preversion.js +0 -14
  337. package/scripts/version.js +0 -26
  338. package/src/BookNavigator/BookModel.js +0 -14
  339. package/src/BookNavigator/BookNavigator.js +0 -459
  340. package/src/BookNavigator/assets/book-loader.js +0 -27
  341. package/src/BookNavigator/assets/icon_checkmark.js +0 -6
  342. package/src/BookNavigator/assets/icon_close.js +0 -3
  343. package/src/BookNavigator/assets/icon_sort_ascending.js +0 -5
  344. package/src/BookNavigator/assets/icon_sort_descending.js +0 -5
  345. package/src/BookNavigator/assets/icon_sort_neutral.js +0 -5
  346. package/src/BookNavigator/assets/icon_volumes.js +0 -11
  347. package/src/BookNavigator/bookmarks/bookmarks-provider.js +0 -53
  348. package/src/BookNavigator/br-fullscreen-mgr.js +0 -83
  349. package/src/BookNavigator/downloads/downloads-provider.js +0 -66
  350. package/src/BookNavigator/search/a-search-result.js +0 -55
  351. package/src/BookNavigator/volumes/volumes-provider.js +0 -83
  352. package/src/BookNavigator/volumes/volumes.js +0 -178
  353. package/src/BookReader/DebugConsole.js +0 -54
  354. package/src/BookReaderComponent/BookReaderComponent.js +0 -112
  355. package/src/ItemNavigator/ItemNavigator.js +0 -376
  356. package/src/ItemNavigator/providers/sharing.js +0 -33
  357. package/src/css/_MobileNav.scss +0 -194
  358. package/src/dragscrollable-br.js +0 -261
  359. package/src/plugins/menu_toggle/plugin.menu_toggle.js +0 -324
  360. package/src/plugins/plugin.mobile_nav.js +0 -287
  361. package/tests/BookReader/BookModel.test.js +0 -312
  362. package/tests/BookReader/BookReaderPublicFunctions.test.js +0 -171
  363. package/tests/BookReader/DebugConsole.test.js +0 -25
  364. package/tests/BookReader/ImageCache.test.js +0 -150
  365. package/tests/BookReader/Mode1Up.test.js +0 -164
  366. package/tests/BookReader/Mode2Up.test.js +0 -247
  367. package/tests/BookReader/Navbar/Navbar.test.js +0 -169
  368. package/tests/BookReader/PageContainer.test.js +0 -115
  369. package/tests/BookReader/ReduceSet.test.js +0 -38
  370. package/tests/BookReader/Toolbar/Toolbar.test.js +0 -26
  371. package/tests/BookReader/utils/classes.test.js +0 -88
  372. package/tests/BookReader/utils.test.js +0 -109
  373. package/tests/BookReader.options.test.js +0 -39
  374. package/tests/BookReader.test.js +0 -301
  375. package/tests/e2e/README.md +0 -75
  376. package/tests/e2e/autoplay.test.js +0 -13
  377. package/tests/e2e/base.test.js +0 -35
  378. package/tests/e2e/helpers/base.js +0 -263
  379. package/tests/e2e/helpers/debug.js +0 -13
  380. package/tests/e2e/helpers/desktopSearch.js +0 -72
  381. package/tests/e2e/helpers/mobileSearch.js +0 -85
  382. package/tests/e2e/helpers/mockSearch.js +0 -93
  383. package/tests/e2e/helpers/rightToLeft.js +0 -29
  384. package/tests/e2e/ia-production/ia-prod-base.js +0 -17
  385. package/tests/e2e/models/BookReader.js +0 -11
  386. package/tests/e2e/models/Navigation.js +0 -56
  387. package/tests/e2e/rightToLeft.test.js +0 -20
  388. package/tests/e2e/viewmode.test.js +0 -37
  389. package/tests/karma/BookNavigator/book-navigator.test.js +0 -139
  390. package/tests/karma/BookNavigator/bookmarks/bookmark-edit.test.js +0 -133
  391. package/tests/karma/BookNavigator/bookmarks/bookmarks-list.test.js +0 -222
  392. package/tests/karma/BookNavigator/search/search-provider.test.js +0 -23
  393. package/tests/karma/BookNavigator/search/search-results.test.js +0 -240
  394. package/tests/karma/BookNavigator/sharing/sharing-provider.test.js +0 -40
  395. package/tests/karma/BookNavigator/visual-adjustments.test.js +0 -201
  396. package/tests/karma/BookNavigator/volumes/volumes-provider.test.js +0 -141
  397. package/tests/karma/BookNavigator/volumes/volumes.test.js +0 -98
  398. package/tests/plugins/menu_toggle/plugin.menu_toggle.test.js +0 -68
  399. package/tests/plugins/plugin.archive_analytics.test.js +0 -23
  400. package/tests/plugins/plugin.autoplay.test.js +0 -52
  401. package/tests/plugins/plugin.chapters.test.js +0 -130
  402. package/tests/plugins/plugin.iframe.test.js +0 -42
  403. package/tests/plugins/plugin.mobile_nav.test.js +0 -66
  404. package/tests/plugins/plugin.resume.test.js +0 -98
  405. package/tests/plugins/plugin.text_selection.test.js +0 -203
  406. package/tests/plugins/plugin.url.test.js +0 -129
  407. package/tests/plugins/plugin.vendor-fullscreen.test.js +0 -65
  408. package/tests/plugins/search/plugin.search.test.js +0 -173
  409. package/tests/plugins/search/plugin.search.view.test.js +0 -106
  410. package/tests/plugins/tts/AbstractTTSEngine.test.js +0 -153
  411. package/tests/plugins/tts/FestivalTTSEngine.test.js +0 -59
  412. package/tests/plugins/tts/PageChunk.test.js +0 -57
  413. package/tests/plugins/tts/PageChunkIterator.test.js +0 -179
  414. package/tests/plugins/tts/WebTTSEngine.test.js +0 -126
  415. package/tests/plugins/tts/utils.test.js +0 -133
  416. package/tests/util/browserSniffing.test.js +0 -56
  417. package/tests/util/docCookies.test.js +0 -15
  418. package/tests/util/strings.test.js +0 -63
  419. package/tests/utils.js +0 -42
  420. package/webpack.config.js +0 -86
@@ -1,79 +1,154 @@
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 { BookReaderTextFragment, renderHighlight, TextSelectionManager } from '../util/TextSelectionManager.js';
8
+ import { genMap, lookAroundWindow, zip } from '../util/generators.js';
9
+ import textSelectionCss from '../css/_TextSelection.scss';
4
10
  /** @typedef {import('../util/strings.js').StringWithVars} StringWithVars */
11
+ /** @typedef {import('../BookReader/PageContainer.js').PageContainer} PageContainer */
5
12
 
6
13
  const BookReader = /** @type {typeof import('../BookReader').default} */(window.BookReader);
7
14
 
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
15
 
17
- /**
18
- * @template T
19
- */
20
- export class Cache {
21
- constructor(maxSize = 10) {
22
- this.maxSize = maxSize;
23
- /** @type {T[]} */
24
- this.entries = [];
16
+ export class TextSelectionPlugin extends BookReaderPlugin {
17
+ options = {
18
+ enabled: true,
19
+ /** @type {StringWithVars} The URL to fetch the entire DJVU xml. Supports options.vars */
20
+ fullDjvuXmlUrl: null,
21
+ /** @type {StringWithVars} The URL to fetch a single page of the DJVU xml. Supports options.vars. Also has {{pageIndex}} */
22
+ singlePageDjvuXmlUrl: null,
23
+ /** Whether to fetch the XML as a jsonp */
24
+ jsonp: false,
25
+ /** Mox words that can be selected when the text layer is protected */
26
+ maxProtectedWords: 200,
25
27
  }
26
28
 
29
+ /**@type {PromiseLike<JQuery<HTMLElement>|undefined>} */
30
+ djvuPagesPromise = null;
31
+
32
+ /** @type {Cache<{index: number, response: any}>} */
33
+ pageTextCache = new Cache();
34
+
35
+ /**
36
+ * Sometimes there are too many words on a page, and the browser becomes near
37
+ * unusable. For now don't render text layer for pages with too many words.
38
+ */
39
+ maxWordRendered = 2500;
40
+
41
+ _jumpedToHighlight = false;
42
+
43
+ /**
44
+ * Isolated document/layout used to performantly measure OCR text-layer
45
+ * elements.
46
+ * @type {Document}
47
+ */
48
+ _measurementDocument;
49
+
27
50
  /**
28
- * @param {T} entry
51
+ * @param {import('../BookReader.js').default} br
29
52
  */
30
- add(entry) {
31
- if (this.entries.length >= this.maxSize) {
32
- this.entries.shift();
53
+ constructor(br) {
54
+ super(br);
55
+ // In the future this should be in the ocr file
56
+ // since a book being right to left doesn't mean the ocr is right to left. But for
57
+ // now we do make that assumption.
58
+ /** Whether the book is right-to-left */
59
+ this.rtl = this.br.pageProgression === 'rl';
60
+ this.textSelectionManager = new TextSelectionManager('.BRtextLayer', this.br, {selectionElement: ['.BRwordElement', '.BRspace', 'mark']}, this.options.maxProtectedWords);
61
+ }
62
+
63
+ /** @override */
64
+ init() {
65
+ if (!this.options.enabled) return;
66
+
67
+ // Setup measurement iframe for OCR
68
+ const measurementIframe = document.createElement('iframe');
69
+ measurementIframe.setAttribute('aria-hidden', 'true');
70
+ measurementIframe.tabIndex = -1;
71
+ measurementIframe.style.cssText = 'position:fixed; top:-99999px; left:-99999px; width:2000px; height:4000px; border:0; visibility:hidden;';
72
+ document.body.appendChild(measurementIframe);
73
+ this._measurementDocument = measurementIframe.contentDocument;
74
+ // Injects _TextSelection.scss so measurements match the real
75
+ // rendering
76
+ const style = this._measurementDocument.createElement('style');
77
+ style.textContent = textSelectionCss;
78
+ this._measurementDocument.head.appendChild(style);
79
+
80
+ this.br.on('pageVisible', (_, {pageContainerEl}) => {
81
+ const textLayer = pageContainerEl.querySelector('.BRtextLayer');
82
+ if (textLayer) {
83
+ this.br.trigger('textLayerVisible', {pageContainerEl, textLayer});
84
+ }
85
+ });
86
+
87
+ this.loadData();
88
+ this.textSelectionManager.init();
89
+
90
+ // Init text fragment
91
+ const textParam = new URLSearchParams(location.search).get('text');
92
+ if (textParam) {
93
+ // save on global BR
94
+ this.br.targetTextFragment = BookReaderTextFragment.fromString(textParam, this.br.book, this.br.firstIndex);
95
+ this.initTextFragment();
33
96
  }
34
- this.entries.push(entry);
35
97
  }
36
- }
37
98
 
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";
99
+ async initTextFragment() {
100
+ if (this.br.options.verifyTextFragment && !await this.br.options.verifyTextFragment(this.br.targetTextFragment)) {
101
+ return;
57
102
  }
58
103
 
59
- /** @type {Cache<{index: number, response: any}>} */
60
- this.pageTextCache = new Cache();
104
+ const targetTextFragment = this.br.targetTextFragment;
105
+ this.br.on('textLayerVisible', async (_, {pageContainerEl, textLayer}) => {
106
+ const pageIndex = targetTextFragment.pageIndex;
107
+ const hasTargetText = pageIndex === parseFloat(pageContainerEl.getAttribute('data-index'));
108
+ if (hasTargetText) {
109
+ const markEls = renderHighlight(textLayer, targetTextFragment, 'BRhighlight--target-text');
110
+ // Only jump once; presumably on first page load.
111
+ if (!this._jumpedToHighlight) {
112
+ this.br.scrollIntoView(markEls[0], {behavior: 'smooth', block: 'center'});
113
+ this._jumpedToHighlight = true;
114
+ }
115
+ }
116
+ });
61
117
 
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;
118
+ // Now jump to the page and open a slot if necessary
119
+ const page = this.br.book.getPage(this.br.targetTextFragment.pageIndex);
120
+ if (!page.isViewable) {
121
+ await this.br.requestPage(page);
122
+ this.br.jumpToIndex(page.index, { ariaLive: true, allowUnviewable: true });
123
+ }
67
124
  }
68
125
 
69
- init() {
126
+ /**
127
+ * @override
128
+ * @param {PageContainer} pageContainer
129
+ * @returns {PageContainer}
130
+ */
131
+ _configurePageContainer(pageContainer) {
132
+ // Disable if thumb mode; it's too janky
133
+ // .page can be null for "pre-cover" region
134
+ if (this.options.enabled && this.br.mode !== this.br.constModeThumb && pageContainer.page?.isViewable) {
135
+ this.createTextLayer(pageContainer);
136
+ }
137
+ return pageContainer;
138
+ }
139
+
140
+ loadData() {
70
141
  // Only fetch the full djvu xml if the single page url isn't there
71
142
  if (this.options.singlePageDjvuXmlUrl) return;
72
143
  this.djvuPagesPromise = $.ajax({
73
144
  type: "GET",
74
- url: applyVariables(this.options.fullDjvuXmlUrl, this.optionVariables),
75
- dataType: "html",
76
- error: (e) => undefined
145
+ url: applyVariables(this.options.fullDjvuXmlUrl, this.br.options.vars),
146
+ dataType: this.options.jsonp ? "jsonp" : "html",
147
+ cache: true,
148
+ xhrFields: {
149
+ withCredentials: this.br.protected,
150
+ },
151
+ error: (e) => undefined,
77
152
  }).then((res) => {
78
153
  try {
79
154
  const xmlMap = $.parseXML(res);
@@ -94,21 +169,24 @@ export class TextSelectionPlugin {
94
169
  if (cachedEntry) {
95
170
  return cachedEntry.response;
96
171
  }
97
- return $.ajax({
172
+ const res = await $.ajax({
98
173
  type: "GET",
99
- url: applyVariables(this.options.singlePageDjvuXmlUrl, this.optionVariables, { pageIndex: index }),
100
- dataType: "html",
174
+ url: applyVariables(this.options.singlePageDjvuXmlUrl, this.br.options.vars, { pageIndex: index }),
175
+ dataType: this.options.jsonp ? "jsonp" : "html",
176
+ cache: true,
177
+ xhrFields: {
178
+ withCredentials: this.br.protected,
179
+ },
101
180
  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
181
  });
182
+ try {
183
+ const xmlDoc = $.parseXML(res);
184
+ const result = xmlDoc && $(xmlDoc).find("OBJECT")[0];
185
+ this.pageTextCache.add({ index, response: result });
186
+ return result;
187
+ } catch (e) {
188
+ return undefined;
189
+ }
112
190
  } else {
113
191
  const XMLpagesArr = await this.djvuPagesPromise;
114
192
  if (XMLpagesArr) return XMLpagesArr[index];
@@ -116,80 +194,25 @@ export class TextSelectionPlugin {
116
194
  }
117
195
 
118
196
  /**
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
197
+ * @param {PageContainer} pageContainer
173
198
  */
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;
199
+ async createTextLayer(pageContainer) {
200
+ const pageIndex = pageContainer.page.index;
201
+ const $container = pageContainer.$container;
202
+ const $textLayers = $container.find('.BRtextLayer');
203
+ if ($textLayers.length) return;
189
204
  const XMLpage = await this.getPageText(pageIndex);
190
205
  if (!XMLpage) return;
191
- const XMLwidth = $(XMLpage).attr("width");
192
- const XMLheight = $(XMLpage).attr("height");
206
+ // Seeing some 0 left and 0 top coordinates in OCR, remove it entirely to prevent odd rendering
207
+ // eg https://archive.org/details/illustratedbooko00robe/page/n11/mode/2up
208
+ $(XMLpage).find("WORD").filter((_, ele) => {
209
+ const [left, , , top] = ele.getAttribute('coords').split(",").map(parseFloat);
210
+ if (left == 0 && top == 0) {
211
+ console.error("Found invalid ocr word coordinates");
212
+ return true;
213
+ }
214
+ }).remove();
215
+ recursivelyAddCoords(XMLpage);
193
216
 
194
217
  const totalWords = $(XMLpage).find("WORD").length;
195
218
  if (totalWords > this.maxWordRendered) {
@@ -197,107 +220,329 @@ export class TextSelectionPlugin {
197
220
  return;
198
221
  }
199
222
 
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",
223
+ const textLayer = createDIVPageLayer(pageContainer.page, 'BRtextLayer');
224
+ // Have to wait to make sure the page container is actually rendered,
225
+ // otherwise width/height are unset after a mode change.
226
+ await Promise.resolve();
227
+ const ratioW = parseFloat(pageContainer.$container[0].style.width) / pageContainer.page.width;
228
+ const ratioH = parseFloat(pageContainer.$container[0].style.height) / pageContainer.page.height;
229
+ textLayer.style.transform = `scale(${ratioW}, ${ratioH})`;
230
+ const bookLangCode = toISO6391(this.br.options.bookLanguage);
231
+ if (bookLangCode) {
232
+ textLayer.setAttribute("lang", bookLangCode);
233
+ }
234
+ textLayer.setAttribute("dir", this.rtl ? "rtl" : "ltr");
235
+
236
+ const ocrParagraphs = $(XMLpage).find("PARAGRAPH[coords]").toArray();
237
+ const paragEls = ocrParagraphs.map(p => {
238
+ const el = this.renderParagraph(p);
239
+ textLayer.appendChild(el);
240
+ return el;
212
241
  });
213
242
 
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
- }
243
+ // Fix up paragraph positions
244
+ const paragraphRects = determineRealRects(textLayer, '.BRparagraphElement', this._measurementDocument);
245
+ let yAdded = 0;
246
+ for (const [ocrParagraph, paragEl] of zip(ocrParagraphs, paragEls)) {
247
+ const ocrParagBounds = $(ocrParagraph).attr("coords").split(",").map(parseFloat);
248
+ const realRect = paragraphRects.get(paragEl);
249
+ const [ocrLeft, , ocrRight, ocrTop] = ocrParagBounds;
250
+ const newStartMargin = this.rtl ? (realRect.right - ocrRight) : (ocrLeft - realRect.left);
251
+ const newTop = ocrTop - (realRect.top + yAdded);
223
252
 
224
- const wordHeightArr = [];
253
+ paragEl.style[this.rtl ? 'marginRight' : 'marginLeft'] = `${newStartMargin}px`;
254
+ paragEl.style.marginTop = `${newTop}px`;
255
+ yAdded += newTop;
256
+ textLayer.appendChild(paragEl);
257
+ textLayer.appendChild(document.createTextNode('\n'));
258
+ }
259
+ $container.append(textLayer);
260
+ this.textSelectionManager.stopPageFlip($container);
261
+ this.br.trigger('textLayerRendered', {
262
+ pageIndex,
263
+ pageContainer,
264
+ });
225
265
 
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);
266
+ // Check if page is visible
267
+ if ($container.hasClass('BRpage-visible')) {
268
+ this.br.trigger('textLayerVisible', {pageContainerEl: $container[0], textLayer});
269
+ }
270
+ }
271
+
272
+ /**
273
+ * @param {HTMLElement} ocrParagraph
274
+ * @returns {HTMLParagraphElement}
275
+ */
276
+ renderParagraph(ocrParagraph) {
277
+ const paragEl = document.createElement('p');
278
+ paragEl.classList.add('BRparagraphElement');
279
+ if (ocrParagraph.getAttribute("x-role")) {
280
+ paragEl.classList.add('ocr-role-header-footer');
281
+ paragEl.ariaHidden = "true";
282
+ }
283
+ const [paragLeft, paragBottom, paragRight, paragTop] = $(ocrParagraph).attr("coords").split(",").map(parseFloat);
284
+ const wordHeightArr = [];
285
+ const lines = $(ocrParagraph).find("LINE[coords]").toArray();
286
+ if (!lines.length) return paragEl;
287
+
288
+
289
+ for (const [prevLine, line, nextLine] of lookAroundWindow(genMap(lines, augmentLine))) {
290
+ const isLastLineOfParagraph = line.ocrElement == lines[lines.length - 1];
291
+ const lineEl = document.createElement('span');
292
+ lineEl.classList.add('BRlineElement');
293
+
294
+ for (const [wordIndex, currWord] of line.words.entries()) {
295
+ const [, bottom, right, top] = $(currWord).attr("coords").split(',').map(parseFloat);
231
296
  const wordHeight = bottom - top;
232
297
  wordHeightArr.push(wordHeight);
233
298
 
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);
299
+ if (wordIndex == 0 && prevLine?.lastWord.textContent.trim().endsWith('-')) {
300
+ // ideally prefer the next line to determine the left position,
301
+ // since the previous line could be the first line of the paragraph
302
+ // and hence have an incorrectly indented first word.
303
+ // E.g. https://archive.org/details/driitaleofdaring00bachuoft/page/360/mode/2up
304
+ const [newLeft, , , ] = $((nextLine || prevLine).firstWord).attr("coords").split(',').map(parseFloat);
305
+ $(currWord).attr("coords", `${newLeft},${bottom},${right},${top}`);
257
306
  }
258
307
 
259
- // Adds newline at the end of paragraph on Firefox
260
- if ((i == words.length - 1 && (this.insertNewlines))) {
261
- paragSvg.appendChild(document.createTextNode("\n"));
308
+ const wordEl = document.createElement('span');
309
+ wordEl.setAttribute("class", "BRwordElement");
310
+ wordEl.textContent = currWord.textContent.trim();
311
+
312
+ if (wordIndex > 0) {
313
+ const space = document.createElement('span');
314
+ space.classList.add('BRspace');
315
+ space.textContent = ' ';
316
+ // Hack to make screen readers (eg NVDA) read spaces correctly;
317
+ // otherwise they ignore elements with just whitespace.
318
+ space.setAttribute('aria-label', '\u00A0');
319
+ lineEl.append(space);
320
+
321
+ // Edge ignores empty elements (like BRspace), so add another
322
+ // space to ensure Edge's ReadAloud works correctly.
323
+ lineEl.appendChild(document.createTextNode(' '));
262
324
  }
325
+
326
+ lineEl.appendChild(wordEl);
263
327
  }
264
328
 
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);
329
+ const hasHyphen = line.lastWord.textContent.trim().endsWith('-');
330
+ const lastWordEl = lineEl.children[lineEl.children.length - 1];
331
+ if (hasHyphen && !isLastLineOfParagraph) {
332
+ lastWordEl.textContent = lastWordEl.textContent.trim().slice(0, -1);
333
+ lastWordEl.classList.add('BRwordElement--hyphen');
334
+ }
335
+
336
+ paragEl.appendChild(lineEl);
337
+ if (!isLastLineOfParagraph && !hasHyphen) {
338
+ // Edge does not correctly have spaces between the lines.
339
+ paragEl.appendChild(document.createTextNode(' '));
340
+ }
341
+ }
342
+
343
+ wordHeightArr.sort((a, b) => a - b);
344
+ const paragWordHeight = wordHeightArr[Math.floor(wordHeightArr.length * 0.85)] + 4;
345
+ paragEl.style.left = `${paragLeft}px`;
346
+ paragEl.style.top = `${paragTop}px`;
347
+ paragEl.style.width = `${paragRight - paragLeft}px`;
348
+ paragEl.style.height = `${paragBottom - paragTop}px`;
349
+ paragEl.style.fontSize = `${paragWordHeight}px`;
350
+
351
+ // Fix up sizes - stretch/crush words as necessary using letter spacing
352
+ let wordRects = determineRealRects(paragEl, '.BRwordElement', this._measurementDocument);
353
+ const ocrWords = $(ocrParagraph).find("WORD").toArray();
354
+ const wordEls = paragEl.querySelectorAll('.BRwordElement');
355
+ for (const [ocrWord, wordEl] of zip(ocrWords, wordEls)) {
356
+ const realRect = wordRects.get(wordEl);
357
+ const [left, , right ] = $(ocrWord).attr("coords").split(',').map(parseFloat);
358
+ let ocrWidth = right - left;
359
+ // Some books (eg theworksofplato01platiala) have a space _inside_ the <WORD>
360
+ // element. That makes it impossible to determine the correct positining
361
+ // of everything, but to avoid the BRspace's being width 0, which makes selection
362
+ // janky on Chrome Android, assume the space is the same width as one of the
363
+ // letters.
364
+ if (ocrWord.textContent.endsWith(' ')) {
365
+ ocrWidth = ocrWidth * (ocrWord.textContent.length - 1) / ocrWord.textContent.length;
366
+ }
367
+ const diff = ocrWidth - realRect.width;
368
+ wordEl.style.letterSpacing = `${diff / (ocrWord.textContent.length - 1)}px`;
369
+ }
370
+
371
+ // Stretch/crush lines as necessary using line spacing
372
+ // Recompute rects after letter spacing
373
+ wordRects = determineRealRects(paragEl, '.BRwordElement', this._measurementDocument);
374
+ const spaceRects = determineRealRects(paragEl, '.BRspace', this._measurementDocument);
375
+
376
+ const ocrLines = $(ocrParagraph).find("LINE[coords]").toArray();
377
+ const lineEls = Array.from(paragEl.querySelectorAll('.BRlineElement'));
378
+
379
+ let ySoFar = paragTop;
380
+ for (const [ocrLine, lineEl] of zip(ocrLines, lineEls)) {
381
+ // shift words using marginLeft to align with the correct x position
382
+ const words = $(ocrLine).find("WORD").toArray();
383
+ // const ocrLineLeft = Math.min(...words.map(w => parseFloat($(w).attr("coords").split(',')[0])));
384
+ let xSoFar = this.rtl ? paragRight : paragLeft;
385
+ for (const [ocrWord, wordEl] of zip(words, lineEl.querySelectorAll('.BRwordElement'))) {
386
+ // start of line, need to compute the offset relative to the OCR words
387
+ const wordRect = wordRects.get(wordEl);
388
+ const [ocrLeft, , ocrRight ] = $(ocrWord).attr("coords").split(',').map(parseFloat);
389
+ const diff = (this.rtl ? -(ocrRight - xSoFar) : ocrLeft - xSoFar);
390
+
391
+ if (wordEl.previousElementSibling) {
392
+ const space = wordEl.previousElementSibling;
393
+ space.style.letterSpacing = `${diff - spaceRects.get(space).width}px`;
394
+ } else {
395
+ wordEl.style[this.rtl ? 'paddingRight' : 'paddingLeft'] = `${diff}px`;
396
+ }
397
+ if (this.rtl) xSoFar -= diff + wordRect.width;
398
+ else xSoFar += diff + wordRect.width;
399
+ }
400
+ // And also fix y position
401
+ const ocrLineTop = Math.min(...words.map(w => parseFloat($(w).attr("coords").split(',')[3])));
402
+ const diff = ocrLineTop - ySoFar;
403
+ if (lineEl.previousElementSibling) {
404
+ lineEl.previousElementSibling.style.lineHeight = `${diff}px`;
405
+ ySoFar += diff;
406
+ }
407
+ }
408
+
409
+ // The last line will have a line height subtracting from the paragraph height
410
+ lineEls[lineEls.length - 1].style.lineHeight = `${paragBottom - ySoFar}px`;
411
+
412
+ // Edge does not include a newline for some reason when copying/pasting the <p> els
413
+ paragEl.appendChild(document.createElement('br'));
414
+ return paragEl;
271
415
  }
272
416
  }
273
417
 
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();
283
- }
284
- super.init();
418
+ BookReader?.registerPlugin('textSelection', TextSelectionPlugin);
419
+
420
+
421
+ /**
422
+ * @param {HTMLElement} parentEl
423
+ * @param {string} selector
424
+ * @param {Document} measurementDocument Isolated document to measure within
425
+ * (see TextSelectionPlugin#_measurementDocument for why).
426
+ * @returns {Map<Element, Rect>}
427
+ */
428
+ function determineRealRects(parentEl, selector, measurementDocument) {
429
+ const initals = {
430
+ position: parentEl.style.position,
431
+ visibility: parentEl.style.visibility,
432
+ top: parentEl.style.top,
433
+ left: parentEl.style.left,
434
+ transform: parentEl.style.transform,
435
+ };
436
+ parentEl.style.position = 'absolute';
437
+ parentEl.style.visibility = 'hidden';
438
+ parentEl.style.top = '0';
439
+ parentEl.style.left = '0';
440
+ parentEl.style.transform = 'none';
441
+ measurementDocument.body.appendChild(parentEl);
442
+ const rects = new Map(
443
+ Array.from(parentEl.querySelectorAll(selector))
444
+ .map(wordEl => {
445
+ const origRect = wordEl.getBoundingClientRect();
446
+ return [wordEl, new Rect(
447
+ origRect.left + measurementDocument.defaultView.scrollX,
448
+ origRect.top + measurementDocument.defaultView.scrollY,
449
+ origRect.width,
450
+ origRect.height,
451
+ )];
452
+ }),
453
+ );
454
+ measurementDocument.body.removeChild(parentEl);
455
+ Object.assign(parentEl.style, initals);
456
+ // Need to restore the document to the main window document
457
+ document.adoptNode(parentEl);
458
+ return rects;
459
+ }
460
+
461
+ /**
462
+ * @param {HTMLElement} line
463
+ */
464
+ function augmentLine(line) {
465
+ const words = $(line).find("WORD").toArray();
466
+ return {
467
+ ocrElement: line,
468
+ words,
469
+ firstWord: words[0],
470
+ lastWord: words[words.length - 1],
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;
285
503
  }
286
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);
298
- }
299
- return pageContainer;
537
+ constructor(x, y, width, height) {
538
+ this.x = x;
539
+ this.y = y;
540
+ this.width = width;
541
+ this.height = height;
300
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; }
301
548
  }
302
- window.BookReader = BookreaderWithTextSelection;
303
- export default BookreaderWithTextSelection;