X-Git-Url: http://git.rot13.org/?a=blobdiff_plain;f=BookReader%2FBookReader.js;h=ee32b3686c9acaecf2760a34f7467af673dd9c40;hb=8121ced192a5b255f5ed00dd44b18be5b043940c;hp=1e2eabdac1c2dda2ecfcfdf9987fd32f83eedd93;hpb=067dbeedbf050f83f8473fca9f09b88c1551a086;p=bookreader.git diff --git a/BookReader/BookReader.js b/BookReader/BookReader.js index 1e2eabd..ee32b36 100644 --- a/BookReader/BookReader.js +++ b/BookReader/BookReader.js @@ -62,7 +62,7 @@ function BookReader() { this.twoPagePopUp = null; this.leafEdgeTmp = null; this.embedPopup = null; - this.printPopup = null; + this.popup = null; this.searchTerm = ''; this.searchResults = {}; @@ -84,19 +84,33 @@ function BookReader() { // 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) - // $$$ TODO dynamically calculate based on page images - this.pageDefaultBackgroundColor = 'rgb(234, 226, 205)'; + // Text-to-Speech params + this.ttsPlaying = false; + this.ttsIndex = null; //leaf index + this.ttsPosition = -1; //chunk (paragraph) number + this.ttsBuffering = false; + this.ttsPoller = null; + this.ttsFormat = null; return this; }; @@ -149,7 +163,7 @@ BookReader.prototype.init = function() { $("#BRcontainer").bind('scroll', this, function(e) { e.data.loadLeafs(); }); - + this.setupKeyListeners(); this.startLocationPolling(); @@ -157,6 +171,9 @@ BookReader.prototype.init = function() { //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 = []; @@ -763,7 +780,6 @@ 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', @@ -783,7 +799,6 @@ BookReader.prototype.drawLeafsTwoPage = function() { 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', @@ -834,33 +849,47 @@ BookReader.prototype.loadLeafs = function() { 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(); @@ -917,6 +946,15 @@ BookReader.prototype.resizePageView1up = function() { 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 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() @@ -1134,7 +1168,7 @@ BookReader.prototype.jumpToPage = function(pageNum) { //______________________________________________________________________________ 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 @@ -1145,7 +1179,7 @@ BookReader.prototype.jumpToIndex = function(index, pageX, pageY) { 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; @@ -1178,6 +1212,7 @@ BookReader.prototype.jumpToIndex = function(index, pageX, pageY) { $('#BRcontainer').animate({scrollTop: leafTop },'fast'); } } else { + // 1up var i; var leafTop = 0; var leafLeft = 0; @@ -1193,12 +1228,18 @@ BookReader.prototype.jumpToIndex = function(index, pageX, pageY) { 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); @@ -1235,16 +1276,19 @@ BookReader.prototype.switchMode = function(mode) { // $$$ 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 } @@ -1386,56 +1430,36 @@ BookReader.prototype.prepareTwoPageView = function(centerPercentageX, centerPerc 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' + top: this.twoPage.bookCoverDivTop+'px' }).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',*/ 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', 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'); @@ -1504,15 +1528,9 @@ BookReader.prototype.prepareTwoPageView = function(centerPercentageX, centerPerc 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(); @@ -1724,6 +1742,55 @@ BookReader.prototype.twoPageGetAutofitReduce = function() { 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 @@ -1814,6 +1881,8 @@ BookReader.prototype.leftmost = function() { // next() //______________________________________________________________________________ BookReader.prototype.next = function() { + //this.ttsStop(); + if (2 == this.mode) { this.autoStop(); this.flipFwdToIndex(null); @@ -1827,6 +1896,8 @@ BookReader.prototype.next = function() { // prev() //______________________________________________________________________________ BookReader.prototype.prev = function() { + //this.ttsStop(); + if (2 == this.mode) { this.autoStop(); this.flipBackToIndex(null); @@ -1849,10 +1920,15 @@ BookReader.prototype.last = function() { //______________________________________________________________________________ // 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 { @@ -1864,10 +1940,15 @@ BookReader.prototype.scrollDown = function() { //______________________________________________________________________________ // 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 { @@ -1875,6 +1956,18 @@ BookReader.prototype.scrollUp = function() { } } +// _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() //______________________________________________________________________________ @@ -1967,16 +2060,12 @@ BookReader.prototype.flipLeftToRight = function(newIndexL, newIndexR) { 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'); @@ -2120,16 +2209,12 @@ BookReader.prototype.flipRightToLeft = function(newIndexL, newIndexR) { 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'); @@ -2235,6 +2320,13 @@ BookReader.prototype.prefetchImg = function(index) { 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; @@ -2276,7 +2368,6 @@ BookReader.prototype.prepareFlipLeftToRight = function(prevL, prevR) { top: top+'px', height: this.twoPage.height, width: scaledW+'px', - backgroundColor: this.getPageBackgroundColor(prevL), borderRight: '1px solid black', zIndex: 1 } @@ -2294,7 +2385,6 @@ BookReader.prototype.prepareFlipLeftToRight = function(prevL, prevR) { top: top+'px', height: this.twoPage.height, width: '0px', - backgroundColor: this.getPageBackgroundColor(prevR), borderLeft: '1px solid black', zIndex: 2 } @@ -2329,7 +2419,6 @@ BookReader.prototype.prepareFlipRightToLeft = function(nextL, nextR) { position: 'absolute', left: gutter+'px', top: top+'px', - backgroundColor: this.getPageBackgroundColor(nextR), height: this.twoPage.height, width: scaledW+'px', borderLeft: '1px solid black', @@ -2347,7 +2436,6 @@ BookReader.prototype.prepareFlipRightToLeft = function(nextL, nextR) { 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', @@ -2722,28 +2810,7 @@ BookReader.prototype.updateSearchHilites2UP = function() { //console.log('appending ' + key); } - // We calculate the reduction factor for the specific page because it can be different - // for each page in the spread - var height = this._getPageHeight(key); - var width = this._getPageWidth(key) - var reduce = this.twoPage.height/height; - var scaledW = parseInt(width*reduce); - - var gutter = this.twoPageGutter(); - var pageL; - if ('L' == this.getPageSide(key)) { - pageL = gutter-scaledW; - } else { - pageL = gutter; - } - var pageT = this.twoPageTop(); - - $(result.div).css({ - width: (result.r-result.l)*reduce + 'px', - height: (result.b-result.t)*reduce + 'px', - left: pageL+(result.l)*reduce + 'px', - top: pageT+(result.t)*reduce +'px' - }); + this.setHilightCss2UP(result.div, key, result.l, result.r, result.t, result.b); } else { //console.log(key + ' not displayed'); @@ -2756,6 +2823,35 @@ BookReader.prototype.updateSearchHilites2UP = function() { } } +// setHilightCss2UP() +//______________________________________________________________________________ +//position calculation shared between search and text-to-speech functions +BookReader.prototype.setHilightCss2UP = function(div, index, left, right, top, bottom) { + + // We calculate the reduction factor for the specific page because it can be different + // for each page in the spread + var height = this._getPageHeight(index); + var width = this._getPageWidth(index) + var reduce = this.twoPage.height/height; + var scaledW = parseInt(width*reduce); + + var gutter = this.twoPageGutter(); + var pageL; + if ('L' == this.getPageSide(index)) { + pageL = gutter-scaledW; + } else { + pageL = gutter; + } + var pageT = this.twoPageTop(); + + $(div).css({ + width: (right-left)*reduce + 'px', + height: (bottom-top)*reduce + 'px', + left: pageL+left*reduce + 'px', + top: pageT+top*reduce +'px' + }); +} + // removeSearchHilites() //______________________________________________________________________________ BookReader.prototype.removeSearchHilites = function() { @@ -2771,62 +2867,6 @@ BookReader.prototype.removeSearchHilites = function() { //______________________________________________________________________________ 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 = '
'; - htmlStr = '

