// Zoom levels
// $$$ provide finer grained zooming
- this.reductionFactors = [0.5, 1, 2, 4, 8, 16];
-
+ this.reductionFactors = [ {reduce: 0.5, autofit: null},
+ {reduce: 1, autofit: null},
+ {reduce: 2, autofit: null},
+ {reduce: 4, autofit: null},
+ {reduce: 8, autofit: null},
+ {reduce: 16, autofit: null} ];
+
+ // Object to hold parameters related to 1up mode
+ this.onePage = {
+ autofit: 'height' // valid values are height, width, none
+ },
+
// Object to hold parameters related to 2up mode
this.twoPage = {
coverInternalPadding: 10, // Width of cover
coverExternalPadding: 10, // Padding outside of cover
bookSpineDivWidth: 30, // Width of book spine $$$ consider sizing based on book length
- autofit: true
+ autofit: 'auto',
};
// Background color for pages (e.g. when loading page image)
$("#BRcontainer").bind('scroll', this, function(e) {
e.data.loadLeafs();
});
-
+
this.setupKeyListeners();
this.startLocationPolling();
//console.log('resize!');
if (1 == e.data.mode) {
//console.log('centering 1page view');
+ if (e.data.autofit) {
+ e.data.resizePageView();
+ }
e.data.centerPageView();
$('#BRpageview').empty()
e.data.displayedIndices = [];
BookReader.prototype.zoom = function(direction) {
switch (this.mode) {
case this.constMode1up:
- return this.zoom1up(direction);
+ if (direction == 1) {
+ // XXX other cases
+ return this.zoom1up('in');
+ } else {
+ return this.zoom1up('out');
+ }
+
case this.constMode2up:
- return this.zoom2up(direction);
+ if (direction == 1) {
+ // XXX other cases
+ return this.zoom2up('in');
+ } else {
+ return this.zoom2up('out');
+ }
+
case this.constModeThumb:
+ // XXX update zoomThumb for named directions
return this.zoomThumb(direction);
+
}
}
// zoom1up(dir)
//______________________________________________________________________________
-BookReader.prototype.zoom1up = function(dir) {
+BookReader.prototype.zoom1up = function(direction) {
if (2 == this.mode) { //can only zoom in 1-page mode
this.switchMode(1);
return;
}
- // $$$ with flexible zoom we could "snap" to /2 page reductions
- // for better scaling
- if (1 == dir) {
- if (this.reduce <= 0.5) return;
- this.reduce*=0.5; //zoom in
- } else {
- if (this.reduce >= 8) return;
- this.reduce*=2; //zoom out
+ var reduceFactor = this.nextReduce(this.reduce, direction, this.onePage.reductionFactors);
+
+ if (this.reduce == reduceFactor.reduce) {
+ // Already at this level
+ return;
}
-
+
+ this.reduce = reduceFactor.reduce; // $$$ incorporate into function
+ this.onePage.autofit = reduceFactor.autofit;
+
this.pageScale = this.reduce; // preserve current reduce
this.resizePageView();
var scrollRatio = 0;
}
+ // Recalculate 1up reduction factors
+ this.onePageCalculateReductionFactors( $('#BRcontainer').attr('clientWidth'),
+ $('#BRcontainer').attr('clientHeight') );
+ // Update current reduce (if in autofit)
+ if (this.onePage.autofit) {
+ var reductionFactor = this.nextReduce(this.reduce, this.onePage.autofit, this.onePage.reductionFactors);
+ this.reduce = reductionFactor.reduce;
+ }
+
for (i=0; i<this.numLeafs; i++) {
viewHeight += parseInt(this._getPageHeight(i)/this.reduce) + this.padding;
var width = parseInt(this._getPageWidth(i)/this.reduce);
// Hard stop autoplay
this.stopFlipAnimations();
- // Get new zoom state
- var newZoom = this.twoPageNextReduce(this.reduce, direction);
- if ((this.reduce == newZoom.reduce) && (this.twoPage.autofit == newZoom.autofit)) {
+ // Recalculate autofit factors
+ this.twoPageCalculateReductionFactors();
+
+ // Get new zoom state
+ var reductionFactor = this.nextReduce(this.reduce, direction, this.twoPage.reductionFactors);
+ if ((this.reduce == reductionFactor.reduce) && (this.twoPage.autofit == reductionFactor.autofit)) {
+ // Same zoom
return;
}
- this.twoPage.autofit = newZoom.autofit;
- this.reduce = newZoom.reduce;
+ this.twoPage.autofit = reductionFactor.autofit;
+ this.reduce = reductionFactor.reduce;
this.pageScale = this.reduce; // preserve current reduce
// Preserve view center position
// quantizeReduce(reduce)
//______________________________________________________________________________
// Quantizes the given reduction factor to closest power of two from set from 12.5% to 200%
-BookReader.prototype.quantizeReduce = function(reduce) {
- var quantized = this.reductionFactors[0];
+BookReader.prototype.quantizeReduce = function(reduce, reductionFactors) {
+ var quantized = reductionFactors[0].reduce;
var distance = Math.abs(reduce - quantized);
- for (var i = 1; i < this.reductionFactors.length; i++) {
- newDistance = Math.abs(reduce - this.reductionFactors[i]);
+ for (var i = 1; i < reductionFactors.length; i++) {
+ newDistance = Math.abs(reduce - reductionFactors[i].reduce);
if (newDistance < distance) {
distance = newDistance;
- quantized = this.reductionFactors[i];
+ quantized = reductionFactors[i].reduce;
}
}
return quantized;
}
-// twoPageNextReduce()
-//______________________________________________________________________________
-// Returns the next reduction level
-BookReader.prototype.twoPageNextReduce = function(reduce, direction) {
- var result = {};
- var autofitReduce = this.twoPageGetAutofitReduce();
+// reductionFactors should be array of sorted reduction factors
+// e.g. [ {reduce: 0.25, autofit: null}, {reduce: 0.3, autofit: 'width'}, {reduce: 1, autofit: null} ]
+BookReader.prototype.nextReduce = function( currentReduce, direction, reductionFactors ) {
- if (0 == direction) { // autofit
- result.autofit = true;
- result.reduce = autofitReduce;
-
- } else if (1 == direction) { // zoom in
- var newReduce = this.reductionFactors[0];
+ // XXX add 'closest', to replace quantize function
- for (var i = 1; i < this.reductionFactors.length; i++) {
- if (this.reductionFactors[i] < reduce) {
- newReduce = this.reductionFactors[i];
+ if (direction == 'in') {
+ var newReduceIndex = 0;
+
+ for (var i = 1; i < reductionFactors.length; i++) {
+ if (reductionFactors[i].reduce < currentReduce) {
+ newReduceIndex = i;
}
}
+ return reductionFactors[newReduceIndex];
- if (!this.twoPage.autofit && (autofitReduce < reduce && autofitReduce > newReduce)) {
- // use autofit
- result.autofit = true;
- result.reduce = autofitReduce;
- } else {
- result.autofit = false;
- result.reduce = newReduce;
- }
-
- } else { // zoom out
- var lastIndex = this.reductionFactors.length - 1;
- var newReduce = this.reductionFactors[lastIndex];
+ } else if (direction == 'out') { // zoom out
+ var lastIndex = reductionFactors.length - 1;
+ var newReduceIndex = lastIndex;
for (var i = lastIndex; i >= 0; i--) {
- if (this.reductionFactors[i] > reduce) {
- newReduce = this.reductionFactors[i];
+ if (reductionFactors[i].reduce > currentReduce) {
+ newReduceIndex = i;
}
}
-
- if (!this.twoPage.autofit && (autofitReduce > reduce && autofitReduce < newReduce)) {
- // use autofit
- result.autofit = true;
- result.reduce = autofitReduce;
- } else {
- result.autofit = false;
- result.reduce = newReduce;
+ return reductionFactors[newReduceIndex];
+ }
+
+ // Asked for specific autofit mode
+ for (var i = 0; i < reductionFactors.length; i++) {
+ if (reductionFactors[i].autofit == direction) {
+ return reductionFactors[i];
}
}
- return result;
+ alert('Could not find reduction factor for direction ' + direction);
+ return reductionFactors[0];
+
+}
+
+BookReader.prototype._reduceSort = function(a, b) {
+ return a.reduce - b.reduce;
}
// jumpToPage()
//______________________________________________________________________________
BookReader.prototype.jumpToIndex = function(index, pageX, pageY) {
- if (2 == this.mode) {
+ if (this.constMode2up == this.mode) {
this.autoStop();
// By checking against min/max we do nothing if requested index
this.flipFwdToIndex(index);
}
- } else if (3 == this.mode) {
+ } else if (this.constModeThumb == this.mode) {
var viewWidth = $('#BRcontainer').attr('scrollWidth') - 20; // width minus buffer
var i;
var leafWidth = 0;
$('#BRcontainer').animate({scrollTop: leafTop },'fast');
}
} else {
+ // 1up
var i;
var leafTop = 0;
var leafLeft = 0;
offset -= $('#BRcontainer').attr('clientHeight') >> 1;
//console.log( 'jumping to ' + leafTop + ' ' + offset);
leafTop += offset;
+ } else {
+ // Show page just a little below the top
+ leafTop -= this.padding / 2;
}
if (pageX) {
// $$$ TODO preserve center of view when switching between mode
// See https://bugs.edge.launchpad.net/gnubook/+bug/416682
+ // XXX maybe better to preserve zoom in each mode
if (1 == mode) {
- this.reduce = this.quantizeReduce(this.reduce);
+ this.onePageCalculateReductionFactors( $('#BRcontainer').attr('clientWidth'), $('#BRcontainer').attr('clientHeight'));
+ this.reduce = this.quantizeReduce(this.reduce, this.onePage.reductionFactors);
this.prepareOnePageView();
} else if (3 == mode) {
- this.reduce = this.quantizeReduce(this.reduce);
+ this.reduce = this.quantizeReduce(this.reduce, this.reductionFactors);
this.prepareThumbnailView();
} else {
// $$$ why don't we save autofit?
- this.twoPage.autofit = false; // Take zoom level from other mode
- this.reduce = this.quantizeReduce(this.reduce);
+ this.twoPage.autofit = null; // Take zoom level from other mode
+ this.twoPageCalculateReductionFactors();
+ this.reduce = this.quantizeReduce(this.reduce, this.twoPage.reductionFactors);
this.prepareTwoPageView();
this.twoPageCenterView(0.5, 0.5); // $$$ TODO preserve center
}
}).appendTo('#BRtwopageview');
this.leafEdgeR = document.createElement('div');
- this.leafEdgeR.className = 'leafEdgeR'; // $$$ the static CSS should be moved into the .css file
+ this.leafEdgeR.className = 'BRleafEdgeR';
$(this.leafEdgeR).css({
- background: 'transparent url(' + this.imagesBaseURL + 'right_edges.png) repeat scroll 0% 0%',
width: this.twoPage.leafEdgeWidthR + 'px',
height: this.twoPage.height-1 + 'px',
- /*right: '10px',*/
left: this.twoPage.gutter+this.twoPage.scaledWR+'px',
top: this.twoPage.bookCoverDivTop+this.twoPage.coverInternalPadding+'px',
- position: 'absolute'
}).appendTo('#BRtwopageview');
this.leafEdgeL = document.createElement('div');
- this.leafEdgeL.className = 'leafEdgeL';
+ this.leafEdgeL.className = 'BRleafEdgeL';
$(this.leafEdgeL).css({
- background: 'transparent url(' + this.imagesBaseURL + 'left_edges.png) repeat scroll 0% 0%',
width: this.twoPage.leafEdgeWidthL + 'px',
height: this.twoPage.height-1 + 'px',
left: this.twoPage.bookCoverDivLeft+this.twoPage.coverInternalPadding+'px',
top: this.twoPage.bookCoverDivTop+this.twoPage.coverInternalPadding+'px',
- position: 'absolute'
}).appendTo('#BRtwopageview');
div = document.createElement('div');
$(div).attr('id', 'BRbookspine').css({
width: this.twoPage.bookSpineDivWidth+'px',
height: this.twoPage.bookSpineDivHeight+'px',
- position: 'absolute',
left: this.twoPage.bookSpineDivLeft+'px',
top: this.twoPage.bookSpineDivTop+'px'
}).appendTo('#BRtwopageview');
BookReader.prototype.prepareTwoPagePopUp = function() {
this.twoPagePopUp = document.createElement('div');
+ this.twoPagePopUp.className = 'BRtwoPagePopUp';
$(this.twoPagePopUp).css({
- border: '1px solid black',
- padding: '2px 6px',
- position: 'absolute',
- fontFamily: 'sans-serif',
- fontSize: '14px',
zIndex: '1000',
- backgroundColor: 'rgb(255, 255, 238)',
- opacity: 0.85
}).appendTo('#BRcontainer');
$(this.twoPagePopUp).hide();
return spreadSize.reduce;
}
+BookReader.prototype.onePageGetAutofitWidth = function() {
+ var widthPadding = 20;
+ return (this.getMedianPageSize().width + 0.0) / ($('#BRcontainer').attr('clientWidth') - widthPadding * 2);
+}
+
+BookReader.prototype.onePageGetAutofitHeight = function() {
+ return (this.getMedianPageSize().height + 0.0) / ($('#BRcontainer').attr('clientHeight') - this.padding * 2); // make sure a little of adjacent pages show
+}
+
+BookReader.prototype.getMedianPageSize = function() {
+ if (this._medianPageSize) {
+ return this._medianPageSize;
+ }
+
+ // A little expensive but we just do it once
+ var widths = [];
+ var heights = [];
+ for (var i = 0; i < this.numLeafs; i++) {
+ widths.push(this.getPageWidth(i));
+ heights.push(this.getPageHeight(i));
+ }
+
+ widths.sort();
+ heights.sort();
+
+ this._medianPageSize = { width: widths[parseInt(widths.length / 2)], height: heights[parseInt(heights.length / 2)] };
+ return this._medianPageSize;
+}
+
+// Update the reduction factors for 1up mode given the available width and height. Recalculates
+// the autofit reduction factors.
+BookReader.prototype.onePageCalculateReductionFactors = function( width, height ) {
+ this.onePage.reductionFactors = this.reductionFactors.concat(
+ [
+ { reduce: this.onePageGetAutofitWidth(), autofit: 'width' },
+ { reduce: this.onePageGetAutofitHeight(), autofit: 'height'}
+ ]);
+ this.onePage.reductionFactors.sort(this._reduceSort);
+}
+
+BookReader.prototype.twoPageCalculateReductionFactors = function() {
+ this.twoPage.reductionFactors = this.reductionFactors.concat(
+ [
+ { reduce: this.getIdealSpreadSize( this.twoPage.currentIndexL, this.twoPage.currentIndexR ).reduce,
+ autofit: 'auto' }
+ ]);
+ this.twoPage.reductionFactors.sort(this._reduceSort);
+}
+
// twoPageSetCursor()
//______________________________________________________________________________
// Set the cursor for two page view
//______________________________________________________________________________
// Scrolls down one screen view
BookReader.prototype.scrollDown = function() {
- if ($.inArray(this.mode, [this.constMode2up, this.constModeThumb]) >= 0) {
+ if ($.inArray(this.mode, [this.constMode1up, this.constModeThumb]) >= 0) {
+ if ( this.mode == this.constMode1up && (this.reduce >= this.onePageGetAutofitHeight()) ) {
+ // Whole pages are visible, scroll whole page only
+ return this.next();
+ }
+
$('#BRcontainer').animate(
- { scrollTop: '+=' + $('#BRcontainer').height() * 0.95 + 'px'},
- 450, 'easeInOutQuint'
+ { scrollTop: '+=' + this._scrollAmount() + 'px'},
+ 400, 'easeInOutExpo'
);
return true;
} else {
//______________________________________________________________________________
// Scrolls up one screen view
BookReader.prototype.scrollUp = function() {
- if ($.inArray(this.mode, [this.constMode2up, this.constModeThumb]) >= 0) {
+ if ($.inArray(this.mode, [this.constMode1up, this.constModeThumb]) >= 0) {
+ if ( this.mode == this.constMode1up && (this.reduce >= this.onePageGetAutofitHeight()) ) {
+ // Whole pages are visible, scroll whole page only
+ return this.prev();
+ }
+
$('#BRcontainer').animate(
- { scrollTop: '-=' + $('#BRcontainer').height() * 0.95 + 'px'},
- 450, 'easeInOutQuint'
+ { scrollTop: '-=' + this._scrollAmount() + 'px'},
+ 400, 'easeInOutExpo'
);
return true;
} else {
}
}
+// _scrollAmount()
+//______________________________________________________________________________
+// The amount to scroll vertically in integer pixels
+BookReader.prototype._scrollAmount = function() {
+ if (this.constMode1up == this.mode) {
+ // Overlap by % of page size
+ return parseInt($('#BRcontainer').attr('clientHeight') - this.getPageHeight(this.currentIndex()) / this.reduce * 0.03);
+ }
+
+ return parseInt(0.9 * $('#BRcontainer').attr('clientHeight'));
+}
+
// flipBackToIndex()
//______________________________________________________________________________
var leftEdgeTmpLeft = gutter - currWidthL - leafEdgeTmpW;
this.leafEdgeTmp = document.createElement('div');
+ this.leafEdgeTmp.className = 'BRleafEdgeTmp';
$(this.leafEdgeTmp).css({
- borderStyle: 'solid none solid solid',
- borderColor: 'rgb(51, 51, 34)',
- borderWidth: '1px 0px 1px 1px',
- background: 'transparent url(' + this.imagesBaseURL + 'left_edges.png) repeat scroll 0% 0%',
width: leafEdgeTmpW + 'px',
height: this.twoPage.height-1 + 'px',
left: leftEdgeTmpLeft + 'px',
- top: top+'px',
- position: 'absolute',
+ top: top+'px',
zIndex:1000
}).appendTo('#BRtwopageview');
var gutter = middle + this.gutterOffsetForIndex(newIndexL);
this.leafEdgeTmp = document.createElement('div');
+ this.leafEdgeTmp.className = 'BRleafEdgeTmp';
$(this.leafEdgeTmp).css({
- borderStyle: 'solid none solid solid',
- borderColor: 'rgb(51, 51, 34)',
- borderWidth: '1px 0px 1px 1px',
- background: 'transparent url(' + this.imagesBaseURL + 'left_edges.png) repeat scroll 0% 0%',
width: leafEdgeTmpW + 'px',
height: this.twoPage.height-1 + 'px',
left: gutter+scaledW+'px',
top: top+'px',
- position: 'absolute',
zIndex:1000
}).appendTo('#BRtwopageview');
// Change to autofit if book is too large
if (this.reduce < this.twoPageGetAutofitReduce()) {
- this.zoom2up(0);
+ this.zoom2up('auto');
}
var self = this;
});
jToolbar.find('.book_up').bind('click', function(e) {
- if ($.inArray(self.mode, [self.constMode2up, self.constModeThumb]) >= 0) {
+ if ($.inArray(self.mode, [self.constMode1up, self.constModeThumb]) >= 0) {
self.scrollUp();
} else {
self.prev();
});
jToolbar.find('.book_down').bind('click', function(e) {
- if ($.inArray(self.mode, [self.constMode2up, self.constModeThumb]) >= 0) {
+ if ($.inArray(self.mode, [self.constMode1up, self.constModeThumb]) >= 0) {
self.scrollDown();
} else {
self.next();
// Update the displayed zoom factor based on reduction factor
BookReader.prototype.updateToolbarZoom = function(reduce) {
var value;
- if (this.constMode2up == this.mode && this.twoPage.autofit) {
- value = 'Auto';
+ var autofit = null;
+
+ // $$$ TODO preserve zoom/fit for each mode
+ if (this.mode == this.constMode2up) {
+ autofit = this.twoPage.autofit;
+ } else {
+ autofit = this.onePage.autofit;
+ }
+
+ if (autofit) {
+ value = autofit[0].toUpperCase() + autofit.slice(1);
} else {
value = (100 / reduce).toFixed(2);
// Strip trailing zeroes and decimal if all zeroes