@internetarchive/bookreader 5.0.0-7 → 5.0.0-70-a1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +17 -15
- package/.github/workflows/node.js.yml +73 -10
- package/.github/workflows/npm-publish.yml +6 -20
- package/.testcaferc.js +10 -0
- package/BookReader/BookReader.css +396 -1129
- package/BookReader/BookReader.js +1 -1
- package/BookReader/BookReader.js.LICENSE.txt +20 -20
- package/BookReader/BookReader.js.map +1 -1
- package/BookReader/ia-bookreader-bundle.js +1788 -0
- package/BookReader/ia-bookreader-bundle.js.LICENSE.txt +19 -0
- package/BookReader/ia-bookreader-bundle.js.map +1 -0
- package/BookReader/icons/1up.svg +1 -1
- package/BookReader/icons/2up.svg +1 -1
- package/BookReader/icons/advance.svg +1 -1
- package/BookReader/icons/chevron-right.svg +1 -1
- package/BookReader/icons/close-circle-dark.svg +1 -1
- package/BookReader/icons/close-circle.svg +1 -1
- package/BookReader/icons/fullscreen.svg +1 -1
- package/BookReader/icons/fullscreen_exit.svg +1 -1
- package/BookReader/icons/hamburger.svg +1 -1
- package/BookReader/icons/left-arrow.svg +1 -1
- package/BookReader/icons/magnify-minus.svg +1 -1
- package/BookReader/icons/magnify-plus.svg +1 -1
- package/BookReader/icons/magnify.svg +1 -1
- package/BookReader/icons/pause.svg +1 -1
- package/BookReader/icons/play.svg +1 -1
- package/BookReader/icons/playback-speed.svg +1 -1
- package/BookReader/icons/read-aloud.svg +1 -1
- package/BookReader/icons/review.svg +1 -1
- package/BookReader/icons/thumbnails.svg +1 -1
- package/BookReader/icons/voice.svg +1 -0
- package/BookReader/icons/volume-full.svg +1 -1
- package/BookReader/images/BRicons.svg +3 -3
- package/BookReader/images/books_graphic.svg +1 -1
- package/BookReader/images/icon_book.svg +1 -1
- package/BookReader/images/icon_bookmark.svg +1 -1
- package/BookReader/images/icon_gear.svg +1 -1
- package/BookReader/images/icon_hamburger.svg +1 -1
- package/BookReader/images/icon_home.svg +1 -1
- package/BookReader/images/icon_info.svg +1 -1
- package/BookReader/images/icon_one_page.svg +1 -1
- package/BookReader/images/icon_pause.svg +1 -1
- package/BookReader/images/icon_play.svg +1 -1
- package/BookReader/images/icon_playback-rate.svg +1 -1
- package/BookReader/images/icon_search_button.svg +1 -1
- package/BookReader/images/icon_share.svg +1 -1
- package/BookReader/images/icon_skip-ahead.svg +1 -1
- package/BookReader/images/icon_skip-back.svg +1 -1
- package/BookReader/images/icon_speaker.svg +1 -1
- package/BookReader/images/icon_speaker_open.svg +1 -1
- package/BookReader/images/icon_thumbnails.svg +1 -1
- package/BookReader/images/icon_toc.svg +1 -1
- package/BookReader/images/icon_two_pages.svg +1 -1
- package/BookReader/images/marker_chap-off.svg +1 -1
- package/BookReader/images/marker_chap-on.svg +1 -1
- package/BookReader/images/marker_srch-on.svg +1 -1
- package/BookReader/jquery-3.js +2 -0
- package/BookReader/jquery-3.js.LICENSE.txt +24 -0
- package/BookReader/plugins/plugin.archive_analytics.js +1 -1
- package/BookReader/plugins/plugin.archive_analytics.js.map +1 -1
- package/BookReader/plugins/plugin.autoplay.js +1 -1
- package/BookReader/plugins/plugin.autoplay.js.map +1 -1
- package/BookReader/plugins/plugin.chapters.js +25 -1
- package/BookReader/plugins/plugin.chapters.js.LICENSE.txt +1 -0
- package/BookReader/plugins/plugin.chapters.js.map +1 -1
- package/BookReader/plugins/plugin.iframe.js +1 -1
- package/BookReader/plugins/plugin.iframe.js.map +1 -1
- package/BookReader/plugins/plugin.resume.js +1 -1
- package/BookReader/plugins/plugin.resume.js.map +1 -1
- package/BookReader/plugins/plugin.search.js +2 -1
- package/BookReader/plugins/plugin.search.js.LICENSE.txt +1 -0
- package/BookReader/plugins/plugin.search.js.map +1 -1
- package/BookReader/plugins/plugin.text_selection.js +2 -1
- package/BookReader/plugins/plugin.text_selection.js.LICENSE.txt +1 -0
- package/BookReader/plugins/plugin.text_selection.js.map +1 -1
- package/BookReader/plugins/plugin.tts.js +1 -1
- package/BookReader/plugins/plugin.tts.js.LICENSE.txt +2 -0
- package/BookReader/plugins/plugin.tts.js.map +1 -1
- package/BookReader/plugins/plugin.url.js +1 -1
- package/BookReader/plugins/plugin.url.js.map +1 -1
- package/BookReader/plugins/plugin.vendor-fullscreen.js +1 -1
- package/BookReader/plugins/plugin.vendor-fullscreen.js.map +1 -1
- package/BookReader/webcomponents-bundle.js +3 -0
- package/BookReader/webcomponents-bundle.js.LICENSE.txt +9 -0
- package/BookReader/webcomponents-bundle.js.map +1 -0
- package/BookReaderDemo/BookReaderDemo.css +16 -19
- package/BookReaderDemo/BookReaderJSAdvanced.js +0 -3
- package/BookReaderDemo/BookReaderJSAutoplay.js +4 -1
- package/BookReaderDemo/BookReaderJSSimple.js +1 -0
- package/BookReaderDemo/IADemoBr.js +147 -0
- package/BookReaderDemo/demo-advanced.html +2 -2
- package/BookReaderDemo/demo-autoplay.html +2 -3
- package/BookReaderDemo/demo-embed-iframe-src.html +2 -1
- package/BookReaderDemo/demo-fullscreen-mobile.html +3 -5
- package/BookReaderDemo/demo-fullscreen.html +2 -4
- package/BookReaderDemo/demo-iiif.html +2 -1
- package/BookReaderDemo/demo-iiif.js +0 -1
- package/BookReaderDemo/demo-internetarchive.html +213 -17
- package/BookReaderDemo/demo-multiple.html +2 -1
- package/BookReaderDemo/demo-preview-pages.html +2 -1
- package/BookReaderDemo/demo-simple.html +2 -1
- package/BookReaderDemo/demo-vendor-fullscreen.html +2 -4
- package/BookReaderDemo/ia-multiple-volumes-manifest.js +170 -0
- package/BookReaderDemo/immersion-1up.html +2 -2
- package/BookReaderDemo/immersion-mode.html +2 -4
- package/BookReaderDemo/toggle_controls.html +3 -2
- package/BookReaderDemo/view_mode.html +2 -1
- package/BookReaderDemo/viewmode-cycle.html +2 -3
- package/CHANGELOG.md +279 -0
- package/README.md +14 -1
- package/babel.config.js +20 -0
- package/codecov.yml +6 -0
- package/index.html +4 -1
- package/jsconfig.json +19 -0
- package/netlify.toml +9 -0
- package/package.json +69 -60
- package/renovate.json +52 -0
- package/scripts/preversion.js +4 -1
- package/src/BookNavigator/assets/bookmark-colors.js +1 -1
- package/src/BookNavigator/assets/button-base.js +4 -2
- package/src/BookNavigator/assets/ia-logo.js +17 -0
- package/src/BookNavigator/assets/icon_checkmark.js +1 -1
- package/src/BookNavigator/assets/icon_close.js +1 -1
- package/src/BookNavigator/book-navigator.js +586 -0
- package/src/BookNavigator/bookmarks/bookmark-button.js +3 -2
- package/src/BookNavigator/bookmarks/bookmark-edit.js +3 -4
- package/src/BookNavigator/bookmarks/bookmarks-list.js +2 -3
- package/src/BookNavigator/bookmarks/bookmarks-loginCTA.js +3 -8
- package/src/BookNavigator/bookmarks/bookmarks-provider.js +27 -17
- package/src/BookNavigator/bookmarks/ia-bookmarks.js +116 -67
- package/src/BookNavigator/delete-modal-actions.js +1 -1
- package/src/BookNavigator/downloads/downloads-provider.js +36 -21
- package/src/BookNavigator/downloads/downloads.js +41 -25
- package/src/BookNavigator/search/search-provider.js +49 -27
- package/src/BookNavigator/search/search-results.js +23 -9
- package/src/BookNavigator/sharing.js +27 -0
- package/src/BookNavigator/visual-adjustments/visual-adjustments-provider.js +11 -10
- package/src/BookNavigator/visual-adjustments/visual-adjustments.js +3 -3
- package/src/BookNavigator/volumes/volumes-provider.js +95 -0
- package/src/BookReader/BookModel.js +64 -34
- package/src/BookReader/DragScrollable.js +233 -0
- package/src/BookReader/Mode1Up.js +56 -351
- package/src/BookReader/Mode1UpLit.js +388 -0
- package/src/BookReader/Mode2Up.js +73 -1318
- package/src/BookReader/Mode2UpLit.js +776 -0
- package/src/BookReader/ModeCoordinateSpace.js +29 -0
- package/src/BookReader/ModeSmoothZoom.js +312 -0
- package/src/BookReader/ModeThumb.js +18 -12
- package/src/BookReader/Navbar/Navbar.js +12 -38
- package/src/BookReader/PageContainer.js +81 -6
- package/src/BookReader/ReduceSet.js +1 -1
- package/src/BookReader/Toolbar/Toolbar.js +10 -37
- package/src/BookReader/events.js +2 -3
- package/src/BookReader/options.js +24 -2
- package/src/BookReader/utils/HTMLDimensionsCacher.js +44 -0
- package/src/BookReader/utils/ScrollClassAdder.js +31 -0
- package/src/BookReader/utils/SelectionObserver.js +43 -0
- package/src/BookReader/utils.js +118 -13
- package/src/BookReader.js +423 -1056
- package/src/assets/icons/magnify-minus.svg +3 -7
- package/src/assets/icons/magnify-plus.svg +3 -7
- package/src/assets/icons/voice.svg +1 -0
- package/src/css/BookReader.scss +1 -5
- package/src/css/_BRBookmarks.scss +1 -1
- package/src/css/_BRComponent.scss +1 -1
- package/src/css/_BRmain.scss +16 -0
- package/src/css/_BRnav.scss +11 -38
- package/src/css/_BRpages.scss +149 -40
- package/src/css/_BRsearch.scss +67 -21
- package/src/css/_TextSelection.scss +87 -27
- package/src/css/_colorbox.scss +2 -2
- package/src/css/_controls.scss +20 -7
- package/src/css/_icons.scss +1 -1
- package/src/ia-bookreader/ia-bookreader.js +224 -0
- package/src/plugins/plugin.archive_analytics.js +3 -3
- package/src/plugins/plugin.autoplay.js +5 -11
- package/src/plugins/plugin.chapters.js +211 -186
- package/src/plugins/plugin.resume.js +3 -3
- package/src/plugins/plugin.text_selection.js +464 -134
- package/src/plugins/plugin.vendor-fullscreen.js +4 -4
- package/src/plugins/search/plugin.search.js +142 -125
- package/src/plugins/search/utils.js +43 -0
- package/src/plugins/search/view.js +33 -58
- package/src/plugins/tts/AbstractTTSEngine.js +46 -37
- package/src/plugins/tts/FestivalTTSEngine.js +13 -14
- package/src/plugins/tts/PageChunk.js +15 -21
- package/src/plugins/tts/PageChunkIterator.js +8 -12
- package/src/plugins/tts/WebTTSEngine.js +87 -71
- package/src/plugins/tts/plugin.tts.js +95 -126
- package/src/plugins/tts/utils.js +0 -25
- package/src/plugins/url/UrlPlugin.js +193 -0
- package/src/plugins/{plugin.url.js → url/plugin.url.js} +45 -16
- package/src/util/browserSniffing.js +22 -0
- package/src/util/docCookies.js +21 -2
- package/tests/e2e/README.md +37 -0
- package/tests/e2e/autoplay.test.js +2 -2
- package/tests/e2e/base.test.js +8 -16
- package/tests/e2e/helpers/base.js +53 -48
- package/tests/e2e/helpers/debug.js +1 -1
- package/tests/e2e/helpers/params.js +17 -0
- package/tests/e2e/helpers/rightToLeft.js +8 -14
- package/tests/e2e/helpers/search.js +73 -0
- package/tests/e2e/models/Navigation.js +20 -37
- package/tests/e2e/rightToLeft.test.js +4 -5
- package/tests/e2e/viewmode.test.js +40 -33
- package/tests/jest/BookNavigator/book-navigator.test.js +658 -0
- package/tests/jest/BookNavigator/bookmarks/bookmark-button.test.js +43 -0
- package/tests/{karma → jest}/BookNavigator/bookmarks/bookmark-edit.test.js +25 -26
- package/tests/{karma → jest}/BookNavigator/bookmarks/bookmarks-list.test.js +41 -42
- package/tests/jest/BookNavigator/bookmarks/ia-bookmarks.test.js +45 -0
- package/tests/jest/BookNavigator/downloads/downloads-provider.test.js +67 -0
- package/tests/jest/BookNavigator/downloads/downloads.test.js +53 -0
- package/tests/jest/BookNavigator/search/search-provider.test.js +167 -0
- package/tests/{karma → jest}/BookNavigator/search/search-results.test.js +109 -60
- package/tests/jest/BookNavigator/sharing/sharing-provider.test.js +49 -0
- package/tests/jest/BookNavigator/visual-adjustments.test.js +200 -0
- package/tests/jest/BookNavigator/volumes/volumes-provider.test.js +80 -0
- package/tests/{BookReader → jest/BookReader}/BookModel.test.js +74 -14
- package/tests/jest/BookReader/BookReaderPublicFunctions.test.js +193 -0
- package/tests/{BookReader → jest/BookReader}/ImageCache.test.js +4 -4
- package/tests/jest/BookReader/Mode1UpLit.test.js +73 -0
- package/tests/jest/BookReader/Mode2Up.test.js +98 -0
- package/tests/jest/BookReader/Mode2UpLit.test.js +190 -0
- package/tests/jest/BookReader/ModeCoordinateSpace.test.js +16 -0
- package/tests/jest/BookReader/ModeSmoothZoom.test.js +218 -0
- package/tests/jest/BookReader/ModeThumb.test.js +71 -0
- package/tests/{BookReader → jest/BookReader}/Navbar/Navbar.test.js +10 -10
- package/tests/{BookReader → jest/BookReader}/PageContainer.test.js +88 -6
- package/tests/{BookReader → jest/BookReader}/ReduceSet.test.js +1 -1
- package/tests/{BookReader → jest/BookReader}/Toolbar/Toolbar.test.js +2 -2
- package/tests/jest/BookReader/utils/HTMLDimensionsCacher.test.js +59 -0
- package/tests/jest/BookReader/utils/ScrollClassAdder.test.js +49 -0
- package/tests/jest/BookReader/utils/SelectionObserver.test.js +43 -0
- package/tests/{BookReader → jest/BookReader}/utils/classes.test.js +1 -1
- package/tests/jest/BookReader/utils.test.js +229 -0
- package/tests/jest/BookReader.keyboard.test.js +190 -0
- package/tests/{BookReader.options.test.js → jest/BookReader.options.test.js} +9 -1
- package/tests/{BookReader.test.js → jest/BookReader.test.js} +26 -37
- package/tests/{plugins → jest/plugins}/plugin.archive_analytics.test.js +2 -2
- package/tests/{plugins → jest/plugins}/plugin.autoplay.test.js +4 -4
- package/tests/jest/plugins/plugin.chapters.test.js +145 -0
- package/tests/{plugins → jest/plugins}/plugin.iframe.test.js +2 -2
- package/tests/{plugins → jest/plugins}/plugin.resume.test.js +3 -3
- package/tests/jest/plugins/plugin.text_selection.test.js +317 -0
- package/tests/{plugins → jest/plugins}/plugin.vendor-fullscreen.test.js +2 -2
- package/tests/{plugins → jest/plugins}/search/plugin.search.test.js +25 -47
- package/tests/{plugins → jest/plugins}/search/plugin.search.view.test.js +39 -6
- package/tests/jest/plugins/search/utils.js +25 -0
- package/tests/jest/plugins/search/utils.test.js +29 -0
- package/tests/{plugins → jest/plugins}/tts/AbstractTTSEngine.test.js +9 -9
- package/tests/{plugins → jest/plugins}/tts/FestivalTTSEngine.test.js +4 -4
- package/tests/{plugins → jest/plugins}/tts/PageChunk.test.js +1 -1
- package/tests/{plugins → jest/plugins}/tts/PageChunkIterator.test.js +3 -3
- package/tests/{plugins → jest/plugins}/tts/WebTTSEngine.test.js +47 -1
- package/tests/{plugins → jest/plugins}/tts/utils.test.js +1 -60
- package/tests/jest/plugins/url/UrlPlugin.test.js +190 -0
- package/tests/{plugins → jest/plugins/url}/plugin.url.test.js +53 -14
- package/tests/jest/setup.js +3 -0
- package/tests/{util → jest/util}/browserSniffing.test.js +1 -1
- package/tests/jest/util/docCookies.test.js +24 -0
- package/tests/{util → jest/util}/strings.test.js +1 -1
- package/tests/{utils.js → jest/utils.js} +38 -0
- package/webpack.config.js +11 -6
- package/.babelrc +0 -12
- package/.dependabot/config.yml +0 -6
- package/.testcaferc.json +0 -5
- package/BookReader/bookreader-component-bundle.js +0 -1450
- package/BookReader/bookreader-component-bundle.js.LICENSE.txt +0 -38
- package/BookReader/bookreader-component-bundle.js.map +0 -1
- package/BookReader/jquery-1.10.1.js +0 -2
- package/BookReader/jquery-1.10.1.js.LICENSE.txt +0 -24
- package/BookReader/plugins/plugin.menu_toggle.js +0 -2
- package/BookReader/plugins/plugin.menu_toggle.js.map +0 -1
- package/BookReader/plugins/plugin.mobile_nav.js +0 -2
- package/BookReader/plugins/plugin.mobile_nav.js.map +0 -1
- package/BookReaderDemo/bookreader-template-bundle.js +0 -7178
- package/BookReaderDemo/demo-plugin-menu-toggle.html +0 -34
- package/karma.conf.js +0 -23
- package/src/BookNavigator/BookModel.js +0 -14
- package/src/BookNavigator/BookNavigator.js +0 -446
- package/src/BookNavigator/assets/book-loader.js +0 -27
- package/src/BookNavigator/br-fullscreen-mgr.js +0 -83
- package/src/BookNavigator/search/a-search-result.js +0 -55
- package/src/BookReader/DebugConsole.js +0 -54
- package/src/BookReaderComponent/BookReaderComponent.js +0 -112
- package/src/ItemNavigator/ItemNavigator.js +0 -376
- package/src/ItemNavigator/providers/sharing.js +0 -29
- package/src/css/_MobileNav.scss +0 -194
- package/src/dragscrollable-br.js +0 -261
- package/src/plugins/menu_toggle/plugin.menu_toggle.js +0 -324
- package/src/plugins/plugin.mobile_nav.js +0 -287
- package/tests/BookReader/BookReaderPublicFunctions.test.js +0 -171
- package/tests/BookReader/DebugConsole.test.js +0 -25
- package/tests/BookReader/Mode1Up.test.js +0 -164
- package/tests/BookReader/Mode2Up.test.js +0 -247
- package/tests/BookReader/utils.test.js +0 -109
- package/tests/e2e/helpers/desktopSearch.js +0 -72
- package/tests/e2e/helpers/mobileSearch.js +0 -85
- package/tests/e2e/ia-production/ia-prod-base.js +0 -17
- package/tests/karma/BookNavigator/book-navigator.test.js +0 -132
- package/tests/karma/BookNavigator/search/search-provider.test.js +0 -23
- package/tests/karma/BookNavigator/visual-adjustments.test.js +0 -201
- package/tests/plugins/menu_toggle/plugin.menu_toggle.test.js +0 -68
- package/tests/plugins/plugin.chapters.test.js +0 -130
- package/tests/plugins/plugin.mobile_nav.test.js +0 -66
- package/tests/plugins/plugin.text_selection.test.js +0 -203
- package/tests/util/docCookies.test.js +0 -15
@@ -1,7 +1,9 @@
|
|
1
1
|
//@ts-check
|
2
|
-
import {
|
2
|
+
import { createDIVPageLayer } from '../BookReader/PageContainer.js';
|
3
|
+
import { SelectionObserver } from '../BookReader/utils/SelectionObserver.js';
|
3
4
|
import { applyVariables } from '../util/strings.js';
|
4
5
|
/** @typedef {import('../util/strings.js').StringWithVars} StringWithVars */
|
6
|
+
/** @typedef {import('../BookReader/PageContainer.js').PageContainer} PageContainer */
|
5
7
|
|
6
8
|
const BookReader = /** @type {typeof import('../BookReader').default} */(window.BookReader);
|
7
9
|
|
@@ -11,6 +13,8 @@ export const DEFAULT_OPTIONS = {
|
|
11
13
|
fullDjvuXmlUrl: null,
|
12
14
|
/** @type {StringWithVars} The URL to fetch a single page of the DJVU xml. Supports options.vars. Also has {{pageIndex}} */
|
13
15
|
singlePageDjvuXmlUrl: null,
|
16
|
+
/** Whether to fetch the XML as a jsonp */
|
17
|
+
jsonp: false,
|
14
18
|
};
|
15
19
|
/** @typedef {typeof DEFAULT_OPTIONS} TextSelectionPluginOptions */
|
16
20
|
|
@@ -36,25 +40,18 @@ export class Cache {
|
|
36
40
|
}
|
37
41
|
|
38
42
|
export class TextSelectionPlugin {
|
39
|
-
|
40
|
-
|
43
|
+
/**
|
44
|
+
* @param {'lr' | 'rl'} pageProgression 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
|
+
*/
|
48
|
+
constructor(options = DEFAULT_OPTIONS, optionVariables, pageProgression = 'lr') {
|
41
49
|
this.options = options;
|
42
50
|
this.optionVariables = optionVariables;
|
43
51
|
/**@type {PromiseLike<JQuery<HTMLElement>|undefined>} */
|
44
52
|
this.djvuPagesPromise = null;
|
45
|
-
|
46
|
-
|
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
|
-
}
|
53
|
+
/** Whether the book is right-to-left */
|
54
|
+
this.rtl = pageProgression === 'rl';
|
58
55
|
|
59
56
|
/** @type {Cache<{index: number, response: any}>} */
|
60
57
|
this.pageTextCache = new Cache();
|
@@ -64,15 +61,34 @@ export class TextSelectionPlugin {
|
|
64
61
|
* unusable. For now don't render text layer for pages with too many words.
|
65
62
|
*/
|
66
63
|
this.maxWordRendered = 2500;
|
64
|
+
|
65
|
+
this.selectionObserver = new SelectionObserver('.BRtextLayer', this._onSelectionChange);
|
66
|
+
}
|
67
|
+
|
68
|
+
/**
|
69
|
+
* @param {'started' | 'cleared'} type
|
70
|
+
* @param {HTMLElement} target
|
71
|
+
*/
|
72
|
+
_onSelectionChange = (type, target) => {
|
73
|
+
if (type === 'started') {
|
74
|
+
this.textSelectingMode(target);
|
75
|
+
} else if (type === 'cleared') {
|
76
|
+
this.defaultMode(target);
|
77
|
+
} else {
|
78
|
+
throw new Error(`Unknown type ${type}`);
|
79
|
+
}
|
67
80
|
}
|
68
81
|
|
69
82
|
init() {
|
83
|
+
this.selectionObserver.attach();
|
84
|
+
|
70
85
|
// Only fetch the full djvu xml if the single page url isn't there
|
71
86
|
if (this.options.singlePageDjvuXmlUrl) return;
|
72
87
|
this.djvuPagesPromise = $.ajax({
|
73
88
|
type: "GET",
|
74
89
|
url: applyVariables(this.options.fullDjvuXmlUrl, this.optionVariables),
|
75
|
-
dataType: "html",
|
90
|
+
dataType: this.options.jsonp ? "jsonp" : "html",
|
91
|
+
cache: true,
|
76
92
|
error: (e) => undefined
|
77
93
|
}).then((res) => {
|
78
94
|
try {
|
@@ -94,21 +110,21 @@ export class TextSelectionPlugin {
|
|
94
110
|
if (cachedEntry) {
|
95
111
|
return cachedEntry.response;
|
96
112
|
}
|
97
|
-
|
113
|
+
const res = await $.ajax({
|
98
114
|
type: "GET",
|
99
115
|
url: applyVariables(this.options.singlePageDjvuXmlUrl, this.optionVariables, { pageIndex: index }),
|
100
|
-
dataType: "html",
|
116
|
+
dataType: this.options.jsonp ? "jsonp" : "html",
|
117
|
+
cache: true,
|
101
118
|
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
119
|
});
|
120
|
+
try {
|
121
|
+
const xmlDoc = $.parseXML(res);
|
122
|
+
const result = xmlDoc && $(xmlDoc).find("OBJECT")[0];
|
123
|
+
this.pageTextCache.add({ index, response: result });
|
124
|
+
return result;
|
125
|
+
} catch (e) {
|
126
|
+
return undefined;
|
127
|
+
}
|
112
128
|
} else {
|
113
129
|
const XMLpagesArr = await this.djvuPagesPromise;
|
114
130
|
if (XMLpagesArr) return XMLpagesArr[index];
|
@@ -129,67 +145,87 @@ export class TextSelectionPlugin {
|
|
129
145
|
|
130
146
|
/**
|
131
147
|
* Applies mouse events when in default mode
|
132
|
-
* @param {
|
148
|
+
* @param {HTMLElement} textLayer
|
133
149
|
*/
|
134
|
-
defaultMode(
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
150
|
+
defaultMode(textLayer) {
|
151
|
+
const $pageContainer = $(textLayer).closest('.BRpagecontainer');
|
152
|
+
textLayer.style.pointerEvents = "none";
|
153
|
+
$pageContainer.find("img").css("pointer-events", "auto");
|
154
|
+
|
155
|
+
$(textLayer).off(".textSelectPluginHandler");
|
156
|
+
const startedMouseDown = this.mouseIsDown;
|
157
|
+
let skipNextMouseup = this.mouseIsDown;
|
158
|
+
if (startedMouseDown) {
|
159
|
+
textLayer.style.pointerEvents = "auto";
|
160
|
+
}
|
161
|
+
|
162
|
+
// Need to stop propagation to prevent DragScrollable from
|
163
|
+
// blocking selection
|
164
|
+
$(textLayer).on("mousedown.textSelectPluginHandler", (event) => {
|
165
|
+
this.mouseIsDown = true;
|
166
|
+
if ($(event.target).is(".BRwordElement, .BRspace")) {
|
167
|
+
event.stopPropagation();
|
168
|
+
}
|
169
|
+
});
|
170
|
+
|
171
|
+
$(textLayer).on("mouseup.textSelectPluginHandler", (event) => {
|
172
|
+
this.mouseIsDown = false;
|
173
|
+
textLayer.style.pointerEvents = "none";
|
174
|
+
if (skipNextMouseup) {
|
175
|
+
skipNextMouseup = false;
|
176
|
+
event.stopPropagation();
|
177
|
+
}
|
178
|
+
});
|
149
179
|
}
|
150
180
|
|
151
181
|
/**
|
152
|
-
*
|
153
|
-
* @param {
|
182
|
+
* This mode is active while there is a selection on the given textLayer
|
183
|
+
* @param {HTMLElement} textLayer
|
154
184
|
*/
|
155
|
-
textSelectingMode(
|
156
|
-
$(
|
157
|
-
|
158
|
-
|
159
|
-
|
185
|
+
textSelectingMode(textLayer) {
|
186
|
+
const $pageContainer = $(textLayer).closest('.BRpagecontainer');
|
187
|
+
// Make text layer consume all events
|
188
|
+
textLayer.style.pointerEvents = "all";
|
189
|
+
// Block img from getting long-press to save while selecting
|
190
|
+
$pageContainer.find("img").css("pointer-events", "none");
|
191
|
+
|
192
|
+
$(textLayer).off(".textSelectPluginHandler");
|
193
|
+
|
194
|
+
$(textLayer).on('mousedown.textSelectPluginHandler', (event) => {
|
195
|
+
this.mouseIsDown = true;
|
160
196
|
event.stopPropagation();
|
161
|
-
})
|
162
|
-
|
197
|
+
});
|
198
|
+
|
199
|
+
// Prevent page flip on click
|
200
|
+
$(textLayer).on('mouseup.textSelectPluginHandler', (event) => {
|
201
|
+
this.mouseIsDown = false;
|
163
202
|
event.stopPropagation();
|
164
|
-
|
165
|
-
$(svg).off(".textSelectPluginHandler");
|
166
|
-
this.defaultMode(svg); }
|
167
|
-
})
|
203
|
+
});
|
168
204
|
}
|
169
205
|
|
170
206
|
/**
|
171
|
-
* Initializes text selection modes if there is
|
207
|
+
* Initializes text selection modes if there is a text layer on the page
|
172
208
|
* @param {JQuery} $container
|
173
209
|
*/
|
174
210
|
stopPageFlip($container) {
|
175
|
-
/** @type {JQuery<
|
176
|
-
const $
|
177
|
-
if (!$
|
178
|
-
$
|
211
|
+
/** @type {JQuery<HTMLElement>} */
|
212
|
+
const $textLayer = $container.find('.BRtextLayer');
|
213
|
+
if (!$textLayer.length) return;
|
214
|
+
$textLayer.each((i, s) => this.defaultMode(s));
|
179
215
|
this.interceptCopy($container);
|
180
216
|
}
|
181
217
|
|
182
218
|
/**
|
183
|
-
* @param {
|
184
|
-
* @param {JQuery} $container
|
219
|
+
* @param {PageContainer} pageContainer
|
185
220
|
*/
|
186
|
-
async createTextLayer(
|
187
|
-
const
|
188
|
-
|
221
|
+
async createTextLayer(pageContainer) {
|
222
|
+
const pageIndex = pageContainer.page.index;
|
223
|
+
const $container = pageContainer.$container;
|
224
|
+
const $textLayers = $container.find('.BRtextLayer');
|
225
|
+
if ($textLayers.length) return;
|
189
226
|
const XMLpage = await this.getPageText(pageIndex);
|
190
227
|
if (!XMLpage) return;
|
191
|
-
|
192
|
-
const XMLheight = $(XMLpage).attr("height");
|
228
|
+
recursivelyAddCoords(XMLpage);
|
193
229
|
|
194
230
|
const totalWords = $(XMLpage).find("WORD").length;
|
195
231
|
if (totalWords > this.maxWordRendered) {
|
@@ -197,77 +233,174 @@ export class TextSelectionPlugin {
|
|
197
233
|
return;
|
198
234
|
}
|
199
235
|
|
200
|
-
const
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
$(
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
"left": "0",
|
236
|
+
const textLayer = createDIVPageLayer(pageContainer.page, 'BRtextLayer');
|
237
|
+
const ratioW = parseFloat(pageContainer.$container[0].style.width) / pageContainer.page.width;
|
238
|
+
const ratioH = parseFloat(pageContainer.$container[0].style.height) / pageContainer.page.height;
|
239
|
+
textLayer.style.transform = `scale(${ratioW}, ${ratioH})`;
|
240
|
+
textLayer.setAttribute("dir", this.rtl ? "rtl" : "ltr");
|
241
|
+
|
242
|
+
const ocrParagraphs = $(XMLpage).find("PARAGRAPH[coords]").toArray();
|
243
|
+
const paragEls = ocrParagraphs.map(p => {
|
244
|
+
const el = this.renderParagraph(p);
|
245
|
+
textLayer.appendChild(el);
|
246
|
+
return el;
|
212
247
|
});
|
213
248
|
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
const
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
249
|
+
// Fix up paragraph positions
|
250
|
+
const paragraphRects = determineRealRects(textLayer, '.BRparagraphElement');
|
251
|
+
let yAdded = 0;
|
252
|
+
for (const [ocrParagraph, paragEl] of zip(ocrParagraphs, paragEls)) {
|
253
|
+
const ocrParagBounds = $(ocrParagraph).attr("coords").split(",").map(parseFloat);
|
254
|
+
const realRect = paragraphRects.get(paragEl);
|
255
|
+
const [ocrLeft, , ocrRight, ocrTop] = ocrParagBounds;
|
256
|
+
const newStartMargin = this.rtl ? (realRect.right - ocrRight) : (ocrLeft - realRect.left);
|
257
|
+
const newTop = ocrTop - (realRect.top + yAdded);
|
223
258
|
|
224
|
-
|
259
|
+
paragEl.style[this.rtl ? 'marginRight' : 'marginLeft'] = `${newStartMargin}px`;
|
260
|
+
paragEl.style.marginTop = `${newTop}px`;
|
261
|
+
yAdded += newTop;
|
262
|
+
textLayer.appendChild(paragEl);
|
263
|
+
}
|
264
|
+
$container.append(textLayer);
|
265
|
+
this.stopPageFlip($container);
|
266
|
+
}
|
225
267
|
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
268
|
+
/**
|
269
|
+
* @param {HTMLElement} ocrParagraph
|
270
|
+
* @returns {HTMLParagraphElement}
|
271
|
+
*/
|
272
|
+
renderParagraph(ocrParagraph) {
|
273
|
+
const paragEl = document.createElement('p');
|
274
|
+
paragEl.classList.add('BRparagraphElement');
|
275
|
+
const [paragLeft, paragBottom, paragRight, paragTop] = $(ocrParagraph).attr("coords").split(",").map(parseFloat);
|
276
|
+
const wordHeightArr = [];
|
277
|
+
const lines = $(ocrParagraph).find("LINE[coords]").toArray();
|
278
|
+
if (!lines.length) return paragEl;
|
279
|
+
|
280
|
+
|
281
|
+
for (const [prevLine, line, nextLine] of lookAroundWindow(genMap(lines, augmentLine))) {
|
282
|
+
const isLastLineOfParagraph = line.ocrElement == lines[lines.length - 1];
|
283
|
+
const lineEl = document.createElement('span');
|
284
|
+
lineEl.classList.add('BRlineElement');
|
285
|
+
|
286
|
+
for (const [wordIndex, currWord] of line.words.entries()) {
|
287
|
+
const [, bottom, right, top] = $(currWord).attr("coords").split(',').map(parseFloat);
|
231
288
|
const wordHeight = bottom - top;
|
232
289
|
wordHeightArr.push(wordHeight);
|
233
290
|
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
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);
|
291
|
+
if (wordIndex == 0 && prevLine?.lastWord.textContent.trim().endsWith('-')) {
|
292
|
+
// ideally prefer the next line to determine the left position,
|
293
|
+
// since the previous line could be the first line of the paragraph
|
294
|
+
// and hence have an incorrectly indented first word.
|
295
|
+
// E.g. https://archive.org/details/driitaleofdaring00bachuoft/page/360/mode/2up
|
296
|
+
const [newLeft, , , ] = $((nextLine || prevLine).firstWord).attr("coords").split(',').map(parseFloat);
|
297
|
+
$(currWord).attr("coords", `${newLeft},${bottom},${right},${top}`);
|
257
298
|
}
|
258
299
|
|
259
|
-
|
260
|
-
|
261
|
-
|
300
|
+
const wordEl = document.createElement('span');
|
301
|
+
wordEl.setAttribute("class", "BRwordElement");
|
302
|
+
wordEl.textContent = currWord.textContent.trim();
|
303
|
+
|
304
|
+
if (wordIndex > 0) {
|
305
|
+
const space = document.createElement('span');
|
306
|
+
space.classList.add('BRspace');
|
307
|
+
space.textContent = ' ';
|
308
|
+
lineEl.append(space);
|
309
|
+
|
310
|
+
// Edge ignores empty elements (like BRspace), so add another
|
311
|
+
// space to ensure Edge's ReadAloud works correctly.
|
312
|
+
lineEl.appendChild(document.createTextNode(' '));
|
262
313
|
}
|
314
|
+
|
315
|
+
lineEl.appendChild(wordEl);
|
263
316
|
}
|
264
317
|
|
265
|
-
|
266
|
-
const
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
318
|
+
const hasHyphen = line.lastWord.textContent.trim().endsWith('-');
|
319
|
+
const lastWordEl = lineEl.children[lineEl.children.length - 1];
|
320
|
+
if (hasHyphen && !isLastLineOfParagraph) {
|
321
|
+
lastWordEl.textContent = lastWordEl.textContent.trim().slice(0, -1);
|
322
|
+
lastWordEl.classList.add('BRwordElement--hyphen');
|
323
|
+
}
|
324
|
+
|
325
|
+
paragEl.appendChild(lineEl);
|
326
|
+
if (!isLastLineOfParagraph && !hasHyphen) {
|
327
|
+
// Edge does not correctly have spaces between the lines.
|
328
|
+
paragEl.appendChild(document.createTextNode(' '));
|
329
|
+
}
|
330
|
+
}
|
331
|
+
|
332
|
+
wordHeightArr.sort((a, b) => a - b);
|
333
|
+
const paragWordHeight = wordHeightArr[Math.floor(wordHeightArr.length * 0.85)] + 4;
|
334
|
+
paragEl.style.left = `${paragLeft}px`;
|
335
|
+
paragEl.style.top = `${paragTop}px`;
|
336
|
+
paragEl.style.width = `${paragRight - paragLeft}px`;
|
337
|
+
paragEl.style.height = `${paragBottom - paragTop}px`;
|
338
|
+
paragEl.style.fontSize = `${paragWordHeight}px`;
|
339
|
+
|
340
|
+
// Fix up sizes - stretch/crush words as necessary using letter spacing
|
341
|
+
let wordRects = determineRealRects(paragEl, '.BRwordElement');
|
342
|
+
const ocrWords = $(ocrParagraph).find("WORD").toArray();
|
343
|
+
const wordEls = paragEl.querySelectorAll('.BRwordElement');
|
344
|
+
for (const [ocrWord, wordEl] of zip(ocrWords, wordEls)) {
|
345
|
+
const realRect = wordRects.get(wordEl);
|
346
|
+
const [left, , right ] = $(ocrWord).attr("coords").split(',').map(parseFloat);
|
347
|
+
let ocrWidth = right - left;
|
348
|
+
// Some books (eg theworksofplato01platiala) have a space _inside_ the <WORD>
|
349
|
+
// element. That makes it impossible to determine the correct positining
|
350
|
+
// of everything, but to avoid the BRspace's being width 0, which makes selection
|
351
|
+
// janky on Chrome Android, assume the space is the same width as one of the
|
352
|
+
// letters.
|
353
|
+
if (ocrWord.textContent.endsWith(' ')) {
|
354
|
+
ocrWidth = ocrWidth * (ocrWord.textContent.length - 1) / ocrWord.textContent.length;
|
355
|
+
}
|
356
|
+
const diff = ocrWidth - realRect.width;
|
357
|
+
wordEl.style.letterSpacing = `${diff / (ocrWord.textContent.length - 1)}px`;
|
358
|
+
}
|
359
|
+
|
360
|
+
// Stretch/crush lines as necessary using line spacing
|
361
|
+
// Recompute rects after letter spacing
|
362
|
+
wordRects = determineRealRects(paragEl, '.BRwordElement');
|
363
|
+
const spaceRects = determineRealRects(paragEl, '.BRspace');
|
364
|
+
|
365
|
+
const ocrLines = $(ocrParagraph).find("LINE[coords]").toArray();
|
366
|
+
const lineEls = Array.from(paragEl.querySelectorAll('.BRlineElement'));
|
367
|
+
|
368
|
+
let ySoFar = paragTop;
|
369
|
+
for (const [ocrLine, lineEl] of zip(ocrLines, lineEls)) {
|
370
|
+
// shift words using marginLeft to align with the correct x position
|
371
|
+
const words = $(ocrLine).find("WORD").toArray();
|
372
|
+
// const ocrLineLeft = Math.min(...words.map(w => parseFloat($(w).attr("coords").split(',')[0])));
|
373
|
+
let xSoFar = this.rtl ? paragRight : paragLeft;
|
374
|
+
for (const [ocrWord, wordEl] of zip(words, lineEl.querySelectorAll('.BRwordElement'))) {
|
375
|
+
// start of line, need to compute the offset relative to the OCR words
|
376
|
+
const wordRect = wordRects.get(wordEl);
|
377
|
+
const [ocrLeft, , ocrRight ] = $(ocrWord).attr("coords").split(',').map(parseFloat);
|
378
|
+
const diff = (this.rtl ? -(ocrRight - xSoFar) : ocrLeft - xSoFar);
|
379
|
+
|
380
|
+
if (wordEl.previousElementSibling) {
|
381
|
+
const space = wordEl.previousElementSibling;
|
382
|
+
space.style.letterSpacing = `${diff - spaceRects.get(space).width}px`;
|
383
|
+
} else {
|
384
|
+
wordEl.style[this.rtl ? 'paddingRight' : 'paddingLeft'] = `${diff}px`;
|
385
|
+
}
|
386
|
+
if (this.rtl) xSoFar -= diff + wordRect.width;
|
387
|
+
else xSoFar += diff + wordRect.width;
|
388
|
+
}
|
389
|
+
// And also fix y position
|
390
|
+
const ocrLineTop = Math.min(...words.map(w => parseFloat($(w).attr("coords").split(',')[3])));
|
391
|
+
const diff = ocrLineTop - ySoFar;
|
392
|
+
if (lineEl.previousElementSibling) {
|
393
|
+
lineEl.previousElementSibling.style.lineHeight = `${diff}px`;
|
394
|
+
ySoFar += diff;
|
395
|
+
}
|
396
|
+
}
|
397
|
+
|
398
|
+
// The last line will have a line height subtracting from the paragraph height
|
399
|
+
lineEls[lineEls.length - 1].style.lineHeight = `${paragBottom - ySoFar}px`;
|
400
|
+
|
401
|
+
// Edge does not include a newline for some reason when copying/pasting the <p> els
|
402
|
+
paragEl.appendChild(document.createElement('br'));
|
403
|
+
return paragEl;
|
271
404
|
}
|
272
405
|
}
|
273
406
|
|
@@ -275,12 +408,24 @@ export class BookreaderWithTextSelection extends BookReader {
|
|
275
408
|
init() {
|
276
409
|
const options = Object.assign({}, DEFAULT_OPTIONS, this.options.plugins.textSelection);
|
277
410
|
if (options.enabled) {
|
278
|
-
this.textSelectionPlugin = new TextSelectionPlugin(options, this.options.vars);
|
411
|
+
this.textSelectionPlugin = new TextSelectionPlugin(options, this.options.vars, this.pageProgression);
|
279
412
|
// Write this back; this way the plugin is the source of truth, and BR just
|
280
413
|
// contains a reference to it.
|
281
414
|
this.options.plugins.textSelection = options;
|
282
415
|
this.textSelectionPlugin.init();
|
416
|
+
|
417
|
+
new SelectionObserver('.BRtextLayer', (selectEvent) => {
|
418
|
+
// Track how often selection is used
|
419
|
+
if (selectEvent == 'started') {
|
420
|
+
this.archiveAnalyticsSendEvent?.('BookReader', 'SelectStart');
|
421
|
+
|
422
|
+
// Set a class on the page to avoid hiding it when zooming/etc
|
423
|
+
this.refs.$br.find('.BRpagecontainer--hasSelection').removeClass('BRpagecontainer--hasSelection');
|
424
|
+
$(window.getSelection().anchorNode).closest('.BRpagecontainer').addClass('BRpagecontainer--hasSelection');
|
425
|
+
}
|
426
|
+
}).attach();
|
283
427
|
}
|
428
|
+
|
284
429
|
super.init();
|
285
430
|
}
|
286
431
|
|
@@ -290,14 +435,199 @@ export class BookreaderWithTextSelection extends BookReader {
|
|
290
435
|
_createPageContainer(index) {
|
291
436
|
const pageContainer = super._createPageContainer(index);
|
292
437
|
// Disable if thumb mode; it's too janky
|
293
|
-
//
|
294
|
-
|
295
|
-
|
296
|
-
if (this.mode !== this.constModeThumb && (index >= 0 && index <= lastPageIndex)) {
|
297
|
-
this.textSelectionPlugin?.createTextLayer(index, pageContainer.$container);
|
438
|
+
// .page can be null for "pre-cover" region
|
439
|
+
if (this.mode !== this.constModeThumb && pageContainer.page) {
|
440
|
+
this.textSelectionPlugin?.createTextLayer(pageContainer);
|
298
441
|
}
|
299
442
|
return pageContainer;
|
300
443
|
}
|
301
444
|
}
|
302
445
|
window.BookReader = BookreaderWithTextSelection;
|
303
446
|
export default BookreaderWithTextSelection;
|
447
|
+
|
448
|
+
|
449
|
+
/**
|
450
|
+
* @param {HTMLElement} parentEl
|
451
|
+
* @param {string} selector
|
452
|
+
* @returns {Map<Element, Rect>}
|
453
|
+
*/
|
454
|
+
function determineRealRects(parentEl, selector) {
|
455
|
+
const initals = {
|
456
|
+
position: parentEl.style.position,
|
457
|
+
visibility: parentEl.style.visibility,
|
458
|
+
top: parentEl.style.top,
|
459
|
+
left: parentEl.style.left,
|
460
|
+
transform: parentEl.style.transform,
|
461
|
+
};
|
462
|
+
parentEl.style.position = 'absolute';
|
463
|
+
parentEl.style.visibility = 'hidden';
|
464
|
+
parentEl.style.top = '0';
|
465
|
+
parentEl.style.left = '0';
|
466
|
+
parentEl.style.transform = 'none';
|
467
|
+
document.body.appendChild(parentEl);
|
468
|
+
const rects = new Map(
|
469
|
+
Array.from(parentEl.querySelectorAll(selector))
|
470
|
+
.map(wordEl => {
|
471
|
+
const origRect = wordEl.getBoundingClientRect();
|
472
|
+
return [wordEl, new Rect(
|
473
|
+
origRect.left + window.scrollX,
|
474
|
+
origRect.top + window.scrollY,
|
475
|
+
origRect.width,
|
476
|
+
origRect.height,
|
477
|
+
)];
|
478
|
+
})
|
479
|
+
);
|
480
|
+
document.body.removeChild(parentEl);
|
481
|
+
Object.assign(parentEl.style, initals);
|
482
|
+
return rects;
|
483
|
+
}
|
484
|
+
|
485
|
+
/**
|
486
|
+
* @param {HTMLElement} line
|
487
|
+
*/
|
488
|
+
function augmentLine(line) {
|
489
|
+
const words = $(line).find("WORD").toArray();
|
490
|
+
return {
|
491
|
+
ocrElement: line,
|
492
|
+
words,
|
493
|
+
firstWord: words[0],
|
494
|
+
lastWord: words[words.length - 1],
|
495
|
+
};
|
496
|
+
}
|
497
|
+
|
498
|
+
/**
|
499
|
+
* @template TFrom, TTo
|
500
|
+
* Generator version of map
|
501
|
+
* @param {Iterable<TFrom>} gen
|
502
|
+
* @param {function(TFrom): TTo} fn
|
503
|
+
* @returns {Iterable<TTo>}
|
504
|
+
*/
|
505
|
+
export function* genMap(gen, fn) {
|
506
|
+
for (const x of gen) yield fn(x);
|
507
|
+
}
|
508
|
+
|
509
|
+
/**
|
510
|
+
* @template T
|
511
|
+
* Generator that provides a sliding window of 3 elements,
|
512
|
+
* prev, current, and next.
|
513
|
+
* @param {Iterable<T>} gen
|
514
|
+
* @returns {Iterable<[T | undefined, T, T | undefined]>}
|
515
|
+
*/
|
516
|
+
export function* lookAroundWindow(gen) {
|
517
|
+
let prev = undefined;
|
518
|
+
let cur = undefined;
|
519
|
+
let next = undefined;
|
520
|
+
for (const x of gen) {
|
521
|
+
if (typeof cur !== 'undefined') {
|
522
|
+
next = x;
|
523
|
+
yield [prev, cur, next];
|
524
|
+
}
|
525
|
+
prev = cur;
|
526
|
+
cur = x;
|
527
|
+
next = undefined;
|
528
|
+
}
|
529
|
+
|
530
|
+
if (typeof cur !== 'undefined') {
|
531
|
+
yield [prev, cur, next];
|
532
|
+
}
|
533
|
+
}
|
534
|
+
|
535
|
+
/**
|
536
|
+
* @template T1, T2
|
537
|
+
* Lazy zip implementation to avoid importing lodash
|
538
|
+
* Expects iterators to be of the same length
|
539
|
+
* @param {Iterable<T1>} gen1
|
540
|
+
* @param {Iterable<T2>} gen2
|
541
|
+
* @returns {Iterable<[T1, T2]>}
|
542
|
+
*/
|
543
|
+
export function* zip(gen1, gen2) {
|
544
|
+
const it1 = gen1[Symbol.iterator]();
|
545
|
+
const it2 = gen2[Symbol.iterator]();
|
546
|
+
while (true) {
|
547
|
+
const r1 = it1.next();
|
548
|
+
const r2 = it2.next();
|
549
|
+
if (r1.done && r2.done) {
|
550
|
+
return;
|
551
|
+
}
|
552
|
+
if (r1.done || r2.done) {
|
553
|
+
throw new Error('zip: one of the iterators is done');
|
554
|
+
}
|
555
|
+
yield [r1.value, r2.value];
|
556
|
+
}
|
557
|
+
}
|
558
|
+
|
559
|
+
/**
|
560
|
+
* [left, bottom, right, top]
|
561
|
+
* @param {Array<[number, number, number, number]>} bounds
|
562
|
+
* @returns {[number, number, number, number]}
|
563
|
+
*/
|
564
|
+
function determineBounds(bounds) {
|
565
|
+
let leftMost = Infinity;
|
566
|
+
let bottomMost = -Infinity;
|
567
|
+
let rightMost = -Infinity;
|
568
|
+
let topMost = Infinity;
|
569
|
+
|
570
|
+
for (const [left, bottom, right, top] of bounds) {
|
571
|
+
leftMost = Math.min(leftMost, left);
|
572
|
+
bottomMost = Math.max(bottomMost, bottom);
|
573
|
+
rightMost = Math.max(rightMost, right);
|
574
|
+
topMost = Math.min(topMost, top);
|
575
|
+
}
|
576
|
+
|
577
|
+
return [leftMost, bottomMost, rightMost, topMost];
|
578
|
+
}
|
579
|
+
|
580
|
+
/**
|
581
|
+
* Recursively traverses the XML tree and adds coords
|
582
|
+
* which are the bounding box of all child coords
|
583
|
+
* @param {Element} xmlEl
|
584
|
+
*/
|
585
|
+
function recursivelyAddCoords(xmlEl) {
|
586
|
+
if ($(xmlEl).attr('coords') || !xmlEl.children) {
|
587
|
+
return;
|
588
|
+
}
|
589
|
+
|
590
|
+
const children = $(xmlEl).children().toArray();
|
591
|
+
if (children.length === 0) {
|
592
|
+
return;
|
593
|
+
}
|
594
|
+
|
595
|
+
for (const child of children) {
|
596
|
+
recursivelyAddCoords(child);
|
597
|
+
}
|
598
|
+
|
599
|
+
const childCoords = [];
|
600
|
+
|
601
|
+
for (const child of children) {
|
602
|
+
if (!$(child).attr('coords')) continue;
|
603
|
+
childCoords.push($(child).attr('coords').split(',').map(parseFloat));
|
604
|
+
}
|
605
|
+
|
606
|
+
const boundingCoords = determineBounds(childCoords);
|
607
|
+
if (Math.abs(boundingCoords[0]) != Infinity) {
|
608
|
+
$(xmlEl).attr('coords', boundingCoords.join(','));
|
609
|
+
}
|
610
|
+
}
|
611
|
+
|
612
|
+
/**
|
613
|
+
* Basically a polyfill for the native DOMRect class
|
614
|
+
*/
|
615
|
+
class Rect {
|
616
|
+
/**
|
617
|
+
* @param {number} x
|
618
|
+
* @param {number} y
|
619
|
+
* @param {number} width
|
620
|
+
* @param {number} height
|
621
|
+
*/
|
622
|
+
constructor(x, y, width, height) {
|
623
|
+
this.x = x;
|
624
|
+
this.y = y;
|
625
|
+
this.width = width;
|
626
|
+
this.height = height;
|
627
|
+
}
|
628
|
+
|
629
|
+
get right() { return this.x + this.width; }
|
630
|
+
get bottom() { return this.y + this.height; }
|
631
|
+
get top() { return this.y; }
|
632
|
+
get left() { return this.x; }
|
633
|
+
}
|