@internetarchive/bookreader 5.0.0-58 → 5.0.0-59

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 (50) hide show
  1. package/BookReader/BookReader.css +110 -39
  2. package/BookReader/BookReader.js +1 -1
  3. package/BookReader/BookReader.js.LICENSE.txt +0 -20
  4. package/BookReader/BookReader.js.map +1 -1
  5. package/BookReader/ia-bookreader-bundle.js +1 -1
  6. package/BookReader/ia-bookreader-bundle.js.map +1 -1
  7. package/BookReader/plugins/plugin.archive_analytics.js +1 -1
  8. package/BookReader/plugins/plugin.archive_analytics.js.map +1 -1
  9. package/BookReader/plugins/plugin.autoplay.js +1 -1
  10. package/BookReader/plugins/plugin.autoplay.js.map +1 -1
  11. package/BookReader/plugins/plugin.resume.js +1 -1
  12. package/BookReader/plugins/plugin.resume.js.map +1 -1
  13. package/BookReader/plugins/plugin.tts.js +1 -1
  14. package/BookReader/plugins/plugin.tts.js.map +1 -1
  15. package/BookReader/plugins/plugin.url.js +1 -1
  16. package/BookReader/plugins/plugin.url.js.map +1 -1
  17. package/BookReaderDemo/BookReaderJSAutoplay.js +4 -1
  18. package/BookReaderDemo/BookReaderJSSimple.js +1 -0
  19. package/BookReaderDemo/IADemoBr.js +1 -2
  20. package/CHANGELOG.md +4 -0
  21. package/package.json +1 -1
  22. package/src/BookReader/BookModel.js +59 -1
  23. package/src/BookReader/Mode1UpLit.js +13 -70
  24. package/src/BookReader/Mode2Up.js +72 -1332
  25. package/src/BookReader/Mode2UpLit.js +774 -0
  26. package/src/BookReader/ModeCoordinateSpace.js +29 -0
  27. package/src/BookReader/ModeSmoothZoom.js +32 -0
  28. package/src/BookReader/options.js +8 -2
  29. package/src/BookReader/utils.js +16 -0
  30. package/src/BookReader.js +24 -217
  31. package/src/css/_BRBookmarks.scss +1 -1
  32. package/src/css/_BRmain.scss +14 -0
  33. package/src/css/_BRpages.scss +113 -41
  34. package/src/plugins/plugin.autoplay.js +1 -6
  35. package/src/plugins/tts/WebTTSEngine.js +2 -2
  36. package/src/plugins/tts/plugin.tts.js +3 -17
  37. package/src/plugins/tts/utils.js +0 -16
  38. package/tests/e2e/helpers/base.js +20 -20
  39. package/tests/e2e/helpers/rightToLeft.js +4 -10
  40. package/tests/e2e/viewmode.test.js +10 -8
  41. package/tests/jest/BookReader/BookModel.test.js +25 -0
  42. package/tests/jest/BookReader/BookReaderPublicFunctions.test.js +28 -11
  43. package/tests/jest/BookReader/Mode1UpLit.test.js +0 -19
  44. package/tests/jest/BookReader/Mode2Up.test.js +55 -225
  45. package/tests/jest/BookReader/Mode2UpLit.test.js +190 -0
  46. package/tests/jest/BookReader/ModeCoordinateSpace.test.js +16 -0
  47. package/tests/jest/BookReader/ModeSmoothZoom.test.js +26 -0
  48. package/tests/jest/BookReader/Navbar/Navbar.test.js +3 -3
  49. package/tests/jest/BookReader/utils.test.js +32 -1
  50. package/tests/jest/plugins/tts/utils.test.js +0 -34
@@ -1,21 +1,10 @@
1
1
  // @ts-check
2
- // effect.js gives acces to extra easing function (e.g. easeInSine)
3
- import 'jquery-ui/ui/effect.js';
4
- import { clamp } from './utils.js';
5
- import { EVENTS } from './events.js';
6
- import { ModeSmoothZoom } from "./ModeSmoothZoom.js";
7
- import { HTMLDimensionsCacher } from './utils/HTMLDimensionsCacher.js';
2
+ import { Mode2UpLit } from './Mode2UpLit.js';
8
3
  import { DragScrollable } from './DragScrollable.js';
9
- import { ScrollClassAdder } from './utils/ScrollClassAdder.js';
10
-
11
4
  /** @typedef {import('../BookReader.js').default} BookReader */
12
5
  /** @typedef {import('./BookModel.js').BookModel} BookModel */
13
6
  /** @typedef {import('./BookModel.js').PageIndex} PageIndex */
14
- /** @typedef {import('./options.js').BookReaderOptions} BookReaderOptions */
15
- /** @typedef {import('./PageContainer.js').PageContainer} PageContainer */
16
- /** @typedef {import('./ModeSmoothZoom').SmoothZoomable} SmoothZoomable */
17
7
 