Click here to print this page

'; - htmlStr += '
' - htmlStr += '

'; - //htmlStr += ' '; - //htmlStr += ''; - htmlStr += 'Prev Next'; - htmlStr += '

'; - htmlStr += '
'; - htmlStr += '

Close popup

'; - htmlStr += '
'; - - 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 @@ -2838,7 +2878,7 @@ BookReader.prototype.getPrintURI = function() { 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); @@ -2944,7 +2984,7 @@ 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; @@ -3096,6 +3136,7 @@ BookReader.prototype.initToolbar = function(mode, ui) { $("#BookReader").append("
" + "" + + "
" @@ -3219,7 +3260,7 @@ BookReader.prototype.bindToolbarNavHandlers = function(jToolbar) { }); 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(); @@ -3228,7 +3269,7 @@ BookReader.prototype.bindToolbarNavHandlers = function(jToolbar) { }); 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(); @@ -3282,8 +3323,17 @@ BookReader.prototype.bindToolbarNavHandlers = function(jToolbar) { // 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 @@ -3654,19 +3704,6 @@ BookReader.prototype.searchHighlightVisible = function() { 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 @@ -3759,3 +3796,412 @@ BookReader.util = { } // The final property here must NOT have a comma after it - IE7 } + + +// ttsToggle() +//______________________________________________________________________________ +BookReader.prototype.ttsToggle = function () { + if (false == this.ttsPlaying) { + if(soundManager.supported()) { + this.ttsStart(); + } else { + soundManager.onready(function(oStatus) { + if (oStatus.success) { + this.ttsStart(); + } else { + alert('Could not load soundManger2, possibly due to FlashBlock. Audio playback is disabled'); + } + }, this); + } + } else { + this.ttsStop(); + } +} + +// ttsStart() +//______________________________________________________________________________ +BookReader.prototype.ttsStart = function () { + if (soundManager.debugMode) console.log('starting readAloud'); + this.ttsPlaying = true; + this.ttsIndex = this.currentIndex(); + this.ttsFormat = 'mp3'; + if ($.browser.mozilla) { + this.ttsFormat = 'ogg'; + } + this.ttsGetText(this.ttsIndex, 'ttsStartCB'); +} + +// ttsStop() +//______________________________________________________________________________ +BookReader.prototype.ttsStop = function () { + if (false == this.ttsPlaying) return; + + if (soundManager.debugMode) console.log('stopping readaloud'); + soundManager.stopAll(); + soundManager.destroySound('chunk'+this.ttsIndex+'-'+this.ttsPosition); + this.ttsRemoveHilites(); + this.ttsRemovePopup(); + + this.ttsPlaying = false; + this.ttsIndex = null; //leaf index + this.ttsPosition = -1; //chunk (paragraph) number + this.ttsBuffering = false; + this.ttsPoller = null; +} + +// ttsGetText() +//______________________________________________________________________________ +BookReader.prototype.ttsGetText = function(index, callback) { + var url = 'http://'+this.server+'/BookReader/BookReaderGetTextWrapper.php?path='+this.bookPath+'_djvu.xml&page='+index; + this.ttsAjax = $.ajax({url:url, dataType:'jsonp', jsonpCallback:callback}); +} + +// ttsStartCB(): text-to-speech callback +//______________________________________________________________________________ +BookReader.prototype.ttsStartCB = function (data) { + if (soundManager.debugMode) console.log('ttsStartCB got data: ' + data); + this.ttsChunks = data; + this.ttsHilites = []; + + //deal with the page being blank + if (0 == data.length) { + if (soundManager.debugMode) console.log('first page is blank!'); + if(this.ttsAdvance(true)) { + this.ttsGetText(this.ttsIndex, 'ttsStartCB'); + } + return; + } + + this.ttsShowPopup(); + + ///// whileloading: broken on safari + ///// onload fires on safari, but *after* the sound starts playing.. + this.ttsPosition = -1; + var snd = soundManager.createSound({ + id: 'chunk'+this.ttsIndex+'-0', + //url: 'http://home.us.archive.org/~rkumar/arctic.ogg', + url: 'http://'+this.server+'/getTTS.php?string=' + escape(data[0][0]) + '&format=.'+this.ttsFormat, //the .ogg is to trick SoundManager2 to use the HTML5 audio player + whileloading: function(){if (this.bytesLoaded == this.bytesTotal) this.br.ttsRemovePopup();}, //onload never fires in FF... + onload: function(){this.br.ttsRemovePopup();} //whileloading never fires in safari... + }); + snd.br = this; + snd.load(); + + this.ttsNextChunk(); +} + +// ttsShowPopup +//______________________________________________________________________________ +BookReader.prototype.ttsShowPopup = function() { + if (soundManager.debugMode) console.log('ttsShowPopup index='+this.ttsIndex+' pos='+this.ttsPosition); + + this.popup = document.createElement("div"); + $(this.popup).css({ + top: $('#BRtoolbar').height() + 'px', + left: $('.read_aloud').position().left + 'px', + width: $('#BRtoolbar').width()-$('.read_aloud').position().left + 'px', + height: '20px', + }).attr('className', 'BRttsPopUp').appendTo('#BookReader'); + + htmlStr = ' '; + + this.popup.innerHTML = htmlStr; +} + +// ttsRemovePopup +//______________________________________________________________________________ +BookReader.prototype.ttsRemovePopup = function() { + $(this.popup).remove(); + this.popup=null; +} + +// ttsNextPageCB +//______________________________________________________________________________ +BookReader.prototype.ttsNextPageCB = function (data) { + this.ttsNextChunks = data; + if (soundManager.debugMode) console.log('preloaded next chunks.. data is ' + data); + + if (true == this.ttsBuffering) { + if (soundManager.debugMode) console.log('ttsNextPageCB: ttsBuffering is true'); + this.ttsBuffering = false; + } +} + +// ttsLoadChunk +//______________________________________________________________________________ +BookReader.prototype.ttsLoadChunk = function (page, pos, string) { + var snd = soundManager.createSound({ + id: 'chunk'+page+'-'+pos, + url: 'http://'+this.server+'/getTTS.php?string=' + escape(string) + '&format=.'+this.ttsFormat //the .ogg is to trick SoundManager2 to use the HTML5 audio player + }); + snd.br = this; + snd.load() +} + + +// ttsNextChunk() +//______________________________________________________________________________ +// This function into two parts: ttsNextChunk gets run before page flip animation +// and ttsNextChunkPhase2 get run after page flip animation. +// If a page flip is necessary, ttsAdvance() will return false so Phase2 isn't +// called. Instead, this.animationFinishedCallback is set, so that Phase2 +// continues after animation is finished. + +BookReader.prototype.ttsNextChunk = function () { + if (soundManager.debugMode) console.log('nextchunk pos=' + this.ttsPosition); + + if (-1 != this.ttsPosition) { + soundManager.destroySound('chunk'+this.ttsIndex+'-'+this.ttsPosition); + } + + this.ttsRemoveHilites(); //remove old hilights + + var moreToPlay = this.ttsAdvance(); + + if (moreToPlay) { + this.ttsNextChunkPhase2(); + } + + //This function is called again when ttsPlay() has finished playback. + //If the next chunk of text has not yet finished loading, ttsPlay() + //will start polling until the next chunk is ready. +} + +// ttsNextChunkPhase2() +//______________________________________________________________________________ +// page flip animation has now completed +BookReader.prototype.ttsNextChunkPhase2 = function () { + if (null == this.ttsChunks) { + alert('error: ttsChunks is null?'); //TODO + return; + } + + if (0 == this.ttsChunks.length) { + if (soundManager.debugMode) console.log('ttsNextChunk2: ttsChunks.length is zero.. hacking...'); + this.ttsStartCB(this.ttsChunks); + return; + } + + if (soundManager.debugMode) console.log('next chunk is ' + this.ttsPosition); + + //prefetch next page of text + if (0 == this.ttsPosition) { + if (this.ttsIndex<(this.numLeafs-1)) { + this.ttsGetText(this.ttsIndex+1, 'ttsNextPageCB'); + } + } + + this.ttsPrefetchAudio(); + + this.ttsPlay(); +} + +// ttsAdvance() +//______________________________________________________________________________ +// 1. advance ttsPosition +// 2. if necessary, advance ttsIndex, and copy ttsNextChunks to ttsChunks +// 3. if necessary, flip to current page, or scroll so chunk is visible +// 4. do something smart is ttsNextChunks has not yet finished preloading (TODO) +// 5. stop playing at end of book + +BookReader.prototype.ttsAdvance = function (starting) { + this.ttsPosition++; + + if (this.ttsPosition >= this.ttsChunks.length) { + + if (this.ttsIndex == (this.numLeafs-1)) { + if (soundManager.debugMode) console.log('tts stop'); + return false; + } else { + if ((null != this.ttsNextChunks) || (starting)) { + if (soundManager.debugMode) console.log('moving to next page!'); + this.ttsIndex++; + this.ttsPosition = 0; + this.ttsChunks = this.ttsNextChunks; + this.ttsNextChunks = null; + + //A page flip might be necessary. This code is confusing since + //ttsNextChunks might be null if we are starting on a blank page. + if (2 == this.mode) { + if ((this.ttsIndex != this.twoPage.currentIndexL) && (this.ttsIndex != this.twoPage.currentIndexR)) { + if (!starting) { + this.animationFinishedCallback = this.ttsNextChunkPhase2; + this.next(); + return false; + } else { + this.next(); + return true; + } + } else { + return true; + } + } + } else { + if (soundManager.debugMode) console.log('ttsAdvance: ttsNextChunks is null'); + return false; + } + } + } + + return true; +} + +// ttsPrefetchAudio() +//______________________________________________________________________________ +BookReader.prototype.ttsPrefetchAudio = function () { + + if(false != this.ttsBuffering) { + alert('TTS Error: prefetch() called while content still buffering!'); + return; + } + + //preload next chunk + var nextPos = this.ttsPosition+1; + if (nextPos < this.ttsChunks.length) { + this.ttsLoadChunk(this.ttsIndex, nextPos, this.ttsChunks[nextPos][0]); + } else { + //for a short page, preload might nt have yet returned.. + if (soundManager.debugMode) console.log('preloading chunk 0 from next page, index='+(this.ttsIndex+1)); + if (null != this.ttsNextChunks) { + console.log(this.ttsNextChunks); + if (0 != this.ttsNextChunks.length) { + this.ttsLoadChunk(this.ttsIndex+1, 0, this.ttsNextChunks[0][0]); + } else { + if (soundManager.debugMode) console.log('prefetchAudio(): ttsNextChunks is zero length!'); + } + } else { + if (soundManager.debugMode) console.log('ttsNextChunks is null, not preloading next page'); + this.ttsBuffering = true; + } + } + +} + +// ttsPlay() +//______________________________________________________________________________ +BookReader.prototype.ttsPlay = function () { + + var chunk = this.ttsChunks[this.ttsPosition]; + if (soundManager.debugMode) { + console.log('ttsPlay position = ' + this.ttsPosition); + console.log('chunk = ' + chunk); + console.log(this.ttsChunks); + } + + //add new hilights + if (2 == this.mode) { + this.ttsHilite2UP(chunk); + } else { + this.ttsHilite1UP(chunk); + } + + this.ttsScrollToChunk(chunk); + + //play current chunk + if (false == this.ttsBuffering) { + soundManager.play('chunk'+this.ttsIndex+'-'+this.ttsPosition,{onfinish:function(){br.ttsNextChunk();}}); + } else { + soundManager.play('chunk'+this.ttsIndex+'-'+this.ttsPosition,{onfinish:function(){br.ttsStartPolling();}}); + } +} + +// scrollToChunk() +//______________________________________________________________________________ +BookReader.prototype.ttsScrollToChunk = function(chunk) { + if (this.constMode1up != this.mode) return; + + var leafTop = 0; + var h; + var i; + for (i=0; i