@internetarchive/bookreader 5.0.0-57 → 5.0.0-59

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) 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 +8 -0
  21. package/babel.config.js +5 -2
  22. package/package.json +10 -9
  23. package/src/BookReader/BookModel.js +59 -1
  24. package/src/BookReader/Mode1Up.js +5 -0
  25. package/src/BookReader/Mode1UpLit.js +19 -73
  26. package/src/BookReader/Mode2Up.js +72 -1332
  27. package/src/BookReader/Mode2UpLit.js +774 -0
  28. package/src/BookReader/ModeCoordinateSpace.js +29 -0
  29. package/src/BookReader/ModeSmoothZoom.js +32 -0
  30. package/src/BookReader/options.js +8 -2
  31. package/src/BookReader/utils.js +16 -0
  32. package/src/BookReader.js +24 -217
  33. package/src/css/_BRBookmarks.scss +1 -1
  34. package/src/css/_BRmain.scss +14 -0
  35. package/src/css/_BRpages.scss +113 -41
  36. package/src/plugins/plugin.autoplay.js +1 -6
  37. package/src/plugins/tts/WebTTSEngine.js +2 -2
  38. package/src/plugins/tts/plugin.tts.js +3 -17
  39. package/src/plugins/tts/utils.js +0 -16
  40. package/tests/e2e/helpers/base.js +20 -20
  41. package/tests/e2e/helpers/rightToLeft.js +4 -10
  42. package/tests/e2e/viewmode.test.js +10 -8
  43. package/tests/jest/BookReader/BookModel.test.js +25 -0
  44. package/tests/jest/BookReader/BookReaderPublicFunctions.test.js +28 -11
  45. package/tests/jest/BookReader/Mode1UpLit.test.js +0 -19
  46. package/tests/jest/BookReader/Mode2Up.test.js +55 -225
  47. package/tests/jest/BookReader/Mode2UpLit.test.js +190 -0
  48. package/tests/jest/BookReader/ModeCoordinateSpace.test.js +16 -0
  49. package/tests/jest/BookReader/ModeSmoothZoom.test.js +26 -0
  50. package/tests/jest/BookReader/Navbar/Navbar.test.js +3 -3
  51. package/tests/jest/BookReader/utils.test.js +32 -1
  52. package/tests/jest/plugins/tts/utils.test.js +0 -34
  53. package/tests/jest/setup.js +3 -0
@@ -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
- */