18
- /** @implements {SmoothZoomable} */
19
8
  export class Mode2Up {
20
9
  /**
21
10
  * @param {BookReader} br
@@ -24,1342 +13,93 @@ export class Mode2Up {
24
13
  constructor(br, bookModel) {
25
14
  this.br = br;
26
15
  this.book = bookModel;
27
-
28
- /** @type {HTMLDivElement} */
29
- this.leafEdgeL = null;
30
- /** @type {HTMLDivElement} */
31
- this.leafEdgeR = null;
32
-
33
- /** @type {{ [index: number]: PageContainer }} */
34
- this.pageContainers = {};
35
-
36
- /** @type {ModeSmoothZoom} */
37
- this.smoothZoomer = null;
38
- this._scale = 1;
39
- this.scaleCenter = { x: 0.5, y: 0.5 };
40
-
41
- /** @type {ScrollClassAdder} */
42
- this.scrollClassAdder = null;
43
- }
44
-
45
- get $container() {
46
- return this.br.refs.$brContainer[0];
47
- }
48
- get $visibleWorld() {
49
- return this.br.refs.$brTwoPageView?.[0];
50
- }
51
-
52
- get scale() { return this._scale; }
53
- set scale(newVal) {
54
- this.$visibleWorld.style.transform = `scale(${newVal})`;
55
- this.updateViewportOnZoom(newVal, this._scale);
56
- this._scale = newVal;
57
- }
58
-
59
- /**
60
- * @param {PageIndex} index
61
- */
62
- jumpToIndex(index) {
63
- // By checking against min/max we do nothing if requested index
64
- // is current
65
- if (index < Math.min(this.br.twoPage.currentIndexL, this.br.twoPage.currentIndexR)) {
66
- this.flipBackToIndex(index);
67
- } else if (index > Math.max(this.br.twoPage.currentIndexL, this.br.twoPage.currentIndexR)) {
68
- this.flipFwdToIndex(index);
69
- }
70
- }
71
-
72
- /**
73
- * Draws book spread,
74
- * sets event handlers,
75
- * sets: `this.br.displayedIndices`
76
- * updates toolbar zoom
77
- * Important: `this.br.refs.$brTwoPageView` parent container must be emptied before calling
78
- */
79
- drawLeafs() {
80
- const $twoPageViewEl = this.br.refs.$brTwoPageView;
81
- const indexL = this.br.twoPage.currentIndexL;
82
- const indexR = this.br.twoPage.currentIndexR;
83
-
84
- this.createPageContainer(indexL).$container
85
- .css(this.leftLeafCss)
86
- .appendTo($twoPageViewEl);
87
- this.createPageContainer(indexR).$container
88
- .css(this.rightLeafCss)
89
- .appendTo($twoPageViewEl);
90
-
91
- this.displayedIndices = [this.br.twoPage.currentIndexL, this.br.twoPage.currentIndexR];
92
- this.br.displayedIndices = this.displayedIndices;
93
- this.br.trigger('pageChanged');
94
- }
95
-
96
- /**
97
- * @param {1} direction
98
- */
99
- zoom(direction) {
100
- this.br.stopFlipAnimations();
101
-
102
- // Recalculate autofit factors
103
- this.calculateReductionFactors();
104
-
105
- // Get new zoom state
106
- const reductionFactor = this.br.nextReduce(this.br.reduce, direction, this.br.twoPage.reductionFactors);
107
- if ((this.br.reduce == reductionFactor.reduce) && (this.br.twoPage.autofit == reductionFactor.autofit)) {
108
- // Same zoom
109
- return;
110
- }
111
- this.br.twoPage.autofit = reductionFactor.autofit;
112
- this.br.reduce = reductionFactor.reduce;
113
- this.br.pageScale = this.br.reduce; // preserve current reduce
114
-
115
- // Preserve view center position
116
- const oldCenter = this.getViewCenter();
117
-
118
- // Prepare view with new center to minimize visual glitches
119
- const drawNewSpread = true;
120
- this.prepare(oldCenter.percentageX, oldCenter.percentageY, drawNewSpread);
121
- }
122
-
123
- /**
124
- * Resize spread containers, does not prefetch
125
- * uses `this.br.twoPage` properties
126
- */
127
- resizeSpread() {
128
- this.br.resizeBRcontainer(false); // no animation
129
- this.calculateSpreadSize();
130
-
131
- this.br.refs?.$brTwoPageView.css(this.mainContainerCss);
132
- this.centerView(); // let function self adjust
133
-
134
- $(this.br.twoPage.coverDiv).css(this.spreadCoverCss); // click sheath is memoized somehow
135
- const $spreadLayers = this.br.refs.$brTwoPageView;
136
-
137
- $spreadLayers.find('.BRleafEdgeR')?.css(this.leafEdgeRCss);
138
- $spreadLayers.find('.BRleafEdgeL')?.css(this.leafEdgeLCss);
139
- $spreadLayers.find('.BRgutter')?.css(this.spineCss);
140
-
141
- const indexL = this.br.twoPage.currentIndexL;
142
- const indexR = this.br.twoPage.currentIndexR;
143
- this.pageContainers[indexL].$container.css(this.leftLeafCss);
144
- this.pageContainers[indexR].$container.css(this.rightLeafCss);
145
- }
146
-
147
- /**
148
- * @param {number} centerPercentageX
149
- * @param {number} centerPercentageY
150
- * @param {Boolean} drawNewSpread
151
- */
152
- prepare(centerPercentageX, centerPercentageY, drawNewSpread = false) {
153
- // Some decisions about two page view:
154
- //
155
- // Both pages will be displayed at the same height, even if they were different physical/scanned
156
- // sizes. This simplifies the animation (from a design as well as technical standpoint). We
157
- // examine the page aspect ratios (in calculateSpreadSize) and use the page with the most "normal"
158
- // aspect ratio to determine the height.
159
- //
160
- // The two page view div is resized to keep the middle of the book in the middle of the div
161
- // even as the page sizes change. To e.g. keep the middle of the book in the middle of the BRcontent
162
- // div requires adjusting the offset of BRtwpageview and/or scrolling in BRcontent.
163
- const startingReduce = this.br.reduce;
164
- const startingIndices = this.br.displayedIndices;
165
-
166
- this.br.refs.$brContainer.empty();
167
- this.br.refs.$brContainer.css('overflow', 'auto');
168
-
169
- // We want to display two facing pages. We may be missing
170
- // one side of the spread because it is the first/last leaf,
171
- // foldouts, missing pages, etc
172
-
173
- const targetLeaf = clamp(this.br.firstIndex, this.br.firstDisplayableIndex(), this.br.lastDisplayableIndex());
174
- const currentSpreadIndices = this.book.getSpreadIndices(targetLeaf);
175
- this.br.twoPage.currentIndexL = currentSpreadIndices[0];
176
- this.br.twoPage.currentIndexR = currentSpreadIndices[1];
177
-
178
- this.calculateSpreadSize(); //sets this.br.reduce, twoPage.width, twoPage.height and others
179
-
180
- /* check if calculations have changed that warrant a new book draw */
181
- const sameReducer = startingReduce == this.br.reduce;
182
- const sameStart = startingIndices == this.br.displayedIndices;
183
- const hasNewDisplayPagesOrDimensions = !sameStart || (sameStart && !sameReducer);
184
-
185
- if (drawNewSpread || hasNewDisplayPagesOrDimensions) {
186
- this.prunePageContainers();
187
- this.prefetch();
188
- }
189
-
190
- // Add the two page view
191
- // $$$ Can we get everything set up and then append?
192
- this.br.refs.$brTwoPageView = this.br.refs.$brTwoPageView || $('<div class="BRtwopageview"></div>');
193
- const $twoPageViewEl = this.br.refs.$brTwoPageView;
194
- $twoPageViewEl.empty();
195
- $twoPageViewEl[0].style.transformOrigin = '0 0';
196
- this.br.refs.$brContainer.append($twoPageViewEl);
197
-
198
- // Attaches to first child, so must come after we add the page view
199
- this.dragScrollable = this.dragScrollable || new DragScrollable(this.br.refs.$brContainer[0], {
200
- preventDefault: true,
201
- // Only handle mouse events; let browser/HammerJS handle touch
202
- dragstart: 'mousedown',
203
- dragcontinue: 'mousemove',
204
- dragend: 'mouseup',
205
- });
206
-
207
- this.attachMouseHandlers();
208
-
209
- // $$$ calculate container size first
210
- this.br.refs?.$brTwoPageView.css(this.mainContainerCss);
211
-
212
- // This will trump the incoming coordinates
213
- // in order to center book when zooming out
214
- if (this.br.twoPage.totalWidth < this.br.refs.$brContainer.prop('clientWidth')) {
215
- centerPercentageX = 0.5;
216
- }
217
- if (this.br.twoPage.totalHeight < this.br.refs.$brContainer.prop('clientHeight')) {
218
- centerPercentageY = 0.5;
219
- }
220
-
221
- this.centerView(centerPercentageX, centerPercentageY);
222
-
223
- // then set
224
- this.br.twoPage.coverDiv = document.createElement('div');
225
- $(this.br.twoPage.coverDiv).attr('class', 'BRbookcover').css(this.spreadCoverCss).appendTo(this.br.refs.$brTwoPageView);
226
-
227
- this.leafEdgeR = document.createElement('div');
228
- this.leafEdgeR.className = 'BRleafEdgeR';
229
- $(this.leafEdgeR).css(this.leafEdgeRCss).appendTo(this.br.refs.$brTwoPageView);
230
-
231
- this.leafEdgeL = document.createElement('div');
232
- this.leafEdgeL.className = 'BRleafEdgeL';
233
- $(this.leafEdgeL).css(this.leafEdgeLCss).appendTo(this.br.refs.$brTwoPageView);
234
-
235
- const div = document.createElement('div');
236
- $(div).attr('class', 'BRgutter').css(this.spineCss).appendTo(this.br.refs.$brTwoPageView);
237
-
238
- this.preparePopUp();
239
-
240
- this.br.displayedIndices = [];
241
-
242
- this.drawLeafs();
243
- this.br.updateBrClasses();
244
-
245
- this.smoothZoomer = this.smoothZoomer || new ModeSmoothZoom(this);
246
- this.smoothZoomer.attach();
247
- if (!this.scrollClassAdder) {
248
- this.scrollClassAdder = new ScrollClassAdder(this.$container, 'BRscrolling-active');
249
- }
250
- this.scrollClassAdder.detach();
251
- this.scrollClassAdder.element = this.$container;
252
- this.scrollClassAdder.attach();
253
-
254
- this.htmlDimensionsCacher = this.htmlDimensionsCacher || new HTMLDimensionsCacher(this.$container);
255
- }
256
-
257
- unprepare() {
258
- // Mode2Up attaches these listeners to the main BR container, so we need to
259
- // detach these or it will cause issues for the other modes.
260
- this.smoothZoomer.detach();
261
- this.scrollClassAdder.detach();
262
- }
263
-
264
- /**
265
- * @param {number} newScale
266
- * @param {number} oldScale
267
- */
268
- updateViewportOnZoom(newScale, oldScale) {
269
- const container = this.br.refs.$brContainer[0];
270
- const { scrollTop: T, scrollLeft: L } = container;
271
- const W = this.htmlDimensionsCacher.clientWidth;
272
- const H = this.htmlDimensionsCacher.clientHeight;
273
-
274
- // Scale factor change
275
- const F = newScale / oldScale;
276
-
277
- // Where in the viewport the zoom is centered on
278
- const XPOS = this.scaleCenter.x;
279
- const YPOS = this.scaleCenter.y;
280
- const oldCenter = {
281
- x: L + XPOS * W,
282
- y: T + YPOS * H,
283
- };
284
- const newCenter = {
285
- x: F * oldCenter.x,
286
- y: F * oldCenter.y,
287
- };
288
- container.scrollTop = newCenter.y - YPOS * H;
289
- container.scrollLeft = newCenter.x - XPOS * W;
290
-
291
- // Also update the visible page containers to load in highres if necessary
292
- this.pageContainers[this.br.twoPage.currentIndexL]?.update({ reduce: this.br.reduce / newScale });
293
- this.pageContainers[this.br.twoPage.currentIndexR]?.update({ reduce: this.br.reduce / newScale });
294
- }
295
-
296
- prunePageContainers() {
297
- for (const index in this.pageContainers) {
298
- if ((index != this.br.twoPage.currentIndexL) && (index != this.br.twoPage.currentIndexR)) {
299
- $(this.pageContainers[index].$container).remove();
300
- }
301
- if ((index < this.br.twoPage.currentIndexL - 4) || (index > this.br.twoPage.currentIndexR + 4)) {
302
- delete this.pageContainers[index];
303
- }
304
- }
305
- }
306
-
307
- /**
308
- * This function prepares the "View Page n" popup that shows while the mouse is
309
- * over the left/right "stack of sheets" edges. It also binds the mouse
310
- * events for these divs.
311
- */
312
- preparePopUp() {
313
- this.br.twoPagePopUp = document.createElement('div');
314
- this.br.twoPagePopUp.className = 'BRtwoPagePopUp';
315
- $(this.br.twoPagePopUp).css({
316
- zIndex: '1000'
317
- }).appendTo(this.br.refs.$brContainer);
318
- $(this.br.twoPagePopUp).hide();
319
-
320
- const leafEdges = [
321
- {
322
- $leafEdge: $(this.leafEdgeL),
323
- /** @type {function(number): PageIndex} */
324
- jumpIndexForPageX: this.jumpIndexForLeftEdgePageX.bind(this),
325
- leftOffset: () => -$(this.br.twoPagePopUp).width() + 120,
326
- },
327
- {
328
- $leafEdge: $(this.leafEdgeR),
329
- /** @type {function(number): PageIndex} */
330
- jumpIndexForPageX: this.jumpIndexForRightEdgePageX.bind(this),
331
- leftOffset: () => -120,
332
- },
333
- ];
334
-
335
- for (const { $leafEdge, jumpIndexForPageX, leftOffset } of leafEdges) {
336
- $leafEdge.on('mouseenter', () => $(this.br.twoPagePopUp).show());
337
- $leafEdge.on('mouseleave', () => $(this.br.twoPagePopUp).hide());
338
-
339
- $leafEdge.on('click', e => {
340
- this.br.trigger(EVENTS.stop);
341
- this.br.jumpToIndex(jumpIndexForPageX(e.pageX));
342
- });
343
-
344
- $leafEdge.on('mousemove', e => {
345
- const jumpIndex = clamp(jumpIndexForPageX(e.pageX), 0, this.book.getNumLeafs() - 1);
346
- $(this.br.twoPagePopUp).text(`View ${this.book.getPageName(jumpIndex)}`);
347
-
348
- // $$$ TODO: Make sure popup is positioned so that it is in view
349
- // (https://bugs.edge.launchpad.net/gnubook/+bug/327456)
350
- $(this.br.twoPagePopUp).css({
351
- left: `${e.pageX - this.br.refs.$brContainer.offset().left + this.br.refs.$brContainer.scrollLeft() + leftOffset()}px`,
352
- top: `${e.pageY - this.br.refs.$brContainer.offset().top + this.br.refs.$brContainer.scrollTop()}px`
16
+ this.mode2UpLit = new Mode2UpLit(bookModel, br);
17
+ this.mode2UpLit.flipSpeed = br.flipSpeed;
18
+
19
+ /** @private */
20
+ this.$el = $(this.mode2UpLit)
21
+ .attr('autoFit', this.br.options.twoPage.autofit)
22
+ // We CANNOT use `br-mode-2up` as a class, because it's the same
23
+ // as the name of the web component, and the webcomponents polyfill
24
+ // uses the name of component as a class for style scoping 😒
25
+ .addClass('br-mode-2up__root BRmode2up');
26
+
27
+ /** Has mode2up ever been rendered before? */
28
+ this.everShown = false;
29
+ }
30
+
31
+ /**
32
+ * This is called when we switch into this mode
33
+ */
34
+ prepare() {
35
+ const startLeaf = this.br.currentIndex();
36
+ this.br.refs.$brContainer
37
+ .empty()
38
+ .css({ overflow: 'hidden' })
39
+ .append(this.$el);
40
+ this.mode2UpLit.style.opacity = '0';
41
+
42
+ // Need this in a setTimeout so that it happens after the browser has _actually_
43
+ // appended the element to the DOM
44
+ setTimeout(async () => {
45
+ if (!this.everShown) {
46
+ this.mode2UpLit.initFirstRender(startLeaf);
47
+ this.everShown = true;
48
+ this.mode2UpLit.requestUpdate();
49
+ await this.mode2UpLit.updateComplete;
50
+
51
+ new DragScrollable(this.mode2UpLit, {
52
+ preventDefault: true,
53
+ dragSelector: '.br-mode-2up__book',
54
+ // Only handle mouse events; let browser/HammerJS handle touch
55
+ dragstart: 'mousedown',
56
+ dragcontinue: 'mousemove',
57
+ dragend: 'mouseup',
353
58
  });
354
- });
355
- }
356
- }
357
-
358
- setSpreadIndices() {
359
- const targetLeaf = clamp(this.br.firstIndex, this.br.firstDisplayableIndex(), this.br.lastDisplayableIndex());
360
- const currentSpreadIndices = this.book.getSpreadIndices(targetLeaf);
361
- this.br.twoPage.currentIndexL = currentSpreadIndices[0];
362
- this.br.twoPage.currentIndexR = currentSpreadIndices[1];
363
- }
364
-
365
- /**
366
- * Calculates 2-page spread dimensions based on this.br.twoPage.currentIndexL and
367
- * this.br.twoPage.currentIndexR
368
- * This function sets this.br.twoPage.height, twoPage.width
369
- */
370
- calculateSpreadSize() {
371
- const firstIndex = this.br.twoPage.currentIndexL;
372
- const secondIndex = this.br.twoPage.currentIndexR;
373
-
374
- // Calculate page sizes and total leaf width
375
- let spreadSize;
376
- if ( this.br.twoPage.autofit) {
377
- spreadSize = this.getIdealSpreadSize(firstIndex, secondIndex);
378
- } else {
379
- // set based on reduction factor
380
- spreadSize = this.getSpreadSizeFromReduce(firstIndex, secondIndex, this.br.reduce);
381
- }
382
- // Both pages together
383
- this.br.twoPage.height = spreadSize.height || 0;
384
- this.br.twoPage.width = spreadSize.width || 0;
385
-
386
- // Individual pages
387
- this.br.twoPage.scaledWL = this.getPageWidth(firstIndex) || 0;
388
- this.br.twoPage.scaledWR = this.getPageWidth(secondIndex) || 0;
389
-
390
- // Leaf edges
391
- this.br.twoPage.edgeWidth = spreadSize.totalLeafEdgeWidth; // The combined width of both edges
392
- this.br.twoPage.leafEdgeWidthL = this.leafEdgeWidth(this.br.twoPage.currentIndexL);
393
- this.br.twoPage.leafEdgeWidthR = this.br.twoPage.edgeWidth - this.br.twoPage.leafEdgeWidthL;
394
-
395
-
396
- // Book cover
397
- // The width of the book cover div. The combined width of both pages, twice the width
398
- // of the book cover internal padding (2*10) and the page edges
399
- this.br.twoPage.bookCoverDivWidth = this.coverWidth(this.br.twoPage.scaledWL + this.br.twoPage.scaledWR);
400
- // The height of the book cover div
401
- this.br.twoPage.bookCoverDivHeight = this.br.twoPage.height + 2 * this.br.twoPage.coverInternalPadding;
402
-
403
-
404
- // We calculate the total width and height for the div so that we can make the book
405
- // spine centered
406
- const leftGutterOffset = this.gutterOffsetForIndex(firstIndex);
407
- const leftWidthFromCenter = this.br.twoPage.scaledWL - leftGutterOffset + this.br.twoPage.leafEdgeWidthL;
408
- const rightWidthFromCenter = this.br.twoPage.scaledWR + leftGutterOffset + this.br.twoPage.leafEdgeWidthR;
409
- const largestWidthFromCenter = Math.max( leftWidthFromCenter, rightWidthFromCenter );
410
- this.br.twoPage.totalWidth = 2 * (largestWidthFromCenter + this.br.twoPage.coverInternalPadding + this.br.twoPage.coverExternalPadding);
411
- this.br.twoPage.totalHeight = this.br.twoPage.height + 2 * (this.br.twoPage.coverInternalPadding + this.br.twoPage.coverExternalPadding);
412
-
413
- // We want to minimize the unused space in two-up mode (maximize the amount of page
414
- // shown). We give width to the leaf edges and these widths change (though the sum
415
- // of the two remains constant) as we flip through the book. With the book
416
- // cover centered and fixed in the BRcontainer div the page images will meet
417
- // at the "gutter" which is generally offset from the center.
418
- this.br.twoPage.middle = this.br.twoPage.totalWidth >> 1;
419
- this.br.twoPage.gutter = this.br.twoPage.middle + this.gutterOffsetForIndex(firstIndex);
420
-
421
- // The left edge of the book cover moves depending on the width of the pages
422
- // $$$ change to getter
423
- this.br.twoPage.bookCoverDivLeft = this.br.twoPage.gutter - this.br.twoPage.scaledWL - this.br.twoPage.leafEdgeWidthL - this.br.twoPage.coverInternalPadding;
424
- // The top edge of the book cover stays a fixed distance from the top
425
- this.br.twoPage.bookCoverDivTop = this.br.twoPage.coverExternalPadding;
426
-
427
- // Book spine
428
- this.br.twoPage.bookSpineDivHeight = this.br.twoPage.height + 2 * this.br.twoPage.coverInternalPadding;
429
- this.br.twoPage.bookSpineDivLeft = this.br.twoPage.middle - (this.br.twoPage.bookSpineDivWidth >> 1);
430
- this.br.twoPage.bookSpineDivTop = this.br.twoPage.bookCoverDivTop;
431
-
432
- this.br.reduce = spreadSize.reduce < 0 ? this.br.reduce : spreadSize.reduce; // $$$ really set this here?
433
- }
434
-
435
- /**
436
- *
437
- * @param {number} firstIndex
438
- * @param {number} secondIndex
439
- * @return {{ width: number, height: number, totalLeafEdgeWidth: number, reduce: number}}
440
- */
441
- getIdealSpreadSize(firstIndex, secondIndex) {
442
- const ideal = {};
443
-
444
- // We check which page is closest to a "normal" page and use that to set the height
445
- // for both pages. This means that foldouts and other odd size pages will be displayed
446
- // smaller than the nominal zoom amount.
447
- const canon5Dratio = 1.5;
448
-
449
- const first = {
450
- height: this.book._getPageHeight(firstIndex),
451
- width: this.book._getPageWidth(firstIndex)
452
- };
453
-
454
- const second = {
455
- height: this.book._getPageHeight(secondIndex),
456
- width: this.book._getPageWidth(secondIndex)
457
- };
458
-
459
- const firstIndexRatio = first.height / first.width;
460
- const secondIndexRatio = second.height / second.width;
461
-
462
- let ratio;
463
- if (Math.abs(firstIndexRatio - canon5Dratio) < Math.abs(secondIndexRatio - canon5Dratio)) {
464
- ratio = firstIndexRatio;
465
- } else {
466
- ratio = secondIndexRatio;
467
- }
468
-
469
- const totalLeafEdgeWidth = Math.floor(this.book.getNumLeafs() * 0.1);
470
- const maxLeafEdgeWidth = Math.floor(this.br.refs.$brContainer.prop('clientWidth') * 0.1);
471
- ideal.totalLeafEdgeWidth = Math.min(totalLeafEdgeWidth, maxLeafEdgeWidth);
472
-
473
- const widthOutsidePages = 2 * (this.br.twoPage.coverInternalPadding + this.br.twoPage.coverExternalPadding) + ideal.totalLeafEdgeWidth;
474
- const heightOutsidePages = 2 * (this.br.twoPage.coverInternalPadding + this.br.twoPage.coverExternalPadding);
475
-
476
- ideal.width = (this.br.refs.$brContainer.width() - widthOutsidePages) >> 1;
477
- ideal.width = ideal.width > 10 ? ideal.width - 10 : 1; // $$$ fudge factor
478
-
479
- ideal.height = this.br.refs.$brContainer.height() - heightOutsidePages;
480
- ideal.height = ideal.height > 15 ? ideal.height - 15 : 1; // $$$ fudge factor
481
-
482
- if (ideal.height / ratio <= ideal.width) {
483
- //use height
484
- ideal.width = Math.floor(ideal.height / ratio) || 1;
485
- } else {
486
- //use width
487
- ideal.height = Math.floor(ideal.width * ratio) || 1;
488
- }
489
-
490
- // $$$ check this logic with large spreads
491
- ideal.reduce = Math.round(((first.height + second.height) / 2) / ideal.height);
492
-
493
- return ideal;
494
- }
495
-
496
- /**
497
- * Returns the spread size calculated from the reduction factor for the given pages
498
- * @param {number} firstIndex
499
- * @param {number} secondIndex
500
- * @return {Object}
501
- */
502
- getSpreadSizeFromReduce(firstIndex, secondIndex, reduce) {
503
- const spreadSize = {};
504
- // $$$ Scale this based on reduce?
505
- const totalLeafEdgeWidth = Math.floor(this.book.getNumLeafs() * 0.1);
506
- // $$$ Assumes leaf edge width constant at all zoom levels
507
- const maxLeafEdgeWidth = Math.floor(this.br.refs.$brContainer.prop('clientWidth') * 0.1);
508
- spreadSize.totalLeafEdgeWidth = Math.min(totalLeafEdgeWidth, maxLeafEdgeWidth);
509
-
510
- // $$$ Possibly incorrect -- we should make height "dominant"
511
- const nativeWidth = this.book._getPageWidth(firstIndex) + this.book._getPageWidth(secondIndex);
512
- const nativeHeight = this.book._getPageHeight(firstIndex) + this.book._getPageHeight(secondIndex);
513
- spreadSize.height = Math.floor( (nativeHeight / 2) / this.br.reduce );
514
- spreadSize.width = Math.floor( (nativeWidth / 2) / this.br.reduce );
515
- spreadSize.reduce = reduce;
516
-
517
- return spreadSize;
518
- }
519
-
520
- /**
521
- * Returns the current ideal reduction factor
522
- * @return {number}
523
- */
524
- getAutofitReduce() {
525
- const spreadSize = this.getIdealSpreadSize(this.br.twoPage.currentIndexL, this.br.twoPage.currentIndexR);
526
- return spreadSize.reduce;
527
- }
528
-
529
- calculateReductionFactors() {
530
- this.br.twoPage.reductionFactors = this.br.reductionFactors.concat([
531
- {
532
- reduce: this.getIdealSpreadSize( this.br.twoPage.currentIndexL, this.br.twoPage.currentIndexR ).reduce,
533
- autofit: 'auto'
59
+ } else {
60
+ await this.mode2UpLit.jumpToIndex(startLeaf, { smooth: false });
61
+ this.resizePageView();
534
62
  }
535
- ]);
536
- this.br.twoPage.reductionFactors.sort(this.br._reduceSort);
537
- }
538
-
539
- /**
540
- * @param {Number|null} index to flip back one spread, pass index=null
541
- */
542
- flipBackToIndex(index) {
543
- if (this.br.constMode1up == this.br.mode) return;
544
- if (this.br.animating) return;
545
-
546
- if (null != this.br.leafEdgeTmp) {
547
- alert('error: leafEdgeTmp should be null!');
548
- return;
549
- }
550
-
551
- if (null == index) {
552
- const {currentIndexL, currentIndexR} = this.br.twoPage;
553
- const minDisplayedIndex = Math.min(currentIndexL, currentIndexR);
554
- const prev = this.book.getPage(minDisplayedIndex).findPrev({ combineConsecutiveUnviewables: true });
555
- if (!prev) return;
556
- index = prev.index;
557
- // Can only flip to a left page
558
- // (downstream code handles index = -1, so this is ok I guess)
559
- if (prev.pageSide == 'R') index--;
560
- }
561
-
562
- this.br._components.navbar.updateNavIndexThrottled(index);
563
-
564
- const previousIndices = this.book.getSpreadIndices(index);
565
-
566
- if (previousIndices[0] < this.br.firstDisplayableIndex() || previousIndices[1] < this.br.firstDisplayableIndex()) {
567
- return;
568
- }
569
-
570
- this.br.animating = true;
571
-
572
- if ('rl' != this.br.pageProgression) {
573
- // Assume LTR and we are going backward
574
- this.prepareFlipLeftToRight(previousIndices[0], previousIndices[1]);
575
- this.flipLeftToRight(previousIndices[0], previousIndices[1]);
576
- } else {
577
- // RTL and going backward
578
- this.prepareFlipRightToLeft(previousIndices[0], previousIndices[1]);
579
- this.flipRightToLeft(previousIndices[0], previousIndices[1]);
580
- }
581
- }
582
-
583
- /**
584
- * Flips the page on the left towards the page on the right
585
- * @param {number} newIndexL
586
- * @param {number} newIndexR
587
- */
588
- flipLeftToRight(newIndexL, newIndexR) {
589
- this.br.refs.$brContainer.addClass("BRpageFlipping");
590
- const leftLeaf = this.br.twoPage.currentIndexL;
591
-
592
- const oldLeafEdgeWidthL = this.leafEdgeWidth(this.br.twoPage.currentIndexL);
593
- const newLeafEdgeWidthL = this.leafEdgeWidth(newIndexL);
594
- const leafEdgeTmpW = oldLeafEdgeWidthL - newLeafEdgeWidthL;
595
-
596
- const currWidthL = this.getPageWidth(leftLeaf);
597
- const newWidthL = this.getPageWidth(newIndexL);
598
- const newWidthR = this.getPageWidth(newIndexR);
599
-
600
- const top = this.top();
601
- const gutter = this.br.twoPage.middle + this.gutterOffsetForIndex(newIndexL);
602
-
603
- //animation strategy:
604
- // 0. remove search highlight, if any.
605
- // 1. create a new div, called leafEdgeTmp to represent the leaf edge between the leftmost edge
606
- // of the left leaf and where the user clicked in the leaf edge.
607
- // Note that if this function was triggered by left() and not a
608
- // mouse click, the width of leafEdgeTmp is very small (zero px).
609
- // 2. animate both leafEdgeTmp to the gutter (without changing its width) and animate
610
- // leftLeaf to width=0.
611
- // 3. When step 2 is finished, animate leafEdgeTmp to right-hand side of new right leaf
612
- // (left=gutter+newWidthR) while also animating the new right leaf from width=0 to
613
- // its new full width.
614
- // 4. After step 3 is finished, do the following:
615
- // - remove leafEdgeTmp from the dom.
616
- // - resize and move the right leaf edge (leafEdgeR) to left=gutter+newWidthR
617
- // and width=twoPage.edgeWidth-newLeafEdgeWidthL.
618
- // - resize and move the left leaf edge (leafEdgeL) to left=gutter-newWidthL-newLeafEdgeWidthL
619
- // and width=newLeafEdgeWidthL.
620
- // - resize the back cover (twoPage.coverDiv) to left=gutter-newWidthL-newLeafEdgeWidthL-10
621
- // and width=newWidthL+newWidthR+twoPage.edgeWidth+20
622
- // - move new left leaf (newIndexL) forward to zindex=2 so it can receive clicks.
623
- // - remove old left and right leafs from the dom [prunePageContainers()].
624
- // - prefetch new adjacent leafs.
625
- // - set up click handlers for both new left and right leafs.
626
- // - redraw the search highlight.
627
- // - update the pagenum box and the url.
628
-
629
- const $twoPageViewEl = this.br.refs.$brTwoPageView;
630
- const leftEdgeTmpLeft = gutter - currWidthL - leafEdgeTmpW;
631
-
632
- this.br.leafEdgeTmp = document.createElement('div');
633
- this.br.leafEdgeTmp.className = 'BRleafEdgeTmp';
634
- $(this.br.leafEdgeTmp).css({
635
- width: `${leafEdgeTmpW}px`,
636
- height: `${this.br.twoPage.height}px`,
637
- left: `${leftEdgeTmpLeft}px`,
638
- top: `${top}px`,
639
- zIndex: 1000,
640
- }).appendTo($twoPageViewEl);
641
-
642
- $(this.leafEdgeL).css({
643
- width: `${newLeafEdgeWidthL}px`,
644
- left: `${gutter - currWidthL - newLeafEdgeWidthL}px`
645
- });
646
-
647
- // Left gets the offset of the current left leaf from the document
648
- const left = this.pageContainers[leftLeaf].$container.offset().left;
649
- // $$$ This seems very similar to the gutter. May be able to consolidate the logic.
650
- const right = `${$twoPageViewEl.prop('clientWidth') - left - this.pageContainers[leftLeaf].$container.width() + $twoPageViewEl.offset().left - 2}px`;
651
-
652
- // We change the left leaf to right positioning
653
- // $$$ This causes animation glitches during resize. See https://bugs.edge.launchpad.net/gnubook/+bug/328327
654
- this.pageContainers[leftLeaf].$container.css({
655
- right,
656
- left: ''
657
- });
658
-
659
- $(this.br.leafEdgeTmp).animate({left: gutter}, this.br.flipSpeed, 'easeInSine');
660
-
661
- this.pageContainers[leftLeaf].$container.animate({width: '0px'}, this.br.flipSpeed, 'easeInSine', () => {
662
-
663
- $(this.br.leafEdgeTmp).animate({left: `${gutter + newWidthR}px`}, this.br.flipSpeed, 'easeOutSine');
664
-
665
- this.br.$('.BRgutter').css({left: `${gutter - this.br.twoPage.bookSpineDivWidth * 0.5}px`});
666
-
667
- this.pageContainers[newIndexR].$container.animate({width: `${newWidthR}px`}, this.br.flipSpeed, 'easeOutSine', () => {
668
- this.pageContainers[newIndexL].$container.css('zIndex', 2);
669
-
670
- $(this.leafEdgeR).css({
671
- // Moves the right leaf edge
672
- width: `${this.br.twoPage.edgeWidth - newLeafEdgeWidthL}px`,
673
- left: `${gutter + newWidthR}px`
674
- });
675
-
676
- $(this.leafEdgeL).css({
677
- // Moves and resizes the left leaf edge
678
- width: `${newLeafEdgeWidthL}px`,
679
- left: `${gutter - newWidthL - newLeafEdgeWidthL}px`
680
- });
681
-
682
- // Resizes the brown border div
683
- $(this.br.twoPage.coverDiv).css({
684
- width: `${this.coverWidth(newWidthL + newWidthR)}px`,
685
- left: `${gutter - newWidthL - newLeafEdgeWidthL - this.br.twoPage.coverInternalPadding}px`
686
- });
687
-
688
- $(this.br.leafEdgeTmp).remove();
689
- this.br.leafEdgeTmp = null;
690
-
691
- // $$$ TODO refactor with opposite direction flip
692
-
693
- this.br.twoPage.currentIndexL = newIndexL;
694
- this.br.twoPage.currentIndexR = newIndexR;
695
- this.br.twoPage.scaledWL = newWidthL;
696
- this.br.twoPage.scaledWR = newWidthR;
697
- this.br.twoPage.gutter = gutter;
698
-
699
- this.br.updateFirstIndex(this.br.twoPage.currentIndexL);
700
- this.br.displayedIndices = [newIndexL, newIndexR];
701
- this.prunePageContainers();
702
- this.br.animating = false;
703
-
704
- this.resizeSpread();
705
-
706
- if (this.br.animationFinishedCallback) {
707
- this.br.animationFinishedCallback();
708
- this.br.animationFinishedCallback = null;
709
- }
710
-
711
- this.br.refs.$brContainer.removeClass("BRpageFlipping");
712
- this.br.textSelectionPlugin?.stopPageFlip(this.br.refs.$brContainer);
713
- this.centerView();
714
- this.br.trigger('pageChanged');
715
-
716
- // get next previous batch immediately
717
- this.prunePageContainers();
718
- this.createPageContainer(newIndexL - 2);
719
- this.createPageContainer(newIndexR - 2);
720
- this.createPageContainer(newIndexL - 3);
721
- this.createPageContainer(newIndexR - 3);
722
- });
63
+ this.mode2UpLit.style.opacity = '1';
723
64
  });
65
+ this.br.updateBrClasses();
724
66
  }
725
67
 
726
68
  /**
69
+ * BREAKING CHANGE: No longer supports pageX/pageY
727
70
  * @param {PageIndex} index
71
+ * @param {number} [pageX] x position on the page (in pixels) to center on
72
+ * @param {number} [pageY] y position on the page (in pixels) to center on
73
+ * @param {boolean} [noAnimate]
728
74
  */
729
- createPageContainer(index) {
730
- if (!this.pageContainers[index]) {
731
- this.pageContainers[index] = this.br._createPageContainer(index);
732
- }
733
- this.pageContainers[index].update({ reduce: this.br.reduce / this.scale });
734
- return this.pageContainers[index];
735
- }
736
-
737
- /**
738
- * Whether we flip left or right is dependent on the page progression
739
- * to flip forward one spread, pass index=null
740
- * @param {number} index
741
- */
742
- flipFwdToIndex(index) {
743
- if (this.br.animating) return;
744
-
745
- if (null != this.br.leafEdgeTmp) {
746
- alert('error: leafEdgeTmp should be null!');
747
- return;
748
- }
749
-
750
- if (null == index) {
751
- // Need to use the max here, since it could be a right to left book
752
- const {currentIndexL, currentIndexR} = this.br.twoPage;
753
- const maxDisplayedIndex = Math.max(currentIndexL, currentIndexR);
754
- const nextPage = this.book.getPage(maxDisplayedIndex).findNext({ combineConsecutiveUnviewables: true });
755
- if (!nextPage) return;
756
- index = nextPage.index;
757
- }
758
- if (index > this.br.lastDisplayableIndex()) return;
759
-
760
- this.br._components.navbar.updateNavIndexThrottled(index);
761
-
762
- this.br.animating = true;
763
-
764
- const nextIndices = this.book.getSpreadIndices(index);
765
-
766
- if ('rl' != this.br.pageProgression) {
767
- // We did not specify RTL
768
- this.prepareFlipRightToLeft(nextIndices[0], nextIndices[1]);
769
- this.flipRightToLeft(nextIndices[0], nextIndices[1]);
770
- } else {
771
- // RTL
772
- this.prepareFlipLeftToRight(nextIndices[0], nextIndices[1]);
773
- this.flipLeftToRight(nextIndices[0], nextIndices[1]);
774
- }
775
- }
776
-
777
- /**
778
- * Flip from left to right and show the nextL and nextR indices on those sides
779
- * $$$ better not to have to pass gutter in
780
- * @param {number} newIndexL
781
- * @param {number} newIndexR
782
- */
783
- flipRightToLeft(newIndexL, newIndexR) {
784
- this.br.refs.$brContainer.addClass("BRpageFlipping");
785
-
786
- const oldLeafEdgeWidthL = this.leafEdgeWidth(this.br.twoPage.currentIndexL);
787
- const oldLeafEdgeWidthR = this.br.twoPage.edgeWidth - oldLeafEdgeWidthL;
788
- const newLeafEdgeWidthL = this.leafEdgeWidth(newIndexL);
789
- const newLeafEdgeWidthR = this.br.twoPage.edgeWidth - newLeafEdgeWidthL;
790
-
791
- const leafEdgeTmpW = oldLeafEdgeWidthR - newLeafEdgeWidthR;
792
-
793
- const top = this.top();
794
- const scaledW = this.getPageWidth(this.br.twoPage.currentIndexR);
795
-
796
- const middle = this.br.twoPage.middle;
797
- const gutter = middle + this.gutterOffsetForIndex(newIndexL);
798
-
799
- const $twoPageViewEl = this.br.refs.$brTwoPageView;
800
-
801
- this.br.leafEdgeTmp = document.createElement('div');
802
- this.br.leafEdgeTmp.className = 'BRleafEdgeTmp';
803
- $(this.br.leafEdgeTmp).css({
804
- width: `${leafEdgeTmpW}px`,
805
- height: `${this.br.twoPage.height}px`,
806
- left: `${gutter + scaledW}px`,
807
- top: `${top}px`,
808
- zIndex:1000
809
- }).appendTo($twoPageViewEl);
810
-
811
- const newWidthL = this.getPageWidth(newIndexL);
812
- const newWidthR = this.getPageWidth(newIndexR);
813
-
814
- $(this.leafEdgeR).css({width: `${newLeafEdgeWidthR}px`, left: `${gutter + newWidthR}px` });
815
- const speed = this.br.flipSpeed;
816
-
817
- $(this.br.leafEdgeTmp).animate({left: gutter}, speed, 'easeInSine');
818
- this.pageContainers[this.br.twoPage.currentIndexR].$container.animate({width: '0px'}, speed, 'easeInSine', () => {
819
- this.br.$('BRgutter').css({left: `${gutter - this.br.twoPage.bookSpineDivWidth * 0.5}px`});
820
- $(this.br.leafEdgeTmp).animate({left: `${gutter - newWidthL - leafEdgeTmpW}px`}, speed, 'easeOutSine');
821
-
822
- // Ensure the new left leaf is right-positioned before animating its width.
823
- // Otherwise, it animates in the wrong direction.
824
- this.pageContainers[newIndexL].$container.css({
825
- right: `${$twoPageViewEl.prop('clientWidth') - gutter}px`,
826
- left: ''
827
- });
828
- this.pageContainers[newIndexL].$container.animate({width: `${newWidthL}px`}, speed, 'easeOutSine', () => {
829
- this.pageContainers[newIndexR].$container.css('zIndex', 2);
830
-
831
- $(this.leafEdgeL).css({
832
- width: `${newLeafEdgeWidthL}px`,
833
- left: `${gutter - newWidthL - newLeafEdgeWidthL}px`
834
- });
835
-
836
- // Resizes the book cover
837
- $(this.br.twoPage.coverDiv).css({
838
- width: `${this.coverWidth(newWidthL + newWidthR)}px`,
839
- left: `${gutter - newWidthL - newLeafEdgeWidthL - this.br.twoPage.coverInternalPadding}px`
840
- });
841
-
842
- $(this.br.leafEdgeTmp).remove();
843
- this.br.leafEdgeTmp = null;
844
-
845
- this.br.twoPage.currentIndexL = newIndexL;
846
- this.br.twoPage.currentIndexR = newIndexR;
847
- this.br.twoPage.scaledWL = newWidthL;
848
- this.br.twoPage.scaledWR = newWidthR;
849
- this.br.twoPage.gutter = gutter;
850
-
851
- this.br.updateFirstIndex(this.br.twoPage.currentIndexL);
852
- this.br.displayedIndices = [newIndexL, newIndexR];
853
- this.prunePageContainers();
854
- this.br.animating = false;
855
-
856
- this.resizeSpread();
857
-
858
- if (this.br.animationFinishedCallback) {
859
- this.br.animationFinishedCallback();
860
- this.br.animationFinishedCallback = null;
861
- }
862
-
863
- this.br.refs.$brContainer.removeClass("BRpageFlipping");
864
- this.br.textSelectionPlugin?.stopPageFlip(this.br.refs.$brContainer);
865
- this.centerView();
866
- this.br.trigger('pageChanged');
867
-
868
- this.prunePageContainers();
869
- this.createPageContainer(newIndexL + 2);
870
- this.createPageContainer(newIndexR + 2);
871
- this.createPageContainer(newIndexL + 3);
872
- this.createPageContainer(newIndexR + 3);
873
- });
874
- });
875
- }
876
-
877
- attachMouseHandlers() {
878
- this.br.refs.$brTwoPageView
879
- .off('mouseup').on('mouseup', ev => {
880
- if (ev.which == 3) {
881
- // right click
882
- return !this.br.protected;
883
- }
884
-
885
- const $page = $(ev.target).closest('.BRpagecontainer');
886
- if ($page.data('side') == 'L') this.br.left();
887
- else if ($page.data('side') == 'R') this.br.right();
888
- });
889
- }
890
-
891
- /**
892
- * Prepare to flip the left page towards the right. This corresponds to moving
893
- * backward when the page progression is left to right.
894
- * @param {number} prevL
895
- * @param {number} prevR
896
- */
897
- prepareFlipLeftToRight(prevL, prevR) {
898
- this.createPageContainer(prevL, true);
899
- this.createPageContainer(prevR, true);
900
-
901
- const $twoPageViewEl = this.br.refs.$brTwoPageView;
902
- const height = this.book._getPageHeight(prevL);
903
- const width = this.book._getPageWidth(prevL);
904
- const middle = this.br.twoPage.middle;
905
- const top = this.top();
906
- const scaledW = this.br.twoPage.height * width / height; // $$$ assumes height of page is dominant
907
-
908
- // The gutter is the dividing line between the left and right pages.
909
- // It is offset from the middle to create the illusion of thickness to the pages
910
- const gutter = middle + this.gutterOffsetForIndex(prevL);
911
-
912
- const leftCSS = {
913
- left: `${gutter - scaledW}px`,
914
- right: '', // clear right property
915
- top: `${top}px`,
916
- height: this.br.twoPage.height,
917
- width: `${scaledW}px`,
918
- zIndex: 1
919
- };
920
-
921
- this.pageContainers[prevL].$container
922
- .css(leftCSS)
923
- .appendTo($twoPageViewEl);
924
-
925
- const rightCSS = {
926
- left: `${gutter}px`,
927
- right: '',
928
- top: `${top}px`,
929
- height: this.br.twoPage.height,
930
- width: '0',
931
- zIndex: 2
932
- };
933
-
934
- this.pageContainers[prevR].$container
935
- .css(rightCSS)
936
- .appendTo($twoPageViewEl);
937
- }
938
-
939
- /**
940
- * // $$$ mang we're adding an extra pixel in the middle. See https://bugs.edge.launchpad.net/gnubook/+bug/411667
941
- */
942
- prepareFlipRightToLeft(nextL, nextR) {
943
- this.createPageContainer(nextL, true);
944
- this.createPageContainer(nextR, true);
945
-
946
- const $twoPageViewEl = this.br.refs.$brTwoPageView;
947
- let height = this.book._getPageHeight(nextR);
948
- let width = this.book._getPageWidth(nextR);
949
- const middle = this.br.twoPage.middle;
950
- const top = this.top();
951
- let scaledW = this.br.twoPage.height * width / height;
952
-
953
- const gutter = middle + this.gutterOffsetForIndex(nextL);
954
-
955
- $(this.pageContainers[nextR].$container).css({
956
- left: `${gutter}px`,
957
- top: `${top}px`,
958
- height: this.br.twoPage.height,
959
- width: `${scaledW}px`,
960
- zIndex: 1,
961
- })
962
- .appendTo($twoPageViewEl);
963
-
964
- height = this.book._getPageHeight(nextL);
965
- width = this.book._getPageWidth(nextL);
966
- scaledW = this.br.twoPage.height * width / height;
967
-
968
- $(this.pageContainers[nextL].$container).css({
969
- right: `${$twoPageViewEl.prop('clientWidth') - gutter}px`,
970
- top: `${top}px`,
971
- height: this.br.twoPage.height,
972
- width: '0px', // Start at 0 width, then grow to the left
973
- zIndex: 2,
974
- })
975
- .appendTo($twoPageViewEl);
976
- }
977
-
978
- getPageWidth(index) {
979
- // We return the width based on the dominant height
980
- const height = this.book._getPageHeight(index);
981
- const width = this.book._getPageWidth(index);
982
- // $$$ we assume width is relative to current spread
983
- return Math.floor(this.br.twoPage.height * width / height);
984
- }
985
-
986
- /**
987
- * Returns the position of the gutter (line between the page images)
988
- */
989
- gutter() {
990
- return this.br.twoPage.middle + this.gutterOffsetForIndex(this.br.twoPage.currentIndexL);
991
- }
992
-
993
- /**
994
- * Returns the offset for the top of the page images
995
- */
996
- top() {
997
- return this.br.twoPage.coverExternalPadding + this.br.twoPage.coverInternalPadding; // $$$ + border?
998
- }
999
-
1000
- /**
1001
- * Returns the width of the cover div given the total page width
1002
- * @param {number} totalPageWidth
1003
- * @return {number}
1004
- */
1005
- coverWidth(totalPageWidth) {
1006
- return totalPageWidth + this.br.twoPage.edgeWidth + 2 * this.br.twoPage.coverInternalPadding;
1007
- }
1008
-
1009
- /**
1010
- * Returns the percentage offset into twopageview div at the center of container div
1011
- */
1012
- getViewCenter() {
1013
- const { $brContainer, $brTwoPageView } = this.br.refs;
1014
- const center = {};
1015
-
1016
- const containerOffset = $brContainer.offset();
1017
- const viewOffset = $brTwoPageView.offset();
1018
- center.percentageX = (containerOffset.left - viewOffset.left + ($brContainer.prop('clientWidth') >> 1)) / this.br.twoPage.totalWidth;
1019
- center.percentageY = (containerOffset.top - viewOffset.top + ($brContainer.prop('clientHeight') >> 1)) / this.br.twoPage.totalHeight;
1020
-
1021
- return center;
1022
- }
1023
-
1024
- /**
1025
- * Centers the point given by percentage from left,top of twopageview
1026
- * @param {number} [percentageX=0.5]
1027
- * @param {number} [percentageY=0.5]
1028
- */
1029
- centerView(percentageX, percentageY) {
1030
-
1031
- if ('undefined' == typeof(percentageX)) {
1032
- percentageX = 0.5;
1033
- }
1034
- if ('undefined' == typeof(percentageY)) {
1035
- percentageY = 0.5;
1036
- }
1037
-
1038
- const viewWidth = this.br.refs.$brTwoPageView.width();
1039
- const containerClientWidth = this.br.refs.$brContainer.prop('clientWidth');
1040
- const intoViewX = percentageX * viewWidth;
1041
-
1042
- const viewHeight = this.br.refs.$brTwoPageView.height();
1043
- const containerClientHeight = this.br.refs.$brContainer.prop('clientHeight');
1044
- const intoViewY = percentageY * viewHeight;
1045
-
1046
- if (viewWidth < containerClientWidth) {
1047
- // Can fit width without scrollbars - center by adjusting offset
1048
- this.br.refs.$brTwoPageView.css('left', `${(containerClientWidth >> 1) - intoViewX}px`);
1049
- } else {
1050
- // Need to scroll to center
1051
- this.br.refs.$brTwoPageView.css('left', 0);
1052
- this.br.refs.$brContainer.scrollLeft(intoViewX - (containerClientWidth >> 1));
1053
- }
1054
-
1055
- if (viewHeight < containerClientHeight) {
1056
- // Fits with scrollbars - add offset
1057
- this.br.refs.$brTwoPageView.css('top', `${(containerClientHeight >> 1) - intoViewY}px`);
1058
- } else {
1059
- this.br.refs.$brTwoPageView.css('top', 0);
1060
- this.br.refs.$brContainer.scrollTop(intoViewY - (containerClientHeight >> 1));
1061
- }
1062
- }
1063
-
1064
- /**
1065
- * Returns the integer height of the click-to-flip areas at the edges of the book
1066
- * @return {number}
1067
- */
1068
- flipAreaHeight() {
1069
- return Math.floor(this.br.twoPage.height);
1070
- }
1071
-
1072
- /**
1073
- * Returns the the integer width of the flip areas
1074
- * @return {number}
1075
- */
1076
- flipAreaWidth() {
1077
- const max = 100; // $$$ TODO base on view width?
1078
- const min = 10;
1079
-
1080
- const width = this.br.twoPage.width * 0.15;
1081
- return Math.floor(clamp(width, min, max));
1082
- }
1083
-
1084
- /**
1085
- * Returns integer top offset for flip areas
1086
- * @return {number}
1087
- */
1088
- flipAreaTop() {
1089
- return Math.floor(this.br.twoPage.bookCoverDivTop + this.br.twoPage.coverInternalPadding);
1090
- }
1091
-
1092
- /**
1093
- * Left offset for left flip area
1094
- * @return {number}
1095
- */
1096
- leftFlipAreaLeft() {
1097
- return Math.floor(this.br.twoPage.gutter - this.br.twoPage.scaledWL);
1098
- }
1099
-
1100
- /**
1101
- * Left offset for right flip area
1102
- * @return {number}
1103
- */
1104
- rightFlipAreaLeft() {
1105
- return Math.floor(this.br.twoPage.gutter + this.br.twoPage.scaledWR - this.flipAreaWidth());
1106
- }
1107
-
1108
- /**
1109
- * Position calculation shared between search and text-to-speech functions
1110
- */
1111
- setHilightCss(div, index, left, right, top, bottom) {
1112
- // We calculate the reduction factor for the specific page because it can be different
1113
- // for each page in the spread
1114
- const height = this.book._getPageHeight(index);
1115
- const width = this.book._getPageWidth(index);
1116
- const reduce = this.br.twoPage.height / height;
1117
- const scaledW = Math.floor(width * reduce);
1118
-
1119
- const gutter = this.gutter();
1120
- let pageL;
1121
- if ('L' == this.book.getPageSide(index)) {
1122
- pageL = gutter - scaledW;
1123
- } else {
1124
- pageL = gutter;
1125
- }
1126
- const pageT = this.top();
1127
-
1128
- $(div).css({
1129
- width: `${(right - left) * reduce}px`,
1130
- height: `${(bottom - top) * reduce}px`,
1131
- left: `${pageL + left * reduce}px`,
1132
- top: `${pageT + top * reduce}px`
1133
- });
1134
- }
1135
-
1136
- /**
1137
- * Returns the gutter offset for the spread containing the given index.
1138
- * This function supports RTL
1139
- * @param {number} pindex
1140
- * @return {number}
1141
- */
1142
- gutterOffsetForIndex(pindex) {
1143
- // To find the offset of the gutter from the middle we calculate our percentage distance
1144
- // through the book (0..1), remap to (-0.5..0.5) and multiply by the total page edge width
1145
- let offset = Math.floor(((pindex / this.book.getNumLeafs()) - 0.5) * this.br.twoPage.edgeWidth);
1146
-
1147
- // But then again for RTL it's the opposite
1148
- if ('rl' == this.br.pageProgression) {
1149
- offset *= -1;
1150
- }
1151
-
1152
- return offset;
75
+ jumpToIndex(index, pageX, pageY, noAnimate) {
76
+ this.mode2UpLit.jumpToIndex(index);
1153
77
  }
1154
78
 
1155
79
  /**
1156
- * Returns the width of the leaf edge div for the page with index given
1157
- * @param {number} pindex
1158
- * @return {number}
80
+ * @param {'in' | 'out'} direction
1159
81
  */
1160
- leafEdgeWidth(pindex) {
1161
- // $$$ could there be single pixel rounding errors for L vs R?
1162
- if ((this.book.getPageSide(pindex) == 'L') && (this.br.pageProgression != 'rl')) {
1163
- return Math.floor( (pindex / this.book.getNumLeafs()) * this.br.twoPage.edgeWidth + 0.5);
1164
- } else {
1165
- return Math.floor( (1 - pindex / this.book.getNumLeafs()) * this.br.twoPage.edgeWidth + 0.5);
82
+ zoom(direction) {
83
+ switch (direction) {
84
+ case 'in':
85
+ this.mode2UpLit.zoomIn();
86
+ break;
87
+ case 'out':
88
+ this.mode2UpLit.zoomOut();
89
+ break;
90
+ default:
91
+ console.error(`Unsupported direction: ${direction}`);
1166
92
  }
1167
93
  }
1168
94
 
1169
- /**
1170
- * Returns the target jump leaf given a page coordinate (inside the left page edge div)
1171
- * @param {number} pageX
1172
- * @return {PageIndex}
1173
- */
1174
- jumpIndexForLeftEdgePageX(pageX) {
1175
- let jumpIndex;
1176
- if ('rl' != this.br.pageProgression) {
1177
- // LTR - flipping backward
1178
- jumpIndex = this.br.twoPage.currentIndexL - ($(this.leafEdgeL).offset().left + $(this.leafEdgeL).width() - pageX) * 10;
1179
-
1180
- // browser may have resized the div due to font size change -- see https://bugs.launchpad.net/gnubook/+bug/333570
1181
- jumpIndex = clamp(Math.round(jumpIndex), this.br.firstDisplayableIndex(), this.br.twoPage.currentIndexL - 2);
1182
- return jumpIndex;
1183
-
1184
- } else {
1185
- jumpIndex = this.br.twoPage.currentIndexL + ($(this.leafEdgeL).offset().left + $(this.leafEdgeL).width() - pageX) * 10;
1186
- jumpIndex = clamp(Math.round(jumpIndex), this.br.twoPage.currentIndexL + 2, this.br.lastDisplayableIndex());
1187
- return jumpIndex;
95
+ resizePageView() {
96
+ this.mode2UpLit.htmlDimensionsCacher.updateClientSizes();
97
+ if (this.mode2UpLit.scale < this.mode2UpLit.initialScale && this.mode2UpLit.autoFit == 'none') {
98
+ this.mode2UpLit.autoFit = 'auto';
1188
99
  }
1189
- }
1190
-
1191
- /**
1192
- * Returns the target jump leaf given a page coordinate (inside the right page edge div)
1193
- * @param {number} pageX
1194
- * @return {PageIndex}
1195
- */
1196
- jumpIndexForRightEdgePageX(pageX) {
1197
- let jumpIndex;
1198
- if ('rl' != this.br.pageProgression) {
1199
- // LTR
1200
- jumpIndex = this.br.twoPage.currentIndexL + (pageX - $(this.leafEdgeR).offset().left) * 10;
1201
- jumpIndex = clamp(Math.round(jumpIndex), this.br.twoPage.currentIndexL + 2, this.br.lastDisplayableIndex());
1202
- return jumpIndex;
1203
- } else {
1204
- jumpIndex = this.br.twoPage.currentIndexL - (pageX - $(this.leafEdgeR).offset().left) * 10;
1205
- jumpIndex = clamp(Math.round(jumpIndex), this.br.firstDisplayableIndex(), this.br.twoPage.currentIndexL - 2);
1206
- return jumpIndex;
100
+ if (this.mode2UpLit.autoFit != 'none') {
101
+ this.mode2UpLit.resizeViaAutofit();
1207
102
  }
103
+ this.mode2UpLit.recenter();
1208
104
  }
1209
-
1210
- /**
1211
- * Fetches the currently displayed images (if not already fetching)
1212
- * as wells as any nearby pages.
1213
- */
1214
- prefetch() {
1215
- // $$$ We should check here if the current indices have finished
1216
- // loading (with some timeout) before loading more page images
1217
- // See https://bugs.edge.launchpad.net/bookreader/+bug/511391
1218
- const { max, min } = Math;
1219
- const { book } = this;
1220
- const { currentIndexL, currentIndexR } = this.br.twoPage;
1221
- const ADJACENT_PAGES_TO_LOAD = 2;
1222
- // currentIndexL can be -1; getPage returns the last page of the book
1223
- // when given -1, so need to prevent that.
1224
- let lowPage = book.getPage(max(0, min(currentIndexL, currentIndexR)));
1225
- let highPage = book.getPage(max(currentIndexL, currentIndexR));
1226
-
1227
- for (let i = 0; i < ADJACENT_PAGES_TO_LOAD + 2; i++) {
1228
- if (lowPage) {
1229
- this.createPageContainer(lowPage.index);
1230
- lowPage = lowPage.findPrev({ combineConsecutiveUnviewables: true });
1231
- }
1232
-
1233
- if (highPage) {
1234
- this.createPageContainer(highPage.index);
1235
- highPage = highPage.findNext({ combineConsecutiveUnviewables: true });
1236
- }
1237
- }
1238
- }
1239
-
1240
- /* 2up Container Sizes */
1241
-
1242
- /** main positions for inner containers */
1243
- get baseLeafCss() {
1244
- return {
1245
- position: 'absolute',
1246
- right: '',
1247
- top: `${this.top()}px`,
1248
- zIndex: 2,
1249
- };
1250
- }
1251
-
1252
- /** main height for inner containers */
1253
- get heightCss() {
1254
- return {
1255
- height: `${this.br.twoPage.height}px`, // $$$ height forced the same for both pages
1256
- };
1257
- }
1258
-
1259
- /** Left Page sizing */
1260
- get leftLeafCss() {
1261
- return {
1262
- ...this.baseLeafCss,
1263
- ...this.heightCss,
1264
- left: `${this.br.twoPage.gutter - this.br.twoPage.scaledWL}px`,
1265
- width: `${this.br.twoPage.scaledWL}px`,
1266
- };
1267
- }
1268
-
1269
- /** Left side book thickness */
1270
- get leafEdgeLCss() {
1271
- return {
1272
- ...this.heightCss,
1273
- width: `${this.br.twoPage.leafEdgeWidthL}px`,
1274
- left: `${this.br.twoPage.bookCoverDivLeft + this.br.twoPage.coverInternalPadding}px`,
1275
- top: `${this.br.twoPage.bookCoverDivTop + this.br.twoPage.coverInternalPadding}px`,
1276
- border: this.br.twoPage.leafEdgeWidthL === 0 ? 'none' : null
1277
- };
1278
- }
1279
-
1280
- /** Right Page sizing */
1281
- get rightLeafCss() {
1282
- return {
1283
- ...this.baseLeafCss,
1284
- ...this.heightCss,
1285
- left: `${this.br.twoPage.gutter}px`,
1286
- width: `${this.br.twoPage.scaledWR}px`,
1287
- };
1288
- }
1289
-
1290
- /** Right side book thickness */
1291
- get leafEdgeRCss() {
1292
- return {
1293
- ...this.heightCss,
1294
- width: `${this.br.twoPage.leafEdgeWidthR}px`,
1295
- left: `${this.br.twoPage.scaledWL + this.br.twoPage.scaledWR + this.br.twoPage.leafEdgeWidthL}px`,
1296
- top: `${this.br.twoPage.bookCoverDivTop + this.br.twoPage.coverInternalPadding}px`,
1297
- border: this.br.twoPage.leafEdgeWidthR === 0 ? 'none' : null
1298
- };
1299
- }
1300
-
1301
- /** main container sizing */
1302
- get mainContainerCss() {
1303
- return {
1304
- height: `${this.br.twoPage.totalHeight}px`,
1305
- width: `${this.br.twoPage.totalWidth}px`,
1306
- position: 'absolute'
1307
- };
1308
- }
1309
-
1310
- /** book cover sizing */
1311
- get spreadCoverCss() {
1312
- return {
1313
- width: `${this.br.twoPage.bookCoverDivWidth}px`,
1314
- height: `${this.br.twoPage.bookCoverDivHeight}px`,
1315
- visibility: 'visible'
1316
- };
1317
- }
1318
-
1319
- /** book spine sizing */
1320
- get spineCss() {
1321
- return {
1322
- width: `${this.br.twoPage.bookSpineDivWidth}px`,
1323
- height: `${this.br.twoPage.bookSpineDivHeight}px`,
1324
- left: `${this.br.twoPage.gutter - (this.br.twoPage.bookSpineDivWidth / 2)}px`,
1325
- top: `${this.br.twoPage.bookSpineDivTop}px`
1326
- };
1327
- }
1328
- /** end CSS */
1329
105
  }
1330
-
1331
- /**
1332
- * @implements {BookReaderOptions["twoPage"]}
1333
- * @typedef {object} TwoPageState
1334
- * @property {number} coverInternalPadding
1335
- * @property {number} coverExternalPadding
1336
- *
1337
- * @property {import('./options.js').AutoFitValues} autofit
1338
- * @property {number} width
1339
- * @property {number} height
1340
- * @property {number} currentIndexL
1341
- * @property {number} currentIndexR
1342
- * @property {number} scaledWL
1343
- * @property {number} scaledWR
1344
- * @property {number} gutter
1345
- * @property {Array<{reduce: number, autofit: import('./options.js').AutoFitValues}>} reductionFactors
1346
- * @property {number} totalHeight
1347
- * @property {number} totalWidth
1348
- *
1349
- * @property {HTMLDivElement} coverDiv
1350
- * @property {number} bookCoverDivTop
1351
- * @property {number} bookCoverDivLeft
1352
- * @property {number} bookCoverDivWidth
1353
- * @property {number} bookCoverDivHeight
1354
- *
1355
- * @property {number} leafEdgeWidthL
1356
- * @property {number} leafEdgeWidthR
1357
- *
1358
- * @property {number} bookSpineDivTop
1359
- * @property {number} bookSpineDivLeft
1360
- * @property {number} bookSpineDivWidth
1361
- * @property {number} bookSpineDivHeight
1362
- *
1363
- * @property {number} edgeWidth
1364
- * @property {number} middle
1365
- */