// You must also add a numLeafs property before calling init().
function BookReader() {
+
+ // Mode constants
+ this.constMode1up = 1;
+ this.constMode2up = 2;
+ this.constModeThumb = 3;
+
this.reduce = 4;
- this.padding = 10;
- this.mode = 1; //1 or 2
- this.ui = 'full'; // UI mode
+ this.padding = 10; // Padding in 1up
+
+ this.mode = this.constMode1up;
+ this.ui = 'full'; // UI mode
+ this.uiAutoHide = true; // Controls whether nav/toolbar will autohide
+
+ // thumbnail mode
+ this.thumbWidth = 100; // will be overridden during prepareThumbnailView
+ this.thumbRowBuffer = 2; // number of rows to pre-cache out a view
+ this.thumbColumns = 6; // default
+ this.thumbMaxLoading = 4; // number of thumbnails to load at once
+ this.thumbPadding = 10; // spacing between thumbnails
+ this.displayedRows=[];
this.displayedIndices = [];
//this.indicesToDisplay = [];
this.lastDisplayableIndex2up = null;
// We link to index.php to avoid redirect which breaks back button
+ // Should be overriden (before init) by custom implmentations.
this.logoURL = 'http://www.archive.org/index.php';
- // Base URL for images
+ // Base URL for UI images - should be overriden (before init) by
+ // custom implementations.
+ // $$$ This is the same directory as the images referenced by relative
+ // path in the CSS. Would be better to automagically find that path.
this.imagesBaseURL = '/bookreader/images/';
- // Mode constants
- this.constMode1up = 1;
- this.constMode2up = 2;
// Zoom levels
- this.reductionFactors = [0.5, 1, 2, 4, 8, 16];
-
+ // $$$ provide finer grained zooming
+ 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
+ coverInternalPadding: 0, // Width of cover
+ coverExternalPadding: 0, // Padding outside of cover
+ bookSpineDivWidth: 0, // Width of book spine $$$ consider sizing based on book length
+ autofit: 'auto'
+ };
+
+ // This object/dictionary controls which optional features are enabled
+ // XXXmang in progress
+ this.features = {
+ // search
+ // read aloud
+ // open library entry
+ // table of contents
+ // embed/share ui
+ // info ui
};
- // Background color for pages (e.g. when loading page image)
- // $$$ TODO dynamically calculate based on page images
- this.pageDefaultBackgroundColor = 'rgb(234, 226, 205)';
+ return this;
};
// init()
BookReader.prototype.init = function() {
var startIndex = undefined;
+ this.pageScale = this.reduce; // preserve current reduce
// Find start index and mode if set in location hash
var params = this.paramsFromFragment(window.location.hash);
+
+ // Sanitize/process parameters
+
+ if ( !this.canSwitchToMode( this.mode ) ) {
+ this.mode = this.constMode1up;
+ }
if ('undefined' != typeof(params.index)) {
startIndex = params.index;
} else if ('undefined' != typeof(params.page)) {
startIndex = this.getPageIndex(params.page);
}
-
+
if ('undefined' == typeof(startIndex)) {
if ('undefined' != typeof(this.titleLeaf)) {
- startIndex = this.leafNumToIndex(this.titleLeaf);
+ // title leaf is known - but only use as default if book has a few pages
+ if (this.numLeafs > 2) {
+ startIndex = this.leafNumToIndex(this.titleLeaf);
+ }
}
}
// search engine visibility
document.title = this.shortTitle(50);
- // Sanitize parameters
- if ( !this.canSwitchToMode( this.mode ) ) {
- this.mode = this.constMode1up;
- }
-
$("#BookReader").empty();
+
this.initToolbar(this.mode, this.ui); // Build inside of toolbar div
+
$("#BookReader").append("<div id='BRcontainer'></div>");
$("#BRcontainer").append("<div id='BRpageview'></div>");
+
+ this.initNavbar();
+ this.bindNavigationHandlers();
+
+ // Autohide nav after showing for awhile
+ var self = this;
+ if (this.uiAutoHide) {
+ $(window).bind('load', function() {
+ setTimeout(function() { self.hideNavigation(); }, 3000);
+ });
+ };
$("#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 = [];
e.data.updateSearchHilites(); //deletes hilights but does not call remove()
e.data.loadLeafs();
+ } else if (3 == e.data.mode){
+ e.data.prepareThumbnailView();
} else {
//console.log('drawing 2 page view');
$('.BRpagediv1up').bind('mousedown', this, function(e) {
// $$$ the purpose of this is to disable selection of the image (makes it turn blue)
// but this also interferes with right-click. See https://bugs.edge.launchpad.net/gnubook/+bug/362626
+ return false;
});
+ // $$$ refactor this so it's enough to set the first index and call preparePageView
+ // (get rid of mode-specific logic at this point)
if (1 == this.mode) {
- this.resizePageView();
this.firstIndex = startIndex;
+ this.prepareOnePageView();
+ this.jumpToIndex(startIndex);
+ } else if (3 == this.mode) {
+ this.firstIndex = startIndex;
+ this.prepareThumbnailView();
this.jumpToIndex(startIndex);
} else {
//this.resizePageView();
// Enact other parts of initial params
this.updateFromParams(params);
+
+ // Start AJAX request for OL data
+ if (this.getOpenLibraryRecord) {
+ this.getOpenLibraryRecord(this.gotOpenLibraryRecord);
+ }
}
BookReader.prototype.setupKeyListeners = function() {
BookReader.prototype.drawLeafs = function() {
if (1 == this.mode) {
this.drawLeafsOnePage();
+ } else if (3 == this.mode) {
+ this.drawLeafsThumbnail();
} else {
this.drawLeafsTwoPage();
}
-}
-
-// setDragHandler()
-//______________________________________________________________________________
-BookReader.prototype.setDragHandler = function(div) {
- div.dragging = false;
-
- $(div).unbind('mousedown').bind('mousedown', function(e) {
- e.preventDefault();
-
- //console.log('mousedown at ' + e.pageY);
-
- this.dragging = true;
- this.prevMouseX = e.pageX;
- this.prevMouseY = e.pageY;
-
- var startX = e.pageX;
- var startY = e.pageY;
- var startTop = $('#BRcontainer').attr('scrollTop');
- var startLeft = $('#BRcontainer').attr('scrollLeft');
-
- });
-
- $(div).unbind('mousemove').bind('mousemove', function(ee) {
- ee.preventDefault();
-
- // console.log('mousemove ' + ee.pageX + ',' + ee.pageY);
-
- var offsetX = ee.pageX - this.prevMouseX;
- var offsetY = ee.pageY - this.prevMouseY;
-
- if (this.dragging) {
- $('#BRcontainer').attr('scrollTop', $('#BRcontainer').attr('scrollTop') - offsetY);
- $('#BRcontainer').attr('scrollLeft', $('#BRcontainer').attr('scrollLeft') - offsetX);
- }
-
- this.prevMouseX = ee.pageX;
- this.prevMouseY = ee.pageY;
-
- });
- $(div).unbind('mouseup').bind('mouseup', function(ee) {
- ee.preventDefault();
- //console.log('mouseup');
-
- this.dragging = false;
- });
-
- $(div).unbind('mouseleave').bind('mouseleave', function(e) {
- e.preventDefault();
- //console.log('mouseleave');
-
- this.dragging = false;
- });
-
- $(div).unbind('mouseenter').bind('mouseenter', function(e) {
- e.preventDefault();
- //console.log('mouseenter');
-
- this.dragging = false;
- });
}
-// setDragHandler2UP()
+// bindGestures(jElement)
//______________________________________________________________________________
-BookReader.prototype.setDragHandler2UP = function(div) {
- div.dragging = false;
-
- $(div).unbind('mousedown').bind('mousedown', function(e) {
- e.preventDefault();
-
- //console.log('mousedown at ' + e.pageY);
-
- this.dragStart = {x: e.pageX, y: e.pageY };
- this.mouseDown = true;
- this.dragging = false; // wait until drag distance
- this.prevMouseX = e.pageX;
- this.prevMouseY = e.pageY;
-
- var startX = e.pageX;
- var startY = e.pageY;
- var startTop = $('#BRcontainer').attr('scrollTop');
- var startLeft = $('#BRcontainer').attr('scrollLeft');
-
- });
-
- $(div).unbind('mousemove').bind('mousemove', function(ee) {
- ee.preventDefault();
-
- // console.log('mousemove ' + ee.pageX + ',' + ee.pageY);
-
- var offsetX = ee.pageX - this.prevMouseX;
- var offsetY = ee.pageY - this.prevMouseY;
-
- var minDragDistance = 5; // $$$ constant
-
- var distance = Math.max(Math.abs(offsetX), Math.abs(offsetY));
-
- if (this.mouseDown && (distance > minDragDistance)) {
- //console.log('drag start!');
-
- this.dragging = true;
- }
-
- if (this.dragging) {
- $('#BRcontainer').attr('scrollTop', $('#BRcontainer').attr('scrollTop') - offsetY);
- $('#BRcontainer').attr('scrollLeft', $('#BRcontainer').attr('scrollLeft') - offsetX);
- this.prevMouseX = ee.pageX;
- this.prevMouseY = ee.pageY;
- }
-
-
- });
-
- /*
- $(div).unbind('mouseup').bind('mouseup', function(ee) {
- ee.preventDefault();
- //console.log('mouseup');
+BookReader.prototype.bindGestures = function(jElement) {
- this.dragging = false;
- this.mouseDown = false;
- });
- */
-
-
- $(div).unbind('mouseleave').bind('mouseleave', function(e) {
+ jElement.unbind('gesturechange').bind('gesturechange', function(e) {
e.preventDefault();
- //console.log('mouseleave');
-
- this.dragging = false;
- this.mouseDown = false;
+ if (e.originalEvent.scale > 1.5) {
+ br.zoom(1);
+ } else if (e.originalEvent.scale < 0.6) {
+ br.zoom(-1);
+ }
});
-
- $(div).unbind('mouseenter').bind('mouseenter', function(e) {
- e.preventDefault();
- //console.log('mouseenter');
- this.dragging = false;
- this.mouseDown = false;
- });
}
BookReader.prototype.setClickHandler2UP = function( element, data, handler) {
//console.log('setting handler');
//console.log(element.tagName);
- $(element).unbind('click').bind('click', data, function(e) {
- e.preventDefault();
-
- //console.log('click!');
-
- if (this.mouseDown && (!this.dragging)) {
- //console.log('click not dragging!');
- handler(e);
- }
-
- this.dragging = false;
- this.mouseDown = false;
+ $(element).unbind('tap').bind('tap', data, function(e) {
+ handler(e);
});
}
leafTop += height +10;
leafBottom += 10;
}
-
+
var firstIndexToDraw = indicesToDisplay[0];
this.firstIndex = firstIndexToDraw;
var index = indicesToDisplay[i];
var height = parseInt(this._getPageHeight(index)/this.reduce);
- if(-1 == jQuery.inArray(indicesToDisplay[i], this.displayedIndices)) {
+ if (BookReader.util.notInArray(indicesToDisplay[i], this.displayedIndices)) {
var width = parseInt(this._getPageWidth(index)/this.reduce);
//console.log("displaying leaf " + indicesToDisplay[i] + ' leafTop=' +leafTop);
var div = document.createElement("div");
$(div).css('height', height+'px');
//$(div).text('loading...');
- this.setDragHandler(div);
-
$('#BRpageview').append(div);
var img = document.createElement("img");
}
for (i=0; i<this.displayedIndices.length; i++) {
- if (-1 == jQuery.inArray(this.displayedIndices[i], indicesToDisplay)) {
+ if (BookReader.util.notInArray(this.displayedIndices[i], indicesToDisplay)) {
var index = this.displayedIndices[i];
//console.log('Removing leaf ' + index);
//console.log('id='+'#pagediv'+index+ ' top = ' +$('#pagediv'+index).css('top'));
}
+// drawLeafsThumbnail()
+//______________________________________________________________________________
+// If seekIndex is defined, the view will be drawn with that page visible (without any
+// animated scrolling)
+BookReader.prototype.drawLeafsThumbnail = function( seekIndex ) {
+ //alert('drawing leafs!');
+ this.timer = null;
+
+ var viewWidth = $('#BRcontainer').attr('scrollWidth') - 20; // width minus buffer
+
+ //console.log('top=' + scrollTop + ' bottom='+scrollBottom);
+
+ var i;
+ var leafWidth;
+ var leafHeight;
+ var rightPos = 0;
+ var bottomPos = 0;
+ var maxRight = 0;
+ var currentRow = 0;
+ var leafIndex = 0;
+ var leafMap = [];
+
+ var self = this;
+
+ // Will be set to top of requested seek index, if set
+ var seekTop;
+
+ // Calculate the position of every thumbnail. $$$ cache instead of calculating on every draw
+ for (i=0; i<this.numLeafs; i++) {
+ leafWidth = this.thumbWidth;
+ if (rightPos + (leafWidth + this.thumbPadding) > viewWidth){
+ currentRow++;
+ rightPos = 0;
+ leafIndex = 0;
+ }
+
+ if (leafMap[currentRow]===undefined) { leafMap[currentRow] = {}; }
+ if (leafMap[currentRow].leafs===undefined) {
+ leafMap[currentRow].leafs = [];
+ leafMap[currentRow].height = 0;
+ leafMap[currentRow].top = 0;
+ }
+ leafMap[currentRow].leafs[leafIndex] = {};
+ leafMap[currentRow].leafs[leafIndex].num = i;
+ leafMap[currentRow].leafs[leafIndex].left = rightPos;
+
+ leafHeight = parseInt((this.getPageHeight(leafMap[currentRow].leafs[leafIndex].num)*this.thumbWidth)/this.getPageWidth(leafMap[currentRow].leafs[leafIndex].num), 10);
+ if (leafHeight > leafMap[currentRow].height) {
+ leafMap[currentRow].height = leafHeight;
+ }
+ if (leafIndex===0) { bottomPos += this.thumbPadding + leafMap[currentRow].height; }
+ rightPos += leafWidth + this.thumbPadding;
+ if (rightPos > maxRight) { maxRight = rightPos; }
+ leafIndex++;
+
+ if (i == seekIndex) {
+ seekTop = bottomPos - this.thumbPadding - leafMap[currentRow].height;
+ }
+ }
+
+ // reset the bottom position based on thumbnails
+ $('#BRpageview').height(bottomPos);
+
+ var pageViewBuffer = Math.floor(($('#BRcontainer').attr('scrollWidth') - maxRight) / 2) - 14;
+
+ // If seekTop is defined, seeking was requested and target found
+ if (typeof(seekTop) != 'undefined') {
+ $('#BRcontainer').scrollTop( seekTop );
+ }
+
+ var scrollTop = $('#BRcontainer').attr('scrollTop');
+ var scrollBottom = scrollTop + $('#BRcontainer').height();
+
+ var leafTop = 0;
+ var leafBottom = 0;
+ var rowsToDisplay = [];
+
+ // Visible leafs with least/greatest index
+ var leastVisible = this.numLeafs - 1;
+ var mostVisible = 0;
+
+ // Determine the thumbnails in view
+ for (i=0; i<leafMap.length; i++) {
+ leafBottom += this.thumbPadding + leafMap[i].height;
+ var topInView = (leafTop >= scrollTop) && (leafTop <= scrollBottom);
+ var bottomInView = (leafBottom >= scrollTop) && (leafBottom <= scrollBottom);
+ var middleInView = (leafTop <=scrollTop) && (leafBottom>=scrollBottom);
+ if (topInView | bottomInView | middleInView) {
+ //console.log('row to display: ' + j);
+ rowsToDisplay.push(i);
+ if (leafMap[i].leafs[0].num < leastVisible) {
+ leastVisible = leafMap[i].leafs[0].num;
+ }
+ if (leafMap[i].leafs[leafMap[i].leafs.length - 1].num > mostVisible) {
+ mostVisible = leafMap[i].leafs[leafMap[i].leafs.length - 1].num;
+ }
+ }
+ if (leafTop > leafMap[i].top) { leafMap[i].top = leafTop; }
+ leafTop = leafBottom;
+ }
+
+ // create a buffer of preloaded rows before and after the visible rows
+ var firstRow = rowsToDisplay[0];
+ var lastRow = rowsToDisplay[rowsToDisplay.length-1];
+ for (i=1; i<this.thumbRowBuffer+1; i++) {
+ if (lastRow+i < leafMap.length) { rowsToDisplay.push(lastRow+i); }
+ }
+ for (i=1; i<this.thumbRowBuffer+1; i++) {
+ if (firstRow-i >= 0) { rowsToDisplay.push(firstRow-i); }
+ }
+
+ // Create the thumbnail divs and images (lazy loaded)
+ var j;
+ var row;
+ var left;
+ var index;
+ var div;
+ var link;
+ var img;
+ var page;
+ for (i=0; i<rowsToDisplay.length; i++) {
+ if (BookReader.util.notInArray(rowsToDisplay[i], this.displayedRows)) {
+ row = rowsToDisplay[i];
+
+ for (j=0; j<leafMap[row].leafs.length; j++) {
+ index = j;
+ leaf = leafMap[row].leafs[j].num;
+
+ leafWidth = this.thumbWidth;
+ leafHeight = parseInt((this.getPageHeight(leaf)*this.thumbWidth)/this.getPageWidth(leaf), 10);
+ leafTop = leafMap[row].top;
+ left = leafMap[row].leafs[index].left + pageViewBuffer;
+ if ('rl' == this.pageProgression){
+ left = viewWidth - leafWidth - left;
+ }
+
+ div = document.createElement("div");
+ div.id = 'pagediv'+leaf;
+ div.style.position = "absolute";
+ div.className = "BRpagedivthumb";
+
+ left += this.thumbPadding;
+ $(div).css('top', leafTop + 'px');
+ $(div).css('left', left+'px');
+ $(div).css('width', leafWidth+'px');
+ $(div).css('height', leafHeight+'px');
+ //$(div).text('loading...');
+
+ // link to page in single page mode
+ link = document.createElement("a");
+ $(link).data('leaf', leaf);
+ $(link).bind('tap', function(event) {
+ self.firstIndex = $(this).data('leaf');
+ self.switchMode(self.constMode1up);
+ event.preventDefault();
+ });
+
+ // $$$ we don't actually go to this URL (click is handled in handler above)
+ link.href = '#page/' + (this.getPageNum(leaf)) +'/mode/1up' ;
+ $(div).append(link);
+
+ $('#BRpageview').append(div);
+
+ img = document.createElement("img");
+ var thumbReduce = Math.floor(this.getPageWidth(leaf) / this.thumbWidth);
+
+ $(img).attr('src', this.imagesBaseURL + 'transparent.png')
+ .css({'width': leafWidth+'px', 'height': leafHeight+'px' })
+ .addClass('BRlazyload')
+ // Store the URL of the image that will replace this one
+ .data('srcURL', this._getPageURI(leaf, thumbReduce));
+ $(link).append(img);
+ //console.log('displaying thumbnail: ' + leaf);
+ }
+ }
+ }
+
+ // Remove thumbnails that are not to be displayed
+ var k;
+ for (i=0; i<this.displayedRows.length; i++) {
+ if (BookReader.util.notInArray(this.displayedRows[i], rowsToDisplay)) {
+ row = this.displayedRows[i];
+
+ // $$$ Safari doesn't like the comprehension
+ //var rowLeafs = [leaf.num for each (leaf in leafMap[row].leafs)];
+ //console.log('Removing row ' + row + ' ' + rowLeafs);
+
+ for (k=0; k<leafMap[row].leafs.length; k++) {
+ index = leafMap[row].leafs[k].num;
+ //console.log('Removing leaf ' + index);
+ $('#pagediv'+index).remove();
+ }
+ } else {
+ /*
+ var mRow = this.displayedRows[i];
+ var mLeafs = '[' + [leaf.num for each (leaf in leafMap[mRow].leafs)] + ']';
+ console.log('NOT Removing row ' + mRow + ' ' + mLeafs);
+ */
+ }
+ }
+
+ // Update which page is considered current to make sure a visible page is the current one
+ var currentIndex = this.currentIndex();
+ // console.log('current ' + currentIndex);
+ // console.log('least visible ' + leastVisible + ' most visible ' + mostVisible);
+ if (currentIndex < leastVisible) {
+ this.setCurrentIndex(leastVisible);
+ } else if (currentIndex > mostVisible) {
+ this.setCurrentIndex(mostVisible);
+ }
+
+ this.displayedRows = rowsToDisplay.slice();
+
+ // Update hash, but only if we're currently displaying a leaf
+ // Hack that fixes #365790
+ if (this.displayedRows.length > 0) {
+ this.updateLocationHash();
+ }
+
+ // remove previous highlights
+ $('.BRpagedivthumb_highlight').removeClass('BRpagedivthumb_highlight');
+
+ // highlight current page
+ $('#pagediv'+this.currentIndex()).addClass('BRpagedivthumb_highlight');
+
+ this.lazyLoadThumbnails();
+
+ // Update page number box. $$$ refactor to function
+ if (null !== this.getPageNum(this.currentIndex())) {
+ $("#BRpagenum").val(this.getPageNum(this.currentIndex()));
+ } else {
+ $("#BRpagenum").val('');
+ }
+
+ this.updateToolbarZoom(this.reduce);
+}
+
+BookReader.prototype.lazyLoadThumbnails = function() {
+
+ // console.log('lazy load');
+
+ // We check the complete property since load may not be fired if loading from the cache
+ $('.BRlazyloading').filter('[complete=true]').removeClass('BRlazyloading');
+
+ var loading = $('.BRlazyloading').length;
+ var toLoad = this.thumbMaxLoading - loading;
+
+ // console.log(' ' + loading + ' thumbnails loading');
+ // console.log(' this.thumbMaxLoading ' + this.thumbMaxLoading);
+
+ var self = this;
+
+ if (toLoad > 0) {
+ // $$$ TODO load those near top (but not beyond) page view first
+ $('#BRpageview img.BRlazyload').filter(':lt(' + toLoad + ')').each( function() {
+ self.lazyLoadImage(this);
+ });
+ }
+}
+
+BookReader.prototype.lazyLoadImage = function (dummyImage) {
+ //console.log(' lazy load started for ' + $(dummyImage).data('srcURL').match('([0-9]{4}).jp2')[1] );
+
+ var img = new Image();
+ var self = this;
+
+ $(img)
+ .addClass('BRlazyloading')
+ .one('load', function() {
+ //if (console) { console.log(' onload ' + $(this).attr('src').match('([0-9]{4}).jp2')[1]); };
+
+ $(this).removeClass('BRlazyloading');
+
+ // $$$ Calling lazyLoadThumbnails here was causing stack overflow on IE so
+ // we call the function after a slight delay. Also the img.complete property
+ // is not yet set in IE8 inside this onload handler
+ setTimeout(function() { self.lazyLoadThumbnails(); }, 100);
+ })
+ .one('error', function() {
+ // Remove class so we no longer count as loading
+ $(this).removeClass('BRlazyloading');
+ })
+ .attr( { width: $(dummyImage).width(),
+ height: $(dummyImage).height(),
+ src: $(dummyImage).data('srcURL')
+ });
+
+ // replace with the new img
+ $(dummyImage).before(img).remove();
+
+ img = null; // tidy up closure
+}
+
+
// drawLeafsTwoPage()
//______________________________________________________________________________
BookReader.prototype.drawLeafsTwoPage = function() {
left: this.twoPage.gutter-this.twoPage.scaledWL+'px',
right: '',
top: top+'px',
- backgroundColor: this.getPageBackgroundColor(indexL),
height: this.twoPage.height +'px', // $$$ height forced the same for both pages
width: this.twoPage.scaledWL + 'px',
- borderRight: '1px solid black',
zIndex: 2
}).appendTo('#BRtwopageview');
left: this.twoPage.gutter+'px',
right: '',
top: top+'px',
- backgroundColor: this.getPageBackgroundColor(indexR),
height: this.twoPage.height + 'px', // $$$ height forced the same for both pages
width: this.twoPage.scaledWR + 'px',
- borderLeft: '1px solid black',
zIndex: 2
}).appendTo('#BRtwopageview');
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();
$('#BRpageview').empty()
// resizePageView()
//______________________________________________________________________________
BookReader.prototype.resizePageView = function() {
+
+ // $$$ This code assumes 1up mode
+ // e.g. does not preserve position in thumbnail mode
+ // See http://bugs.launchpad.net/bookreader/+bug/552972
+
+ switch (this.mode) {
+ case this.constMode1up:
+ case this.constMode2up:
+ this.resizePageView1up();
+ break;
+ case this.constModeThumb:
+ this.prepareThumbnailView( this.currentIndex() );
+ break;
+ default:
+ alert('Resize not implemented for this mode');
+ }
+}
+
+BookReader.prototype.resizePageView1up = function() {
var i;
var viewHeight = 0;
//var viewWidth = $('#BRcontainer').width(); //includes scrollBar
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);
if (width>viewWidth) viewWidth=width;
}
$('#BRpageview').height(viewHeight);
- $('#BRpageview').width(viewWidth);
+ $('#BRpageview').width(viewWidth);
var newCenterY = scrollRatio*viewHeight;
var newTop = Math.max(0, Math.floor( newCenterY - $('#BRcontainer').height()/2 ));
//this.centerPageView();
this.loadLeafs();
-
- // Not really needed until there is 1up autofit
+
this.removeSearchHilites();
this.updateSearchHilites();
}
+
// centerX1up()
//______________________________________________________________________________
// Returns the current offset of the viewport center in scaled document coordinates.
// 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
var oldCenter = this.twoPageGetViewCenter();
this.prepareTwoPageView(oldCenter.percentageX, oldCenter.percentageY);
}
+BookReader.prototype.zoomThumb = function(direction) {
+ var oldColumns = this.thumbColumns;
+ switch (direction) {
+ case -1:
+ this.thumbColumns += 1;
+ break;
+ case 1:
+ this.thumbColumns -= 1;
+ break;
+ }
+
+ // clamp
+ if (this.thumbColumns < 2) {
+ this.thumbColumns = 2;
+ } else if (this.thumbColumns > 8) {
+ this.thumbColumns = 8;
+ }
+
+ if (this.thumbColumns != oldColumns) {
+ this.prepareThumbnailView();
+ }
+}
+
+// Returns the width per thumbnail to display the requested number of columns
+// Note: #BRpageview must already exist since its width is used to calculate the
+// thumbnail width
+BookReader.prototype.getThumbnailWidth = function(thumbnailColumns) {
+ var padding = (thumbnailColumns + 1) * this.thumbPadding;
+ var width = ($('#BRpageview').width() - padding) / (thumbnailColumns + 0.5); // extra 0.5 is for some space at sides
+ return parseInt(width);
+}
// 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
+
+ if (direction == 'in') {
+ var newReduceIndex = 0;
- for (var i = 1; i < this.reductionFactors.length; i++) {
- if (this.reductionFactors[i] < reduce) {
- newReduce = this.reductionFactors[i];
+ 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 {
+ } else if (this.constModeThumb == this.mode) {
+ var viewWidth = $('#BRcontainer').attr('scrollWidth') - 20; // width minus buffer
+ var i;
+ var leafWidth = 0;
+ var leafHeight = 0;
+ var rightPos = 0;
+ var bottomPos = 0;
+ var rowHeight = 0;
+ var leafTop = 0;
+ var leafIndex = 0;
+
+ for (i=0; i<(index+1); i++) {
+ leafWidth = this.thumbWidth;
+ if (rightPos + (leafWidth + this.thumbPadding) > viewWidth){
+ rightPos = 0;
+ rowHeight = 0;
+ leafIndex = 0;
+ }
+
+ leafHeight = parseInt((this.getPageHeight(leafIndex)*this.thumbWidth)/this.getPageWidth(leafIndex), 10);
+ if (leafHeight > rowHeight) { rowHeight = leafHeight; }
+ if (leafIndex==0) { leafTop = bottomPos; }
+ if (leafIndex==0) { bottomPos += this.thumbPadding + rowHeight; }
+ rightPos += leafWidth + this.thumbPadding;
+ leafIndex++;
+ }
+ this.firstIndex=index;
+ if ($('#BRcontainer').attr('scrollTop') == leafTop) {
+ this.loadLeafs();
+ } else {
+ $('#BRcontainer').animate({scrollTop: leafTop },'fast');
+ }
+ } else {
+ // 1up
var i;
var leafTop = 0;
var leafLeft = 0;
h = parseInt(this._getPageHeight(i)/this.reduce);
leafTop += h + this.padding;
}
-
+
if (pageY) {
//console.log('pageY ' + pageY);
var offset = parseInt( (pageY) / this.reduce);
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) {
var offset = parseInt( (pageX) / this.reduce);
offset -= $('#BRcontainer').attr('clientWidth') >> 1;
leafLeft += offset;
- }
+ } else {
+ // Preserve left position
+ leafLeft = $('#BRcontainer').scrollLeft();
+ }
//$('#BRcontainer').attr('scrollTop', leafTop);
$('#BRcontainer').animate({scrollTop: leafTop, scrollLeft: leafLeft },'fast');
//console.log(' asked to switch to mode ' + mode + ' from ' + this.mode);
- if (mode == this.mode) return;
+ if (mode == this.mode) {
+ return;
+ }
if (!this.canSwitchToMode(mode)) {
return;
this.removeSearchHilites();
this.mode = mode;
-
- this.switchToolbarMode(mode);
+ //this.switchToolbarMode(mode);
+
+ // reinstate scale if moving from thumbnail view
+ if (this.pageScale != this.reduce) {
+ this.reduce = this.pageScale;
+ }
// $$$ 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) {
+ $('button.thumb').hide();
+ $('button.twopg').show();
+ this.reduce = this.quantizeReduce(this.reduce, this.reductionFactors);
+ this.prepareThumbnailView();
} else {
- this.twoPage.autofit = false; // Take zoom level from other mode
- this.reduce = this.quantizeReduce(this.reduce);
+ // $$$ why don't we save autofit?
+ // this.twoPage.autofit = null; // Take zoom level from other mode
+ this.twoPageCalculateReductionFactors();
+ this.reduce = this.quantizeReduce(this.reduce, this.twoPage.reductionFactors);
+ $('button.thumb').show();
+ $('button.twopg').hide();
this.prepareTwoPageView();
this.twoPageCenterView(0.5, 0.5); // $$$ TODO preserve center
}
// var startLeaf = this.displayedIndices[0];
var startLeaf = this.currentIndex();
-
+
$('#BRcontainer').empty();
$('#BRcontainer').css({
overflowY: 'scroll',
overflowX: 'auto'
});
+
+ $("#BRcontainer").append("<div id='BRpageview'></div>");
+
+ // Attaches to first child - child must be present
+ $('#BRcontainer').dragscrollable();
+ this.bindGestures($('#BRcontainer'));
+
+ // $$$ keep select enabled for now since disabling it breaks keyboard
+ // nav in FF 3.6 (https://bugs.edge.launchpad.net/bookreader/+bug/544666)
+ // BookReader.util.disableSelect($('#BRpageview'));
- var brPageView = $("#BRcontainer").append("<div id='BRpageview'></div>");
-
- this.resizePageView();
+ this.resizePageView();
this.jumpToIndex(startLeaf);
this.displayedIndices = [];
this.drawLeafsOnePage();
+}
+
+//prepareThumbnailView()
+//______________________________________________________________________________
+BookReader.prototype.prepareThumbnailView = function() {
+
+ $('#BRcontainer').empty();
+ $('#BRcontainer').css({
+ overflowY: 'scroll',
+ overflowX: 'auto'
+ });
- // Bind mouse handlers
- // Disable mouse click to avoid selected/highlighted page images - bug 354239
- brPageView.bind('mousedown', function(e) {
- // $$$ check here for right-click and don't disable. Also use jQuery style
- // for stopping propagation. See https://bugs.edge.launchpad.net/gnubook/+bug/362626
- return false;
- })
- // Special hack for IE7
- brPageView[0].onselectstart = function(e) { return false; };
+ $("#BRcontainer").append("<div id='BRpageview'></div>");
+
+ $('#BRcontainer').dragscrollable();
+ this.bindGestures($('#BRcontainer'));
+
+ // $$$ keep select enabled for now since disabling it breaks keyboard
+ // nav in FF 3.6 (https://bugs.edge.launchpad.net/bookreader/+bug/544666)
+ // BookReader.util.disableSelect($('#BRpageview'));
+
+ this.thumbWidth = this.getThumbnailWidth(this.thumbColumns);
+ this.reduce = this.getPageWidth(0)/this.thumbWidth;
+
+ this.displayedRows = [];
+
+ // Draw leafs with current index directly in view (no animating to the index)
+ this.drawLeafsThumbnail( this.currentIndex() );
+
}
// prepareTwoPageView()
// Add the two page view
// $$$ Can we get everything set up and then append?
$('#BRcontainer').append('<div id="BRtwopageview"></div>');
+
+ // Attaches to first child, so must come after we add the page view
+ $('#BRcontainer').dragscrollable();
+ this.bindGestures($('#BRcontainer'));
// $$$ calculate first then set
$('#BRtwopageview').css( {
this.twoPage.coverDiv = document.createElement('div');
$(this.twoPage.coverDiv).attr('id', 'BRbookcover').css({
- border: '1px solid rgb(68, 25, 17)',
width: this.twoPage.bookCoverDivWidth + 'px',
height: this.twoPage.bookCoverDivHeight+'px',
visibility: 'visible',
- position: 'absolute',
- backgroundColor: '#663929',
- left: this.twoPage.bookCoverDivLeft + 'px',
- top: this.twoPage.bookCoverDivTop+'px',
- MozBorderRadiusTopleft: '7px',
- MozBorderRadiusTopright: '7px',
- MozBorderRadiusBottomright: '7px',
- MozBorderRadiusBottomleft: '7px'
}).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({
- borderStyle: 'solid solid solid none',
- borderColor: 'rgb(51, 51, 34)',
- borderWidth: '1px 1px 1px 0px',
- 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',*/
+ height: this.twoPage.height + 'px',
left: this.twoPage.gutter+this.twoPage.scaledWR+'px',
- top: this.twoPage.bookCoverDivTop+this.twoPage.coverInternalPadding+'px',
- position: 'absolute'
+ top: this.twoPage.bookCoverDivTop+this.twoPage.coverInternalPadding+'px'
}).appendTo('#BRtwopageview');
this.leafEdgeL = document.createElement('div');
- this.leafEdgeL.className = 'leafEdgeL';
- $(this.leafEdgeL).css({ // $$$ static CSS should be moved to file
- 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%',
+ this.leafEdgeL.className = 'BRleafEdgeL';
+ $(this.leafEdgeL).css({
width: this.twoPage.leafEdgeWidthL + 'px',
- height: this.twoPage.height-1 + 'px',
+ height: this.twoPage.height + 'px',
left: this.twoPage.bookCoverDivLeft+this.twoPage.coverInternalPadding+'px',
- top: this.twoPage.bookCoverDivTop+this.twoPage.coverInternalPadding+'px',
- position: 'absolute'
+ top: this.twoPage.bookCoverDivTop+this.twoPage.coverInternalPadding+'px'
}).appendTo('#BRtwopageview');
div = document.createElement('div');
$(div).attr('id', 'BRbookspine').css({
- border: '1px solid rgb(68, 25, 17)',
width: this.twoPage.bookSpineDivWidth+'px',
height: this.twoPage.bookSpineDivHeight+'px',
- position: 'absolute',
- backgroundColor: 'rgb(68, 25, 17)',
left: this.twoPage.bookSpineDivLeft+'px',
top: this.twoPage.bookSpineDivTop+'px'
}).appendTo('#BRtwopageview');
top: this.twoPageFlipAreaTop() + 'px',
cursor: 'w-resize',
zIndex: 100
- }).bind('click', function(e) {
+ }).click(function(e) {
self.left();
}).bind('mousedown', function(e) {
e.preventDefault();
top: this.twoPageFlipAreaTop() + 'px',
cursor: 'e-resize',
zIndex: 100
- }).bind('click', function(e) {
+ }).click(function(e) {
self.right();
}).bind('mousedown', function(e) {
e.preventDefault();
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
+ zIndex: '1000'
}).appendTo('#BRcontainer');
$(this.twoPagePopUp).hide();
// $$$ TODO: Make sure popup is positioned so that it is in view
// (https://bugs.edge.launchpad.net/gnubook/+bug/327456)
$(e.data.twoPagePopUp).css({
- left: e.pageX- $('#BRcontainer').offset().left + $('#BRcontainer').scrollLeft() + 20 + 'px',
+ left: e.pageX- $('#BRcontainer').offset().left + $('#BRcontainer').scrollLeft() - 100 + 'px',
top: e.pageY - $('#BRcontainer').offset().top + $('#BRcontainer').scrollTop() + 'px'
});
});
// $$$ TODO: Make sure popup is positioned so that it is in view
// (https://bugs.edge.launchpad.net/gnubook/+bug/327456)
$(e.data.twoPagePopUp).css({
- left: e.pageX - $('#BRcontainer').offset().left + $('#BRcontainer').scrollLeft() - $(e.data.twoPagePopUp).width() - 25 + 'px',
+ left: e.pageX - $('#BRcontainer').offset().left + $('#BRcontainer').scrollLeft() - $(e.data.twoPagePopUp).width() + 100 + 'px',
top: e.pageY-$('#BRcontainer').offset().top + $('#BRcontainer').scrollTop() + 'px'
});
});
// Book cover
// The width of the book cover div. The combined width of both pages, twice the width
// of the book cover internal padding (2*10) and the page edges
- this.twoPage.bookCoverDivWidth = this.twoPage.scaledWL + this.twoPage.scaledWR + 2 * this.twoPage.coverInternalPadding + this.twoPage.edgeWidth;
+ this.twoPage.bookCoverDivWidth = this.twoPageCoverWidth(this.twoPage.scaledWL + this.twoPage.scaledWR);
// The height of the book cover div
this.twoPage.bookCoverDivHeight = this.twoPage.height + 2 * this.twoPage.coverInternalPadding;
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
// Returns the currently active index.
BookReader.prototype.currentIndex = function() {
// $$$ we should be cleaner with our idea of which index is active in 1up/2up
- if (this.mode == this.constMode1up) {
+ if (this.mode == this.constMode1up || this.mode == this.constModeThumb) {
return this.firstIndex; // $$$ TODO page in center of view would be better
} else if (this.mode == this.constMode2up) {
// Only allow indices that are actually present in book
}
}
+// setCurrentIndex(index)
+//______________________________________________________________________________
+// Sets the idea of current index without triggering other actions such as animation.
+// Compare to jumpToIndex which animates to that index
+BookReader.prototype.setCurrentIndex = function(index) {
+ this.firstIndex = index;
+}
+
+
// right()
//______________________________________________________________________________
// Flip the right page over onto the left
this.jumpToIndex(this.lastDisplayableIndex());
}
+// scrollDown()
+//______________________________________________________________________________
+// Scrolls down one screen view
+BookReader.prototype.scrollDown = function() {
+ 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: '+=' + this._scrollAmount() + 'px'},
+ 400, 'easeInOutExpo'
+ );
+ return true;
+ } else {
+ return false;
+ }
+}
+
+// scrollUp()
+//______________________________________________________________________________
+// Scrolls up one screen view
+BookReader.prototype.scrollUp = function() {
+ 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: '-=' + this._scrollAmount() + 'px'},
+ 400, 'easeInOutExpo'
+ );
+ return true;
+ } else {
+ return false;
+ }
+}
+
+// _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()
//______________________________________________________________________________
// to flip back one spread, pass index=null
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',
+ height: this.twoPage.height + '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',
+ height: this.twoPage.height + 'px',
left: gutter+scaledW+'px',
top: top+'px',
- position: 'absolute',
zIndex:1000
}).appendTo('#BRtwopageview');
// setMouseHandlers2UP
//______________________________________________________________________________
BookReader.prototype.setMouseHandlers2UP = function() {
- /*
- $(this.prefetchedImgs[this.twoPage.currentIndexL]).bind('dblclick', function() {
- //self.prevPage();
- self.autoStop();
- self.left();
- });
- $(this.prefetchedImgs[this.twoPage.currentIndexR]).bind('dblclick', function() {
- //self.nextPage();'
- self.autoStop();
- self.right();
- });
- */
-
- this.setDragHandler2UP( this.prefetchedImgs[this.twoPage.currentIndexL] );
this.setClickHandler2UP( this.prefetchedImgs[this.twoPage.currentIndexL],
{ self: this },
function(e) {
e.data.self.left();
+ e.preventDefault();
}
);
- this.setDragHandler2UP( this.prefetchedImgs[this.twoPage.currentIndexR] );
this.setClickHandler2UP( this.prefetchedImgs[this.twoPage.currentIndexR],
{ self: this },
function(e) {
e.data.self.right();
+ e.preventDefault();
}
);
}
if (loadImage) {
//console.log('prefetching ' + index);
var img = document.createElement("img");
+ img.className = 'BRpageimage';
+ if (index < 0 || index > (this.numLeafs - 1) ) {
+ // Facing page at beginning or end, or beyond
+ $(img).css({
+ 'background-color': 'transparent'
+ });
+ }
img.src = pageURI;
img.uri = pageURI; // browser may rewrite src so we stash raw URI here
this.prefetchedImgs[index] = img;
top: top+'px',
height: this.twoPage.height,
width: scaledW+'px',
- backgroundColor: this.getPageBackgroundColor(prevL),
- borderRight: '1px solid black',
+ borderRight: '1px solid black', // XXXmang check
zIndex: 1
}
right: '',
top: top+'px',
height: this.twoPage.height,
- width: '0px',
- backgroundColor: this.getPageBackgroundColor(prevR),
- borderLeft: '1px solid black',
+ borderLeft: '1px solid black', // XXXmang check
+ width: '0',
zIndex: 2
}
position: 'absolute',
left: gutter+'px',
top: top+'px',
- backgroundColor: this.getPageBackgroundColor(nextR),
height: this.twoPage.height,
width: scaledW+'px',
- borderLeft: '1px solid black',
zIndex: 1
});
position: 'absolute',
right: $('#BRtwopageview').attr('clientWidth')-gutter+'px',
top: top+'px',
- backgroundColor: this.getPageBackgroundColor(nextL),
height: this.twoPage.height,
width: 0+'px', // Start at 0 width, then grow to the left
- borderRight: '1px solid black',
zIndex: 2
});
//______________________________________________________________________________
BookReader.prototype.prefetch = function() {
+ // $$$ We should check here if the current indices have finished
+ // loading (with some timeout) before loading more page images
+ // See https://bugs.edge.launchpad.net/bookreader/+bug/511391
+
// prefetch visible pages first
this.prefetchImg(this.twoPage.currentIndexL);
this.prefetchImg(this.twoPage.currentIndexR);
-
+
var adjacentPagesToLoad = 3;
var lowCurrent = Math.min(this.twoPage.currentIndexL, this.twoPage.currentIndexR);
//console.log(pages[i].getAttribute('file').substr(1) +'-'+ parseInt(pages[i].getAttribute('file').substr(1), 10));
- var re = new RegExp (/_(\d{4})/);
+ var re = new RegExp (/_(\d{4})\.djvu/);
var reMatch = re.exec(pages[i].getAttribute('file'));
var index = parseInt(reMatch[1], 10);
//var index = parseInt(pages[i].getAttribute('file').substr(1), 10);
for (var key in this.searchResults) {
- if (-1 != jQuery.inArray(parseInt(key), this.displayedIndices)) {
+ if (jQuery.inArray(parseInt(key), this.displayedIndices) >= 0) {
var result = this.searchResults[key];
- if(null == result.div) {
+ if (null == result.div) {
result.div = document.createElement('div');
$(result.div).attr('className', 'BookReaderSearchHilite').appendTo('#pagediv'+key);
//console.log('appending ' + key);
for (var key in this.searchResults) {
key = parseInt(key, 10);
- if (-1 != jQuery.inArray(key, this.displayedIndices)) {
+ if (jQuery.inArray(key, this.displayedIndices) >= 0) {
var result = this.searchResults[key];
- if(null == result.div) {
+ if (null == result.div) {
result.div = document.createElement('div');
$(result.div).attr('className', 'BookReaderSearchHilite').css('zIndex', 3).appendTo('#BRtwopageview');
//console.log('appending ' + key);
//______________________________________________________________________________
BookReader.prototype.printPage = function() {
window.open(this.getPrintURI(), 'printpage', 'width=400, height=500, resizable=yes, scrollbars=no, toolbar=no, location=no');
-
- /* iframe implementation
-
- if (null != this.printPopup) { // check if already showing
- return;
- }
- this.printPopup = document.createElement("div");
- $(this.printPopup).css({
- position: 'absolute',
- top: '20px',
- left: ($('#BRcontainer').width()-400)/2 + 'px',
- width: '400px',
- padding: "20px",
- border: "3px double #999999",
- zIndex: 3,
- backgroundColor: "#fff"
- }).appendTo('#BookReader');
-
- var indexToPrint;
- if (this.constMode1up == this.mode) {
- indexToPrint = this.firstIndex;
- } else {
- indexToPrint = this.twoPage.currentIndexL;
- }
-
- this.indexToPrint = indexToPrint;
-
- var htmlStr = '<div style="text-align: center;">';
- htmlStr = '<p style="text-align:center;"><b><a href="javascript:void(0);" onclick="window.frames[0].focus(); window.frames[0].print(); return false;">Click here to print this page</a></b></p>';
- htmlStr += '<div id="printDiv" name="printDiv" style="text-align: center; width: 233px; margin: auto">'
- htmlStr += '<p style="text-align:right; margin: 0; font-size: 0.85em">';
- //htmlStr += '<button class="BRicon rollover book_up" onclick="br.updatePrintFrame(-1); return false;"></button> ';
- //htmlStr += '<button class="BRicon rollover book_down" onclick="br.updatePrintFrame(1); return false;"></button>';
- htmlStr += '<a href="#" onclick="br.updatePrintFrame(-1); return false;">Prev</a> <a href="#" onclick="br.updatePrintFrame(1); return false;">Next</a>';
- htmlStr += '</p>';
- htmlStr += '</div>';
- htmlStr += '<p style="text-align:center;"><a href="" onclick="br.printPopup = null; $(this.parentNode.parentNode).remove(); return false">Close popup</a></p>';
- htmlStr += '</div>';
-
- this.printPopup.innerHTML = htmlStr;
-
- var iframe = document.createElement('iframe');
- iframe.id = 'printFrame';
- iframe.name = 'printFrame';
- iframe.width = '233px'; // 8.5 x 11 aspect
- iframe.height = '300px';
-
- var self = this; // closure
-
- $(iframe).load(function() {
- var doc = BookReader.util.getIFrameDocument(this);
- $('body', doc).html(self.getPrintFrameContent(self.indexToPrint));
- });
-
- $('#printDiv').prepend(iframe);
- */
}
// Get print URI from current indices and mode
indexToPrint = this.firstIndex; // $$$ the index in the middle of the viewport would make more sense
}
- var options = 'id=' + this.bookId + '&server=' + this.server + '&zip=' + this.zip
+ var options = 'id=' + this.subPrefix + '&server=' + this.server + '&zip=' + this.zip
+ '&format=' + this.imageFormat + '&file=' + this._getPageFile(indexToPrint)
+ '&width=' + this._getPageWidth(indexToPrint) + '&height=' + this._getPageHeight(indexToPrint);
// showEmbedCode()
//______________________________________________________________________________
BookReader.prototype.showEmbedCode = function() {
- if (null != this.embedPopup) { // check if already showing
- return;
- }
- this.autoStop();
this.embedPopup = document.createElement("div");
$(this.embedPopup).css({
position: 'absolute',
- top: '20px',
+ top: ($('#BRcontainer').attr('clientHeight')-250)/2 + 'px',
left: ($('#BRcontainer').attr('clientWidth')-400)/2 + 'px',
width: '400px',
- padding: "20px",
- border: "3px double #999999",
- zIndex: 3,
- backgroundColor: "#fff"
+ height: '250px',
+ padding: '0',
+ fontSize: '12px',
+ color: '#333',
+ zIndex: 300,
+ border: '10px solid #615132',
+ backgroundColor: "#fff",
+ MozBorderRadius: '8px',
+ MozBoxShadow: '0 0 6px #000',
+ WebkitBorderRadius: '8px',
+ WebkitBoxShadow: '0 0 6px #000'
}).appendTo('#BookReader');
- htmlStr = '<p style="text-align:center;"><b>Embed Bookreader in your blog!</b></p>';
- htmlStr += '<p>The bookreader uses iframes for embedding. It will not work on web hosts that block iframes. The embed feature has been tested on blogspot.com blogs as well as self-hosted Wordpress blogs. This feature will NOT work on wordpress.com blogs.</p>';
- htmlStr += '<p>Embed Code: <input type="text" size="40" value="' + this.getEmbedCode() + '"></p>';
- htmlStr += '<p style="text-align:center;"><a href="" onclick="br.embedPopup = null; $(this.parentNode.parentNode).remove(); return false">Close popup</a></p>';
+ htmlStr = '<h3 style="background:#615132;padding:10px;margin:0 0 10px;color:#fff;">Embed Bookreader</h3>';
+ htmlStr += '<p style="padding:10px;line-height:18px;">The bookreader uses iframes for embedding. It will not work on web hosts that block iframes. The embed feature has been tested on blogspot.com blogs as well as self-hosted Wordpress blogs. This feature will NOT work on wordpress.com blogs.</p>';
+ htmlStr += '<textarea rows="2" cols="40" style="margin-left:10px;width:368px;height:40px;color:#333;font-size:12px;border:2px inset #ccc;background:#efefef;padding:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;">' + this.getEmbedCode() + '</textarea>';
+ htmlStr += '<a href="javascript:;" class="popOff" onclick="$(this.parentNode).remove();$(\'.coverUp\').hide();return false" style="color:#999;"><span>Close</span></a>';
this.embedPopup.innerHTML = htmlStr;
- $(this.embedPopup).find('input').bind('click', function() {
+ $('#BookReader').append('<div class="coverUp" style="position:absolute;z-index:299;width:100%;height:100%;background:#000;opacity:.4;filter:alpha(opacity=40);" onclick="$(\'.popped\').hide();$(this).hide();"></div>');
+ $(this.embedPopup).find('textarea').click(function() {
+ this.select();
+ })
+ $(this.embedPopup).addClass("popped");
+}
+
+// showBookmarkCode()
+//______________________________________________________________________________
+BookReader.prototype.showBookmarkCode = function() {
+ this.bookmarkPopup = document.createElement("div");
+ $(this.bookmarkPopup).css({
+ position: 'absolute',
+ top: ($('#BRcontainer').attr('clientHeight')-250)/2 + 'px',
+ left: ($('#BRcontainer').attr('clientWidth')-400)/2 + 'px',
+ width: '400px',
+ height: '250px',
+ padding: '0',
+ fontSize: '12px',
+ color: '#333',
+ zIndex: 300,
+ border: '10px solid #615132',
+ backgroundColor: "#fff",
+ MozBorderRadius: '8px',
+ MozBoxShadow: '0 0 6px #000',
+ WebkitBorderRadius: '8px',
+ WebkitBoxShadow: '0 0 6px #000'
+ }).appendTo('#BookReader');
+
+ htmlStr = '<h3 style="background:#615132;padding:10px;margin:0 0 10px;color:#fff;">Add a bookmark</h3>';
+ htmlStr += '<p style="padding:10px;line-height:18px;">You can add a bookmark to any page in any book. If you elect to make your bookmark public, other readers will be able to see it. <em>You must be logged in to your <a href="">Open Library account</a> to add bookmarks.</em></p>';
+ htmlStr += '<form name="bookmark" id="bookmark" style="line-height:20px;margin-left:10px;"><label style="padding-bottom"10px;><input type="radio" name="privacy" id="p2" disabled="disabled" checked="checked"/> Make this bookmark public.</label><br/><label style="padding-bottom:10px;"><input type="radio" name="privacy" id="p1" disabled="disabled"/> Keep this bookmark private.</label><br/><br/><button type="submit" style="font-size:20px;" disabled="disabled">Add a bookmark</button></form>';
+ htmlStr += '<a href="javascript:;" class="popOff" onclick="$(this.parentNode).remove();$(\'.coverUp\').hide();return false;" style="color:#999;"><span>Close</span></a>';
+
+ this.bookmarkPopup.innerHTML = htmlStr;
+ $('#BookReader').append('<div class="coverUp" style="position:absolute;z-index:299;width:100%;height:100%;background:#000;opacity:.4;filter:alpha(opacity=40);" onclick="$(\'.popped\').hide();$(this).hide();"></div>');
+ $(this.bookmarkPopup).find('textarea').click(function() {
this.select();
})
+ $(this.bookmarkPopup).addClass("popped");
}
+
// autoToggle()
//______________________________________________________________________________
BookReader.prototype.autoToggle = function() {
// Change to autofit if book is too large
if (this.reduce < this.twoPageGetAutofitReduce()) {
- this.zoom2up(0);
+ this.zoom2up('auto');
}
var self = this;
}
}
+// initNavbar
+//______________________________________________________________________________
+// Initialize the navigation bar.
+// $$$ this could also add the base elements to the DOM, so disabling the nav bar
+// could be as simple as not calling this function
+BookReader.prototype.initNavbar = function() {
+ // Setup nav / chapter / search results bar
+
+ // $$$ should make this work inside the BookReader div (self-contained), rather than after
+ $('#BookReader').after(
+ '<div id="BRnav">'
+ + '<div id="BRpage">'
+ + '<button class="BRicon book_left"></button>'
+ + '<button class="BRicon book_right"></button>'
+ + '</div>'
+ + '<div id="BRnavpos">'
+ + '<div id="BRfiller"></div>'
+ + '<div id="BRpager">'
+ + '<div id="BRslider">'
+ + '<div id="slider"></div>'
+ + '<div id="pager"></div>'
+ // XXXmang update code to update pagenum
+ + '<div id="pagenum"><span>n141</span> / 325</div>'
+ + '</div>'
+ + '</div>'
+ + '<div id="BRnavline">'
+ + '<div class="BRnavend" id="BRnavleft"></div>'
+ + '<div class="BRnavend" id="BRnavright"></div>'
+ + '</div>'
+ + '</div>'
+ + '</div>'
+ );
+
+/*
+ <div class="searchChap" style="left:49%;" title="Search result">
+ <div class="query">
+ A related distinction is between the emotion and the results of the emotion, principally behaviors and <strong><a href="">emotional</a></strong> expressions. People often behave in certain ways as a direct result of their <strong><a href="">emotional</a></strong> state, such as crying, fighting or fleeing. <span>Page 163</span>
+ <div class="queryChap">IV. The Witch <span>|</span> Page 163</div>
+ </div>
+ </div>
+*/
+
+ /* $$$mang search results and chapters should automatically coalesce
+ $('.searchChap').bt({
+ contentSelector: '$(this).find(".query")',
+ trigger: 'click',
+ closeWhenOthersOpen: true,
+ cssStyles: {
+ width: '250px',
+ padding: '10px 10px 15px',
+ backgroundColor: '#fff',
+ border: '3px solid #e2dcc5',
+ borderBottom: 'none',
+ fontFamily: '"Lucida Grande","Arial",sans-serif',
+ fontSize: '12px',
+ lineHeight: '18px',
+ color: '#615132'
+ },
+ shrinkToFit: false,
+ width: '230px',
+ padding: 0,
+ spikeGirth: 0,
+ spikeLength: 0,
+ overlap: '10px',
+ overlay: false,
+ killTitle: true,
+ textzIndex: 9999,
+ boxzIndex: 9998,
+ wrapperzIndex: 9997,
+ offsetParent: null,
+ positions: ['top'],
+ fill: 'white',
+ windowMargin: 10,
+ strokeWidth: 3,
+ strokeStyle: '#e2dcc5',
+ cornerRadius: 0,
+ centerPointX: 0,
+ centerPointY: 0,
+ shadow: false
+ });
+ $('.searchChap').each(function(){
+ $(this).hover(function(){
+ $(this).addClass('front');
+ },function(){
+ $(this).removeClass('front');
+ });
+ });
+ */
+}
+
+BookReader.prototype.addSearchResult = function(queryString, pageNumber, pageIndex) {
+ var uiStringSearch = "Search result"; // i18n
+ var uiStringPage = "Page"; // i18n
+
+ var percentThrough = BookReader.util.cssPercentage(pageIndex, this.numLeafs);
+
+ // $$$mang add click-through to page
+ $('<div class="search" style="left:' + percentThrough + ';" title="' + uiStringSearch + '"><div class="query">'
+ + queryString + '<span>' + uiStringPage + ' ' + pageNumber + '</span></div>')
+ .appendTo('#BRnavpos').bt({
+ contentSelector: '$(this).find(".query")',
+ trigger: 'click',
+ closeWhenOthersOpen: true,
+ cssStyles: {
+ padding: '10px 10px 15px',
+ backgroundColor: '#fff',
+ border: '3px solid #e2dcc5',
+ borderBottom: 'none',
+ fontFamily: '"Lucida Grande","Arial",sans-serif',
+ fontSize: '12px',
+ lineHeight: '18px',
+ color: '#615132'
+ },
+ shrinkToFit: false,
+ width: '230px',
+ padding: 0,
+ spikeGirth: 0,
+ spikeLength: 0,
+ overlap: '10px',
+ overlay: false,
+ killTitle: false,
+ textzIndex: 9999,
+ boxzIndex: 9998,
+ wrapperzIndex: 9997,
+ offsetParent: null,
+ positions: ['top'],
+ fill: 'white',
+ windowMargin: 10,
+ strokeWidth: 3,
+ strokeStyle: '#e2dcc5',
+ cornerRadius: 0,
+ centerPointX: 0,
+ centerPointY: 0,
+ shadow: false
+ })
+ .hover(function(){
+ $(this).addClass('front');
+ },function(){
+ $(this).removeClass('front');
+ }
+ );
+}
+
+BookReader.prototype.removeSearchResults = function() {
+ $('#BRnavpos .search').remove();
+}
+
+BookReader.prototype.addChapter = function(chapterTitle, pageNumber, pageIndex) {
+ var uiStringPage = 'Page'; // i18n
+
+ var percentThrough = BookReader.util.cssPercentage(pageIndex, this.numLeafs);
+
+ $('<div class="chapter" style="left:' + percentThrough + ';"><div class="title">'
+ + chapterTitle + '<span>|</span> ' + uiStringPage + ' ' + pageNumber + '</div></div>')
+ .appendTo('#BRnavpos')
+ .data({'self': this, 'pageIndex': pageIndex })
+ .bt({
+ contentSelector: '$(this).find(".title")',
+ trigger: 'hover',
+ closeWhenOthersOpen: true,
+ cssStyles: {
+ backgroundColor: '#000',
+ border: '2px solid #e2dcc5',
+ borderBottom: 'none',
+ padding: '5px 10px',
+ fontFamily: '"Arial", sans-serif',
+ fontSize: '11px',
+ fontWeight: '700',
+ color: '#fff',
+ whiteSpace: 'nowrap'
+ },
+ shrinkToFit: true,
+ width: '200px',
+ padding: 0,
+ spikeGirth: 0,
+ spikeLength: 0,
+ overlap: '16px',
+ overlay: false,
+ killTitle: true,
+ textzIndex: 9999,
+ boxzIndex: 9998,
+ wrapperzIndex: 9997,
+ offsetParent: null,
+ positions: ['top'],
+ fill: 'black',
+ windowMargin: 10,
+ strokeWidth: 0,
+ cornerRadius: 0,
+ centerPointX: 0,
+ centerPointY: 0,
+ shadow: false
+ })
+ .hover( function() {
+ $(this).addClass('front');
+ }, function() {
+ $(this).removeClass('front');
+ }
+ )
+ .bind('click', function() {
+ $(this).data('self').jumpToIndex($(this).data('pageIndex'));
+ });
+}
+
+/*
+ * Remove all chapters.
+ */
+BookReader.prototype.removeChapters = function() {
+ $('#BRnavpos .chapter').remove();
+}
+
+/*
+ * Update the table of contents based on array of TOC entries.
+ */
+BookReader.prototype.updateTOC = function(tocEntries) {
+ this.removeChapters();
+ for (var i = 0; i < tocEntries.length; i++) {
+ this.addChapterFromEntry(tocEntries[i]);
+ }
+}
+
+/*
+ * Example table of contents entry - this format is defined by Open Library
+ * {
+ * "pagenum": "17",
+ * "level": 1,
+ * "label": "CHAPTER I",
+ * "type": {"key": "/type/toc_item"},
+ * "title": "THE COUNTRY AND THE MISSION"
+ * }
+ */
+BookReader.prototype.addChapterFromEntry = function(tocEntryObject) {
+ console.log(tocEntryObject);
+ var pageIndex = this.getPageIndex(tocEntryObject['pagenum']);
+ // Only add if we know where it is
+ if (pageIndex) {
+ this.addChapter(tocEntryObject['title'], tocEntryObject['pagenum'], pageIndex);
+ }
+ $('.chapter').each(function(){
+ $(this).hover(function(){
+ $(this).addClass('front');
+ },function(){
+ $(this).removeClass('front');
+ });
+ });
+ $('.search').each(function(){
+ $(this).hover(function(){
+ $(this).addClass('front');
+ },function(){
+ $(this).removeClass('front');
+ });
+ });
+ $('.searchChap').each(function(){
+ $(this).hover(function(){
+ $(this).addClass('front');
+ },function(){
+ $(this).removeClass('front');
+ });
+ });
+ $("#BRslider").draggable({axis:'x',containment:'parent'});
+ $("#BRzoombtn").draggable({axis:'y',containment:'parent'});
+ $("#BRslider").hover(
+ function(){
+ $("#pagenum").show();
+ },function(){
+ $("#pagenum").hide();
+ });
+}
+
BookReader.prototype.initToolbar = function(mode, ui) {
- $("#BookReader").append("<div id='BRtoolbar'>"
- + "<span id='BRtoolbarbuttons' style='float: right'>"
- + "<button class='BRicon print rollover' /> <button class='BRicon rollover embed' />"
- + "<form class='BRpageform' action='javascript:' onsubmit='br.jumpToPage(this.elements[0].value)'> <span class='label'>Page:<input id='BRpagenum' type='text' size='3' onfocus='br.autoStop();'></input></span></form>"
- + "<div class='BRtoolbarmode2' style='display: none'><button class='BRicon rollover book_leftmost' /><button class='BRicon rollover book_left' /><button class='BRicon rollover book_right' /><button class='BRicon rollover book_rightmost' /></div>"
- + "<div class='BRtoolbarmode1' style='display: none'><button class='BRicon rollover book_top' /><button class='BRicon rollover book_up' /> <button class='BRicon rollover book_down' /><button class='BRicon rollover book_bottom' /></div>"
- + "<button class='BRicon rollover play' /><button class='BRicon rollover pause' style='display: none' />"
- + "</span>"
+ // $$$mang should be contained within the BookReader div instead of body
+ $("body").append(
+ "<div id='BRtoolbar'>"
+ + "<span id='BRtoolbarbuttons'>"
+ /* XXXmang integrate search */
+ + "<form method='get' id='booksearch'><input type='search' id='textSrch' name='textSrch' val='' placeholder='Search inside'/><button type='submit' id='btnSrch' name='btnSrch'>GO</button></form>"
+ // XXXmang icons incorrect or handlers wrong
+ + "<button class='BRicon info' onclick='br.switchMode(3); return false;'></button>"
+ + "<button class='BRicon share' onclick='br.switchMode(2); return false;'></button>"
+ + "<button class='BRicon read modal'></button>"
+ + "<button class='BRicon full'></button>"
+ + "</span>"
+ + "<span><a class='logo' href='" + this.logoURL + "'></a></span>"
+ + "<span id='BRreturn'><span>Back to</span><a href='" + this.bookUrl + "'>" + this.bookTitle + "</a></span>"
+ + "</div>"
- + "<span>"
- + "<a class='BRicon logo rollover' href='" + this.logoURL + "'> </a>"
- + " <button class='BRicon rollover zoom_out' onclick='br.zoom(-1); return false;'/>"
- + "<button class='BRicon rollover zoom_in' onclick='br.zoom(1); return false;'/>"
- + " <span class='label'>Zoom: <span id='BRzoom'>"+parseInt(100/this.reduce)+"</span></span>"
- + " <button class='BRicon rollover one_page_mode' onclick='br.switchMode(1); return false;'/>"
- + " <button class='BRicon rollover two_page_mode' onclick='br.switchMode(2); return false;'/>"
- + "</span>"
-
- + "<span id='#BRbooktitle'>"
- + " <a class='BRblack title' href='"+this.bookUrl+"' target='_blank'>"+this.bookTitle+"</a>"
- + "</span>"
+ + "<div id='BRzoomer'>"
+ + "<div id='BRzoompos'>"
+ + "<button class='BRicon zoom_out'></button>"
+ + "<div id='BRzoomcontrol'>"
+ + "<div id='BRzoomstrip'></div>"
+ + "<div id='BRzoombtn'></div>"
+ + "</div>"
+ + "<button class='BRicon zoom_in'></button>"
+ + "</div>"
+ "</div>");
this.updateToolbarZoom(this.reduce); // Pretty format
- if (ui == "embed") {
+ if (ui == "embed" || ui == "touch") {
$("#BookReader a.logo").attr("target","_blank");
}
var titles = { '.logo': 'Go to Archive.org',
'.zoom_in': 'Zoom in',
'.zoom_out': 'Zoom out',
- '.one_page_mode': 'One-page view',
- '.two_page_mode': 'Two-page view',
+ '.onepg': 'One-page view',
+ '.twopg': 'Two-page view',
+ '.thumb': 'Thumbnail view',
'.print': 'Print this page',
- '.embed': 'Embed bookreader',
+ '.embed': 'Embed BookReader',
+ '.link': 'Link to this book (and page)',
+ '.bookmark': 'Bookmark this page',
+ '.read': 'Allow BookReader to read this aloud',
+ '.full': 'Show fullscreen',
'.book_left': 'Flip left',
'.book_right': 'Flip right',
'.book_up': 'Page up',
// Hide mode buttons and autoplay if 2up is not available
// $$$ if we end up with more than two modes we should show the applicable buttons
if ( !this.canSwitchToMode(this.constMode2up) ) {
- jToolbar.find('.one_page_mode, .two_page_mode, .play, .pause').hide();
+ jToolbar.find('.two_page_mode, .play, .pause').hide();
+ }
+ if ( !this.canSwitchToMode(this.constModeThumb) ) {
+ jToolbar.find('.thumbnail_mode').hide();
+ }
+
+ // Hide one page button if it is the only mode available
+ if ( ! (this.canSwitchToMode(this.constMode2up) || this.canSwitchToMode(this.constModeThumb)) ) {
+ jToolbar.find('.one_page_mode').hide();
}
// Switch to requested mode -- binds other click handlers
- this.switchToolbarMode(mode);
+ //this.switchToolbarMode(mode);
}
//______________________________________________________________________________
// Update the toolbar for the given mode (changes navigation buttons)
// $$$ we should soon split the toolbar out into its own module
-BookReader.prototype.switchToolbarMode = function(mode) {
+BookReader.prototype.switchToolbarMode = function(mode) {
if (1 == mode) {
- // 1-up
+ // 1-up
+ $('#BRtoolbar .BRtoolbarzoom').show().css('display', 'inline');
$('#BRtoolbar .BRtoolbarmode2').hide();
+ $('#BRtoolbar .BRtoolbarmode3').hide();
$('#BRtoolbar .BRtoolbarmode1').show().css('display', 'inline');
- } else {
+ } else if (2 == mode) {
// 2-up
+ $('#BRtoolbar .BRtoolbarzoom').show().css('display', 'inline');
$('#BRtoolbar .BRtoolbarmode1').hide();
+ $('#BRtoolbar .BRtoolbarmode3').hide();
$('#BRtoolbar .BRtoolbarmode2').show().css('display', 'inline');
+ } else {
+ // 3-up
+ $('#BRtoolbar .BRtoolbarzoom').hide();
+ $('#BRtoolbar .BRtoolbarmode2').hide();
+ $('#BRtoolbar .BRtoolbarmode1').hide();
+ $('#BRtoolbar .BRtoolbarmode3').show().css('display', 'inline');
}
}
var self = this; // closure
- jToolbar.find('.book_left').bind('click', function(e) {
+ jToolbar.find('.book_left').click(function(e) {
self.left();
return false;
});
- jToolbar.find('.book_right').bind('click', function(e) {
+ jToolbar.find('.book_right').click(function(e) {
self.right();
return false;
});
jToolbar.find('.book_up').bind('click', function(e) {
- self.prev();
+ if ($.inArray(self.mode, [self.constMode1up, self.constModeThumb]) >= 0) {
+ self.scrollUp();
+ } else {
+ self.prev();
+ }
return false;
});
jToolbar.find('.book_down').bind('click', function(e) {
- self.next();
+ if ($.inArray(self.mode, [self.constMode1up, self.constModeThumb]) >= 0) {
+ self.scrollDown();
+ } else {
+ self.next();
+ }
return false;
});
- jToolbar.find('.print').bind('click', function(e) {
+ jToolbar.find('.print').click(function(e) {
self.printPage();
return false;
});
- jToolbar.find('.embed').bind('click', function(e) {
+ jToolbar.find('.embed').click(function(e) {
self.showEmbedCode();
return false;
});
- jToolbar.find('.play').bind('click', function(e) {
+ jToolbar.find('.bookmark').click(function(e) {
+ self.showBookmarkCode();
+ return false;
+ });
+
+ jToolbar.find('.play').click(function(e) {
self.autoToggle();
return false;
});
- jToolbar.find('.pause').bind('click', function(e) {
+ jToolbar.find('.pause').click(function(e) {
self.autoToggle();
return false;
});
- jToolbar.find('.book_top').bind('click', function(e) {
+ jToolbar.find('.book_top').click(function(e) {
self.first();
return false;
});
- jToolbar.find('.book_bottom').bind('click', function(e) {
+ jToolbar.find('.book_bottom').click(function(e) {
self.last();
return false;
});
- jToolbar.find('.book_leftmost').bind('click', function(e) {
+ jToolbar.find('.book_leftmost').click(function(e) {
self.leftmost();
return false;
});
- jToolbar.find('.book_rightmost').bind('click', function(e) {
+ jToolbar.find('.book_rightmost').click(function(e) {
self.rightmost();
return false;
});
// 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.slice(0,1).toUpperCase() + autofit.slice(1);
} else {
value = (100 / reduce).toFixed(2);
// Strip trailing zeroes and decimal if all zeroes
$('#BRzoom').text(value);
}
+// bindNavigationHandlers
+//______________________________________________________________________________
+// Bind navigation handlers
+BookReader.prototype.bindNavigationHandlers = function() {
+ $('#BookReader').die('mousemove.navigation').live('mousemove.navigation',
+ { 'br': this },
+ this.navigationMousemoveHandler
+ );
+}
+
+// unbindNavigationHandlers
+//______________________________________________________________________________
+// Unbind navigation handlers
+BookReader.prototype.unbindNavigationHandlers = function() {
+ $('#BookReader').die('mousemove.navigation');
+}
+
+// navigationMousemoveHandler
+//______________________________________________________________________________
+// Handle mousemove related to navigation. Bind at #BookReader level to allow autohide.
+BookReader.prototype.navigationMousemoveHandler = function(event) {
+ // $$$ possibly not great to be calling this for every mousemove
+
+ if (event.data['br'].uiAutoHide) {
+ var navkey = $(document).height() - 75;
+ if ((event.pageY < 76) || (event.pageY > navkey)) {
+ // inside or near navigation elements
+ event.data['br'].hideNavigation();
+ } else {
+ event.data['br'].showNavigation();
+ }
+ }
+}
+
+// navigationIsVisible
+//______________________________________________________________________________
+// Returns true if the navigation elements are currently visible
+BookReader.prototype.navigationIsVisible = function() {
+ // $$$ doesn't account for transitioning states, nav must be fully visible to return true
+ var toolpos = $('#BRtoolbar').offset();
+ var tooltop = toolpos.top;
+ if (tooltop == 0) {
+ return true;
+ }
+ return false;
+}
+
+// hideNavigation
+//______________________________________________________________________________
+// Hide navigation elements, if visible
+BookReader.prototype.hideNavigation = function() {
+ // Check if navigation is showing
+ if (this.navigationIsVisible()) {
+ // $$$ don't hardcode height
+ $('#BRtoolbar').animate({top:-60});
+ $('#BRnav').animate({bottom:-60});
+ $('#BRzoomer').animate({right:-26});
+ }
+}
+
+// showNavigation
+//______________________________________________________________________________
+// Show navigation elements
+BookReader.prototype.showNavigation = function() {
+ // Check if navigation is hidden
+ if (!this.navigationIsVisible()) {
+ $('#BRtoolbar').animate({top:0});
+ $('#BRnav').animate({bottom:0});
+ $('#BRzoomer').animate({right:0});
+ }
+}
+
// firstDisplayableIndex
//______________________________________________________________________________
// Returns the index of the first visible page, dependent on the mode.
// E.g paramsFromFragment(window.location.hash)
BookReader.prototype.paramsFromFragment = function(urlFragment) {
// URL fragment syntax specification: http://openlibrary.org/dev/docs/bookurls
-
+
var params = {};
// For convenience we allow an initial # character (as from window.location.hash)
params.mode = this.constMode1up;
} else if ('2up' == urlHash['mode']) {
params.mode = this.constMode2up;
+ } else if ('thumb' == urlHash['mode']) {
+ params.mode = this.constModeThumb;
}
// Index and page
// See http://openlibrary.org/dev/docs/bookurls for an explanation of the fragment syntax.
BookReader.prototype.fragmentFromParams = function(params) {
var separator = '/';
-
+
var fragments = [];
if ('undefined' != typeof(params.page)) {
fragments.push('mode', '1up');
} else if (params.mode == this.constMode2up) {
fragments.push('mode', '2up');
+ } else if (params.mode == this.constModeThumb) {
+ fragments.push('mode', 'thumb');
} else {
throw 'fragmentFromParams called with unknown mode ' + params.mode;
}
//________
// Returns true if we can switch to the requested mode
BookReader.prototype.canSwitchToMode = function(mode) {
- if (mode == this.constMode2up) {
+ if (mode == this.constMode2up || mode == this.constModeThumb) {
// check there are enough pages to display
// $$$ this is a workaround for the mis-feature that we can't display
// short books in 2up mode
- if (this.numLeafs < 6) {
+ if (this.numLeafs < 2) {
return false;
}
}
return false;
}
-// getPageBackgroundColor
-//--------
-// Returns a CSS property string for the background color for the given page
-// $$$ turn into regular CSS?
-BookReader.prototype.getPageBackgroundColor = function(index) {
- if (index >= 0 && index < this.numLeafs) {
- // normal page
- return this.pageDefaultBackgroundColor;
- }
-
- return '';
-}
-
// _getPageWidth
//--------
// Returns the page width for the given index, or first or last page if out of range
if ('undefined' == typeof(reduce)) {
// reduce not passed in
+ // $$$ this probably won't work for thumbnail mode
var ratio = this.getPageHeight(index) / this.twoPage.height;
var scale;
// $$$ we make an assumption here that the scales are available pow2 (like kakadu)
return this.getPageURI(index, reduce, rotate);
}
+/*
+ * Update based on received record from Open Library.
+ */
+BookReader.prototype.gotOpenLibraryRecord = function(self, olObject) {
+ // $$$ could refactor this so that 'this' is available
+ if (olObject) {
+ if (olObject['table_of_contents']) {
+ self.updateTOC(olObject['table_of_contents']);
+ }
+ }
+}
+
// Library functions
BookReader.util = {
+ disableSelect: function(jObject) {
+ // Bind mouse handlers
+ // Disable mouse click to avoid selected/highlighted page images - bug 354239
+ jObject.bind('mousedown', function(e) {
+ // $$$ check here for right-click and don't disable. Also use jQuery style
+ // for stopping propagation. See https://bugs.edge.launchpad.net/gnubook/+bug/362626
+ return false;
+ });
+ // Special hack for IE7
+ jObject[0].onselectstart = function(e) { return false; };
+ },
+
clamp: function(value, min, max) {
return Math.min(Math.max(value, min), max);
},
+
+ // Given value and maximum, calculate a percentage suitable for CSS
+ cssPercentage: function(value, max) {
+ return parseInt(((value + 0.0) / max) * 100) + '%';
+ },
+
+ notInArray: function(value, array) {
+ // inArray returns -1 or undefined if value not in array
+ return ! (jQuery.inArray(value, array) >= 0);
+ },
getIFrameDocument: function(iframe) {
// Adapted from http://xkr.us/articles/dom/iframe-document/