1 2 3// BookReader() 4//______________________________________________________________________________ 5// After you instantiate this object, you must supply the following 6// book-specific functions, before calling init(). Some of these functions 7// can just be stubs for simple books. 8// - getPageWidth() 9// - getPageHeight() 10// - getPageURI() 11// - getPageSide() 12// - canRotatePage() 13// - getPageNum() 14// - getSpreadIndices() 15// You must also add a numLeafs property before calling init(). 16 17function BookReader() { 18 19 // Mode constants 20 this.constMode1up = 1; 21 this.constMode2up = 2; 22 this.constModeThumb = 3; 23 24 this.reduce = 4; 25 this.padding = 10; // Padding in 1up 26 27 this.mode = this.constMode1up; 28 this.ui = 'full'; // UI mode 29 this.uiAutoHide = false; // Controls whether nav/toolbar will autohide 30 31 // thumbnail mode 32 this.thumbWidth = 100; // will be overridden during prepareThumbnailView 33 this.thumbRowBuffer = 2; // number of rows to pre-cache out a view 34 this.thumbColumns = 6; // default 35 this.thumbMaxLoading = 4; // number of thumbnails to load at once 36 this.thumbPadding = 10; // spacing between thumbnails 37 this.displayedRows=[]; 38 39 this.displayedIndices = []; 40 //this.indicesToDisplay = []; 41 this.imgs = {}; 42 this.prefetchedImgs = {}; //an object with numeric keys cooresponding to page index 43 44 this.timer = null; 45 this.animating = false; 46 this.auto = false; 47 this.autoTimer = null; 48 this.flipSpeed = 'fast'; 49 50 this.twoPagePopUp = null; 51 this.leafEdgeTmp = null; 52 this.embedPopup = null; 53 this.printPopup = null; 54 55 this.searchTerm = ''; 56 this.searchResults = null; 57 58 this.firstIndex = null; 59 60 this.lastDisplayableIndex2up = null; 61 62 // Should be overriden (before init) by custom implmentations. 63 this.logoURL = 'http://www.xraylibrary.ru'; 64 65 // Base URL for UI images - should be overriden (before init) by 66 // custom implementations. 67 // $$$ This is the same directory as the images referenced by relative 68 // path in the CSS. Would be better to automagically find that path. 69 this.imagesBaseURL = '/images/'; 70 71 72 // Zoom levels 73 // $$$ provide finer grained zooming 74 /* 75 this.reductionFactors = [ {reduce: 0.5, autofit: null}, 76 {reduce: 1, autofit: null}, 77 {reduce: 2, autofit: null}, 78 {reduce: 4, autofit: null}, 79 {reduce: 8, autofit: null}, 80 {reduce: 16, autofit: null} ]; 81 */ 82 /* The autofit code ensures that fit to width and fit to height will be available */ 83 this.reductionFactors = [ {reduce: 0.5, autofit: null}, 84 {reduce: 1, autofit: null}, 85 {reduce: 2, autofit: null}, 86 {reduce: 3, autofit: null}, 87 {reduce: 4, autofit: null}, 88 {reduce: 6, autofit: null} ]; 89 90 91 // Object to hold parameters related to 1up mode 92 this.onePage = { 93 autofit: 'height' // valid values are height, width, none 94 }; 95 96 // Object to hold parameters related to 2up mode 97 this.twoPage = { 98 coverInternalPadding: 0, // Width of cover 99 coverExternalPadding: 0, // Padding outside of cover 100 bookSpineDivWidth: 64, // Width of book spine $$$ consider sizing based on book length 101 autofit: 'auto' 102 }; 103 104 // This object/dictionary controls which optional features are enabled 105 // XXXmang in progress 106 this.features = { 107 // search 108 // read aloud 109 // open library entry 110 // table of contents 111 // embed/share ui 112 // info ui 113 }; 114 115 // Text-to-Speech params 116 this.ttsPlaying = false; 117 this.ttsIndex = null; //leaf index 118 this.ttsPosition = -1; //chunk (paragraph) number 119 this.ttsBuffering = false; 120 this.ttsPoller = null; 121 this.ttsFormat = null; 122 123 return this; 124}; 125 126(function ($) { 127// init() 128//______________________________________________________________________________ 129BookReader.prototype.init = function() { 130 131 var startIndex = undefined; 132 this.pageScale = this.reduce; // preserve current reduce 133 134 // Find start index and mode if set in location hash 135 var params = {}; 136 if (window.location.hash) { 137 // params explicitly set in URL 138 params = this.paramsFromFragment(window.location.hash); 139 } else { 140 // params not explicitly set, use defaults if we have them 141 if ('defaults' in this) { 142 params = this.paramsFromFragment(this.defaults); 143 } 144 } 145 146 // Sanitize/process parameters 147 148 if ( !this.canSwitchToMode( this.mode ) ) { 149 this.mode = this.constMode1up; 150 } 151 152 if ('undefined' != typeof(params.index)) { 153 startIndex = params.index; 154 } else if ('undefined' != typeof(params.page)) { 155 startIndex = this.getPageIndex(params.page); 156 } 157 158 if ('undefined' == typeof(startIndex)) { 159 if ('undefined' != typeof(this.titleLeaf)) { 160 // title leaf is known - but only use as default if book has a few pages 161 if (this.numLeafs > 2) { 162 startIndex = this.leafNumToIndex(this.titleLeaf); 163 } 164 } 165 } 166 167 if ('undefined' == typeof(startIndex)) { 168 startIndex = 0; 169 } 170 171 if ('undefined' != typeof(params.mode)) { 172 this.mode = params.mode; 173 } 174 175 // Set document title -- may have already been set in enclosing html for 176 // search engine visibility 177 document.title = this.shortTitle(50); 178 179 $("#BookReader").empty(); 180 181 this.initToolbar(this.mode, this.ui); // Build inside of toolbar div 182 $("#BookReader").append("<div id='BRcontainer' dir='ltr'></div>"); 183 $("#BRcontainer").append("<div id='BRpageview'></div>"); 184 185 $("#BRcontainer").bind('scroll', this, function(e) { 186 e.data.loadLeafs(); 187 }); 188 189 this.setupKeyListeners(); 190 this.startLocationPolling(); 191 192 $(window).bind('resize', this, function(e) { 193 //console.log('resize!'); 194 195 if (1 == e.data.mode) { 196 //console.log('centering 1page view'); 197 if (e.data.autofit) { 198 e.data.resizePageView(); 199 } 200 e.data.centerPageView(); 201 $('#BRpageview').empty() 202 e.data.displayedIndices = []; 203 e.data.updateSearchHilites(); //deletes hilights but does not call remove() 204 e.data.loadLeafs(); 205 } else if (3 == e.data.mode){ 206 e.data.prepareThumbnailView(); 207 } else { 208 //console.log('drawing 2 page view'); 209 210 // We only need to prepare again in autofit (size of spread changes) 211 if (e.data.twoPage.autofit) { 212 e.data.prepareTwoPageView(); 213 } else { 214 // Re-center if the scrollbars have disappeared 215 var center = e.data.twoPageGetViewCenter(); 216 var doRecenter = false;
217 if (e.data.twoPage.totalWidth < $('#BRcontainer').attr('clientWidth')) { 218 center.percentageX = 0.5; 219 doRecenter = true; 220 } 221 if (e.data.twoPage.totalHeight < $('#BRcontainer').attr('clientHeight')) { 222 center.percentageY = 0.5; 223 doRecenter = true; 224 } 225 if (doRecenter) { 226 e.data.twoPageCenterView(center.percentageX, center.percentageY); 227 } 228 } 229 } 230 }); 231 232 if (this.protected) { 233 $('.BRpagediv1up').live('contextmenu dragstart', this, function(e) { 234 return false; 235 }); 236 237 $('.BRpageimage').live('contextmenu dragstart', this, function(e) { 238 return false; 239 }); 240 241 $('.BRpagedivthumb').live('contextmenu dragstart', this, function(e) { 242 return false; 243 }); 244 245 $('.BRicon.share').hide(); 246 } 247 248 $('.BRpagediv1up').bind('mousedown', this, function(e) { 249 // $$$ the purpose of this is to disable selection of the image (makes it turn blue) 250 // but this also interferes with right-click. See https://bugs.edge.launchpad.net/gnubook/+bug/362626 251 return false; 252 }); 253 254 // $$$ refactor this so it's enough to set the first index and call preparePageView 255 // (get rid of mode-specific logic at this point) 256 if (1 == this.mode) { 257 this.firstIndex = startIndex; 258 this.prepareOnePageView(); 259 this.jumpToIndex(startIndex); 260 } else if (3 == this.mode) { 261 this.firstIndex = startIndex; 262 this.prepareThumbnailView(); 263 this.jumpToIndex(startIndex); 264 } else { 265 this.displayedIndices=[0]; 266 this.firstIndex = startIndex; 267 this.displayedIndices = [this.firstIndex]; 268 //console.log('titleLeaf: %d', this.titleLeaf); 269 //console.log('displayedIndices: %s', this.displayedIndices); 270 this.prepareTwoPageView(); 271 } 272 273 // Enact other parts of initial params 274 this.updateFromParams(params); 275 276 // We init the nav bar after the params processing so that the nav slider knows where 277 // it should start (doesn't jump after init) 278 if (this.ui == "embed") { 279 this.initEmbedNavbar(); 280 } else { 281 this.initNavbar(); 282 } 283 this.bindNavigationHandlers(); 284 285 // Set strings in the UI 286 this.initUIStrings(); 287 288 // Start AJAX request for OL data 289 if (this.getOpenLibraryRecord) { 290 this.getOpenLibraryRecord(this.gotOpenLibraryRecord); 291 } 292 293} 294 295BookReader.prototype.setupKeyListeners = function() { 296 var self = this; 297 298 var KEY_PGUP = 33; 299 var KEY_PGDOWN = 34; 300 var KEY_END = 35; 301 var KEY_HOME = 36; 302 303 var KEY_LEFT = 37; 304 var KEY_UP = 38; 305 var KEY_RIGHT = 39; 306 var KEY_DOWN = 40; 307 308 // We use document here instead of window to avoid a bug in jQuery on IE7 309 $(document).keydown(function(e) { 310 311 // Keyboard navigation 312 if (!self.keyboardNavigationIsDisabled(e)) { 313 switch(e.keyCode) { 314 case KEY_PGUP: 315 case KEY_UP: 316 // In 1up mode page scrolling is handled by browser 317 if (2 == self.mode) { 318 e.preventDefault(); 319 self.prev(); 320 } 321 break; 322 case KEY_DOWN: 323 case KEY_PGDOWN: 324 if (2 == self.mode) { 325 e.preventDefault(); 326 self.next(); 327 } 328 break; 329 case KEY_END: 330 e.preventDefault(); 331 self.last(); 332 break; 333 case KEY_HOME: 334 e.preventDefault(); 335 self.first(); 336 break; 337 case KEY_LEFT: 338 if (2 == self.mode) { 339 e.preventDefault(); 340 self.left(); 341 } 342 break; 343 case KEY_RIGHT: 344 if (2 == self.mode) { 345 e.preventDefault(); 346 self.right(); 347 } 348 break; 349 } 350 } 351 }); 352} 353 354// drawLeafs() 355//______________________________________________________________________________ 356BookReader.prototype.drawLeafs = function() { 357 if (1 == this.mode) { 358 this.drawLeafsOnePage(); 359 } else if (3 == this.mode) { 360 this.drawLeafsThumbnail(); 361 } else { 362 this.drawLeafsTwoPage(); 363 } 364 365} 366 367// bindGestures(jElement) 368//______________________________________________________________________________ 369BookReader.prototype.bindGestures = function(jElement) { 370 371 jElement.unbind('gesturechange').bind('gesturechange', function(e) { 372 e.preventDefault(); 373 if (e.originalEvent.scale > 1.5) { 374 br.zoom(1); 375 } else if (e.originalEvent.scale < 0.6) { 376 br.zoom(-1); 377 } 378 }); 379} 380 381BookReader.prototype.setClickHandler2UP = function( element, data, handler) { 382 //console.log('setting handler'); 383 //console.log(element.tagName); 384 385 $(element).unbind('click').bind('click', data, function(e) { 386 handler(e); 387 }); 388} 389 390// drawLeafsOnePage() 391//______________________________________________________________________________ 392BookReader.prototype.drawLeafsOnePage = function() { 393 //alert('drawing leafs!'); 394 this.timer = null; 395 396 397 var scrollTop = $('#BRcontainer').attr('scrollTop'); 398 var scrollBottom = scrollTop + $('#BRcontainer').height();
399 //console.log('top=' + scrollTop + ' bottom='+scrollBottom); 400 401 var indicesToDisplay = []; 402 403 var i; 404 var leafTop = 0; 405 var leafBottom = 0; 406 for (i=0; i<this.numLeafs; i++) { 407 var height = parseInt(this._getPageHeight(i)/this.reduce); 408 409 leafBottom += height; 410 //console.log('leafTop = '+leafTop+ ' pageH = ' + this.pageH[i] + 'leafTop>=scrollTop=' + (leafTop>=scrollTop)); 411 var topInView = (leafTop >= scrollTop) && (leafTop <= scrollBottom); 412 var bottomInView = (leafBottom >= scrollTop) && (leafBottom <= scrollBottom); 413 var middleInView = (leafTop <=scrollTop) && (leafBottom>=scrollBottom); 414 if (topInView | bottomInView | middleInView) { 415 //console.log('displayed: ' + this.displayedIndices); 416 //console.log('to display: ' + i); 417 indicesToDisplay.push(i); 418 } 419 leafTop += height +10; 420 leafBottom += 10; 421 } 422 423 // Based of the pages displayed in the view we set the current index 424 // $$$ we should consider the page in the center of the view to be the current one 425 var firstIndexToDraw = indicesToDisplay[0]; 426 if (firstIndexToDraw != this.firstIndex) { 427 this.willChangeToIndex(firstIndexToDraw); 428 } 429 this.firstIndex = firstIndexToDraw; 430 431 // Update hash, but only if we're currently displaying a leaf 432 // Hack that fixes #365790 433 if (this.displayedIndices.length > 0) { 434 this.updateLocationHash(); 435 } 436 437 if ((0 != firstIndexToDraw) && (1 < this.reduce)) { 438 firstIndexToDraw--; 439 indicesToDisplay.unshift(firstIndexToDraw); 440 } 441 442 var lastIndexToDraw = indicesToDisplay[indicesToDisplay.length-1]; 443 if ( ((this.numLeafs-1) != lastIndexToDraw) && (1 < this.reduce) ) { 444 indicesToDisplay.push(lastIndexToDraw+1); 445 } 446 447 leafTop = 0; 448 var i; 449 for (i=0; i<firstIndexToDraw; i++) { 450 leafTop += parseInt(this._getPageHeight(i)/this.reduce) +10; 451 } 452 453 //var viewWidth = $('#BRpageview').width(); //includes scroll bar width 454 var viewWidth = $('#BRcontainer').attr('scrollWidth'); 455 456 457 for (i=0; i<indicesToDisplay.length; i++) { 458 var index = indicesToDisplay[i]; 459 var height = parseInt(this._getPageHeight(index)/this.reduce); 460 461 if (BookReader.util.notInArray(indicesToDisplay[i], this.displayedIndices)) { 462 var width = parseInt(this._getPageWidth(index)/this.reduce); 463 //console.log("displaying leaf " + indicesToDisplay[i] + ' leafTop=' +leafTop); 464 var div = document.createElement("div"); 465 div.className = 'BRpagediv1up'; 466 div.id = 'pagediv'+index; 467 div.style.position = "absolute"; 468 $(div).css('top', leafTop + 'px'); 469 var left = (viewWidth-width)>>1; 470 if (left<0) left = 0; 471 $(div).css('left', left+'px'); 472 $(div).css('width', width+'px'); 473 $(div).css('height', height+'px'); 474 //$(div).text('loading...'); 475 476 $('#BRpageview').append(div); 477 478 var img = document.createElement("img"); 479 img.src = this._getPageURI(index, this.reduce, 0); 480 $(img).addClass('BRnoselect'); 481 $(img).css('width', width+'px'); 482 $(img).css('height', height+'px'); 483 $(div).append(img); 484 485 } else { 486 //console.log("not displaying " + indicesToDisplay[i] + ' score=' + jQuery.inArray(indicesToDisplay[i], this.displayedIndices)); 487 } 488 489 leafTop += height +10; 490 491 } 492 493 for (i=0; i<this.displayedIndices.length; i++) { 494 if (BookReader.util.notInArray(this.displayedIndices[i], indicesToDisplay)) { 495 var index = this.displayedIndices[i]; 496 //console.log('Removing leaf ' + index); 497 //console.log('id='+'#pagediv'+index+ ' top = ' +$('#pagediv'+index).css('top')); 498 $('#pagediv'+index).remove(); 499 } else { 500 //console.log('NOT Removing leaf ' + this.displayedIndices[i]); 501 } 502 } 503 504 this.displayedIndices = indicesToDisplay.slice(); 505 this.updateSearchHilites(); 506 507 if (null != this.getPageNum(firstIndexToDraw)) { 508 $("#BRpagenum").val(this.getPageNum(this.currentIndex())); 509 } else { 510 $("#BRpagenum").val(''); 511 } 512 513 this.updateToolbarZoom(this.reduce); 514 515} 516 517// drawLeafsThumbnail() 518//______________________________________________________________________________ 519// If seekIndex is defined, the view will be drawn with that page visible (without any 520// animated scrolling) 521BookReader.prototype.drawLeafsThumbnail = function( seekIndex ) { 522 //alert('drawing leafs!'); 523 this.timer = null; 524 525 var viewWidth = $('#BRcontainer').attr('scrollWidth') - 20; // width minus buffer 526
527 //console.log('top=' + scrollTop + ' bottom='+scrollBottom); 528 529 var i; 530 var leafWidth; 531 var leafHeight; 532 var rightPos = 0; 533 var bottomPos = 0; 534 var maxRight = 0; 535 var currentRow = 0; 536 var leafIndex = 0; 537 var leafMap = []; 538 539 var self = this; 540 541 // Will be set to top of requested seek index, if set 542 var seekTop; 543 544 // Calculate the position of every thumbnail. $$$ cache instead of calculating on every draw 545 for (i=0; i<this.numLeafs; i++) { 546 leafWidth = this.thumbWidth; 547 if (rightPos + (leafWidth + this.thumbPadding) > viewWidth){ 548 currentRow++; 549 rightPos = 0; 550 leafIndex = 0; 551 } 552 553 if (leafMap[currentRow]===undefined) { leafMap[currentRow] = {}; } 554 if (leafMap[currentRow].leafs===undefined) { 555 leafMap[currentRow].leafs = []; 556 leafMap[currentRow].height = 0; 557 leafMap[currentRow].top = 0; 558 } 559 leafMap[currentRow].leafs[leafIndex] = {}; 560 leafMap[currentRow].leafs[leafIndex].num = i; 561 leafMap[currentRow].leafs[leafIndex].left = rightPos; 562 563 leafHeight = parseInt((this.getPageHeight(leafMap[currentRow].leafs[leafIndex].num)*this.thumbWidth)/this.getPageWidth(leafMap[currentRow].leafs[leafIndex].num), 10); 564 if (leafHeight > leafMap[currentRow].height) { 565 leafMap[currentRow].height = leafHeight; 566 } 567 if (leafIndex===0) { bottomPos += this.thumbPadding + leafMap[currentRow].height; } 568 rightPos += leafWidth + this.thumbPadding; 569 if (rightPos > maxRight) { maxRight = rightPos; } 570 leafIndex++; 571 572 if (i == seekIndex) { 573 seekTop = bottomPos - this.thumbPadding - leafMap[currentRow].height; 574 } 575 } 576 577 // reset the bottom position based on thumbnails 578 $('#BRpageview').height(bottomPos); 579 580 var pageViewBuffer = Math.floor(($('#BRcontainer').attr('scrollWidth') - maxRight) / 2) - 14; 581 582 // If seekTop is defined, seeking was requested and target found 583 if (typeof(seekTop) != 'undefined') { 584 $('#BRcontainer').scrollTop( seekTop ); 585 } 586 587 var scrollTop = $('#BRcontainer').attr('scrollTop'); 588 var scrollBottom = scrollTop + $('#BRcontainer').height(); 589 590 var leafTop = 0; 591 var leafBottom = 0; 592 var rowsToDisplay = []; 593 594 // Visible leafs with least/greatest index 595 var leastVisible = this.numLeafs - 1; 596 var mostVisible = 0; 597 598 // Determine the thumbnails in view 599 for (i=0; i<leafMap.length; i++) { 600 leafBottom += this.thumbPadding + leafMap[i].height; 601 var topInView = (leafTop >= scrollTop) && (leafTop <= scrollBottom); 602 var bottomInView = (leafBottom >= scrollTop) && (leafBottom <= scrollBottom); 603 var middleInView = (leafTop <=scrollTop) && (leafBottom>=scrollBottom); 604 if (topInView | bottomInView | middleInView) { 605 //console.log('row to display: ' + j); 606 rowsToDisplay.push(i); 607 if (leafMap[i].leafs[0].num < leastVisible) { 608 leastVisible = leafMap[i].leafs[0].num; 609 } 610 if (leafMap[i].leafs[leafMap[i].leafs.length - 1].num > mostVisible) { 611 mostVisible = leafMap[i].leafs[leafMap[i].leafs.length - 1].num; 612 } 613 } 614 if (leafTop > leafMap[i].top) { leafMap[i].top = leafTop; } 615 leafTop = leafBottom; 616 } 617 618 // create a buffer of preloaded rows before and after the visible rows 619 var firstRow = rowsToDisplay[0]; 620 var lastRow = rowsToDisplay[rowsToDisplay.length-1]; 621 for (i=1; i<this.thumbRowBuffer+1; i++) { 622 if (lastRow+i < leafMap.length) { rowsToDisplay.push(lastRow+i); } 623 } 624 for (i=1; i<this.thumbRowBuffer+1; i++) { 625 if (firstRow-i >= 0) { rowsToDisplay.push(firstRow-i); } 626 } 627 628 // Create the thumbnail divs and images (lazy loaded) 629 var j; 630 var row; 631 var left; 632 var index; 633 var div; 634 var link; 635 var img; 636 var page; 637 for (i=0; i<rowsToDisplay.length; i++) { 638 if (BookReader.util.notInArray(rowsToDisplay[i], this.displayedRows)) { 639 row = rowsToDisplay[i]; 640 641 for (j=0; j<leafMap[row].leafs.length; j++) { 642 index = j; 643 leaf = leafMap[row].leafs[j].num; 644 645 leafWidth = this.thumbWidth; 646 leafHeight = parseInt((this.getPageHeight(leaf)*this.thumbWidth)/this.getPageWidth(leaf), 10); 647 leafTop = leafMap[row].top; 648 left = leafMap[row].leafs[index].left + pageViewBuffer; 649 if ('rl' == this.pageProgression){ 650 left = viewWidth - leafWidth - left; 651 } 652 653 div = document.createElement("div"); 654 div.id = 'pagediv'+leaf; 655 div.style.position = "absolute"; 656 div.className = "BRpagedivthumb"; 657 658 left += this.thumbPadding; 659 $(div).css('top', leafTop + 'px'); 660 $(div).css('left', left+'px'); 661 $(div).css('width', leafWidth+'px'); 662 $(div).css('height', leafHeight+'px'); 663 //$(div).text('loading...'); 664 665 // link to page in single page mode 666 link = document.createElement("a"); 667 $(link).data('leaf', leaf); 668 $(link).bind('tap', function(event) { 669 self.firstIndex = $(this).data('leaf'); 670 self.switchMode(self.constMode1up); 671 event.preventDefault(); 672 event.stopPropagation(); 673 }); 674 $(div).append(link); 675 676 $('#BRpageview').append(div); 677 678 img = document.createElement("img"); 679 var thumbReduce = Math.floor(this.getPageWidth(leaf) / this.thumbWidth); 680 681 $(img).attr('src', this.imagesBaseURL + 'transparent.png') 682 .css({'width': leafWidth+'px', 'height': leafHeight+'px' }) 683 .addClass('BRlazyload') 684 // Store the URL of the image that will replace this one 685 .data('srcURL', this._getPageURI(leaf, thumbReduce)); 686 $(link).append(img); 687 //console.log('displaying thumbnail: ' + leaf); 688 } 689 } 690 } 691 692 // Remove thumbnails that are not to be displayed 693 var k; 694 for (i=0; i<this.displayedRows.length; i++) { 695 if (BookReader.util.notInArray(this.displayedRows[i], rowsToDisplay)) { 696 row = this.displayedRows[i]; 697 698 // $$$ Safari doesn't like the comprehension 699 //var rowLeafs = [leaf.num for each (leaf in leafMap[row].leafs)]; 700 //console.log('Removing row ' + row + ' ' + rowLeafs); 701 702 for (k=0; k<leafMap[row].leafs.length; k++) { 703 index = leafMap[row].leafs[k].num; 704 //console.log('Removing leaf ' + index); 705 $('#pagediv'+index).remove(); 706 } 707 } else { 708 // var mRow = this.displayedRows[i]; 709 // var mLeafs = '[' + [leaf.num for each (leaf in leafMap[mRow].leafs)] + ']'; 710 // console.log('NOT Removing row ' + mRow + ' ' + mLeafs); 711 } 712 } 713 714 // Update which page is considered current to make sure a visible page is the current one 715 var currentIndex = this.currentIndex(); 716 // console.log('current ' + currentIndex); 717 // console.log('least visible ' + leastVisible + ' most visible ' + mostVisible); 718 if (currentIndex < leastVisible) { 719 this.willChangeToIndex(leastVisible); 720 this.setCurrentIndex(leastVisible); 721 } else if (currentIndex > mostVisible) { 722 this.willChangeToIndex(mostVisible); 723 this.setCurrentIndex(mostVisible); 724 } 725 726 this.displayedRows = rowsToDisplay.slice(); 727 728 // Update hash, but only if we're currently displaying a leaf 729 // Hack that fixes #365790 730 if (this.displayedRows.length > 0) { 731 this.updateLocationHash(); 732 } 733 734 // remove previous highlights 735 $('.BRpagedivthumb_highlight').removeClass('BRpagedivthumb_highlight'); 736 737 // highlight current page 738 $('#pagediv'+this.currentIndex()).addClass('BRpagedivthumb_highlight'); 739 740 this.lazyLoadThumbnails(); 741 742 // Update page number box. $$$ refactor to function 743 if (null !== this.getPageNum(this.currentIndex())) { 744 $("#BRpagenum").val(this.getPageNum(this.currentIndex())); 745 } else { 746 $("#BRpagenum").val(''); 747 } 748 749 this.updateToolbarZoom(this.reduce); 750} 751 752BookReader.prototype.lazyLoadThumbnails = function() { 753 754 // console.log('lazy load'); 755 756 // We check the complete property since load may not be fired if loading from the cache 757 $('.BRlazyloading').filter('[complete=true]').removeClass('BRlazyloading'); 758 759 var loading = $('.BRlazyloading').length; 760 var toLoad = this.thumbMaxLoading - loading; 761 762 // console.log(' ' + loading + ' thumbnails loading'); 763 // console.log(' this.thumbMaxLoading ' + this.thumbMaxLoading); 764 765 var self = this; 766 767 if (toLoad > 0) { 768 // $$$ TODO load those near top (but not beyond) page view first 769 $('#BRpageview img.BRlazyload').filter(':lt(' + toLoad + ')').each( function() { 770 self.lazyLoadImage(this); 771 }); 772 } 773} 774 775BookReader.prototype.lazyLoadImage = function (dummyImage) { 776 //console.log(' lazy load started for ' + $(dummyImage).data('srcURL').match('([0-9]{4}).jp2')[1] ); 777 778 var img = new Image(); 779 var self = this; 780 781 $(img) 782 .addClass('BRlazyloading') 783 .one('load', function() { 784 //if (console) { console.log(' onload ' + $(this).attr('src').match('([0-9]{4}).jp2')[1]); }; 785 786 $(this).removeClass('BRlazyloading'); 787 788 // $$$ Calling lazyLoadThumbnails here was causing stack overflow on IE so 789 // we call the function after a slight delay. Also the img.complete property 790 // is not yet set in IE8 inside this onload handler 791 setTimeout(function() { self.lazyLoadThumbnails(); }, 100); 792 }) 793 .one('error', function() { 794 // Remove class so we no longer count as loading 795 $(this).removeClass('BRlazyloading'); 796 }) 797
798 //the width set with .attr is ignored by Internet Explorer, causing it to show the image at its original size 799 //but with this one line of css, even IE shows the image at the proper size 800 .css({ 801 'width': $(dummyImage).width()+'px', 802 'height': $(dummyImage).height()+'px' 803 }) 804 .attr({ 805 'width': $(dummyImage).width(), 806 'height': $(dummyImage).height(), 807 'src': $(dummyImage).data('srcURL') 808 }); 809 810 // replace with the new img 811 $(dummyImage).before(img).remove(); 812 813 img = null; // tidy up closure 814} 815 816 817// drawLeafsTwoPage() 818//______________________________________________________________________________ 819BookReader.prototype.drawLeafsTwoPage = function() { 820 var scrollTop = $('#BRtwopageview').attr('scrollTop'); 821 var scrollBottom = scrollTop + $('#BRtwopageview').height(); 822 823 // $$$ we should use calculated values in this.twoPage (recalc if necessary) 824 825 var indexL = this.twoPage.currentIndexL; 826 827 var heightL = this._getPageHeight(indexL); 828 var widthL = this._getPageWidth(indexL); 829
830 var leafEdgeWidthL = this.leafEdgeWidth(indexL); 831 var leafEdgeWidthR = this.twoPage.edgeWidth - leafEdgeWidthL; 832 //var bookCoverDivWidth = this.twoPage.width*2 + 20 + this.twoPage.edgeWidth; // $$$ hardcoded cover width 833 var bookCoverDivWidth = this.twoPage.bookCoverDivWidth; 834 //console.log(leafEdgeWidthL); 835 836 var middle = this.twoPage.middle; // $$$ getter instead? 837 var top = this.twoPageTop(); 838 var bookCoverDivLeft = this.twoPage.bookCoverDivLeft; 839 840 this.twoPage.scaledWL = this.getPageWidth2UP(indexL); 841 this.twoPage.gutter = this.twoPageGutter(); 842 843 this.prefetchImg(indexL); 844 $(this.prefetchedImgs[indexL]).css({ 845 position: 'absolute', 846 left: this.twoPage.gutter-this.twoPage.scaledWL+'px', 847 right: '', 848 top: top+'px', 849 height: this.twoPage.height +'px', // $$$ height forced the same for both pages 850 width: this.twoPage.scaledWL + 'px', 851 zIndex: 2 852 }).appendTo('#BRtwopageview'); 853 854 var indexR = this.twoPage.currentIndexR; 855 var heightR = this._getPageHeight(indexR); 856 var widthR = this._getPageWidth(indexR); 857 858 // $$$ should use getwidth2up? 859 //var scaledWR = this.twoPage.height*widthR/heightR; 860 this.twoPage.scaledWR = this.getPageWidth2UP(indexR); 861 this.prefetchImg(indexR); 862 $(this.prefetchedImgs[indexR]).css({ 863 position: 'absolute', 864 left: this.twoPage.gutter+'px', 865 right: '', 866 top: top+'px', 867 height: this.twoPage.height + 'px', // $$$ height forced the same for both pages 868 width: this.twoPage.scaledWR + 'px', 869 zIndex: 2 870 }).appendTo('#BRtwopageview'); 871 872 873 this.displayedIndices = [this.twoPage.currentIndexL, this.twoPage.currentIndexR]; 874 this.setMouseHandlers2UP(); 875 this.twoPageSetCursor(); 876 877 this.updatePageNumBox2UP(); 878 this.updateToolbarZoom(this.reduce); 879 880 // this.twoPagePlaceFlipAreas(); // No longer used 881 882} 883 884// updatePageNumBox2UP 885//______________________________________________________________________________ 886BookReader.prototype.updatePageNumBox2UP = function() { 887 if (null != this.getPageNum(this.twoPage.currentIndexL)) { 888 $("#BRpagenum").val(this.getPageNum(this.currentIndex())); 889 } else { 890 $("#BRpagenum").val(''); 891 } 892 this.updateLocationHash(); 893} 894 895// loadLeafs() 896//______________________________________________________________________________ 897BookReader.prototype.loadLeafs = function() { 898 899 900 var self = this; 901 if (null == this.timer) { 902 this.timer=setTimeout(function(){self.drawLeafs()},250); 903 } else { 904 clearTimeout(this.timer); 905 this.timer=setTimeout(function(){self.drawLeafs()},250); 906 } 907} 908 909// zoom(direction) 910// 911// Pass 1 to zoom in, anything else to zoom out 912//______________________________________________________________________________ 913BookReader.prototype.zoom = function(direction) { 914 switch (this.mode) { 915 case this.constMode1up: 916 if (direction == 1) { 917 // XXX other cases 918 return this.zoom1up('in'); 919 } else { 920 return this.zoom1up('out'); 921 } 922 923 case this.constMode2up: 924 if (direction == 1) { 925 // XXX other cases 926 return this.zoom2up('in'); 927 } else { 928 return this.zoom2up('out'); 929 } 930 931 case this.constModeThumb: 932 // XXX update zoomThumb for named directions 933 return this.zoomThumb(direction); 934 935 } 936} 937 938// zoom1up(dir) 939//______________________________________________________________________________ 940BookReader.prototype.zoom1up = function(direction) { 941 942 if (2 == this.mode) { //can only zoom in 1-page mode 943 this.switchMode(1); 944 return; 945 } 946 947 var reduceFactor = this.nextReduce(this.reduce, direction, this.onePage.reductionFactors); 948 949 if (this.reduce == reduceFactor.reduce) { 950 // Already at this level 951 return; 952 } 953 954 this.reduce = reduceFactor.reduce; // $$$ incorporate into function 955 this.onePage.autofit = reduceFactor.autofit; 956 957 this.pageScale = this.reduce; // preserve current reduce 958 this.resizePageView(); 959 960 $('#BRpageview').empty() 961 this.displayedIndices = []; 962 this.loadLeafs(); 963 964 this.updateToolbarZoom(this.reduce); 965 966 // Recalculate search hilites 967 this.removeSearchHilites(); 968 this.updateSearchHilites(); 969 970} 971 972// resizePageView() 973//______________________________________________________________________________ 974BookReader.prototype.resizePageView = function() { 975 976 // $$$ This code assumes 1up mode 977 // e.g. does not preserve position in thumbnail mode 978 // See http://bugs.launchpad.net/bookreader/+bug/552972 979 980 switch (this.mode) { 981 case this.constMode1up: 982 case this.constMode2up: 983 this.resizePageView1up(); // $$$ necessary in non-1up mode? 984 break; 985 case this.constModeThumb: 986 this.prepareThumbnailView( this.currentIndex() ); 987 break; 988 default: 989 alert('Resize not implemented for this mode'); 990 } 991} 992 993// Resize the current one page view 994BookReader.prototype.resizePageView1up = function() { 995 var i; 996 var viewHeight = 0; 997 //var viewWidth = $('#BRcontainer').width(); //includes scrollBar 998 var viewWidth = $('#BRcontainer').attr('clientWidth'); 999 1000 var oldScrollTop = $('#BRcontainer').attr('scrollTop'); 1001 var oldScrollLeft = $('#BRcontainer').attr('scrollLeft'); 1002 1003 var oldPageViewHeight = $('#BRpageview').height(); 1004 var oldPageViewWidth = $('#BRpageview').width(); 1005 1006 // May have come here after preparing the view, in which case the scrollTop and view height are not set 1007 1008 var scrollRatio = 0; 1009 if (oldScrollTop > 0) { 1010 // We have scrolled - implies view has been set up 1011 var oldCenterY = this.centerY1up(); 1012 var oldCenterX = this.centerX1up(); 1013 scrollRatio = oldCenterY / oldPageViewHeight; 1014 } else {
1015 // Have not scrolled, e.g. because in new container 1016 1017 // We set the scroll ratio so that the current index will still be considered the 1018 // current index in drawLeafsOnePage after we create the new view container 1019 1020 // Make sure this will count as current page after resize 1021 // console.log('fudging for index ' + this.currentIndex() + ' (page ' + this.getPageNum(this.currentIndex()) + ')'); 1022 var fudgeFactor = (this.getPageHeight(this.currentIndex()) / this.reduce) * 0.6; 1023 var oldLeafTop = this.onePageGetPageTop(this.currentIndex()) + fudgeFactor; 1024 var oldViewDimensions = this.onePageCalculateViewDimensions(this.reduce, this.padding); 1025 scrollRatio = oldLeafTop / oldViewDimensions.height; 1026 } 1027 1028 // Recalculate 1up reduction factors 1029 this.onePageCalculateReductionFactors( $('#BRcontainer').attr('clientWidth'), 1030 $('#BRcontainer').attr('clientHeight') ); 1031 // Update current reduce (if in autofit) 1032 if (this.onePage.autofit) { 1033 var reductionFactor = this.nextReduce(this.reduce, this.onePage.autofit, this.onePage.reductionFactors); 1034 this.reduce = reductionFactor.reduce; 1035 } 1036 1037 var viewDimensions = this.onePageCalculateViewDimensions(this.reduce, this.padding); 1038 $('#BRpageview').height(viewDimensions.height); 1039 $('#BRpageview').width(viewDimensions.width); 1040 1041 var newCenterY = scrollRatio*viewDimensions.height; 1042 var newTop = Math.max(0, Math.floor( newCenterY - $('#BRcontainer').height()/2 )); 1043 $('#BRcontainer').attr('scrollTop', newTop); 1044 1045 // We use clientWidth here to avoid miscalculating due to scroll bar 1046 var newCenterX = oldCenterX * (viewWidth / oldPageViewWidth); 1047 var newLeft = newCenterX - $('#BRcontainer').attr('clientWidth') / 2; 1048 newLeft = Math.max(newLeft, 0); 1049 $('#BRcontainer').attr('scrollLeft', newLeft); 1050 //console.log('oldCenterX ' + oldCenterX + ' newCenterX ' + newCenterX + ' newLeft ' + newLeft); 1051 1052 //this.centerPageView(); 1053 this.loadLeafs(); 1054 1055 this.removeSearchHilites(); 1056 this.updateSearchHilites(); 1057} 1058 1059// Calculate the dimensions for a one page view with images at the given reduce and padding 1060BookReader.prototype.onePageCalculateViewDimensions = function(reduce, padding) { 1061 var viewWidth = 0; 1062 var viewHeight = 0; 1063 for (i=0; i<this.numLeafs; i++) { 1064 viewHeight += parseInt(this._getPageHeight(i)/this.reduce) + this.padding; 1065 var width = parseInt(this._getPageWidth(i)/this.reduce); 1066 if (width>viewWidth) viewWidth=width; 1067 } 1068 return { width: viewWidth, height: viewHeight } 1069} 1070 1071// centerX1up() 1072//______________________________________________________________________________ 1073// Returns the current offset of the viewport center in scaled document coordinates. 1074BookReader.prototype.centerX1up = function() { 1075 var centerX; 1076 if ($('#BRpageview').width() < $('#BRcontainer').attr('clientWidth')) { // fully shown 1077 centerX = $('#BRpageview').width(); 1078 } else { 1079 centerX = $('#BRcontainer').attr('scrollLeft') + $('#BRcontainer').attr('clientWidth') / 2; 1080 } 1081 centerX = Math.floor(centerX); 1082 return centerX; 1083} 1084 1085// centerY1up() 1086//______________________________________________________________________________ 1087// Returns the current offset of the viewport center in scaled document coordinates. 1088BookReader.prototype.centerY1up = function() { 1089 var centerY = $('#BRcontainer').attr('scrollTop') + $('#BRcontainer').height() / 2; 1090 return Math.floor(centerY); 1091} 1092 1093// centerPageView() 1094//______________________________________________________________________________ 1095BookReader.prototype.centerPageView = function() { 1096 1097 var scrollWidth = $('#BRcontainer').attr('scrollWidth'); 1098 var clientWidth = $('#BRcontainer').attr('clientWidth'); 1099 //console.log('sW='+scrollWidth+' cW='+clientWidth); 1100 if (scrollWidth > clientWidth) { 1101 $('#BRcontainer').attr('scrollLeft', (scrollWidth-clientWidth)/2); 1102 } 1103 1104} 1105 1106// zoom2up(direction) 1107//______________________________________________________________________________ 1108BookReader.prototype.zoom2up = function(direction) { 1109 1110 // Hard stop autoplay 1111 this.stopFlipAnimations(); 1112 1113 // Recalculate autofit factors 1114 this.twoPageCalculateReductionFactors(); 1115 1116 // Get new zoom state 1117 var reductionFactor = this.nextReduce(this.reduce, direction, this.twoPage.reductionFactors); 1118 if ((this.reduce == reductionFactor.reduce) && (this.twoPage.autofit == reductionFactor.autofit)) { 1119 // Same zoom 1120 return; 1121 } 1122 this.twoPage.autofit = reductionFactor.autofit; 1123 this.reduce = reductionFactor.reduce; 1124 this.pageScale = this.reduce; // preserve current reduce 1125 1126 // Preserve view center position 1127 var oldCenter = this.twoPageGetViewCenter(); 1128 1129 // If zooming in, reload imgs. DOM elements will be removed by prepareTwoPageView 1130 // $$$ An improvement would be to use the low res image until the larger one is loaded. 1131 if (1 == direction) { 1132 for (var img in this.prefetchedImgs) { 1133 delete this.prefetchedImgs[img]; 1134 } 1135 } 1136 1137 // Prepare view with new center to minimize visual glitches 1138 this.prepareTwoPageView(oldCenter.percentageX, oldCenter.percentageY); 1139} 1140 1141BookReader.prototype.zoomThumb = function(direction) { 1142 var oldColumns = this.thumbColumns; 1143 switch (direction) { 1144 case -1: 1145 this.thumbColumns += 1; 1146 break; 1147 case 1: 1148 this.thumbColumns -= 1; 1149 break; 1150 } 1151 1152 // clamp 1153 if (this.thumbColumns < 2) { 1154 this.thumbColumns = 2; 1155 } else if (this.thumbColumns > 8) { 1156 this.thumbColumns = 8; 1157 } 1158 1159 if (this.thumbColumns != oldColumns) { 1160 this.prepareThumbnailView(); 1161 } 1162} 1163 1164// Returns the width per thumbnail to display the requested number of columns 1165// Note: #BRpageview must already exist since its width is used to calculate the 1166// thumbnail width
1167BookReader.prototype.getThumbnailWidth = function(thumbnailColumns) { 1168 var padding = (thumbnailColumns + 1) * this.thumbPadding; 1169 var width = ($('#BRpageview').width() - padding) / (thumbnailColumns + 0.5); // extra 0.5 is for some space at sides 1170 return parseInt(width); 1171} 1172 1173// quantizeReduce(reduce) 1174//______________________________________________________________________________ 1175// Quantizes the given reduction factor to closest power of two from set from 12.5% to 200% 1176BookReader.prototype.quantizeReduce = function(reduce, reductionFactors) { 1177 var quantized = reductionFactors[0].reduce; 1178 var distance = Math.abs(reduce - quantized); 1179 for (var i = 1; i < reductionFactors.length; i++) { 1180 newDistance = Math.abs(reduce - reductionFactors[i].reduce); 1181 if (newDistance < distance) { 1182 distance = newDistance; 1183 quantized = reductionFactors[i].reduce; 1184 } 1185 } 1186 1187 return quantized; 1188} 1189 1190// reductionFactors should be array of sorted reduction factors 1191// e.g. [ {reduce: 0.25, autofit: null}, {reduce: 0.3, autofit: 'width'}, {reduce: 1, autofit: null} ] 1192BookReader.prototype.nextReduce = function( currentReduce, direction, reductionFactors ) { 1193 1194 // XXX add 'closest', to replace quantize function 1195 1196 if (direction == 'in') { 1197 var newReduceIndex = 0; 1198 1199 for (var i = 1; i < reductionFactors.length; i++) { 1200 if (reductionFactors[i].reduce < currentReduce) { 1201 newReduceIndex = i; 1202 } 1203 } 1204 return reductionFactors[newReduceIndex]; 1205 1206 } else if (direction == 'out') { // zoom out 1207 var lastIndex = reductionFactors.length - 1; 1208 var newReduceIndex = lastIndex; 1209 1210 for (var i = lastIndex; i >= 0; i--) { 1211 if (reductionFactors[i].reduce > currentReduce) { 1212 newReduceIndex = i; 1213 } 1214 } 1215 return reductionFactors[newReduceIndex]; 1216 } 1217 1218 // Asked for specific autofit mode 1219 for (var i = 0; i < reductionFactors.length; i++) { 1220 if (reductionFactors[i].autofit == direction) { 1221 return reductionFactors[i]; 1222 } 1223 } 1224 1225 alert('Could not find reduction factor for direction ' + direction); 1226 return reductionFactors[0]; 1227 1228} 1229 1230BookReader.prototype._reduceSort = function(a, b) { 1231 return a.reduce - b.reduce; 1232} 1233 1234// jumpToPage() 1235//______________________________________________________________________________ 1236// Attempts to jump to page. Returns true if page could be found, false otherwise. 1237BookReader.prototype.jumpToPage = function(pageNum) { 1238 1239 var pageIndex; 1240 1241 // Check for special "leaf" 1242 var re = new RegExp('^leaf(\\d+)'); 1243 leafMatch = re.exec(pageNum); 1244 if (leafMatch) { 1245 console.log(leafMatch[1]); 1246 pageIndex = this.leafNumToIndex(parseInt(leafMatch[1],10)); 1247 if (pageIndex === null) { 1248 pageIndex = undefined; // to match return type of getPageIndex 1249 } 1250 1251 } else { 1252 pageIndex = this.getPageIndex(pageNum); 1253 } 1254 1255 if ('undefined' != typeof(pageIndex)) { 1256 var leafTop = 0; 1257 var h; 1258 this.jumpToIndex(pageIndex); 1259 $('#BRcontainer').attr('scrollTop', leafTop); 1260 return true; 1261 } 1262 1263 // Page not found 1264 return false; 1265} 1266 1267// jumpToIndex() 1268//______________________________________________________________________________ 1269BookReader.prototype.jumpToIndex = function(index, pageX, pageY) { 1270 1271 this.willChangeToIndex(index); 1272 1273 this.ttsStop(); 1274 1275 if (this.constMode2up == this.mode) { 1276 this.autoStop(); 1277 1278 // By checking against min/max we do nothing if requested index 1279 // is current 1280 if (index < Math.min(this.twoPage.currentIndexL, this.twoPage.currentIndexR)) { 1281 this.flipBackToIndex(index); 1282 } else if (index > Math.max(this.twoPage.currentIndexL, this.twoPage.currentIndexR)) { 1283 this.flipFwdToIndex(index); 1284 } 1285 1286 } else if (this.constModeThumb == this.mode) { 1287 var viewWidth = $('#BRcontainer').attr('scrollWidth') - 20; // width minus buffer 1288 var i; 1289 var leafWidth = 0; 1290 var leafHeight = 0;
1291 var rightPos = 0; 1292 var bottomPos = 0; 1293 var rowHeight = 0; 1294 var leafTop = 0; 1295 var leafIndex = 0; 1296 1297 for (i=0; i<(index+1); i++) { 1298 leafWidth = this.thumbWidth; 1299 if (rightPos + (leafWidth + this.thumbPadding) > viewWidth){ 1300 rightPos = 0; 1301 rowHeight = 0; 1302 leafIndex = 0; 1303 } 1304 1305 leafHeight = parseInt((this.getPageHeight(leafIndex)*this.thumbWidth)/this.getPageWidth(leafIndex), 10); 1306 if (leafHeight > rowHeight) { rowHeight = leafHeight; } 1307 if (leafIndex==0) { leafTop = bottomPos; } 1308 if (leafIndex==0) { bottomPos += this.thumbPadding + rowHeight; } 1309 rightPos += leafWidth + this.thumbPadding; 1310 leafIndex++; 1311 } 1312 this.firstIndex=index; 1313 if ($('#BRcontainer').attr('scrollTop') == leafTop) { 1314 this.loadLeafs(); 1315 } else { 1316 $('#BRcontainer').animate({scrollTop: leafTop },'fast'); 1317 } 1318 } else { 1319 // 1up 1320 var leafTop = this.onePageGetPageTop(index); 1321 1322 if (pageY) { 1323 //console.log('pageY ' + pageY); 1324 var offset = parseInt( (pageY) / this.reduce); 1325 offset -= $('#BRcontainer').attr('clientHeight') >> 1; 1326 //console.log( 'jumping to ' + leafTop + ' ' + offset); 1327 leafTop += offset; 1328 } else { 1329 // Show page just a little below the top 1330 leafTop -= this.padding / 2; 1331 } 1332 1333 if (pageX) { 1334 var offset = parseInt( (pageX) / this.reduce); 1335 offset -= $('#BRcontainer').attr('clientWidth') >> 1; 1336 leafLeft += offset; 1337 } else { 1338 // Preserve left position 1339 leafLeft = $('#BRcontainer').scrollLeft(); 1340 } 1341 1342 //$('#BRcontainer').attr('scrollTop', leafTop); 1343 $('#BRcontainer').animate({scrollTop: leafTop, scrollLeft: leafLeft },'fast'); 1344 } 1345} 1346 1347// switchMode() 1348//______________________________________________________________________________ 1349BookReader.prototype.switchMode = function(mode) { 1350 1351 if (mode == this.mode) { 1352 return; 1353 } 1354 1355 if (!this.canSwitchToMode(mode)) { 1356 return; 1357 } 1358 1359 this.autoStop(); 1360 this.ttsStop(); 1361 this.removeSearchHilites(); 1362 1363 this.mode = mode; 1364 //this.switchToolbarMode(mode); 1365 1366 // reinstate scale if moving from thumbnail view 1367 if (this.pageScale != this.reduce) { 1368 this.reduce = this.pageScale; 1369 } 1370 1371 // $$$ TODO preserve center of view when switching between mode 1372 // See https://bugs.edge.launchpad.net/gnubook/+bug/416682 1373 1374 // XXX maybe better to preserve zoom in each mode 1375 if (1 == mode) { 1376 this.onePageCalculateReductionFactors( $('#BRcontainer').attr('clientWidth'), $('#BRcontainer').attr('clientHeight')); 1377 this.reduce = this.quantizeReduce(this.reduce, this.onePage.reductionFactors); 1378 this.prepareOnePageView(); 1379 } else if (3 == mode) { 1380 this.reduce = this.quantizeReduce(this.reduce, this.reductionFactors); 1381 this.prepareThumbnailView(); 1382 } else { 1383 // $$$ why don't we save autofit? 1384 // this.twoPage.autofit = null; // Take zoom level from other mode 1385 this.twoPageCalculateReductionFactors(); 1386 this.reduce = this.quantizeReduce(this.reduce, this.twoPage.reductionFactors); 1387 this.prepareTwoPageView(); 1388 this.twoPageCenterView(0.5, 0.5); // $$$ TODO preserve center 1389 } 1390 1391} 1392 1393//prepareOnePageView() 1394//______________________________________________________________________________ 1395BookReader.prototype.prepareOnePageView = function() { 1396 1397 // var startLeaf = this.displayedIndices[0]; 1398 var startLeaf = this.currentIndex(); 1399 1400 $('#BRcontainer').empty(); 1401 $('#BRcontainer').css({ 1402 overflowY: 'scroll', 1403 overflowX: 'auto' 1404 }); 1405 1406 $("#BRcontainer").append("<div id='BRpageview'></div>"); 1407 1408 // Attaches to first child - child must be present 1409 $('#BRcontainer').dragscrollable(); 1410 this.bindGestures($('#BRcontainer')); 1411 1412 // $$$ keep select enabled for now since disabling it breaks keyboard 1413 // nav in FF 3.6 (https://bugs.edge.launchpad.net/bookreader/+bug/544666) 1414 // BookReader.util.disableSelect($('#BRpageview')); 1415 1416 this.resizePageView(); 1417 1418 this.jumpToIndex(startLeaf); 1419 this.displayedIndices = []; 1420 1421 this.drawLeafsOnePage(); 1422} 1423 1424//prepareThumbnailView() 1425//______________________________________________________________________________ 1426BookReader.prototype.prepareThumbnailView = function() { 1427 1428 $('#BRcontainer').empty(); 1429 $('#BRcontainer').css({ 1430 overflowY: 'scroll', 1431 overflowX: 'auto' 1432 }); 1433 1434 $("#BRcontainer").append("<div id='BRpageview'></div>"); 1435 1436 $('#BRcontainer').dragscrollable(); 1437 this.bindGestures($('#BRcontainer')); 1438 1439 // $$$ keep select enabled for now since disabling it breaks keyboard 1440 // nav in FF 3.6 (https://bugs.edge.launchpad.net/bookreader/+bug/544666) 1441 // BookReader.util.disableSelect($('#BRpageview')); 1442 1443 this.thumbWidth = this.getThumbnailWidth(this.thumbColumns); 1444 this.reduce = this.getPageWidth(0)/this.thumbWidth; 1445 1446 this.displayedRows = []; 1447 1448 // Draw leafs with current index directly in view (no animating to the index) 1449 this.drawLeafsThumbnail( this.currentIndex() ); 1450 1451} 1452 1453// prepareTwoPageView() 1454//______________________________________________________________________________ 1455// Some decisions about two page view: 1456// 1457// Both pages will be displayed at the same height, even if they were different physical/scanned 1458// sizes. This simplifies the animation (from a design as well as technical standpoint). We 1459// examine the page aspect ratios (in calculateSpreadSize) and use the page with the most "normal" 1460// aspect ratio to determine the height. 1461// 1462// The two page view div is resized to keep the middle of the book in the middle of the div 1463// even as the page sizes change. To e.g. keep the middle of the book in the middle of the BRcontent 1464// div requires adjusting the offset of BRtwpageview and/or scrolling in BRcontent. 1465BookReader.prototype.prepareTwoPageView = function(centerPercentageX, centerPercentageY) { 1466 $('#BRcontainer').empty(); 1467 $('#BRcontainer').css('overflow', 'auto'); 1468 1469 // We want to display two facing pages. We may be missing 1470 // one side of the spread because it is the first/last leaf, 1471 // foldouts, missing pages, etc 1472
1473 //var targetLeaf = this.displayedIndices[0]; 1474 var targetLeaf = this.firstIndex; 1475 1476 if (targetLeaf < this.firstDisplayableIndex()) { 1477 targetLeaf = this.firstDisplayableIndex(); 1478 } 1479 1480 if (targetLeaf > this.lastDisplayableIndex()) { 1481 targetLeaf = this.lastDisplayableIndex(); 1482 } 1483 1484 //this.twoPage.currentIndexL = null; 1485 //this.twoPage.currentIndexR = null; 1486 //this.pruneUnusedImgs(); 1487 1488 var currentSpreadIndices = this.getSpreadIndices(targetLeaf); 1489 this.twoPage.currentIndexL = currentSpreadIndices[0]; 1490 this.twoPage.currentIndexR = currentSpreadIndices[1]; 1491 this.firstIndex = this.twoPage.currentIndexL; 1492 1493 this.calculateSpreadSize(); //sets twoPage.width, twoPage.height and others 1494 1495 this.pruneUnusedImgs(); 1496 this.prefetch(); // Preload images or reload if scaling has changed 1497 1498 //console.dir(this.twoPage); 1499 1500 // Add the two page view 1501 // $$$ Can we get everything set up and then append? 1502 $('#BRcontainer').append('<div id="BRtwopageview"></div>'); 1503 1504 // Attaches to first child, so must come after we add the page view 1505 //$('#BRcontainer').dragscrollable(); 1506 this.bindGestures($('#BRcontainer')); 1507 1508 // $$$ calculate first then set 1509 $('#BRtwopageview').css( { 1510 height: this.twoPage.totalHeight + 'px', 1511 width: this.twoPage.totalWidth + 'px', 1512 position: 'absolute' 1513 }); 1514 1515 // If there will not be scrollbars (e.g. when zooming out) we center the book 1516 // since otherwise the book will be stuck off-center 1517 if (this.twoPage.totalWidth < $('#BRcontainer').attr('clientWidth')) { 1518 centerPercentageX = 0.5; 1519 } 1520 if (this.twoPage.totalHeight < $('#BRcontainer').attr('clientHeight')) { 1521 centerPercentageY = 0.5; 1522 } 1523 1524 this.twoPageCenterView(centerPercentageX, centerPercentageY); 1525 1526 this.twoPage.coverDiv = document.createElement('div'); 1527 $(this.twoPage.coverDiv).attr('id', 'BRbookcover').css({ 1528 width: this.twoPage.bookCoverDivWidth + 'px', 1529 height: this.twoPage.bookCoverDivHeight+'px', 1530 visibility: 'visible' 1531 }).appendTo('#BRtwopageview'); 1532 1533 this.leafEdgeR = document.createElement('div'); 1534 this.leafEdgeR.className = 'BRleafEdgeR'; 1535 $(this.leafEdgeR).css({ 1536 width: this.twoPage.leafEdgeWidthR + 'px', 1537 height: this.twoPage.height + 'px', 1538 left: this.twoPage.gutter+this.twoPage.scaledWR+'px', 1539 top: this.twoPage.bookCoverDivTop+this.twoPage.coverInternalPadding+'px' 1540 }).appendTo('#BRtwopageview'); 1541 1542 this.leafEdgeL = document.createElement('div'); 1543 this.leafEdgeL.className = 'BRleafEdgeL'; 1544 $(this.leafEdgeL).css({ 1545 width: this.twoPage.leafEdgeWidthL + 'px', 1546 height: this.twoPage.height + 'px', 1547 left: this.twoPage.bookCoverDivLeft+this.twoPage.coverInternalPadding+'px', 1548 top: this.twoPage.bookCoverDivTop+this.twoPage.coverInternalPadding+'px' 1549 }).appendTo('#BRtwopageview'); 1550 1551 div = document.createElement('div'); 1552 $(div).attr('id', 'BRgutter').css({ 1553 width: this.twoPage.bookSpineDivWidth+'px', 1554 height: this.twoPage.bookSpineDivHeight+'px', 1555 left: (this.twoPage.gutter - this.twoPage.bookSpineDivWidth*0.5)+'px', 1556 top: this.twoPage.bookSpineDivTop+'px' 1557 }).appendTo('#BRtwopageview'); 1558 1559 var self = this; // for closure 1560 1561 /* Flip areas no longer used 1562 this.twoPage.leftFlipArea = document.createElement('div'); 1563 this.twoPage.leftFlipArea.className = 'BRfliparea'; 1564 $(this.twoPage.leftFlipArea).attr('id', 'BRleftflip').css({ 1565 border: '0', 1566 width: this.twoPageFlipAreaWidth() + 'px', 1567 height: this.twoPageFlipAreaHeight() + 'px', 1568 position: 'absolute', 1569 left: this.twoPageLeftFlipAreaLeft() + 'px', 1570 top: this.twoPageFlipAreaTop() + 'px', 1571 cursor: 'w-resize', 1572 zIndex: 100 1573 }).click(function(e) { 1574 self.left(); 1575 }).bind('mousedown', function(e) { 1576 e.preventDefault(); 1577 }).appendTo('#BRtwopageview'); 1578 1579 this.twoPage.rightFlipArea = document.createElement('div'); 1580 this.twoPage.rightFlipArea.className = 'BRfliparea'; 1581 $(this.twoPage.rightFlipArea).attr('id', 'BRrightflip').css({ 1582 border: '0', 1583 width: this.twoPageFlipAreaWidth() + 'px', 1584 height: this.twoPageFlipAreaHeight() + 'px', 1585 position: 'absolute', 1586 left: this.twoPageRightFlipAreaLeft() + 'px', 1587 top: this.twoPageFlipAreaTop() + 'px', 1588 cursor: 'e-resize', 1589 zIndex: 100 1590 }).click(function(e) { 1591 self.right(); 1592 }).bind('mousedown', function(e) { 1593 e.preventDefault(); 1594 }).appendTo('#BRtwopageview'); 1595 */ 1596 1597 this.prepareTwoPagePopUp(); 1598 1599 this.displayedIndices = []; 1600 1601 //this.indicesToDisplay=[firstLeaf, firstLeaf+1]; 1602 //console.log('indicesToDisplay: ' + this.indicesToDisplay[0] + ' ' + this.indicesToDisplay[1]); 1603 1604 this.drawLeafsTwoPage(); 1605 this.updateToolbarZoom(this.reduce); 1606 1607 this.prefetch(); 1608 1609 this.removeSearchHilites(); 1610 this.updateSearchHilites(); 1611 1612} 1613 1614// prepareTwoPagePopUp() 1615// 1616// This function prepares the "View Page n" popup that shows while the mouse is 1617// over the left/right "stack of sheets" edges. It also binds the mouse 1618// events for these divs. 1619//______________________________________________________________________________ 1620BookReader.prototype.prepareTwoPagePopUp = function() { 1621 1622 this.twoPagePopUp = document.createElement('div'); 1623 this.twoPagePopUp.className = 'BRtwoPagePopUp'; 1624 $(this.twoPagePopUp).css({ 1625 zIndex: '1000' 1626 }).appendTo('#BRcontainer'); 1627 $(this.twoPagePopUp).hide(); 1628 1629 $(this.leafEdgeL).add(this.leafEdgeR).bind('mouseenter', this, function(e) { 1630 $(e.data.twoPagePopUp).show(); 1631 }); 1632 1633 $(this.leafEdgeL).add(this.leafEdgeR).bind('mouseleave', this, function(e) { 1634 $(e.data.twoPagePopUp).hide(); 1635 }); 1636 1637 $(this.leafEdgeL).bind('click', this, function(e) { 1638 e.data.autoStop(); 1639 e.data.ttsStop(); 1640 var jumpIndex = e.data.jumpIndexForLeftEdgePageX(e.pageX); 1641 e.data.jumpToIndex(jumpIndex); 1642 }); 1643 1644 $(this.leafEdgeR).bind('click', this, function(e) { 1645 e.data.autoStop(); 1646 e.data.ttsStop(); 1647 var jumpIndex = e.data.jumpIndexForRightEdgePageX(e.pageX); 1648 e.data.jumpToIndex(jumpIndex); 1649 }); 1650 1651 $(this.leafEdgeR).bind('mousemove', this, function(e) { 1652 1653 var jumpIndex = e.data.jumpIndexForRightEdgePageX(e.pageX); 1654 $(e.data.twoPagePopUp).text('View ' + e.data.getPageName(jumpIndex)); 1655
1656 // $$$ TODO: Make sure popup is positioned so that it is in view 1657 // (https://bugs.edge.launchpad.net/gnubook/+bug/327456) 1658 $(e.data.twoPagePopUp).css({ 1659 left: e.pageX- $('#BRcontainer').offset().left + $('#BRcontainer').scrollLeft() - 100 + 'px', 1660 top: e.pageY - $('#BRcontainer').offset().top + $('#BRcontainer').scrollTop() + 'px' 1661 }); 1662 }); 1663 1664 $(this.leafEdgeL).bind('mousemove', this, function(e) { 1665 1666 var jumpIndex = e.data.jumpIndexForLeftEdgePageX(e.pageX); 1667 $(e.data.twoPagePopUp).text('View '+ e.data.getPageName(jumpIndex)); 1668 1669 // $$$ TODO: Make sure popup is positioned so that it is in view 1670 // (https://bugs.edge.launchpad.net/gnubook/+bug/327456) 1671 $(e.data.twoPagePopUp).css({ 1672 left: e.pageX - $('#BRcontainer').offset().left + $('#BRcontainer').scrollLeft() - $(e.data.twoPagePopUp).width() + 100 + 'px', 1673 top: e.pageY-$('#BRcontainer').offset().top + $('#BRcontainer').scrollTop() + 'px' 1674 }); 1675 }); 1676} 1677 1678// calculateSpreadSize() 1679//______________________________________________________________________________ 1680// Calculates 2-page spread dimensions based on this.twoPage.currentIndexL and 1681// this.twoPage.currentIndexR 1682// This function sets this.twoPage.height, twoPage.width 1683 1684BookReader.prototype.calculateSpreadSize = function() { 1685 1686 var firstIndex = this.twoPage.currentIndexL; 1687 var secondIndex = this.twoPage.currentIndexR; 1688 //console.log('first page is ' + firstIndex); 1689 1690 // Calculate page sizes and total leaf width 1691 var spreadSize; 1692 if ( this.twoPage.autofit) { 1693 spreadSize = this.getIdealSpreadSize(firstIndex, secondIndex); 1694 } else { 1695 // set based on reduction factor 1696 spreadSize = this.getSpreadSizeFromReduce(firstIndex, secondIndex, this.reduce); 1697 } 1698 1699 // Both pages together 1700 this.twoPage.height = spreadSize.height; 1701 this.twoPage.width = spreadSize.width; 1702 1703 // Individual pages 1704 this.twoPage.scaledWL = this.getPageWidth2UP(firstIndex); 1705 this.twoPage.scaledWR = this.getPageWidth2UP(secondIndex); 1706 1707 // Leaf edges 1708 this.twoPage.edgeWidth = spreadSize.totalLeafEdgeWidth; // The combined width of both edges
1709 this.twoPage.leafEdgeWidthL = this.leafEdgeWidth(this.twoPage.currentIndexL); 1710 this.twoPage.leafEdgeWidthR = this.twoPage.edgeWidth - this.twoPage.leafEdgeWidthL; 1711 1712 1713 // Book cover 1714 // The width of the book cover div. The combined width of both pages, twice the width 1715 // of the book cover internal padding (2*10) and the page edges 1716 this.twoPage.bookCoverDivWidth = this.twoPageCoverWidth(this.twoPage.scaledWL + this.twoPage.scaledWR); 1717 // The height of the book cover div 1718 this.twoPage.bookCoverDivHeight = this.twoPage.height + 2 * this.twoPage.coverInternalPadding; 1719 1720 1721 // We calculate the total width and height for the div so that we can make the book 1722 // spine centered 1723 var leftGutterOffset = this.gutterOffsetForIndex(firstIndex); 1724 var leftWidthFromCenter = this.twoPage.scaledWL - leftGutterOffset + this.twoPage.leafEdgeWidthL; 1725 var rightWidthFromCenter = this.twoPage.scaledWR + leftGutterOffset + this.twoPage.leafEdgeWidthR; 1726 var largestWidthFromCenter = Math.max( leftWidthFromCenter, rightWidthFromCenter ); 1727 this.twoPage.totalWidth = 2 * (largestWidthFromCenter + this.twoPage.coverInternalPadding + this.twoPage.coverExternalPadding); 1728 this.twoPage.totalHeight = this.twoPage.height + 2 * (this.twoPage.coverInternalPadding + this.twoPage.coverExternalPadding); 1729 1730 // We want to minimize the unused space in two-up mode (maximize the amount of page 1731 // shown). We give width to the leaf edges and these widths change (though the sum 1732 // of the two remains constant) as we flip through the book. With the book 1733 // cover centered and fixed in the BRcontainer div the page images will meet 1734 // at the "gutter" which is generally offset from the center. 1735 this.twoPage.middle = this.twoPage.totalWidth >> 1; 1736 this.twoPage.gutter = this.twoPage.middle + this.gutterOffsetForIndex(firstIndex); 1737 1738 // The left edge of the book cover moves depending on the width of the pages 1739 // $$$ change to getter 1740 this.twoPage.bookCoverDivLeft = this.twoPage.gutter - this.twoPage.scaledWL - this.twoPage.leafEdgeWidthL - this.twoPage.coverInternalPadding; 1741 // The top edge of the book cover stays a fixed distance from the top 1742 this.twoPage.bookCoverDivTop = this.twoPage.coverExternalPadding; 1743 1744 // Book spine 1745 this.twoPage.bookSpineDivHeight = this.twoPage.height + 2*this.twoPage.coverInternalPadding; 1746 this.twoPage.bookSpineDivLeft = this.twoPage.middle - (this.twoPage.bookSpineDivWidth >> 1); 1747 this.twoPage.bookSpineDivTop = this.twoPage.bookCoverDivTop; 1748 1749 1750 this.reduce = spreadSize.reduce; // $$$ really set this here? 1751} 1752 1753BookReader.prototype.getIdealSpreadSize = function(firstIndex, secondIndex) { 1754 var ideal = {}; 1755 1756 // We check which page is closest to a "normal" page and use that to set the height 1757 // for both pages. This means that foldouts and other odd size pages will be displayed 1758 // smaller than the nominal zoom amount. 1759 var canon5Dratio = 1.5; 1760 1761 var first = { 1762 height: this._getPageHeight(firstIndex), 1763 width: this._getPageWidth(firstIndex) 1764 } 1765 1766 var second = { 1767 height: this._getPageHeight(secondIndex), 1768 width: this._getPageWidth(secondIndex) 1769 } 1770 1771 var firstIndexRatio = first.height / first.width; 1772 var secondIndexRatio = second.height / second.width; 1773 //console.log('firstIndexRatio = ' + firstIndexRatio + ' secondIndexRatio = ' + secondIndexRatio); 1774 1775 var ratio; 1776 if (Math.abs(firstIndexRatio - canon5Dratio) < Math.abs(secondIndexRatio - canon5Dratio)) { 1777 ratio = firstIndexRatio; 1778 } else { 1779 ratio = secondIndexRatio; 1780 } 1781 1782 var totalLeafEdgeWidth = parseInt(this.numLeafs * 0.1); 1783 var maxLeafEdgeWidth = parseInt($('#BRcontainer').attr('clientWidth') * 0.1); 1784 ideal.totalLeafEdgeWidth = Math.min(totalLeafEdgeWidth, maxLeafEdgeWidth); 1785 1786 var widthOutsidePages = 2 * (this.twoPage.coverInternalPadding + this.twoPage.coverExternalPadding) + ideal.totalLeafEdgeWidth; 1787 var heightOutsidePages = 2* (this.twoPage.coverInternalPadding + this.twoPage.coverExternalPadding); 1788 1789 ideal.width = ($('#BRcontainer').width() - widthOutsidePages) >> 1; 1790 ideal.width -= 10; // $$$ fudge factor 1791 ideal.height = $('#BRcontainer').height() - heightOutsidePages; 1792 ideal.height -= 20; // fudge factor 1793 //console.log('init idealWidth='+ideal.width+' idealHeight='+ideal.height + ' ratio='+ratio); 1794 1795 if (ideal.height/ratio <= ideal.width) { 1796 //use height 1797 ideal.width = parseInt(ideal.height/ratio); 1798 } else { 1799 //use width 1800 ideal.height = parseInt(ideal.width*ratio); 1801 } 1802 1803 // $$$ check this logic with large spreads
1804 ideal.reduce = ((first.height + second.height) / 2) / ideal.height; 1805 1806 return ideal; 1807} 1808 1809// getSpreadSizeFromReduce() 1810//______________________________________________________________________________ 1811// Returns the spread size calculated from the reduction factor for the given pages 1812BookReader.prototype.getSpreadSizeFromReduce = function(firstIndex, secondIndex, reduce) { 1813 var spreadSize = {}; 1814 // $$$ Scale this based on reduce? 1815 var totalLeafEdgeWidth = parseInt(this.numLeafs * 0.1); 1816 var maxLeafEdgeWidth = parseInt($('#BRcontainer').attr('clientWidth') * 0.1); // $$$ Assumes leaf edge width constant at all zoom levels 1817 spreadSize.totalLeafEdgeWidth = Math.min(totalLeafEdgeWidth, maxLeafEdgeWidth); 1818 1819 // $$$ Possibly incorrect -- we should make height "dominant" 1820 var nativeWidth = this._getPageWidth(firstIndex) + this._getPageWidth(secondIndex); 1821 var nativeHeight = this._getPageHeight(firstIndex) + this._getPageHeight(secondIndex); 1822 spreadSize.height = parseInt( (nativeHeight / 2) / this.reduce ); 1823 spreadSize.width = parseInt( (nativeWidth / 2) / this.reduce ); 1824 spreadSize.reduce = reduce; 1825 1826 return spreadSize; 1827} 1828 1829// twoPageGetAutofitReduce() 1830//______________________________________________________________________________ 1831// Returns the current ideal reduction factor 1832BookReader.prototype.twoPageGetAutofitReduce = function() { 1833 var spreadSize = this.getIdealSpreadSize(this.twoPage.currentIndexL, this.twoPage.currentIndexR); 1834 return spreadSize.reduce; 1835} 1836 1837// twoPageIsZoomedIn 1838//______________________________________________________________________________ 1839// Returns true if the pages extend past the edge of the view 1840BookReader.prototype.twoPageIsZoomedIn = function() { 1841 var autofitReduce = this.twoPageGetAutofitReduce(); 1842 var isZoomedIn = false; 1843 if (this.twoPage.autofit != 'auto') { 1844 if (this.reduce < this.twoPageGetAutofitReduce()) { 1845 isZoomedIn = true; 1846 } 1847 } 1848 return isZoomedIn; 1849} 1850 1851BookReader.prototype.onePageGetAutofitWidth = function() { 1852 var widthPadding = 20; 1853 return (this.getMedianPageSize().width + 0.0) / ($('#BRcontainer').attr('clientWidth') - widthPadding * 2); 1854} 1855 1856BookReader.prototype.onePageGetAutofitHeight = function() { 1857 return (this.getMedianPageSize().height + 0.0) / ($('#BRcontainer').attr('clientHeight') - this.padding * 2); // make sure a little of adjacent pages show 1858} 1859 1860// Returns where the top of the page with given index should be in one page view 1861BookReader.prototype.onePageGetPageTop = function(index) 1862{ 1863 var i; 1864 var leafTop = 0; 1865 var leafLeft = 0; 1866 var h; 1867 for (i=0; i<index; i++) { 1868 h = parseInt(this._getPageHeight(i)/this.reduce); 1869 leafTop += h + this.padding; 1870 } 1871 return leafTop; 1872} 1873 1874BookReader.prototype.getMedianPageSize = function() { 1875 if (this._medianPageSize) { 1876 return this._medianPageSize; 1877 } 1878 1879 // A little expensive but we just do it once 1880 var widths = []; 1881 var heights = []; 1882 for (var i = 0; i < this.numLeafs; i++) { 1883 widths.push(this.getPageWidth(i)); 1884 heights.push(this.getPageHeight(i)); 1885 } 1886 1887 widths.sort(); 1888 heights.sort(); 1889 1890 this._medianPageSize = { width: widths[parseInt(widths.length / 2)], height: heights[parseInt(heights.length / 2)] }; 1891 return this._medianPageSize; 1892} 1893 1894// Update the reduction factors for 1up mode given the available width and height. Recalculates 1895// the autofit reduction factors. 1896BookReader.prototype.onePageCalculateReductionFactors = function( width, height ) { 1897 this.onePage.reductionFactors = this.reductionFactors.concat( 1898 [ 1899 { reduce: this.onePageGetAutofitWidth(), autofit: 'width' }, 1900 { reduce: this.onePageGetAutofitHeight(), autofit: 'height'} 1901 ]); 1902 this.onePage.reductionFactors.sort(this._reduceSort); 1903} 1904 1905BookReader.prototype.twoPageCalculateReductionFactors = function() { 1906 this.twoPage.reductionFactors = this.reductionFactors.concat( 1907 [ 1908 { reduce: this.getIdealSpreadSize( this.twoPage.currentIndexL, this.twoPage.currentIndexR ).reduce, 1909 autofit: 'auto' } 1910 ]); 1911 this.twoPage.reductionFactors.sort(this._reduceSort); 1912} 1913 1914// twoPageSetCursor() 1915//______________________________________________________________________________ 1916// Set the cursor for two page view 1917BookReader.prototype.twoPageSetCursor = function() { 1918 // console.log('setting cursor'); 1919 if ( ($('#BRtwopageview').width() > $('#BRcontainer').attr('clientWidth')) || 1920 ($('#BRtwopageview').height() > $('#BRcontainer').attr('clientHeight')) ) { 1921 $(this.prefetchedImgs[this.twoPage.currentIndexL]).css('cursor','move'); 1922 $(this.prefetchedImgs[this.twoPage.currentIndexR]).css('cursor','move'); 1923 } else { 1924 $(this.prefetchedImgs[this.twoPage.currentIndexL]).css('cursor',''); 1925 $(this.prefetchedImgs[this.twoPage.currentIndexR]).css('cursor',''); 1926 } 1927} 1928 1929// currentIndex() 1930//______________________________________________________________________________ 1931// Returns the currently active index. 1932BookReader.prototype.currentIndex = function() { 1933 // $$$ we should be cleaner with our idea of which index is active in 1up/2up 1934 if (this.mode == this.constMode1up || this.mode == this.constModeThumb) { 1935 return this.firstIndex;
1935 // $$$ TODO page in center of view would be better 1936 } else if (this.mode == this.constMode2up) { 1937 // Only allow indices that are actually present in book 1938 return BookReader.util.clamp(this.firstIndex, 0, this.numLeafs - 1); 1939 } else { 1940 throw 'currentIndex called for unimplemented mode ' + this.mode; 1941 } 1942} 1943 1944// setCurrentIndex(index) 1945//______________________________________________________________________________ 1946// Sets the idea of current index without triggering other actions such as animation. 1947// Compare to jumpToIndex which animates to that index 1948BookReader.prototype.setCurrentIndex = function(index) { 1949 this.firstIndex = index; 1950} 1951 1952 1953// right() 1954//______________________________________________________________________________ 1955// Flip the right page over onto the left 1956BookReader.prototype.right = function() { 1957 if ('rl' != this.pageProgression) { 1958 // LTR 1959 this.next(); 1960 } else { 1961 // RTL 1962 this.prev(); 1963 } 1964} 1965 1966// rightmost() 1967//______________________________________________________________________________ 1968// Flip to the rightmost page 1969BookReader.prototype.rightmost = function() { 1970 if ('rl' != this.pageProgression) { 1971 this.last(); 1972 } else { 1973 this.first(); 1974 } 1975} 1976 1977// left() 1978//______________________________________________________________________________ 1979// Flip the left page over onto the right. 1980BookReader.prototype.left = function() { 1981 if ('rl' != this.pageProgression) { 1982 // LTR 1983 this.prev(); 1984 } else { 1985 // RTL 1986 this.next(); 1987 } 1988} 1989 1990// leftmost() 1991//______________________________________________________________________________ 1992// Flip to the leftmost page 1993BookReader.prototype.leftmost = function() { 1994 if ('rl' != this.pageProgression) { 1995 this.first(); 1996 } else { 1997 this.last(); 1998 } 1999} 2000 2001// next() 2002//______________________________________________________________________________ 2003BookReader.prototype.next = function() { 2004 if (2 == this.mode) { 2005 this.autoStop(); 2006 this.flipFwdToIndex(null); 2007 } else { 2008 if (this.firstIndex < this.lastDisplayableIndex()) { 2009 this.jumpToIndex(this.firstIndex+1); 2010 } 2011 } 2012} 2013 2014// prev() 2015//______________________________________________________________________________ 2016BookReader.prototype.prev = function() { 2017 if (2 == this.mode) { 2018 this.autoStop(); 2019 this.flipBackToIndex(null); 2020 } else { 2021 if (this.firstIndex >= 1) { 2022 this.jumpToIndex(this.firstIndex-1); 2023 } 2024 } 2025} 2026 2027BookReader.prototype.first = function() { 2028 this.jumpToIndex(this.firstDisplayableIndex()); 2029} 2030 2031BookReader.prototype.last = function() { 2032 this.jumpToIndex(this.lastDisplayableIndex()); 2033} 2034 2035// scrollDown() 2036//______________________________________________________________________________ 2037// Scrolls down one screen view 2038BookReader.prototype.scrollDown = function() { 2039 if ($.inArray(this.mode, [this.constMode1up, this.constModeThumb]) >= 0) { 2040 if ( this.mode == this.constMode1up && (this.reduce >= this.onePageGetAutofitHeight()) ) { 2041 // Whole pages are visible, scroll whole page only 2042 return this.next(); 2043 } 2044 2045 $('#BRcontainer').animate( 2046 { scrollTop: '+=' + this._scrollAmount() + 'px'}, 2047 400, 'easeInOutExpo' 2048 ); 2049 return true; 2050 } else { 2051 return false; 2052 } 2053} 2054 2055// scrollUp() 2056//______________________________________________________________________________ 2057// Scrolls up one screen view 2058BookReader.prototype.scrollUp = function() { 2059 if ($.inArray(this.mode, [this.constMode1up, this.constModeThumb]) >= 0) { 2060 if ( this.mode == this.constMode1up && (this.reduce >= this.onePageGetAutofitHeight()) ) { 2061 // Whole pages are visible, scroll whole page only 2062 return this.prev(); 2063 } 2064 2065 $('#BRcontainer').animate( 2066 { scrollTop: '-=' + this._scrollAmount() + 'px'}, 2067 400, 'easeInOutExpo' 2068 ); 2069 return true; 2070 } else { 2071 return false; 2072 } 2073} 2074 2075// _scrollAmount() 2076//______________________________________________________________________________ 2077// The amount to scroll vertically in integer pixels 2078BookReader.prototype._scrollAmount = function() { 2079 if (this.constMode1up == this.mode) { 2080 // Overlap by % of page size 2081 return parseInt($('#BRcontainer').attr('clientHeight') - this.getPageHeight(this.currentIndex()) / this.reduce * 0.03); 2082 } 2083 2084 return parseInt(0.9 * $('#BRcontainer').attr('clientHeight')); 2085} 2086 2087
2088// flipBackToIndex() 2089//______________________________________________________________________________ 2090// to flip back one spread, pass index=null 2091BookReader.prototype.flipBackToIndex = function(index) { 2092 2093 if (1 == this.mode) return; 2094 2095 var leftIndex = this.twoPage.currentIndexL; 2096 2097 if (this.animating) return; 2098 2099 if (null != this.leafEdgeTmp) { 2100 alert('error: leafEdgeTmp should be null!'); 2101 return; 2102 } 2103 2104 if (null == index) { 2105 index = leftIndex-2; 2106 } 2107 //if (index<0) return; 2108 2109 this.willChangeToIndex(index); 2110 2111 var previousIndices = this.getSpreadIndices(index); 2112 2113 if (previousIndices[0] < this.firstDisplayableIndex() || previousIndices[1] < this.firstDisplayableIndex()) { 2114 return; 2115 } 2116 2117 this.animating = true; 2118 2119 if ('rl' != this.pageProgression) { 2120 // Assume LTR and we are going backward 2121 this.prepareFlipLeftToRight(previousIndices[0], previousIndices[1]); 2122 this.flipLeftToRight(previousIndices[0], previousIndices[1]); 2123 } else { 2124 // RTL and going backward 2125 var gutter = this.prepareFlipRightToLeft(previousIndices[0], previousIndices[1]); 2126 this.flipRightToLeft(previousIndices[0], previousIndices[1], gutter); 2127 } 2128} 2129 2130// flipLeftToRight() 2131//______________________________________________________________________________ 2132// Flips the page on the left towards the page on the right 2133BookReader.prototype.flipLeftToRight = function(newIndexL, newIndexR) { 2134 2135 var leftLeaf = this.twoPage.currentIndexL; 2136
2137 var oldLeafEdgeWidthL = this.leafEdgeWidth(this.twoPage.currentIndexL); 2138 var newLeafEdgeWidthL = this.leafEdgeWidth(newIndexL); 2139 var leafEdgeTmpW = oldLeafEdgeWidthL - newLeafEdgeWidthL; 2140 2141 var currWidthL = this.getPageWidth2UP(leftLeaf); 2142 var newWidthL = this.getPageWidth2UP(newIndexL); 2143 var newWidthR = this.getPageWidth2UP(newIndexR); 2144 2145 var top = this.twoPageTop(); 2146 var gutter = this.twoPage.middle + this.gutterOffsetForIndex(newIndexL); 2147 2148 //console.log('leftEdgeTmpW ' + leafEdgeTmpW); 2149 //console.log(' gutter ' + gutter + ', scaledWL ' + scaledWL + ', newLeafEdgeWL ' + newLeafEdgeWidthL); 2150 2151 //animation strategy: 2152 // 0. remove search highlight, if any. 2153 // 1. create a new div, called leafEdgeTmp to represent the leaf edge between the leftmost edge 2154 // of the left leaf and where the user clicked in the leaf edge. 2155 // Note that if this function was triggered by left() and not a 2156 // mouse click, the width of leafEdgeTmp is very small (zero px). 2157 // 2. animate both leafEdgeTmp to the gutter (without changing its width) and animate 2158 // leftLeaf to width=0. 2159 // 3. When step 2 is finished, animate leafEdgeTmp to right-hand side of new right leaf 2160 // (left=gutter+newWidthR) while also animating the new right leaf from width=0 to 2161 // its new full width. 2162 // 4. After step 3 is finished, do the following: 2163 // - remove leafEdgeTmp from the dom. 2164 // - resize and move the right leaf edge (leafEdgeR) to left=gutter+newWidthR 2165 // and width=twoPage.edgeWidth-newLeafEdgeWidthL. 2166 // - resize and move the left leaf edge (leafEdgeL) to left=gutter-newWidthL-newLeafEdgeWidthL 2167 // and width=newLeafEdgeWidthL. 2168 // - resize the back cover (twoPage.coverDiv) to left=gutter-newWidthL-newLeafEdgeWidthL-10 2169 // and width=newWidthL+newWidthR+twoPage.edgeWidth+20 2170 // - move new left leaf (newIndexL) forward to zindex=2 so it can receive clicks. 2171 // - remove old left and right leafs from the dom [pruneUnusedImgs()]. 2172 // - prefetch new adjacent leafs. 2173 // - set up click handlers for both new left and right leafs. 2174 // - redraw the search highlight. 2175 // - update the pagenum box and the url. 2176 2177 2178 var leftEdgeTmpLeft = gutter - currWidthL - leafEdgeTmpW; 2179 2180 this.leafEdgeTmp = document.createElement('div'); 2181 this.leafEdgeTmp.className = 'BRleafEdgeTmp'; 2182 $(this.leafEdgeTmp).css({ 2183 width: leafEdgeTmpW + 'px', 2184 height: this.twoPage.height + 'px', 2185 left: leftEdgeTmpLeft + 'px', 2186 top: top+'px', 2187 zIndex:1000 2188 }).appendTo('#BRtwopageview'); 2189 2190 //$(this.leafEdgeL).css('width', newLeafEdgeWidthL+'px'); 2191 $(this.leafEdgeL).css({ 2192 width: newLeafEdgeWidthL+'px', 2193 left: gutter-currWidthL-newLeafEdgeWidthL+'px' 2194 }); 2195 2196 // Left gets the offset of the current left leaf from the document 2197 var left = $(this.prefetchedImgs[leftLeaf]).offset().left; 2198 // $$$ This seems very similar to the gutter. May be able to consolidate the logic. 2199 var right = $('#BRtwopageview').attr('clientWidth')-left-$(this.prefetchedImgs[leftLeaf]).width()+$('#BRtwopageview').offset().left-2+'px'; 2200 2201 // We change the left leaf to right positioning 2202 // $$$ This causes animation glitches during resize. See https://bugs.edge.launchpad.net/gnubook/+bug/328327 2203 $(this.prefetchedImgs[leftLeaf]).css({ 2204 right: right, 2205 left: '' 2206 }); 2207 2208 $(this.leafEdgeTmp).animate({left: gutter}, this.flipSpeed, 'easeInSine'); 2209 //$(this.prefetchedImgs[leftLeaf]).animate({width: '0px'}, 'slow', 'easeInSine'); 2210 2211 var self = this; 2212 2213 this.removeSearchHilites(); 2214 2215 //console.log('animating leafLeaf ' + leftLeaf + ' to 0px'); 2216 $(this.prefetchedImgs[leftLeaf]).animate({width: '0px'}, self.flipSpeed, 'easeInSine', function() { 2217 2218 //console.log(' and now leafEdgeTmp to left: gutter+newWidthR ' + (gutter + newWidthR)); 2219 $(self.leafEdgeTmp).animate({left: gutter+newWidthR+'px'}, self.flipSpeed, 'easeOutSine'); 2220 2221 $('#BRgutter').css({left: (gutter - self.twoPage.bookSpineDivWidth*0.5)+'px'}); 2222 2223 //console.log(' animating newIndexR ' + newIndexR + ' to ' + newWidthR + ' from ' + $(self.prefetchedImgs[newIndexR]).width()); 2224 $(self.prefetchedImgs[newIndexR]).animate({width: newWidthR+'px'}, self.flipSpeed, 'easeOutSine', function() { 2225 $(self.prefetchedImgs[newIndexL]).css('zIndex', 2); 2226 2227 //jquery adds display:block to the element style, which interferes with our print css 2228 $(self.prefetchedImgs[newIndexL]).css('display', ''); 2229 $(self.prefetchedImgs[newIndexR]).css('display', ''); 2230 2231 $(self.leafEdgeR).css({ 2232 // Moves the right leaf edge 2233 width: self.twoPage.edgeWidth-newLeafEdgeWidthL+'px', 2234 left: gutter+newWidthR+'px' 2235 }); 2236 2237 $(self.leafEdgeL).css({ 2238 // Moves and resizes the left leaf edge 2239 width: newLeafEdgeWidthL+'px', 2240 left: gutter-newWidthL-newLeafEdgeWidthL+'px' 2241 }); 2242 2243 // Resizes the brown border div 2244 $(self.twoPage.coverDiv).css({ 2245 width: self.twoPageCoverWidth(newWidthL+newWidthR)+'px', 2246 left: gutter-newWidthL-newLeafEdgeWidthL-self.twoPage.coverInternalPadding+'px' 2247 }); 2248 2249 $(self.leafEdgeTmp).remove(); 2250 self.leafEdgeTmp = null; 2251 2252 // $$$ TODO refactor with opposite direction flip 2253 2254 self.twoPage.currentIndexL = newIndexL; 2255 self.twoPage.currentIndexR = newIndexR; 2256 self.twoPage.scaledWL = newWidthL; 2257 self.twoPage.scaledWR = newWidthR; 2258 self.twoPage.gutter = gutter; 2259 2260 self.firstIndex = self.twoPage.currentIndexL;
2261 self.displayedIndices = [newIndexL, newIndexR]; 2262 self.pruneUnusedImgs(); 2263 self.prefetch(); 2264 self.animating = false; 2265 2266 self.updateSearchHilites2UP(); 2267 self.updatePageNumBox2UP(); 2268 2269 // self.twoPagePlaceFlipAreas(); // No longer used 2270 self.setMouseHandlers2UP(); 2271 self.twoPageSetCursor(); 2272 2273 if (self.animationFinishedCallback) { 2274 self.animationFinishedCallback(); 2275 self.animationFinishedCallback = null; 2276 } 2277 }); 2278 }); 2279 2280} 2281 2282// flipFwdToIndex() 2283//______________________________________________________________________________ 2284// Whether we flip left or right is dependent on the page progression 2285// to flip forward one spread, pass index=null 2286BookReader.prototype.flipFwdToIndex = function(index) { 2287 2288 if (this.animating) return; 2289 2290 if (null != this.leafEdgeTmp) { 2291 alert('error: leafEdgeTmp should be null!'); 2292 return; 2293 } 2294 2295 if (null == index) { 2296 index = this.twoPage.currentIndexR+2; // $$$ assumes indices are continuous 2297 } 2298 if (index > this.lastDisplayableIndex()) return; 2299 2300 this.willChangeToIndex(index); 2301 2302 this.animating = true; 2303 2304 var nextIndices = this.getSpreadIndices(index); 2305 2306 //console.log('flipfwd to indices ' + nextIndices[0] + ',' + nextIndices[1]); 2307 2308 if ('rl' != this.pageProgression) { 2309 // We did not specify RTL 2310 var gutter = this.prepareFlipRightToLeft(nextIndices[0], nextIndices[1]); 2311 this.flipRightToLeft(nextIndices[0], nextIndices[1], gutter); 2312 } else { 2313 // RTL 2314 var gutter = this.prepareFlipLeftToRight(nextIndices[0], nextIndices[1]); 2315 this.flipLeftToRight(nextIndices[0], nextIndices[1]); 2316 } 2317} 2318 2319/* 2320 * Put handlers here for when we will navigate to a new page 2321 */ 2322BookReader.prototype.willChangeToIndex = function(index) 2323{ 2324 // Update navbar position icon - leads page change animation 2325 this.updateNavIndex(index); 2326} 2327 2328// flipRightToLeft(nextL, nextR, gutter) 2329// $$$ better not to have to pass gutter in 2330//______________________________________________________________________________ 2331// Flip from left to right and show the nextL and nextR indices on those sides 2332BookReader.prototype.flipRightToLeft = function(newIndexL, newIndexR) {
2333 var oldLeafEdgeWidthL = this.leafEdgeWidth(this.twoPage.currentIndexL); 2334 var oldLeafEdgeWidthR = this.twoPage.edgeWidth-oldLeafEdgeWidthL; 2335 var newLeafEdgeWidthL = this.leafEdgeWidth(newIndexL); 2336 var newLeafEdgeWidthR = this.twoPage.edgeWidth-newLeafEdgeWidthL; 2337 2338 var leafEdgeTmpW = oldLeafEdgeWidthR - newLeafEdgeWidthR; 2339 2340 var top = this.twoPageTop(); 2341 var scaledW = this.getPageWidth2UP(this.twoPage.currentIndexR); 2342 2343 var middle = this.twoPage.middle; 2344 var gutter = middle + this.gutterOffsetForIndex(newIndexL); 2345 2346 this.leafEdgeTmp = document.createElement('div'); 2347 this.leafEdgeTmp.className = 'BRleafEdgeTmp'; 2348 $(this.leafEdgeTmp).css({ 2349 width: leafEdgeTmpW + 'px', 2350 height: this.twoPage.height + 'px', 2351 left: gutter+scaledW+'px', 2352 top: top+'px', 2353 zIndex:1000 2354 }).appendTo('#BRtwopageview'); 2355 2356 //var scaledWR = this.getPageWidth2UP(newIndexR); // $$$ should be current instead? 2357 //var scaledWL = this.getPageWidth2UP(newIndexL); // $$$ should be current instead? 2358 2359 var currWidthL = this.getPageWidth2UP(this.twoPage.currentIndexL); 2360 var currWidthR = this.getPageWidth2UP(this.twoPage.currentIndexR); 2361 var newWidthL = this.getPageWidth2UP(newIndexL); 2362 var newWidthR = this.getPageWidth2UP(newIndexR); 2363 2364 $(this.leafEdgeR).css({width: newLeafEdgeWidthR+'px', left: gutter+newWidthR+'px' }); 2365 2366 var self = this; // closure-tastic! 2367 2368 var speed = this.flipSpeed; 2369 2370 this.removeSearchHilites(); 2371 2372 $(this.leafEdgeTmp).animate({left: gutter}, speed, 'easeInSine'); 2373 $(this.prefetchedImgs[this.twoPage.currentIndexR]).animate({width: '0px'}, speed, 'easeInSine', function() { 2374 $('#BRgutter').css({left: (gutter - self.twoPage.bookSpineDivWidth*0.5)+'px'}); 2375 $(self.leafEdgeTmp).animate({left: gutter-newWidthL-leafEdgeTmpW+'px'}, speed, 'easeOutSine'); 2376 $(self.prefetchedImgs[newIndexL]).animate({width: newWidthL+'px'}, speed, 'easeOutSine', function() { 2377 $(self.prefetchedImgs[newIndexR]).css('zIndex', 2); 2378 2379 //jquery adds display:block to the element style, which interferes with our print css 2380 $(self.prefetchedImgs[newIndexL]).css('display', ''); 2381 $(self.prefetchedImgs[newIndexR]).css('display', ''); 2382 2383 $(self.leafEdgeL).css({ 2384 width: newLeafEdgeWidthL+'px', 2385 left: gutter-newWidthL-newLeafEdgeWidthL+'px' 2386 }); 2387 2388 // Resizes the book cover 2389 $(self.twoPage.coverDiv).css({ 2390 width: self.twoPageCoverWidth(newWidthL+newWidthR)+'px', 2391 left: gutter - newWidthL - newLeafEdgeWidthL - self.twoPage.coverInternalPadding + 'px' 2392 }); 2393 2394 $(self.leafEdgeTmp).remove(); 2395 self.leafEdgeTmp = null; 2396 2397 self.twoPage.currentIndexL = newIndexL; 2398 self.twoPage.currentIndexR = newIndexR; 2399 self.twoPage.scaledWL = newWidthL; 2400 self.twoPage.scaledWR = newWidthR; 2401 self.twoPage.gutter = gutter; 2402 2403 self.firstIndex = self.twoPage.currentIndexL; 2404 self.displayedIndices = [newIndexL, newIndexR]; 2405 self.pruneUnusedImgs(); 2406 self.prefetch(); 2407 self.animating = false; 2408 2409 2410 self.updateSearchHilites2UP(); 2411 self.updatePageNumBox2UP(); 2412 2413 // self.twoPagePlaceFlipAreas(); // No longer used 2414 self.setMouseHandlers2UP(); 2415 self.twoPageSetCursor(); 2416 2417 if (self.animationFinishedCallback) { 2418 self.animationFinishedCallback(); 2419 self.animationFinishedCallback = null; 2420 } 2421 }); 2422 }); 2423} 2424 2425// setMouseHandlers2UP 2426//______________________________________________________________________________ 2427BookReader.prototype.setMouseHandlers2UP = function() { 2428 this.setClickHandler2UP( this.prefetchedImgs[this.twoPage.currentIndexL], 2429 { self: this }, 2430 function(e) { 2431 if (e.which == 3) { 2432 // right click 2433 if (e.data.self.protected) { 2434 return false;
2435 } 2436 return true; 2437 } 2438 2439 if (! e.data.self.twoPageIsZoomedIn()) { 2440 e.data.self.ttsStop(); 2441 e.data.self.left(); 2442 } 2443 e.preventDefault(); 2444 } 2445 ); 2446 2447 this.setClickHandler2UP( this.prefetchedImgs[this.twoPage.currentIndexR], 2448 { self: this }, 2449 function(e) { 2450 if (e.which == 3) { 2451 // right click 2452 if (e.data.self.protected) { 2453 return false; 2454 } 2455 return true; 2456 } 2457 2458 if (! e.data.self.twoPageIsZoomedIn()) { 2459 e.data.self.ttsStop(); 2460 e.data.self.right(); 2461 } 2462 e.preventDefault(); 2463 } 2464 ); 2465} 2466 2467// prefetchImg() 2468//______________________________________________________________________________ 2469BookReader.prototype.prefetchImg = function(index) { 2470 var pageURI = this._getPageURI(index); 2471 2472 // Load image if not loaded or URI has changed (e.g. due to scaling) 2473 var loadImage = false; 2474 if (undefined == this.prefetchedImgs[index]) { 2475 //console.log('no image for ' + index); 2476 loadImage = true; 2477 } else if (pageURI != this.prefetchedImgs[index].uri) { 2478 //console.log('uri changed for ' + index); 2479 loadImage = true; 2480 } 2481 2482 if (loadImage) { 2483 //console.log('prefetching ' + index); 2484 var img = document.createElement("img"); 2485 $(img).addClass('BRpageimage').addClass('BRnoselect'); 2486 if (index < 0 || index > (this.numLeafs - 1) ) { 2487 // Facing page at beginning or end, or beyond 2488 $(img).css({ 2489 'background-color': '#efefef' 2490 }); 2491 } 2492 img.src = pageURI; 2493 img.uri = pageURI; // browser may rewrite src so we stash raw URI here 2494 this.prefetchedImgs[index] = img; 2495 } 2496} 2497 2498 2499// prepareFlipLeftToRight() 2500// 2501//______________________________________________________________________________ 2502// 2503// Prepare to flip the left page towards the right. This corresponds to moving 2504// backward when the page progression is left to right. 2505BookReader.prototype.prepareFlipLeftToRight = function(prevL, prevR) { 2506 2507 //console.log(' preparing left->right for ' + prevL + ',' + prevR); 2508 2509 this.prefetchImg(prevL); 2510 this.prefetchImg(prevR); 2511 2512 var height = this._getPageHeight(prevL); 2513 var width = this._getPageWidth(prevL); 2514 var middle = this.twoPage.middle; 2515 var top = this.twoPageTop(); 2516 var scaledW = this.twoPage.height*width/height; // $$$ assumes height of page is dominant 2517 2518 // The gutter is the dividing line between the left and right pages. 2519 // It is offset from the middle to create the illusion of thickness to the pages 2520 var gutter = middle + this.gutterOffsetForIndex(prevL); 2521 2522 //console.log(' gutter for ' + prevL + ' is ' + gutter); 2523 //console.log(' prevL.left: ' + (gutter - scaledW) + 'px'); 2524 //console.log(' changing prevL ' + prevL + ' to left: ' + (gutter-scaledW) + ' width: ' + scaledW); 2525 2526 var leftCSS = { 2527 position: 'absolute', 2528 left: gutter-scaledW+'px', 2529 right: '', // clear right property 2530 top: top+'px', 2531 height: this.twoPage.height, 2532 width: scaledW+'px', 2533 zIndex: 1 2534 } 2535 2536 $(this.prefetchedImgs[prevL]).css(leftCSS); 2537 2538 $('#BRtwopageview').append(this.prefetchedImgs[prevL]); 2539 2540 //console.log(' changing prevR ' + prevR + ' to left: ' + gutter + ' width: 0'); 2541 2542 var rightCSS = { 2543 position: 'absolute', 2544 left: gutter+'px', 2545 right: '', 2546 top: top+'px', 2547 height: this.twoPage.height, 2548 width: '0', 2549 zIndex: 2 2550 } 2551 2552 $(this.prefetchedImgs[prevR]).css(rightCSS); 2553 2554 $('#BRtwopageview').append(this.prefetchedImgs[prevR]); 2555 2556} 2557 2558// $$$ mang we're adding an extra pixel in the middle. See https://bugs.edge.launchpad.net/gnubook/+bug/411667 2559// prepareFlipRightToLeft() 2560//______________________________________________________________________________ 2561BookReader.prototype.prepareFlipRightToLeft = function(nextL, nextR) { 2562 2563 //console.log(' preparing left<-right for ' + nextL + ',' + nextR); 2564 2565 // Prefetch images 2566 this.prefetchImg(nextL); 2567 this.prefetchImg(nextR); 2568 2569 var height = this._getPageHeight(nextR); 2570 var width = this._getPageWidth(nextR); 2571 var middle = this.twoPage.middle; 2572 var top = this.twoPageTop(); 2573 var scaledW = this.twoPage.height*width/height; 2574 2575 var gutter = middle + this.gutterOffsetForIndex(nextL); 2576 2577 //console.log(' prepareRTL changing nextR ' + nextR + ' to left: ' + gutter); 2578 $(this.prefetchedImgs[nextR]).css({ 2579 position: 'absolute', 2580 left: gutter+'px', 2581 top: top+'px', 2582 height: this.twoPage.height, 2583 width: scaledW+'px', 2584 zIndex: 1 2585 }); 2586 2587 $('#BRtwopageview').append(this.prefetchedImgs[nextR]); 2588 2589 height = this._getPageHeight(nextL); 2590 width = this._getPageWidth(nextL); 2591 scaledW = this.twoPage.height*width/height; 2592 2593 //console.log(' prepareRTL changing nextL ' + nextL + ' to right: ' + $('#BRcontainer').width()-gutter); 2594 $(this.prefetchedImgs[nextL]).css({ 2595 position: 'absolute', 2596 right: $('#BRtwopageview').attr('clientWidth')-gutter+'px', 2597 top: top+'px', 2598 height: this.twoPage.height, 2599 width: 0+'px', // Start at 0 width, then grow to the left 2600 zIndex: 2 2601 }); 2602 2603 $('#BRtwopageview').append(this.prefetchedImgs[nextL]); 2604 2605} 2606 2607// getNextLeafs() -- NOT RTL AWARE 2608//______________________________________________________________________________ 2609// BookReader.prototype.getNextLeafs = function(o) {
2610// //TODO: we might have two left or two right leafs in a row (damaged book) 2611// //For now, assume that leafs are contiguous. 2612// 2613// //return [this.twoPage.currentIndexL+2, this.twoPage.currentIndexL+3]; 2614// o.L = this.twoPage.currentIndexL+2; 2615// o.R = this.twoPage.currentIndexL+3; 2616// } 2617 2618// getprevLeafs() -- NOT RTL AWARE 2619//______________________________________________________________________________ 2620// BookReader.prototype.getPrevLeafs = function(o) { 2621// //TODO: we might have two left or two right leafs in a row (damaged book) 2622// //For now, assume that leafs are contiguous. 2623// 2624// //return [this.twoPage.currentIndexL-2, this.twoPage.currentIndexL-1]; 2625// o.L = this.twoPage.currentIndexL-2; 2626// o.R = this.twoPage.currentIndexL-1; 2627// } 2628 2629// pruneUnusedImgs() 2630//______________________________________________________________________________ 2631BookReader.prototype.pruneUnusedImgs = function() { 2632 //console.log('current: ' + this.twoPage.currentIndexL + ' ' + this.twoPage.currentIndexR); 2633 for (var key in this.prefetchedImgs) { 2634 //console.log('key is ' + key); 2635 if ((key != this.twoPage.currentIndexL) && (key != this.twoPage.currentIndexR)) { 2636 //console.log('removing key '+ key); 2637 $(this.prefetchedImgs[key]).remove(); 2638 } 2639 if ((key < this.twoPage.currentIndexL-4) || (key > this.twoPage.currentIndexR+4)) { 2640 //console.log('deleting key '+ key); 2641 delete this.prefetchedImgs[key]; 2642 } 2643 } 2644} 2645 2646// prefetch() 2647//______________________________________________________________________________ 2648BookReader.prototype.prefetch = function() { 2649 2650 // $$$ We should check here if the current indices have finished 2651 // loading (with some timeout) before loading more page images 2652 // See https://bugs.edge.launchpad.net/bookreader/+bug/511391 2653 2654 // prefetch visible pages first 2655 this.prefetchImg(this.twoPage.currentIndexL); 2656 this.prefetchImg(this.twoPage.currentIndexR); 2657 2658 var adjacentPagesToLoad = 3; 2659 2660 var lowCurrent = Math.min(this.twoPage.currentIndexL, this.twoPage.currentIndexR); 2661 var highCurrent = Math.max(this.twoPage.currentIndexL, this.twoPage.currentIndexR); 2662 2663 var start = Math.max(lowCurrent - adjacentPagesToLoad, 0); 2664 var end = Math.min(highCurrent + adjacentPagesToLoad, this.numLeafs - 1); 2665 2666 // Load images spreading out from current 2667 for (var i = 1; i <= adjacentPagesToLoad; i++) { 2668 var goingDown = lowCurrent - i; 2669 if (goingDown >= start) { 2670 this.prefetchImg(goingDown); 2671 } 2672 var goingUp = highCurrent + i; 2673 if (goingUp <= end) { 2674 this.prefetchImg(goingUp); 2675 } 2676 } 2677 2678 /* 2679 var lim = this.twoPage.currentIndexL-4; 2680 var i; 2681 lim = Math.max(lim, 0); 2682 for (i = lim; i < this.twoPage.currentIndexL; i++) { 2683 this.prefetchImg(i); 2684 } 2685 2686 if (this.numLeafs > (this.twoPage.currentIndexR+1)) { 2687 lim = Math.min(this.twoPage.currentIndexR+4, this.numLeafs-1); 2688 for (i=this.twoPage.currentIndexR+1; i<=lim; i++) { 2689 this.prefetchImg(i); 2690 } 2691 } 2692 */ 2693} 2694 2695// getPageWidth2UP() 2696//______________________________________________________________________________ 2697BookReader.prototype.getPageWidth2UP = function(index) { 2698 // We return the width based on the dominant height 2699 var height = this._getPageHeight(index); 2700 var width = this._getPageWidth(index); 2701 return Math.floor(this.twoPage.height*width/height); // $$$ we assume width is relative to current spread 2702} 2703 2704// search() 2705//______________________________________________________________________________ 2706BookReader.prototype.search = function(term) { 2707 //console.log('search called with term=' + term); 2708 2709 $('#textSrch').blur(); //cause mobile safari to hide the keyboard 2710 2711 var url = 'https://'+this.server.replace(/:.+/, ''); //remove the port and userdir 2712 url += '/fulltext/inside.php?item_id='+this.bookId; 2713 url += '&doc='+this.subPrefix; //TODO: test with subitem 2714 url += '&path='+this.bookPath.replace(new RegExp('/'+this.subPrefix+'$'), ''); //remove subPrefix from end of path 2715 url += '&q='+escape(term); 2716 //console.log('search url='+url); 2717 2718 term = term.replace(/\//g, ' '); // strip slashes, since this goes in the url 2719 this.searchTerm = term; 2720 2721 this.removeSearchResults(); 2722 this.showProgressPopup('<img id="searchmarker" src="'+this.imagesBaseURL + 'marker_srch-on.png'+'"> Search results will appear below...'); 2723 $.ajax({url:url, dataType:'jsonp', jsonpCallback:'br.BRSearchCallback'}); 2724} 2725 2726// BRSearchCallback() 2727//______________________________________________________________________________ 2728BookReader.prototype.BRSearchCallback = function(results) { 2729 //console.log('got ' + results.matches.length + ' results'); 2730 br.removeSearchResults(); 2731 br.searchResults = results; 2732 //console.log(br.searchResults); 2733 2734 if (0 == results.matches.length) { 2735 var errStr = 'No matches were found.'; 2736 var timeout = 1000; 2737 if (false === results.indexed) { 2738 errStr = "<p>This book hasn't been indexed for searching yet. We've just started indexing it, so search should be available soon. Please try again later. Thanks!</p>"; 2739 timeout = 5000; 2740 } 2741 $(br.popup).html(errStr); 2742 setTimeout(function(){ 2743 $(br.popup).fadeOut('slow', function() { 2744 br.removeProgressPopup(); 2745 }) 2746 },timeout); 2747 return; 2748 } 2749 2750 var i; 2751 for (i=0; i<results.matches.length; i++) { 2752 br.addSearchResult(results.matches[i].text, br.leafNumToIndex(results.matches[i].par[0].page)); 2753 } 2754 br.updateSearchHilites(); 2755 br.removeProgressPopup(); 2756} 2757 2758 2759// updateSearchHilites() 2760//______________________________________________________________________________ 2761BookReader.prototype.updateSearchHilites = function() { 2762 if (2 == this.mode) { 2763 this.updateSearchHilites2UP(); 2764 } else { 2765 this.updateSearchHilites1UP(); 2766 } 2767} 2768 2769// showSearchHilites1UP() 2770//______________________________________________________________________________ 2771BookReader.prototype.updateSearchHilites1UP = function() { 2772 var results = this.searchResults; 2773 if (null == results) return; 2774 var i, j; 2775 for (i=0; i<results.matches.length; i++) { 2776 //console.log(results.matches[i].par[0]); 2777 for (j=0; j<results.matches[i].par[0].boxes.length; j++) { 2778 var box = results.matches[i].par[0].boxes[j]; 2779 var pageIndex = this.leafNumToIndex(box.page); 2780 if (jQuery.inArray(pageIndex, this.displayedIndices) >= 0) { 2781 if (null == box.div) { 2782 //create a div for the search highlight, and stash it in the box object 2783 box.div = document.createElement('div'); 2784 $(box.div).attr('className', 'BookReaderSearchHilite').appendTo('#pagediv'+pageIndex); 2785 } 2786 $(box.div).css({ 2787 width: (box.r-box.l)/this.reduce + 'px', 2788 height: (box.b-box.t)/this.reduce + 'px', 2789 left: (box.l)/this.reduce + 'px', 2790 top: (box.t)/this.reduce +'px' 2791 }); 2792 } else { 2793 if (null != box.div) { 2794 //console.log('removing search highlight div'); 2795 $(box.div).remove(); 2796 box.div=null; 2797 } 2798 } 2799 } 2800 } 2801 2802} 2803 2804 2805// twoPageGutter() 2806//______________________________________________________________________________ 2807// Returns the position of the gutter (line between the page images) 2808BookReader.prototype.twoPageGutter = function() { 2809 return this.twoPage.middle + this.gutterOffsetForIndex(this.twoPage.currentIndexL); 2810} 2811 2812// twoPageTop() 2813//______________________________________________________________________________ 2814// Returns the offset for the top of the page images 2815BookReader.prototype.twoPageTop = function() { 2816 return this.twoPage.coverExternalPadding + this.twoPage.coverInternalPadding; // $$$ + border? 2817} 2818 2819// twoPageCoverWidth() 2820//______________________________________________________________________________ 2821// Returns the width of the cover div given the total page width 2822BookReader.prototype.twoPageCoverWidth = function(totalPageWidth) { 2823 return totalPageWidth + this.twoPage.edgeWidth + 2*this.twoPage.coverInternalPadding; 2824} 2825 2826// twoPageGetViewCenter() 2827//______________________________________________________________________________ 2828// Returns the percentage offset into twopageview div at the center of container div 2829// { percentageX: float, percentageY: float } 2830BookReader.prototype.twoPageGetViewCenter = function() { 2831 var center = {}; 2832 2833 var containerOffset = $('#BRcontainer').offset(); 2834 var viewOffset = $('#BRtwopageview').offset(); 2835 center.percentageX = (containerOffset.left - viewOffset.left + ($('#BRcontainer').attr('clientWidth') >> 1)) / this.twoPage.totalWidth; 2836 center.percentageY = (containerOffset.top - viewOffset.top + ($('#BRcontainer').attr('clientHeight') >> 1)) / this.twoPage.totalHeight; 2837 2838 return center; 2839} 2840 2841// twoPageCenterView(percentageX, percentageY) 2842//______________________________________________________________________________ 2843// Centers the point given by percentage from left,top of twopageview 2844BookReader.prototype.twoPageCenterView = function(percentageX, percentageY) {
2845 if ('undefined' == typeof(percentageX)) { 2846 percentageX = 0.5; 2847 } 2848 if ('undefined' == typeof(percentageY)) { 2849 percentageY = 0.5; 2850 } 2851 2852 var viewWidth = $('#BRtwopageview').width(); 2853 var containerClientWidth = $('#BRcontainer').attr('clientWidth'); 2854 var intoViewX = percentageX * viewWidth; 2855 2856 var viewHeight = $('#BRtwopageview').height(); 2857 var containerClientHeight = $('#BRcontainer').attr('clientHeight'); 2858 var intoViewY = percentageY * viewHeight; 2859 2860 if (viewWidth < containerClientWidth) { 2861 // Can fit width without scrollbars - center by adjusting offset 2862 $('#BRtwopageview').css('left', (containerClientWidth >> 1) - intoViewX + 'px'); 2863 } else { 2864 // Need to scroll to center 2865 $('#BRtwopageview').css('left', 0); 2866 $('#BRcontainer').scrollLeft(intoViewX - (containerClientWidth >> 1)); 2867 } 2868 2869 if (viewHeight < containerClientHeight) { 2870 // Fits with scrollbars - add offset 2871 $('#BRtwopageview').css('top', (containerClientHeight >> 1) - intoViewY + 'px'); 2872 } else { 2873 $('#BRtwopageview').css('top', 0); 2874 $('#BRcontainer').scrollTop(intoViewY - (containerClientHeight >> 1)); 2875 } 2876} 2877 2878// twoPageFlipAreaHeight 2879//______________________________________________________________________________ 2880// Returns the integer height of the click-to-flip areas at the edges of the book 2881BookReader.prototype.twoPageFlipAreaHeight = function() { 2882 return parseInt(this.twoPage.height); 2883} 2884 2885// twoPageFlipAreaWidth 2886//______________________________________________________________________________ 2887// Returns the integer width of the flip areas 2888BookReader.prototype.twoPageFlipAreaWidth = function() { 2889 var max = 100; // $$$ TODO base on view width? 2890 var min = 10; 2891 2892 var width = this.twoPage.width * 0.15; 2893 return parseInt(BookReader.util.clamp(width, min, max)); 2894} 2895 2896// twoPageFlipAreaTop 2897//______________________________________________________________________________ 2898// Returns integer top offset for flip areas 2899BookReader.prototype.twoPageFlipAreaTop = function() { 2900 return parseInt(this.twoPage.bookCoverDivTop + this.twoPage.coverInternalPadding); 2901} 2902 2903// twoPageLeftFlipAreaLeft 2904//______________________________________________________________________________ 2905// Left offset for left flip area 2906BookReader.prototype.twoPageLeftFlipAreaLeft = function() { 2907 return parseInt(this.twoPage.gutter - this.twoPage.scaledWL); 2908} 2909 2910// twoPageRightFlipAreaLeft 2911//______________________________________________________________________________ 2912// Left offset for right flip area 2913BookReader.prototype.twoPageRightFlipAreaLeft = function() { 2914 return parseInt(this.twoPage.gutter + this.twoPage.scaledWR - this.twoPageFlipAreaWidth()); 2915} 2916 2917// twoPagePlaceFlipAreas 2918//______________________________________________________________________________ 2919// Readjusts position of flip areas based on current layout 2920BookReader.prototype.twoPagePlaceFlipAreas = function() { 2921 // We don't set top since it shouldn't change relative to view 2922 $(this.twoPage.leftFlipArea).css({ 2923 left: this.twoPageLeftFlipAreaLeft() + 'px', 2924 width: this.twoPageFlipAreaWidth() + 'px' 2925 }); 2926 $(this.twoPage.rightFlipArea).css({ 2927 left: this.twoPageRightFlipAreaLeft() + 'px', 2928 width: this.twoPageFlipAreaWidth() + 'px' 2929 }); 2930} 2931 2932// showSearchHilites2UPNew() 2933//______________________________________________________________________________ 2934BookReader.prototype.updateSearchHilites2UP = function() { 2935 //console.log('updateSearchHilites2UP results = ' + this.searchResults); 2936 var results = this.searchResults; 2937 if (null == results) return; 2938 var i, j; 2939 for (i=0; i<results.matches.length; i++) { 2940 //console.log(results.matches[i].par[0]); 2941 //TODO: loop over all par objects 2942 var pageIndex = this.leafNumToIndex(results.matches[i].par[0].page); 2943 for (j=0; j<results.matches[i].par[0].boxes.length; j++) { 2944 var box = results.matches[i].par[0].boxes[j]; 2945 if (jQuery.inArray(pageIndex, this.displayedIndices) >= 0) { 2946 if (null == box.div) { 2947 //create a div for the search highlight, and stash it in the box object 2948 box.div = document.createElement('div'); 2949 $(box.div).attr('className', 'BookReaderSearchHilite').css('zIndex', 3).appendTo('#BRtwopageview'); 2950 //console.log('appending new div'); 2951 } 2952 this.setHilightCss2UP(box.div, pageIndex, box.l, box.r, box.t, box.b); 2953 } else { 2954 if (null != box.div) { 2955 //console.log('removing search highlight div'); 2956 $(box.div).remove(); 2957 box.div=null; 2958 } 2959 } 2960 } 2961 } 2962 2963} 2964 2965// setHilightCss2UP() 2966//______________________________________________________________________________ 2967//position calculation shared between search and text-to-speech functions 2968BookReader.prototype.setHilightCss2UP = function(div, index, left, right, top, bottom) { 2969 2970 // We calculate the reduction factor for the specific page because it can be different 2971 // for each page in the spread 2972 var height = this._getPageHeight(index); 2973 var width = this._getPageWidth(index) 2974 var reduce = this.twoPage.height/height; 2975 var scaledW = parseInt(width*reduce); 2976 2977 var gutter = this.twoPageGutter(); 2978 var pageL; 2979 if ('L' == this.getPageSide(index)) { 2980 pageL = gutter-scaledW; 2981 } else { 2982 pageL = gutter; 2983 } 2984 var pageT = this.twoPageTop(); 2985 2986 $(div).css({ 2987 width: (right-left)*reduce + 'px', 2988 height: (bottom-top)*reduce + 'px', 2989 left: pageL+left*reduce + 'px', 2990 top: pageT+top*reduce +'px' 2991 }); 2992} 2993 2994// removeSearchHilites() 2995//______________________________________________________________________________ 2996BookReader.prototype.removeSearchHilites = function() { 2997 var results = this.searchResults; 2998 if (null == results) return; 2999 var i, j; 3000 for (i=0; i<results.matches.length; i++) { 3001 for (j=0; j<results.matches[i].par[0].boxes.length; j++) { 3002 var box = results.matches[i].par[0].boxes[j]; 3003 if (null != box.div) { 3004 $(box.div).remove(); 3005 box.div=null; 3006 } 3007 } 3008 } 3009} 3010 3011 3012// printPage 3013//______________________________________________________________________________ 3014BookReader.prototype.printPage = function() { 3015 window.open(this.getPrintURI(), 'printpage', 'width=400, height=500, resizable=yes, scrollbars=no, toolbar=no, location=no'); 3016} 3017 3018// Get print URI from current indices and mode 3019BookReader.prototype.getPrintURI = function() { 3020 var indexToPrint; 3021 if (this.constMode2up == this.mode) { 3022 indexToPrint = this.twoPage.currentIndexL; 3023 } else { 3024 indexToPrint = this.firstIndex; // $$$ the index in the middle of the viewport would make more sense 3025 } 3026
3027 var options = 'id=' + this.subPrefix + '&server=' + this.server + '&zip=' + this.zip 3028 + '&format=' + this.imageFormat + '&file=' + this._getPageFile(indexToPrint) 3029 + '&width=' + this._getPageWidth(indexToPrint) + '&height=' + this._getPageHeight(indexToPrint); 3030 3031 if (this.constMode2up == this.mode) { 3032 options += '&file2=' + this._getPageFile(this.twoPage.currentIndexR) + '&width2=' + this._getPageWidth(this.twoPage.currentIndexR); 3033 options += '&height2=' + this._getPageHeight(this.twoPage.currentIndexR); 3034 options += '&title=' + encodeURIComponent(this.shortTitle(50) + ' - Pages ' + this.getPageNum(this.twoPage.currentIndexL) + ', ' + this.getPageNum(this.twoPage.currentIndexR)); 3035 } else { 3036 options += '&title=' + encodeURIComponent(this.shortTitle(50) + ' - Page ' + this.getPageNum(indexToPrint)); 3037 } 3038 3039 return '/bookreader/print.php?' + options; 3040} 3041 3042// showEmbedCode() 3043// 3044// Note: Has been replaced by the share dialog 3045//______________________________________________________________________________ 3046BookReader.prototype.showEmbedCode = function() { 3047 if (null != this.embedPopup) { // check if already showing 3048 return; 3049 } 3050 this.autoStop(); 3051 this.ttsStop(); 3052 3053 this.embedPopup = document.createElement("div"); 3054 $(this.embedPopup).css({ 3055 position: 'absolute', 3056 top: ($('#BRcontainer').attr('clientHeight')-250)/2 + 'px', 3057 left: ($('#BRcontainer').attr('clientWidth')-400)/2 + 'px', 3058 width: '400px', 3059 height: '250px', 3060 padding: '0', 3061 fontSize: '12px', 3062 color: '#333', 3063 zIndex: 300, 3064 border: '10px solid #615132', 3065 backgroundColor: "#fff", 3066 MozBorderRadius: '8px', 3067 MozBoxShadow: '0 0 6px #000', 3068 WebkitBorderRadius: '8px', 3069 WebkitBoxShadow: '0 0 6px #000' 3070 }).appendTo('#BookReader'); 3071 3072 htmlStr = '<h3 style="background:#615132;padding:10px;margin:0 0 10px;color:#fff;">Embed Bookreader</h3>'; 3073 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>'; 3074 htmlStr += '<a href="javascript:;" class="popOff" onclick="$(this.parentNode).remove();$(\'.coverUp\').hide();return false" style="color:#999;"><span>Close</span></a>'; 3075 3076 this.embedPopup.innerHTML = htmlStr; 3077 $('#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>'); 3078 $(this.embedPopup).find('textarea').click(function() { 3079 this.select(); 3080 }) 3081 $(this.embedPopup).addClass("popped"); 3082} 3083 3084// showBookmarkCode() 3085//______________________________________________________________________________ 3086BookReader.prototype.showBookmarkCode = function() { 3087 this.bookmarkPopup = document.createElement("div"); 3088 $(this.bookmarkPopup).css({ 3089 position: 'absolute', 3090 top: ($('#BRcontainer').attr('clientHeight')-250)/2 + 'px', 3091 left: ($('#BRcontainer').attr('clientWidth')-400)/2 + 'px', 3092 width: '400px', 3093 height: '250px', 3094 padding: '0', 3095 fontSize: '12px', 3096 color: '#333', 3097 zIndex: 300, 3098 border: '10px solid #615132', 3099 backgroundColor: "#fff", 3100 MozBorderRadius: '8px', 3101 MozBoxShadow: '0 0 6px #000', 3102 WebkitBorderRadius: '8px', 3103 WebkitBoxShadow: '0 0 6px #000' 3104 }).appendTo('#BookReader'); 3105 3106 htmlStr = '<h3 style="background:#615132;padding:10px;margin:0 0 10px;color:#fff;">Add a bookmark</h3>'; 3107 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>'; 3108 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>'; 3109 htmlStr += '<a href="javascript:;" class="popOff" onclick="$(this.parentNode).remove();$(\'.coverUp\').hide();return false;" style="color:#999;"><span>Close</span></a>'; 3110 3111 this.bookmarkPopup.innerHTML = htmlStr; 3112 $('#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>'); 3113 $(this.bookmarkPopup).find('textarea').click(function() { 3114 this.select(); 3115 }) 3116 $(this.bookmarkPopup).addClass("popped"); 3117} 3118 3119
3120// autoToggle() 3121//______________________________________________________________________________ 3122BookReader.prototype.autoToggle = function() { 3123 3124 this.ttsStop(); 3125 3126 var bComingFrom1up = false; 3127 if (2 != this.mode) { 3128 bComingFrom1up = true; 3129 this.switchMode(2); 3130 } 3131 3132 // Change to autofit if book is too large 3133 if (this.reduce < this.twoPageGetAutofitReduce()) { 3134 this.zoom2up('auto'); 3135 } 3136 3137 var self = this; 3138 if (null == this.autoTimer) { 3139 this.flipSpeed = 2000; 3140 3141 // $$$ Draw events currently cause layout problems when they occur during animation. 3142 // There is a specific problem when changing from 1-up immediately to autoplay in RTL so 3143 // we workaround for now by not triggering immediate animation in that case. 3144 // See https://bugs.launchpad.net/gnubook/+bug/328327 3145 if (('rl' == this.pageProgression) && bComingFrom1up) { 3146 // don't flip immediately -- wait until timer fires 3147 } else { 3148 // flip immediately 3149 this.flipFwdToIndex(); 3150 } 3151 3152 $('#BRtoolbar .play').hide(); 3153 $('#BRtoolbar .pause').show(); 3154 this.autoTimer=setInterval(function(){ 3155 if (self.animating) {return;} 3156 3157 if (Math.max(self.twoPage.currentIndexL, self.twoPage.currentIndexR) >= self.lastDisplayableIndex()) { 3158 self.flipBackToIndex(1); // $$$ really what we want? 3159 } else { 3160 self.flipFwdToIndex(); 3161 } 3162 },5000); 3163 } else { 3164 this.autoStop(); 3165 } 3166} 3167 3168// autoStop() 3169//______________________________________________________________________________ 3170// Stop autoplay mode, allowing animations to finish 3171BookReader.prototype.autoStop = function() { 3172 if (null != this.autoTimer) { 3173 clearInterval(this.autoTimer); 3174 this.flipSpeed = 'fast'; 3175 $('#BRtoolbar .pause').hide(); 3176 $('#BRtoolbar .play').show(); 3177 this.autoTimer = null; 3178 } 3179} 3180 3181// stopFlipAnimations 3182//______________________________________________________________________________ 3183// Immediately stop flip animations. Callbacks are triggered. 3184BookReader.prototype.stopFlipAnimations = function() { 3185 3186 this.autoStop(); // Clear timers 3187 3188 // Stop animation, clear queue, trigger callbacks 3189 if (this.leafEdgeTmp) { 3190 $(this.leafEdgeTmp).stop(false, true); 3191 } 3192 jQuery.each(this.prefetchedImgs, function() { 3193 $(this).stop(false, true); 3194 }); 3195 3196 // And again since animations also queued in callbacks 3197 if (this.leafEdgeTmp) { 3198 $(this.leafEdgeTmp).stop(false, true); 3199 } 3200 jQuery.each(this.prefetchedImgs, function() { 3201 $(this).stop(false, true); 3202 }); 3203 3204} 3205 3206// keyboardNavigationIsDisabled(event) 3207// - returns true if keyboard navigation should be disabled for the event 3208//______________________________________________________________________________ 3209BookReader.prototype.keyboardNavigationIsDisabled = function(event) { 3210 if (event.target.tagName == "INPUT") { 3211 return true; 3212 } 3213 return false; 3214} 3215 3216// gutterOffsetForIndex 3217//______________________________________________________________________________ 3218// 3219// Returns the gutter offset for the spread containing the given index. 3220// This function supports RTL 3221BookReader.prototype.gutterOffsetForIndex = function(pindex) { 3222 3223 // To find the offset of the gutter from the middle we calculate our percentage distance 3224 // through the book (0..1), remap to (-0.5..0.5) and multiply by the total page edge width 3225 var offset = parseInt(((pindex / this.numLeafs) - 0.5) * this.twoPage.edgeWidth); 3226 3227 // But then again for RTL it's the opposite 3228 if ('rl' == this.pageProgression) { 3229 offset = -offset; 3230 } 3231 3232 return offset; 3233} 3234 3235// leafEdgeWidth 3236//______________________________________________________________________________ 3237// Returns the width of the leaf edge div for the page with index given 3238BookReader.prototype.leafEdgeWidth = function(pindex) { 3239 // $$$ could there be single pixel rounding errors for L vs R? 3240 if ((this.getPageSide(pindex) == 'L') && (this.pageProgression != 'rl')) { 3241 return parseInt( (pindex/this.numLeafs) * this.twoPage.edgeWidth + 0.5); 3242 } else { 3243 return parseInt( (1 - pindex/this.numLeafs) * this.twoPage.edgeWidth + 0.5); 3244 } 3245} 3246 3247// jumpIndexForLeftEdgePageX 3248//______________________________________________________________________________ 3249// Returns the target jump leaf given a page coordinate (inside the left page edge div) 3250BookReader.prototype.jumpIndexForLeftEdgePageX = function(pageX) { 3251 if ('rl' != this.pageProgression) { 3252 // LTR - flipping backward 3253 var jumpIndex = this.twoPage.currentIndexL - ($(this.leafEdgeL).offset().left + $(this.leafEdgeL).width() - pageX) * 10; 3254 3255 // browser may have resized the div due to font size change -- see https://bugs.launchpad.net/gnubook/+bug/333570 3256 jumpIndex = BookReader.util.clamp(Math.round(jumpIndex), this.firstDisplayableIndex(), this.twoPage.currentIndexL - 2); 3257 return jumpIndex; 3258 3259 } else { 3260 var jumpIndex = this.twoPage.currentIndexL + ($(this.leafEdgeL).offset().left + $(this.leafEdgeL).width() - pageX) * 10; 3261 jumpIndex = BookReader.util.clamp(Math.round(jumpIndex), this.twoPage.currentIndexL + 2, this.lastDisplayableIndex()); 3262 return jumpIndex; 3263 } 3264} 3265 3266// jumpIndexForRightEdgePageX 3267//______________________________________________________________________________ 3268// Returns the target jump leaf given a page coordinate (inside the right page edge div) 3269BookReader.prototype.jumpIndexForRightEdgePageX = function(pageX) { 3270 if ('rl' != this.pageProgression) { 3271 // LTR 3272 var jumpIndex = this.twoPage.currentIndexR + (pageX - $(this.leafEdgeR).offset().left) * 10; 3273 jumpIndex = BookReader.util.clamp(Math.round(jumpIndex), this.twoPage.currentIndexR + 2, this.lastDisplayableIndex()); 3274 return jumpIndex; 3275 } else { 3276 var jumpIndex = this.twoPage.currentIndexR - (pageX - $(this.leafEdgeR).offset().left) * 10; 3277 jumpIndex = BookReader.util.clamp(Math.round(jumpIndex), this.firstDisplayableIndex(), this.twoPage.currentIndexR - 2); 3278 return jumpIndex; 3279 } 3280} 3281 3282// initNavbar 3283//______________________________________________________________________________ 3284// Initialize the navigation bar. 3285// $$$ this could also add the base elements to the DOM, so disabling the nav bar 3286// could be as simple as not calling this function 3287BookReader.prototype.initNavbar = function() { 3288 // Setup nav / chapter / search results bar 3289 3290 $('#BookReader').append( 3291 '<div id="BRnav">' 3292 + '<div id="BRpage">' // Page turn buttons 3293 + '<button class="BRicon onepg"></button>' 3294 + '<button class="BRicon twopg"></button>' 3295 + '<button class="BRicon thumb"></button>' 3296 // $$$ not yet implemented 3297 //+ '<button class="BRicon fit"></button>' 3298 + '<button class="BRicon zoom_in"></button>' 3299 + '<button class="BRicon zoom_out"></button>' 3300 + '<button class="BRicon book_left"></button>' 3301 + '<button class="BRicon book_right"></button>' 3302 + '</div>' 3303 + '<div id="BRnavpos">' // Page slider and nav line 3304 //+ '<div id="BRfiller"></div>' 3305 + '<div id="BRpager"></div>' // Page slider 3306 + '<div id="BRnavline">' // Nav line with e.g. chapter markers 3307 + '<div class="BRnavend" id="BRnavleft"></div>' 3308 + '<div class="BRnavend" id="BRnavright"></div>' 3309 + '</div>' 3310 + '</div>' 3311 + '<div id="BRnavCntlBtm" class="BRnavCntl BRdn"></div>' 3312 + '</div>' 3313 ); 3314 3315 var self = this; 3316 $('#BRpager').slider({ 3317 animate: true, 3318 min: 0, 3319 max: this.numLeafs - 1, 3320 value: this.currentIndex() 3321 }) 3322 .bind('slide', function(event, ui) { 3323 self.updateNavPageNum(ui.value); 3324 $("#pagenum").show(); 3325 return true; 3326 }) 3327 .bind('slidechange', function(event, ui) { 3328 self.updateNavPageNum(ui.value); // hiding now but will show later 3329 $("#pagenum").hide(); 3330 3331 // recursion prevention for jumpToIndex 3332 if ( $(this).data('swallowchange') ) { 3333 $(this).data('swallowchange', false); 3334 } else { 3335 self.jumpToIndex(ui.value); 3336 } 3337 return true; 3338 }) 3339 .hover(function() { 3340 $("#pagenum").show(); 3341 },function(){ 3342 // XXXmang not triggering on iPad - probably due to touch event translation layer 3343 $("#pagenum").hide(); 3344 } 3345 ); 3346 3347 //append icon to handle 3348 var handleHelper = $('#BRpager .ui-slider-handle') 3349 .append('<div id="pagenum"><span class="currentpage"></span></div>'); 3350 //.wrap('<div class="ui-handle-helper-parent"></div>').parent(); // XXXmang is this used for hiding the tooltip? 3351 3352 this.updateNavPageNum(this.currentIndex()); 3353 3354 $("#BRzoombtn").draggable({axis:'y',containment:'parent'}); 3355 3356 /* Initial hiding 3357 $('#BRtoolbar').delay(3000).animate({top:-40}); 3358 $('#BRnav').delay(3000).animate({bottom:-53}); 3359 changeArrow(); 3360 $('.BRnavCntl').delay(3000).animate({height:'43px'}).delay(1000).animate({opacity:.25},1000); 3361 */ 3362} 3363 3364// initEmbedNavbar 3365//______________________________________________________________________________ 3366// Initialize the navigation bar when embedded 3367BookReader.prototype.initEmbedNavbar = function() { 3368 var thisLink = (window.location + '').replace('?ui=embed',''); // IA-specific 3369 3370 /* $('#BookReader').append( 3371 '<div id="BRnav">' 3372 + "<span id='BRtoolbarbuttons'>" 3373 + '<button class="BRicon full"></button>' 3374 + '<button class="BRicon book_left"></button>' 3375 + '<button class="BRicon book_right"></button>' 3376 + "</span>" 3377 + "<span><a class='logo' href='" + this.logoURL + "' 'target='_blank' ></a></span>" 3378 + "<span id='BRembedreturn'><a href='" + thisLink + "' target='_blank' ></a></span>" 3379 + '</div>' 3380 ); */ 3381 $('#BRembedreturn a').text(this.bookTitle); 3382} 3383 3384BookReader.prototype.updateNavPageNum = function(index) { 3385 var pageNum = this.getPageNum(index); 3386 var pageStr; 3387 if (pageNum[0] == 'n') { // funny index 3388 pageStr = index + 1 + ' / ' + this.numLeafs; // Accessible index starts at 0 (alas) so we add 1 to make human 3389 } else { 3390 pageStr = 'Page ' + pageNum; 3391 } 3392 3393 $('#pagenum .currentpage').text(pageStr); 3394} 3395 3396/* 3397 * Update the nav bar display - does not cause navigation. 3398 */ 3399BookReader.prototype.updateNavIndex = function(index) { 3400 // We want to update the value, but normally moving the slider 3401 // triggers jumpToIndex which triggers this method 3402 $('#BRpager').data('swallowchange', true).slider('value', index); 3403} 3404 3405BookReader.prototype.addSearchResult = function(queryString, pageIndex) { 3406 var pageNumber = this.getPageNum(pageIndex); 3407 var uiStringSearch = "Search result"; // i18n 3408 var uiStringPage = "Page"; // i18n 3409 3410 var percentThrough = BookReader.util.cssPercentage(pageIndex, this.numLeafs - 1); 3411 var pageDisplayString = ''; 3412 if (pageNumber) { 3413 pageDisplayString = uiStringPage + ' ' + pageNumber; 3414 } 3415 3416 var re = new RegExp('{{{(.+?)}}}', 'g'); 3417 queryString = queryString.replace(re, '<a href="#" onclick="br.jumpToIndex('+pageIndex+'); return false;">$1</a>') 3418
3419 var marker = $('<div class="search" style="top:'+(-$('#BRcontainer').height())+'px; left:' + percentThrough + ';" title="' + uiStringSearch + '"><div class="query">' 3420 + queryString + '<span>' + uiStringPage + ' ' + pageNumber + '</span></div>') 3421 .data({'self': this, 'pageIndex': pageIndex }) 3422 .appendTo('#BRnavline').bt({ 3423 contentSelector: '$(this).find(".query")', 3424 trigger: 'hover', 3425 closeWhenOthersOpen: true, 3426 cssStyles: { 3427 padding: '12px 14px', 3428 backgroundColor: '#fff', 3429 border: '4px solid #e2dcc5', 3430 fontFamily: '"Lucida Grande","Arial",sans-serif', 3431 fontSize: '13px', 3432 //lineHeight: '18px', 3433 color: '#615132' 3434 }, 3435 shrinkToFit: false, 3436 width: '230px', 3437 padding: 0, 3438 spikeGirth: 0, 3439 spikeLength: 0, 3440 overlap: '22px', 3441 overlay: false, 3442 killTitle: false, 3443 textzIndex: 9999, 3444 boxzIndex: 9998, 3445 wrapperzIndex: 9997, 3446 offsetParent: null, 3447 positions: ['top'], 3448 fill: 'white', 3449 windowMargin: 10, 3450 strokeWidth: 0, 3451 cornerRadius: 0, 3452 centerPointX: 0, 3453 centerPointY: 0, 3454 shadow: false 3455 }) 3456 .hover( function() { 3457 // remove from other markers then turn on just for this 3458 // XXX should be done when nav slider moves 3459 $('.search,.chapter').removeClass('front'); 3460 $(this).addClass('front'); 3461 }, function() { 3462 $(this).removeClass('front'); 3463 } 3464 ) 3465 .bind('click', function() { 3466 $(this).data('self').jumpToIndex($(this).data('pageIndex')); 3467 }); 3468 3469 $(marker).animate({top:'-25px'}, 'slow'); 3470 3471} 3472 3473BookReader.prototype.removeSearchResults = function() { 3474 this.removeSearchHilites(); //be sure to set all box.divs to null 3475 $('#BRnavpos .search').remove(); 3476} 3477 3478BookReader.prototype.addChapter = function(chapterTitle, pageNumber, pageIndex) { 3479 var uiStringPage = 'Page'; // i18n 3480 3481 var percentThrough = BookReader.util.cssPercentage(pageIndex, this.numLeafs - 1); 3482 3483 $('<div class="chapter" style="left:' + percentThrough + ';"><div class="title">' 3484 + chapterTitle + '<span>|</span> ' + uiStringPage + ' ' + pageNumber + '</div></div>') 3485 .appendTo('#BRnavline') 3486 .data({'self': this, 'pageIndex': pageIndex }) 3487 .bt({ 3488 contentSelector: '$(this).find(".title")', 3489 trigger: 'hover', 3490 closeWhenOthersOpen: true, 3491 cssStyles: { 3492 padding: '12px 14px', 3493 backgroundColor: '#000', 3494 border: '4px solid #e2dcc5', 3495 //borderBottom: 'none', 3496 fontFamily: '"Arial", sans-serif', 3497 fontSize: '12px', 3498 fontWeight: '700', 3499 color: '#fff', 3500 whiteSpace: 'nowrap' 3501 }, 3502 shrinkToFit: true, 3503 width: '200px', 3504 padding: 0, 3505 spikeGirth: 0, 3506 spikeLength: 0, 3507 overlap: '21px', 3508 overlay: false, 3509 killTitle: true, 3510 textzIndex: 9999, 3511 boxzIndex: 9998, 3512 wrapperzIndex: 9997, 3513 offsetParent: null, 3514 positions: ['top'], 3515 fill: 'black', 3516 windowMargin: 10, 3517 strokeWidth: 0, 3518 cornerRadius: 0, 3519 centerPointX: 0, 3520 centerPointY: 0, 3521 shadow: false 3522 }) 3523 .hover( function() { 3524 // remove hover effect from other markers then turn on just for this 3525 $('.search,.chapter').removeClass('front'); 3526 $(this).addClass('front'); 3527 }, function() { 3528 $(this).removeClass('front'); 3529 } 3530 ) 3531 .bind('click', function() { 3532 $(this).data('self').jumpToIndex($(this).data('pageIndex')); 3533 }); 3534} 3535 3536/* 3537 * Remove all chapters. 3538 */ 3539BookReader.prototype.removeChapters = function() { 3540 $('#BRnavpos .chapter').remove(); 3541} 3542 3543/* 3544 * Update the table of contents based on array of TOC entries. 3545 */ 3546BookReader.prototype.updateTOC = function(tocEntries) { 3547 this.removeChapters(); 3548 for (var i = 0; i < tocEntries.length; i++) { 3549 this.addChapterFromEntry(tocEntries[i]); 3550 } 3551} 3552 3553/*
3554 * Example table of contents entry - this format is defined by Open Library 3555 * { 3556 * "pagenum": "17", 3557 * "level": 1, 3558 * "label": "CHAPTER I", 3559 * "type": {"key": "/type/toc_item"}, 3560 * "title": "THE COUNTRY AND THE MISSION" 3561 * } 3562 */ 3563BookReader.prototype.addChapterFromEntry = function(tocEntryObject) { 3564 var pageIndex = this.getPageIndex(tocEntryObject['pagenum']); 3565 // Only add if we know where it is 3566 if (pageIndex) { 3567 this.addChapter(tocEntryObject['title'], tocEntryObject['pagenum'], pageIndex); 3568 } 3569 $('.chapter').each(function(){ 3570 $(this).hover(function(){ 3571 $(this).addClass('front'); 3572 },function(){ 3573 $(this).removeClass('front'); 3574 }); 3575 }); 3576 $('.search').each(function(){ 3577 $(this).hover(function(){ 3578 $(this).addClass('front'); 3579 },function(){ 3580 $(this).removeClass('front'); 3581 }); 3582 }); 3583 $('.searchChap').each(function(){ 3584 $(this).hover(function(){ 3585 $(this).addClass('front'); 3586 },function(){ 3587 $(this).removeClass('front'); 3588 }); 3589 }); 3590} 3591 3592BookReader.prototype.initToolbar = function(mode, ui) { 3593 if (ui == "embed") { 3594 return; // No toolbar at top in embed mode 3595 } 3596 3597 // $$$mang should be contained within the BookReader div instead of body 3598 var readIcon = ''; 3599 if (!navigator.userAgent.match(/mobile/i)) { 3600 readIcon = "<button class='BRicon read modal'></button>"; 3601 } 3602 3603 $("#BookReader").append( 3604 "<div id='BRtoolbar'>" 3605 + "<span id='BRtoolbarbuttons'>" 3606 + "<form action='javascript:br.search($(\"#textSrch\").val());' id='booksearch'><input type='search' id='textSrch' name='textSrch' val='' placeholder='Search inside'/><button type='submit' id='btnSrch' name='btnSrch'>GO</button></form>" 3607 + "<button class='BRicon play'></button>" 3608 + "<button class='BRicon pause'></button>" 3609 /* + "<button class='BRicon info'></button>" 3610 + "<button class='BRicon share'></button>" */ 3611 + readIcon 3612 //+ "<button class='BRicon full'></button>" 3613 + "</span>" 3614 + "<span><a class='logo' href='" + this.logoURL + "'></a></span>" 3615 + "<span id='BRreturn'><a></a></span>" 3616 + "<div id='BRnavCntlTop' class='BRnabrbuvCntl'></div>" 3617 + "</div>" 3618 /* 3619 + "<div id='BRzoomer'>" 3620 + "<div id='BRzoompos'>" 3621 + "<button class='BRicon zoom_out'></button>" 3622 + "<div id='BRzoomcontrol'>" 3623 + "<div id='BRzoomstrip'></div>" 3624 + "<div id='BRzoombtn'></div>" 3625 + "</div>" 3626 + "<button class='BRicon zoom_in'></button>" 3627 + "</div>" 3628 + "</div>" 3629 */ 3630 ); 3631 3632 // Browser hack - bug with colorbox on iOS 3 see https://bugs.launchpad.net/bookreader/+bug/686220 3633 /* if ( navigator.userAgent.match(/ipad/i) && $.browser.webkit && (parseInt($.browser.version, 10) <= 531) ) { 3634 $('#BRtoolbarbuttons .info').hide(); 3635 $('#BRtoolbarbuttons .share').hide(); 3636 } 3637 */ 3638 $('#BRreturn a').attr('href', this.bookUrl).text(this.bookTitle); 3639 3640 $('#BRtoolbar .BRnavCntl').addClass('BRup'); 3641 $('#BRtoolbar .pause').hide(); 3642 3643 this.updateToolbarZoom(this.reduce); // Pretty format 3644 3645 if (ui == "embed" || ui == "touch") { 3646 $("#BookReader a.logo").attr("target","_blank"); 3647 } 3648 3649 // $$$ turn this into a member variable 3650 var jToolbar = $('#BRtoolbar'); // j prefix indicates jQuery object 3651 3652 // We build in mode 2 3653 jToolbar.append(); 3654 3655 // Hide mode buttons and autoplay if 2up is not available 3656 // $$$ if we end up with more than two modes we should show the applicable buttons 3657 if ( !this.canSwitchToMode(this.constMode2up) ) { 3658 jToolbar.find('.two_page_mode, .play, .pause').hide(); 3659 } 3660 if ( !this.canSwitchToMode(this.constModeThumb) ) { 3661 jToolbar.find('.thumbnail_mode').hide(); 3662 } 3663 3664 // Hide one page button if it is the only mode available 3665 if ( ! (this.canSwitchToMode(this.constMode2up) || this.canSwitchToMode(this.constModeThumb)) ) { 3666 jToolbar.find('.one_page_mode').hide(); 3667 } 3668 3669 // $$$ Don't hardcode ids 3670 var self = this;
3671 jToolbar.find('.share').colorbox({inline: true, opacity: "0.5", href: "#BRshare", onLoad: function() { self.autoStop(); self.ttsStop(); } }); 3672 jToolbar.find('.info').colorbox({inline: true, opacity: "0.5", href: "#BRinfo", onLoad: function() { self.autoStop(); self.ttsStop(); } }); 3673 3674 $('<div style="display: none;"></div>').append(this.blankShareDiv()).append(this.blankInfoDiv()).appendTo($('body')); 3675 3676 $('#BRinfo .BRfloatTitle a').attr( {'href': this.bookUrl} ).text(this.bookTitle).addClass('title'); 3677 3678 // These functions can be overridden 3679 this.buildInfoDiv($('#BRinfo')); 3680 this.buildShareDiv($('#BRshare')); 3681 3682 // Switch to requested mode -- binds other click handlers 3683 //this.switchToolbarMode(mode); 3684 3685} 3686 3687BookReader.prototype.blankInfoDiv = function() { 3688 return $([ 3689 '<div class="BRfloat" id="BRinfo">', 3690 '<div class="BRfloatHead">About this book', 3691 '<a class="floatShut" href="javascript:;" onclick="$.fn.colorbox.close();"><span class="shift">Close</span></a>', 3692 '</div>', 3693 '<div class="BRfloatBody">', 3694 '<div class="BRfloatCover">', 3695 '</div>', 3696 '<div class="BRfloatMeta">', 3697 '<div class="BRfloatTitle">', 3698 '<h2><a/></h2>', 3699 '</div>', 3700 '</div>', 3701 '</div>', 3702 '<div class="BRfloatFoot">', 3703 '</div>', 3704 '</div>'].join('\n') 3705 ); 3706} 3707 3708BookReader.prototype.blankShareDiv = function() { 3709 return $([ 3710 '<div class="BRfloat" id="BRshare">', 3711 '<div class="BRfloatHead">', 3712 'Share', 3713 '<a class="floatShut" href="javascript:;" onclick="$.fn.colorbox.close();"><span class="shift">Close</span></a>', 3714 '</div>', 3715 '</div>'].join('\n') 3716 ); 3717} 3718 3719 3720// switchToolbarMode 3721//______________________________________________________________________________ 3722// Update the toolbar for the given mode (changes navigation buttons) 3723// $$$ we should soon split the toolbar out into its own module 3724BookReader.prototype.switchToolbarMode = function(mode) { 3725 if (1 == mode) { 3726 // 1-up 3727 $('#BRtoolbar .BRtoolbarzoom').show().css('display', 'inline'); 3728 $('#BRtoolbar .BRtoolbarmode2').hide(); 3729 $('#BRtoolbar .BRtoolbarmode3').hide(); 3730 $('#BRtoolbar .BRtoolbarmode1').show().css('display', 'inline'); 3731 } else if (2 == mode) { 3732 // 2-up 3733 $('#BRtoolbar .BRtoolbarzoom').show().css('display', 'inline'); 3734 $('#BRtoolbar .BRtoolbarmode1').hide(); 3735 $('#BRtoolbar .BRtoolbarmode3').hide(); 3736 $('#BRtoolbar .BRtoolbarmode2').show().css('display', 'inline'); 3737 } else { 3738 // 3-up 3739 $('#BRtoolbar .BRtoolbarzoom').hide(); 3740 $('#BRtoolbar .BRtoolbarmode2').hide(); 3741 $('#BRtoolbar .BRtoolbarmode1').hide(); 3742 $('#BRtoolbar .BRtoolbarmode3').show().css('display', 'inline'); 3743 } 3744} 3745 3746// updateToolbarZoom(reduce) 3747//______________________________________________________________________________ 3748// Update the displayed zoom factor based on reduction factor 3749BookReader.prototype.updateToolbarZoom = function(reduce) { 3750 var value; 3751 var autofit = null; 3752 3753 // $$$ TODO preserve zoom/fit for each mode 3754 if (this.mode == this.constMode2up) { 3755 autofit = this.twoPage.autofit; 3756 } else { 3757 autofit = this.onePage.autofit; 3758 } 3759 3760 if (autofit) { 3761 value = autofit.slice(0,1).toUpperCase() + autofit.slice(1); 3762 } else { 3763 value = (100 / reduce).toFixed(2); 3764 // Strip trailing zeroes and decimal if all zeroes 3765 value = value.replace(/0+$/,''); 3766 value = value.replace(/\.$/,''); 3767 value += '%'; 3768 } 3769 $('#BRzoom').text(value); 3770} 3771 3772// bindNavigationHandlers 3773//______________________________________________________________________________ 3774// Bind navigation handlers 3775BookReader.prototype.bindNavigationHandlers = function() { 3776 3777 var self = this; // closure 3778 jIcons = $('.BRicon'); 3779 3780 jIcons.filter('.onepg').bind('click', function(e) { 3781 self.switchMode(self.constMode1up); 3782 }); 3783 3784 jIcons.filter('.twopg').bind('click', function(e) { 3785 self.switchMode(self.constMode2up); 3786 }); 3787 3788 jIcons.filter('.thumb').bind('click', function(e) { 3789 self.switchMode(self.constModeThumb); 3790 }); 3791 3792 jIcons.filter('.fit').bind('fit', function(e) { 3793 // XXXmang implement autofit zoom 3794 }); 3795 3796 jIcons.filter('.book_left').click(function(e) { 3797 self.ttsStop(); 3798 self.left(); 3799 return false;
3800 }); 3801 3802 jIcons.filter('.book_right').click(function(e) { 3803 self.ttsStop(); 3804 self.right(); 3805 return false; 3806 }); 3807 3808 jIcons.filter('.book_up').bind('click', function(e) { 3809 if ($.inArray(self.mode, [self.constMode1up, self.constModeThumb]) >= 0) { 3810 self.scrollUp(); 3811 } else { 3812 self.prev(); 3813 } 3814 return false; 3815 }); 3816 3817 jIcons.filter('.book_down').bind('click', function(e) { 3818 if ($.inArray(self.mode, [self.constMode1up, self.constModeThumb]) >= 0) { 3819 self.scrollDown(); 3820 } else { 3821 self.next(); 3822 } 3823 return false; 3824 }); 3825 3826 jIcons.filter('.print').click(function(e) { 3827 self.printPage(); 3828 return false; 3829 }); 3830 3831 // Note: Functionality has been replaced by .share 3832 jIcons.filter('.embed').click(function(e) { 3833 self.showEmbedCode(); 3834 return false; 3835 }); 3836 3837 jIcons.filter('.bookmark').click(function(e) { 3838 self.showBookmarkCode(); 3839 return false; 3840 }); 3841 3842 jIcons.filter('.play').click(function(e) { 3843 self.autoToggle(); 3844 return false; 3845 }); 3846 3847 jIcons.filter('.pause').click(function(e) { 3848 self.autoToggle(); 3849 return false; 3850 }); 3851 3852 jIcons.filter('.book_top').click(function(e) { 3853 self.first(); 3854 return false; 3855 }); 3856 3857 jIcons.filter('.book_bottom').click(function(e) { 3858 self.last(); 3859 return false; 3860 }); 3861 3862 jIcons.filter('.book_leftmost').click(function(e) { 3863 self.leftmost(); 3864 return false; 3865 }); 3866 3867 jIcons.filter('.book_rightmost').click(function(e) { 3868 self.rightmost(); 3869 return false; 3870 }); 3871 3872 jIcons.filter('.read').click(function(e) { 3873 self.ttsToggle(); 3874 return false; 3875 }); 3876 3877 jIcons.filter('.zoom_in').bind('click', function() { 3878 self.ttsStop(); 3879 self.zoom(1); 3880 return false; 3881 }); 3882 3883 jIcons.filter('.zoom_out').bind('click', function() { 3884 self.ttsStop(); 3885 self.zoom(-1); 3886 return false; 3887 }); 3888 3889 jIcons.filter('.full').bind('click', function() { 3890 if (self.ui == 'embed') { 3891 // $$$ bit of a hack, IA-specific 3892 var url = (window.location + '').replace("?ui=embed",""); 3893 window.open(url); 3894 } 3895 3896 // Not implemented 3897 }); 3898 3899 $('.BRnavCntl').click( 3900 function(){ 3901 if ($('#BRnavCntlBtm').hasClass('BRdn')) { 3902 $('#BRtoolbar').animate({top:-40}); 3903 $('#BRnav').animate({bottom:-55}); 3904 $('#BRnavCntlBtm').addClass('BRup').removeClass('BRdn'); 3905 $('#BRnavCntlTop').addClass('BRdn').removeClass('BRup'); 3906 $('#BRnavCntlBtm.BRnavCntl').animate({height:'45px'}); 3907 $('.BRnavCntl').delay(1000).animate({opacity:.25},1000); 3908 } else { 3909 $('#BRtoolbar').animate({top:0}); 3910 $('#BRnav').animate({bottom:0}); 3911 $('#BRnavCntlBtm').addClass('BRdn').removeClass('BRup'); 3912 $('#BRnavCntlTop').addClass('BRup').removeClass('BRdn'); 3913 $('#BRnavCntlBtm.BRnavCntl').animate({height:'30px'}); 3914 $('.BRvavCntl').animate({opacity:1}) 3915 }; 3916 } 3917 ); 3918 $('#BRnavCntlBtm').mouseover(function(){ 3919 if ($(this).hasClass('BRup')) { 3920 $('.BRnavCntl').animate({opacity:1},250); 3921 }; 3922 }); 3923 $('#BRnavCntlBtm').mouseleave(function(){ 3924 if ($(this).hasClass('BRup')) { 3925 $('.BRnavCntl').animate({opacity:.25},250); 3926 }; 3927 }); 3928 $('#BRnavCntlTop').mouseover(function(){ 3929 if ($(this).hasClass('BRdn')) { 3930 $('.BRnavCntl').animate({opacity:1},250); 3931 }; 3932 }); 3933 $('#BRnavCntlTop').mouseleave(function(){ 3934 if ($(this).hasClass('BRdn')) { 3935 $('.BRnavCntl').animate({opacity:.25},250); 3936 }; 3937 }); 3938 3939 3940 this.initSwipeData(); 3941 $('#BookReader').die('mousemove.navigation').live('mousemove.navigation', 3942 { 'br': this }, 3943 this.navigationMousemoveHandler 3944 ); 3945 3946 $('.BRpageimage').die('mousedown.swipe').live('mousedown.swipe', 3947 { 'br': this }, 3948 this.swipeMousedownHandler 3949 ); 3950 3951 this.bindMozTouchHandlers(); 3952} 3953 3954// unbindNavigationHandlers 3955//______________________________________________________________________________ 3956// Unbind navigation handlers 3957BookReader.prototype.unbindNavigationHandlers = function() { 3958 $('#BookReader').die('mousemove.navigation'); 3959} 3960 3961// navigationMousemoveHandler 3962//______________________________________________________________________________ 3963// Handle mousemove related to navigation. Bind at #BookReader level to allow autohide. 3964BookReader.prototype.navigationMousemoveHandler = function(event) { 3965 // $$$ possibly not great to be calling this for every mousemove 3966 3967 if (event.data['br'].uiAutoHide) { 3968 var navkey = $(document).height() - 75; 3969 if ((event.pageY < 76) || (event.pageY > navkey)) { 3970 // inside or near navigation elements 3971 event.data['br'].hideNavigation(); 3972 } else { 3973 event.data['br'].showNavigation(); 3974 } 3975 } 3976} 3977 3978BookReader.prototype.initSwipeData = function(clientX, clientY) { 3979 /* 3980 * Based on the really quite awesome "Today's Guardian" at http://guardian.gyford.com/ 3981 */ 3982 this._swipe = { 3983 mightBeSwiping: false,
3984 didSwipe: false, 3985 mightBeDraggin: false, 3986 didDrag: false, 3987 startTime: (new Date).getTime(), 3988 startX: clientX, 3989 startY: clientY, 3990 lastX: clientX, 3991 lastY: clientY, 3992 deltaX: 0, 3993 deltaY: 0, 3994 deltaT: 0 3995 } 3996} 3997 3998BookReader.prototype.swipeMousedownHandler = function(event) { 3999 //console.log('swipe mousedown'); 4000 //console.log(event); 4001 4002 var self = event.data['br']; 4003 4004 // We should be the last bubble point for the page images 4005 // Disable image drag and select, but keep right-click 4006 if (event.which == 3) { 4007 if (self.protected) { 4008 return false; 4009 } 4010 return true; 4011 } 4012 4013 $(event.target).bind('mouseout.swipe', 4014 { 'br': self}, 4015 self.swipeMouseupHandler 4016 ).bind('mouseup.swipe', 4017 { 'br': self}, 4018 self.swipeMouseupHandler 4019 ).bind('mousemove.swipe', 4020 { 'br': self }, 4021 self.swipeMousemoveHandler 4022 ); 4023 4024 self.initSwipeData(event.clientX, event.clientY); 4025 self._swipe.mightBeSwiping = true; 4026 self._swipe.mightBeDragging = true; 4027 4028 event.preventDefault(); 4029 event.returnValue = false; 4030 event.cancelBubble = true; 4031 return false; 4032} 4033 4034BookReader.prototype.swipeMousemoveHandler = function(event) { 4035 //console.log('swipe move ' + event.clientX + ',' + event.clientY); 4036 4037 var _swipe = event.data['br']._swipe; 4038 if (! _swipe.mightBeSwiping) { 4039 return; 4040 } 4041 4042 // Update swipe data 4043 _swipe.deltaX = event.clientX - _swipe.startX; 4044 _swipe.deltaY = event.clientY - _swipe.startY; 4045 _swipe.deltaT = (new Date).getTime() - _swipe.startTime; 4046 4047 var absX = Math.abs(_swipe.deltaX); 4048 var absY = Math.abs(_swipe.deltaY); 4049 4050 // Minimum distance in the amount of tim to trigger the swipe 4051 var minSwipeLength = Math.min($('#BookReader').width() / 5, 80); 4052 var maxSwipeTime = 400; 4053 4054 // Check for horizontal swipe 4055 if (absX > absY && (absX > minSwipeLength) && _swipe.deltaT < maxSwipeTime) { 4056 //console.log('swipe! ' + _swipe.deltaX + ',' + _swipe.deltaY + ' ' + _swipe.deltaT + 'ms'); 4057 4058 _swipe.mightBeSwiping = false; // only trigger once 4059 _swipe.didSwipe = true; 4060 if (event.data['br'].mode == event.data['br'].constMode2up) { 4061 if (_swipe.deltaX < 0) { 4062 event.data['br'].right(); 4063 } else { 4064 event.data['br'].left(); 4065 } 4066 } 4067 } 4068 4069 if ( _swipe.deltaT > maxSwipeTime && !_swipe.didSwipe) { 4070 if (_swipe.mightBeDragging) { 4071 // Dragging 4072 _swipe.didDrag = true; 4073 $('#BRcontainer') 4074 .scrollTop($('#BRcontainer').scrollTop() - event.clientY + _swipe.lastY) 4075 .scrollLeft($('#BRcontainer').scrollLeft() - event.clientX + _swipe.lastX); 4076 } 4077 } 4078 _swipe.lastX = event.clientX; 4079 _swipe.lastY = event.clientY; 4080 4081 event.preventDefault(); 4082 event.returnValue = false; 4083 event.cancelBubble = true; 4084 return false; 4085} 4086BookReader.prototype.swipeMouseupHandler = function(event) { 4087 var _swipe = event.data['br']._swipe; 4088 //console.log('swipe mouseup - did swipe ' + _swipe.didSwipe); 4089 _swipe.mightBeSwiping = false; 4090 _swipe.mightBeDragging = false; 4091 4092 $(event.target).unbind('mouseout.swipe').unbind('mouseup.swipe').unbind('mousemove.swipe'); 4093 4094 if (_swipe.didSwipe || _swipe.didDrag) { 4095 // Swallow event if completed swipe gesture 4096 event.preventDefault(); 4097 event.returnValue = false; 4098 event.cancelBubble = true; 4099 return false; 4100 } 4101 return true; 4102} 4103BookReader.prototype.bindMozTouchHandlers = function() { 4104 var self = this; 4105 4106 // Currently only want touch handlers in 2up 4107 $('#BookReader').bind('MozTouchDown', function(event) { 4108 //console.log('MozTouchDown ' + event.originalEvent.streamId + ' ' + event.target + ' ' + event.clientX + ',' + event.clientY); 4109 if (this.mode == this.constMode2up) { 4110 event.preventDefault(); 4111 } 4112 }) 4113 .bind('MozTouchMove', function(event) { 4114 //console.log('MozTouchMove - ' + event.originalEvent.streamId + ' ' + event.target + ' ' + event.clientX + ',' + event.clientY) 4115 if (this.mode == this.constMode2up) { 4116 event.preventDefault(); 4117 } 4118 }) 4119 .bind('MozTouchUp', function(event) { 4120 //console.log('MozTouchUp - ' + event.originalEvent.streamId + ' ' + event.target + ' ' + event.clientX + ',' + event.clientY); 4121 if (this.mode = this.constMode2up) { 4122 event.preventDefault(); 4123 } 4124 }); 4125} 4126 4127// navigationIsVisible 4128//______________________________________________________________________________ 4129// Returns true if the navigation elements are currently visible 4130BookReader.prototype.navigationIsVisible = function() { 4131 // $$$ doesn't account for transitioning states, nav must be fully visible to return true 4132 var toolpos = $('#BRtoolbar').offset(); 4133 var tooltop = toolpos.top; 4134 if (tooltop == 0) { 4135 return true; 4136 } 4137 return false;
4138} 4139 4140// hideNavigation 4141//______________________________________________________________________________ 4142// Hide navigation elements, if visible 4143BookReader.prototype.hideNavigation = function() { 4144 // Check if navigation is showing 4145 if (this.navigationIsVisible()) { 4146 // $$$ don't hardcode height 4147 $('#BRtoolbar').animate({top:-60}); 4148 $('#BRnav').animate({bottom:-60}); 4149 //$('#BRzoomer').animate({right:-26}); 4150 } 4151} 4152 4153// showNavigation 4154//______________________________________________________________________________ 4155// Show navigation elements 4156BookReader.prototype.showNavigation = function() { 4157 // Check if navigation is hidden 4158 if (!this.navigationIsVisible()) { 4159 $('#BRtoolbar').animate({top:0}); 4160 $('#BRnav').animate({bottom:0}); 4161 //$('#BRzoomer').animate({right:0}); 4162 } 4163} 4164 4165// changeArrow 4166//______________________________________________________________________________ 4167// Change the nav bar arrow 4168function changeArrow(){ 4169 setTimeout(function(){ 4170 $('#BRnavCntlBtm').removeClass('BRdn').addClass('BRup'); 4171 },3000); 4172}; 4173 4174 4175// firstDisplayableIndex 4176//______________________________________________________________________________ 4177// Returns the index of the first visible page, dependent on the mode. 4178// $$$ Currently we cannot display the front/back cover in 2-up and will need to update 4179// this function when we can as part of https://bugs.launchpad.net/gnubook/+bug/296788 4180BookReader.prototype.firstDisplayableIndex = function() { 4181 if (this.mode != this.constMode2up) { 4182 return 0; 4183 } 4184 4185 if ('rl' != this.pageProgression) { 4186 // LTR 4187 if (this.getPageSide(0) == 'L') { 4188 return 0; 4189 } else { 4190 return -1; 4191 } 4192 } else { 4193 // RTL 4194 if (this.getPageSide(0) == 'R') { 4195 return 0; 4196 } else { 4197 return -1; 4198 } 4199 } 4200} 4201 4202// lastDisplayableIndex 4203//______________________________________________________________________________ 4204// Returns the index of the last visible page, dependent on the mode. 4205// $$$ Currently we cannot display the front/back cover in 2-up and will need to update 4206// this function when we can as pa rt of https://bugs.launchpad.net/gnubook/+bug/296788 4207BookReader.prototype.lastDisplayableIndex = function() { 4208 4209 var lastIndex = this.numLeafs - 1; 4210 4211 if (this.mode != this.constMode2up) { 4212 return lastIndex; 4213 } 4214 4215 if ('rl' != this.pageProgression) { 4216 // LTR 4217 if (this.getPageSide(lastIndex) == 'R') { 4218 return lastIndex; 4219 } else { 4220 return lastIndex + 1; 4221 } 4222 } else { 4223 // RTL 4224 if (this.getPageSide(lastIndex) == 'L') { 4225 return lastIndex; 4226 } else { 4227 return lastIndex + 1; 4228 } 4229 } 4230} 4231 4232// shortTitle(maximumCharacters) 4233//________ 4234// Returns a shortened version of the title with the maximum number of characters 4235BookReader.prototype.shortTitle = function(maximumCharacters) { 4236 if (this.bookTitle.length < maximumCharacters) { 4237 return this.bookTitle; 4238 } 4239 4240 var title = this.bookTitle.substr(0, maximumCharacters - 3); 4241 title += '...'; 4242 return title; 4243} 4244 4245// Parameter related functions 4246 4247// updateFromParams(params) 4248//________ 4249// Update ourselves from the params object. 4250// 4251// e.g. this.updateFromParams(this.paramsFromFragment(window.location.hash)) 4252BookReader.prototype.updateFromParams = function(params) { 4253 if ('undefined' != typeof(params.mode)) { 4254 this.switchMode(params.mode); 4255 } 4256 4257 // process /search 4258 if ('undefined' != typeof(params.searchTerm)) { 4259 if (this.searchTerm != params.searchTerm) { 4260 this.search(params.searchTerm); 4261 } 4262 } 4263 4264 // $$$ process /zoom 4265 4266 // We only respect page if index is not set 4267 if ('undefined' != typeof(params.index)) { 4268 if (params.index != this.currentIndex()) { 4269 this.jumpToIndex(params.index); 4270 } 4271 } else if ('undefined' != typeof(params.page)) { 4272 // $$$ this assumes page numbers are unique 4273 if (params.page != this.getPageNum(this.currentIndex())) { 4274 this.jumpToPage(params.page); 4275 } 4276 } 4277 4278 // $$$ process /region 4279 // $$$ process /highlight 4280} 4281 4282// paramsFromFragment(urlFragment) 4283//________ 4284// Returns a object with configuration parametes from a URL fragment. 4285// 4286// E.g paramsFromFragment(window.location.hash) 4287BookReader.prototype.paramsFromFragment = function(urlFragment) { 4288 4289 4290 var params = {}; 4291 4292 // For convenience we allow an initial # character (as from window.location.hash) 4293 // but don't require it 4294 if (urlFragment.substr(0,1) == '#') { 4295 urlFragment = urlFragment.substr(1); 4296 } 4297 4298 // Simple #nn syntax 4299 var oldStyleLeafNum = parseInt( /^\d+$/.exec(urlFragment) ); 4300 if ( !isNaN(oldStyleLeafNum) ) { 4301 params.index = oldStyleLeafNum; 4302 4303 // Done processing if using old-style syntax 4304 return params; 4305 } 4306 4307 // Split into key-value pairs 4308 var urlArray = urlFragment.split('/'); 4309 var urlHash = {}; 4310 for (var i = 0; i < urlArray.length; i += 2) { 4311 urlHash[urlArray[i]] = urlArray[i+1]; 4312 } 4313 4314 // Mode 4315 if ('1up' == urlHash['mode']) { 4316 params.mode = this.constMode1up; 4317 } else if ('2up' == urlHash['mode']) { 4318 params.mode = this.constMode2up; 4319 } else if ('thumb' == urlHash['mode']) { 4320 params.mode = this.constModeThumb; 4321 } 4322 4323 // Index and page 4324 if ('undefined' != typeof(urlHash['page'])) { 4325 // page was set -- may not be int 4326 params.page = urlHash['page']; 4327 } 4328 4329 // $$$ process /region 4330 // $$$ process /search 4331 4332 if (urlHash['search'] != undefined) { 4333 params.searchTerm = BookReader.util.decodeURIComponentPlus(urlHash['search']); 4334 } 4335 4336 // $$$ process /highlight 4337 4338 return params; 4339} 4340 4341// paramsFromCurrent() 4342//________ 4343// Create a params object from the current parameters. 4344BookReader.prototype.paramsFromCurrent = function() { 4345 4346 var params = {}; 4347 4348 var index = this.currentIndex(); 4349 var pageNum = this.getPageNum(index); 4350 if ((pageNum === 0) || pageNum) { 4351 params.page = pageNum; 4352 } 4353 4354 params.index = index; 4355 params.mode = this.mode; 4356 4357 // $$$ highlight 4358 // $$$ region 4359 4360 // search 4361 if (this.searchHighlightVisible()) { 4362 params.searchTerm = this.searchTerm; 4363 } 4364 4365 return params; 4366} 4367 4368// fragmentFromParams(params) 4369//________ 4370// Create a fragment string from the params object. 4371 4372BookReader.prototype.fragmentFromParams = function(params) { 4373 var separator = '/'; 4374 4375 var fragments = []; 4376 4377 if ('undefined' != typeof(params.page)) { 4378 fragments.push('page', params.page); 4379 } else { 4380 if ('undefined' != typeof(params.index)) { 4381 // Don't have page numbering but we do have the index 4382 fragments.push('page', 'n' + params.index); 4383 } 4384 } 4385 4386 // $$$ highlight 4387 // $$$ region 4388 4389 // mode 4390 if ('undefined' != typeof(params.mode)) { 4391 if (params.mode == this.constMode1up) { 4392 fragments.push('mode', '1up'); 4393 } else if (params.mode == this.constMode2up) { 4394 fragments.push('mode', '2up'); 4395 } else if (params.mode == this.constModeThumb) { 4396 fragments.push('mode', 'thumb'); 4397 } else { 4398 throw 'fragmentFromParams called with unknown mode ' + params.mode; 4399 } 4400 } 4401 4402 // search 4403 if (params.searchTerm) { 4404 fragments.push('search', params.searchTerm); 4405 } 4406 4407 return BookReader.util.encodeURIComponentPlus(fragments.join(separator)).replace(/%2F/g, '/'); 4408} 4409 4410// getPageIndex(pageNum) 4411//________ 4412// Returns the *highest* index the given page number, or undefined 4413BookReader.prototype.getPageIndex = function(pageNum) { 4414 var pageIndices = this.getPageIndices(pageNum); 4415 4416 if (pageIndices.length > 0) { 4417 return pageIndices[pageIndices.length - 1]; 4418 } 4419 4420 return undefined; 4421} 4422 4423// getPageIndices(pageNum) 4424//________ 4425// Returns an array (possibly empty) of the indices with the given page number 4426BookReader.prototype.getPageIndices = function(pageNum) { 4427 var indices = []; 4428 4429 // Check for special "nXX" page number 4430 if (pageNum.slice(0,1) == 'n') { 4431 try { 4432 var pageIntStr = pageNum.slice(1, pageNum.length); 4433 var pageIndex = parseInt(pageIntStr); 4434 indices.push(pageIndex); 4435 return indices; 4436 } catch(err) {
4437 // Do nothing... will run through page names and see if one matches 4438 } 4439 } 4440 4441 var i; 4442 for (i=0; i<this.numLeafs; i++) { 4443 if (this.getPageNum(i) == pageNum) { 4444 indices.push(i); 4445 } 4446 } 4447 4448 return indices; 4449} 4450 4451// getPageName(index) 4452//________ 4453// Returns the name of the page as it should be displayed in the user interface 4454BookReader.prototype.getPageName = function(index) { 4455 return 'Page ' + this.getPageNum(index); 4456} 4457 4458// updateLocationHash 4459//________ 4460// Update the location hash from the current parameters. Call this instead of manually 4461// using window.location.replace 4462BookReader.prototype.updateLocationHash = function() { 4463 var newHash = '#' + this.fragmentFromParams(this.paramsFromCurrent()); 4464 window.location.replace(newHash); 4465 4466 // This is the variable checked in the timer. Only user-generated changes 4467 // to the URL will trigger the event. 4468 this.oldLocationHash = newHash; 4469} 4470 4471// startLocationPolling 4472//________ 4473// Starts polling of window.location to see hash fragment changes 4474BookReader.prototype.startLocationPolling = function() { 4475 var self = this; // remember who I am 4476 self.oldLocationHash = window.location.hash; 4477 4478 if (this.locationPollId) { 4479 clearInterval(this.locationPollID); 4480 this.locationPollId = null; 4481 } 4482 4483 this.locationPollId = setInterval(function() { 4484 var newHash = window.location.hash; 4485 if (newHash != self.oldLocationHash) { 4486 if (newHash != self.oldUserHash) { // Only process new user hash once 4487 //console.log('url change detected ' + self.oldLocationHash + " -> " + newHash); 4488 4489 self.ttsStop(); 4490 4491 // Queue change if animating 4492 if (self.animating) { 4493 self.autoStop(); 4494 self.animationFinishedCallback = function() { 4495 self.updateFromParams(self.paramsFromFragment(newHash)); 4496 } 4497 } else { // update immediately 4498 self.updateFromParams(self.paramsFromFragment(newHash)); 4499 } 4500 self.oldUserHash = newHash; 4501 } 4502 } 4503 }, 500); 4504} 4505 4506// canSwitchToMode 4507//________ 4508// Returns true if we can switch to the requested mode 4509BookReader.prototype.canSwitchToMode = function(mode) { 4510 if (mode == this.constMode2up || mode == this.constModeThumb) { 4511 // check there are enough pages to display 4512 // $$$ this is a workaround for the mis-feature that we can't display 4513 // short books in 2up mode 4514 if (this.numLeafs < 2) { 4515 return false; 4516 } 4517 } 4518 4519 return true; 4520} 4521 4522// searchHighlightVisible 4523//________ 4524// Returns true if a search highlight is currently being displayed 4525BookReader.prototype.searchHighlightVisible = function() { 4526 var results = this.searchResults; 4527 if (null == results) return false; 4528 4529 if (this.constMode2up == this.mode) { 4530 var visiblePages = Array(this.twoPage.currentIndexL, this.twoPage.currentIndexR); 4531 } else if (this.constMode1up == this.mode) { 4532 var visiblePages = Array(); 4533 visiblePages[0] = this.currentIndex(); 4534 } else { 4535 return false; 4536 } 4537 4538 var i, j; 4539 for (i=0; i<results.matches.length; i++) { 4540 //console.log(results.matches[i].par[0]); 4541 for (j=0; j<results.matches[i].par[0].boxes.length; j++) { 4542 var box = results.matches[i].par[0].boxes[j]; 4543 var pageIndex = this.leafNumToIndex(box.page); 4544 if (jQuery.inArray(pageIndex, visiblePages) >= 0) { 4545 return true; 4546 } 4547 } 4548 } 4549 4550 return false; 4551} 4552 4553// _getPageWidth 4554//-------- 4555// Returns the page width for the given index, or first or last page if out of range 4556BookReader.prototype._getPageWidth = function(index) { 4557 // Synthesize a page width for pages not actually present in book. 4558 // May or may not be the best approach. 4559 // If index is out of range we return the width of first or last page 4560 index = BookReader.util.clamp(index, 0, this.numLeafs - 1); 4561 return this.getPageWidth(index); 4562} 4563 4564// _getPageHeight 4565//-------- 4566// Returns the page height for the given index, or first or last page if out of range 4567BookReader.prototype._getPageHeight= function(index) { 4568 index = BookReader.util.clamp(index, 0, this.numLeafs - 1); 4569 return this.getPageHeight(index); 4570} 4571 4572// _getPageURI 4573//-------- 4574// Returns the page URI or transparent image if out of range 4575BookReader.prototype._getPageURI = function(index, reduce, rotate) { 4576 if (index < 0 || index >= this.numLeafs) { // Synthesize page 4577 return this.imagesBaseURL + "transparent.png"; 4578 } 4579 4580 if ('undefined' == typeof(reduce)) { 4581 // reduce not passed in 4582 // $$$ this probably won't work for thumbnail mode 4583 var ratio = this.getPageHeight(index) / this.twoPage.height; 4584 var scale; 4585 // $$$ we make an assumption here that the scales are available pow2 (like kakadu) 4586 if (ratio < 2) { 4587 scale = 1; 4588 } else if (ratio < 4) { 4589 scale = 2; 4590 } else if (ratio < 8) { 4591 scale = 4; 4592 } else if (ratio < 16) { 4593 scale = 8; 4594 } else if (ratio < 32) { 4595 scale = 16; 4596 } else { 4597 scale = 32; 4598 } 4599 reduce = scale; 4600 } 4601 4602 return this.getPageURI(index, reduce, rotate); 4603} 4604 4605/* 4606 * Update based on received record from Open Library. 4607 */ 4608BookReader.prototype.gotOpenLibraryRecord = function(self, olObject) { 4609 // $$$ could refactor this so that 'this' is available 4610 if (olObject) { 4611 // console.log(olObject); 4612 if (olObject['table_of_contents']) { 4613 // XXX check here that TOC is valid 4614 self.updateTOC(olObject['table_of_contents']); 4615 } 4616 4617 // $$$mang cleanup 4618 self.bookUrl = self.olHost + olObject.key; 4619 self.bookTitle = olObject['title']; 4620 $('#BRreturn a').attr( {'href': self.bookUrl, 'title': "Go to this book's page on Open Library" } ); 4621 $('#BRreturn a').text(self.bookTitle); 4622 4623 $('#BRinfo').remove(); 4624 $('#BRshare').after(self.blankInfoDiv()); 4625 self.buildInfoDiv($('#BRinfo')); 4626 4627 // Check for borrowed book 4628 if (self.olAuth) { 4629 var returnUrl = self.olHost + olObject.key + '/do_return/borrow'; 4630 var borrowUrl = self.olHost + olObject.key + '/borrow'; 4631 4632 /* 4633 $('<a/>') 4634 .attr('href', borrowUrl) 4635 .text('Return this book') 4636 .click(function(event) { 4637 event.preventDefault(); 4638 $('#BRreturnform').trigger('submit'); 4639 }) 4640 .appendTo('#BRreturn'); 4641 */ 4642 4643 $('<form id="BRreturnform" action="' + returnUrl + '" method="post"><input type="submit" value="Return book" onclick="olAuth.deleteCookies();"/><input type="hidden" name="action" value="return" /></form>') 4644 .appendTo('#BRreturn'); 4645 4646 } else { 4647 $('<a/>').attr( { 'href': self.bookUrl, 'title': 'Go to this book\'s page on Open Library' } ) 4648 .text('On openlibrary.org') 4649 .appendTo('#BRreturn'); 4650 } 4651 4652 $('#BRreturn').css({ 'line-height': '19px'} ); 4653 $('#BRreturn a').css( {'height': '18px' } ); 4654 4655 4656 } 4657} 4658 4659// Library functions 4660BookReader.util = { 4661 disableSelect: function(jObject) { 4662 // Bind mouse handlers 4663 // Disable mouse click to avoid selected/highlighted page images - bug 354239 4664 jObject.bind('mousedown', function(e) { 4665 // $$$ check here for right-click and don't disable. Also use jQuery style 4666 // for stopping propagation. See https://bugs.edge.launchpad.net/gnubook/+bug/362626 4667 return false;
4668 }); 4669 // Special hack for IE7 4670 jObject[0].onselectstart = function(e) { return false; }; 4671 }, 4672 4673 clamp: function(value, min, max) { 4674 return Math.min(Math.max(value, min), max); 4675 }, 4676 4677 // Given value and maximum, calculate a percentage suitable for CSS 4678 cssPercentage: function(value, max) { 4679 return (((value + 0.0) / max) * 100) + '%'; 4680 }, 4681 4682 notInArray: function(value, array) { 4683 // inArray returns -1 or undefined if value not in array 4684 return ! (jQuery.inArray(value, array) >= 0); 4685 }, 4686 4687 getIFrameDocument: function(iframe) { 4688 // Adapted from http://xkr.us/articles/dom/iframe-document/ 4689 var outer = (iframe.contentWindow || iframe.contentDocument); 4690 return (outer.document || outer); 4691 }, 4692 4693 escapeHTML: function (str) { 4694 return( 4695 str.replace(/&/g,'&'). 4696 replace(/>/g,'>'). 4697 replace(/</g,'<'). 4698 replace(/"/g,'"') 4699 ); 4700 }, 4701 4702 decodeURIComponentPlus: function(value) { 4703 // Decodes a URI component and converts '+' to ' ' 4704 return decodeURIComponent(value).replace(/\+/g, ' '); 4705 }, 4706 4707 encodeURIComponentPlus: function(value) { 4708 // Encodes a URI component and converts ' ' to '+' 4709 return encodeURIComponent(value).replace(/%20/g, '+'); 4710 } 4711 // The final property here must NOT have a comma after it - IE7 4712} 4713 4714 4715// ttsToggle() 4716//______________________________________________________________________________ 4717BookReader.prototype.ttsToggle = function () { 4718 4719 this.autoStop(); 4720 4721 if (false == this.ttsPlaying) { 4722 this.ttsPlaying = true; 4723 this.showProgressPopup('Loading audio...'); 4724 if(soundManager.supported()) { 4725 this.ttsStart(); 4726 } else { 4727 soundManager.onready(function(oStatus) { 4728 if (oStatus.success) { 4729 this.ttsStart(); 4730 } else { 4731 alert('Could not load soundManager2, possibly due to FlashBlock. Audio playback is disabled'); 4732 } 4733 }, this); 4734 } 4735 } else { 4736 this.ttsStop(); 4737 } 4738} 4739 4740// ttsStart() 4741//______________________________________________________________________________ 4742BookReader.prototype.ttsStart = function () { 4743 if (soundManager.debugMode) console.log('starting readAloud'); 4744 if (this.constModeThumb == this.mode) this.switchMode(this.constMode1up); 4745 4746 //this.ttsPlaying = true; //set this in ttsToggle() 4747 this.ttsIndex = this.currentIndex(); 4748 this.ttsFormat = 'mp3'; 4749 if ($.browser.mozilla) { 4750 this.ttsFormat = 'ogg'; 4751 } 4752 this.ttsGetText(this.ttsIndex, 'ttsStartCB'); 4753} 4754 4755// ttsStop() 4756//______________________________________________________________________________ 4757BookReader.prototype.ttsStop = function () { 4758 if (false == this.ttsPlaying) return; 4759 4760 if (soundManager.debugMode) console.log('stopping readaloud'); 4761 soundManager.stopAll(); 4762 soundManager.destroySound('chunk'+this.ttsIndex+'-'+this.ttsPosition); 4763 this.ttsRemoveHilites(); 4764 this.removeProgressPopup(); 4765 4766 this.ttsPlaying = false; 4767 this.ttsIndex = null; //leaf index 4768 this.ttsPosition = -1; //chunk (paragraph) number 4769 this.ttsBuffering = false; 4770 this.ttsPoller = null; 4771} 4772 4773// ttsGetText() 4774//______________________________________________________________________________ 4775BookReader.prototype.ttsGetText = function(index, callback) { 4776 var url = 'https://'+this.server+'/BookReader/BookReaderGetTextWrapper.php?path='+this.bookPath+'_djvu.xml&page='+index; 4777 this.ttsAjax = $.ajax({url:url, dataType:'jsonp', jsonpCallback:callback}); 4778} 4779 4780// ttsStartCB(): text-to-speech callback 4781//______________________________________________________________________________ 4782BookReader.prototype.ttsStartCB = function (data) { 4783 if (soundManager.debugMode) console.log('ttsStartCB got data: ' + data); 4784 this.ttsChunks = data; 4785 this.ttsHilites = []; 4786 4787 //deal with the page being blank 4788 if (0 == data.length) { 4789 if (soundManager.debugMode) console.log('first page is blank!'); 4790 if(this.ttsAdvance(true)) { 4791 this.ttsGetText(this.ttsIndex, 'ttsStartCB'); 4792 } 4793 return; 4794 } 4795 4796 this.showProgressPopup('Loading audio...'); 4797 4798 ///// Many soundManger2 callbacks are broken when using HTML5 audio. 4799 ///// whileloading: broken on safari, worked in FF4, but broken on FireFox 5 4800 ///// onload: fires on safari, but *after* the sound starts playing, and does not fire in FF or IE9 4801 ///// onbufferchange: fires in FF5 using HTML5 audio, but not in safari using flash audio 4802 ///// whileplaying: fires everywhere 4803 this.ttsPosition = -1; 4804 var snd = soundManager.createSound({ 4805 id: 'chunk'+this.ttsIndex+'-0',
4806 url: 'https://'+this.server+'/BookReader/BookReaderGetTTS.php?string=' + escape(data[0][0]) + '&format=.'+this.ttsFormat, //the .ogg is to trick SoundManager2 to use the HTML5 audio player 4807 onload: function(){this.br.removeProgressPopup();}, //fires in safari... 4808 onbufferchange: function(){if (false == this.isBuffering) this.br.removeProgressPopup();} //fires in FF and IE9 4809 }); 4810 snd.br = this; 4811 snd.load(); 4812 4813 this.ttsNextChunk(); 4814} 4815 4816// showProgressPopup 4817//______________________________________________________________________________ 4818BookReader.prototype.showProgressPopup = function(msg) { 4819 //if (soundManager.debugMode) console.log('showProgressPopup index='+this.ttsIndex+' pos='+this.ttsPosition); 4820 if (this.popup) return; 4821 4822 this.popup = document.createElement("div"); 4823 $(this.popup).css({ 4824 top: ($('#BookReader').height()*0.5-100) + 'px', 4825 left: ($('#BookReader').width()-300)*0.5 + 'px' 4826 }).attr('className', 'BRprogresspopup'); 4827 4828 var bar = document.createElement("div"); 4829 $(bar).css({ 4830 height: '20px' 4831 }).attr('className', 'BRprogressbar'); 4832 $(this.popup).append(bar); 4833 4834 if (msg) { 4835 var msgdiv = document.createElement("div"); 4836 msgdiv.innerHTML = msg; 4837 $(this.popup).append(msgdiv); 4838 } 4839 4840 $(this.popup).appendTo('#BookReader'); 4841} 4842 4843// removeProgressPopup 4844//______________________________________________________________________________ 4845BookReader.prototype.removeProgressPopup = function() { 4846 $(this.popup).remove(); 4847 this.popup=null; 4848} 4849 4850// ttsNextPageCB 4851//______________________________________________________________________________ 4852BookReader.prototype.ttsNextPageCB = function (data) { 4853 this.ttsNextChunks = data; 4854 if (soundManager.debugMode) console.log('preloaded next chunks.. data is ' + data); 4855 4856 if (true == this.ttsBuffering) { 4857 if (soundManager.debugMode) console.log('ttsNextPageCB: ttsBuffering is true'); 4858 this.ttsBuffering = false; 4859 } 4860} 4861 4862// ttsLoadChunk 4863//______________________________________________________________________________ 4864BookReader.prototype.ttsLoadChunk = function (page, pos, string) { 4865 var snd = soundManager.createSound({ 4866 id: 'chunk'+page+'-'+pos, 4867 url: 'https://'+this.server+'/BookReader/BookReaderGetTTS.php?string=' + escape(string) + '&format=.'+this.ttsFormat //the .ogg is to trick SoundManager2 to use the HTML5 audio player 4868 }); 4869 snd.br = this; 4870 snd.load() 4871} 4872 4873 4874// ttsNextChunk() 4875//______________________________________________________________________________ 4876// This function into two parts: ttsNextChunk gets run before page flip animation 4877// and ttsNextChunkPhase2 get run after page flip animation. 4878// If a page flip is necessary, ttsAdvance() will return false so Phase2 isn't 4879// called. Instead, this.animationFinishedCallback is set, so that Phase2 4880// continues after animation is finished. 4881 4882BookReader.prototype.ttsNextChunk = function () { 4883 if (soundManager.debugMode) console.log('nextchunk pos=' + this.ttsPosition); 4884 4885 if (-1 != this.ttsPosition) { 4886 soundManager.destroySound('chunk'+this.ttsIndex+'-'+this.ttsPosition); 4887 } 4888 4889 this.ttsRemoveHilites(); //remove old hilights 4890 4891 var moreToPlay = this.ttsAdvance(); 4892 4893 if (moreToPlay) { 4894 this.ttsNextChunkPhase2(); 4895 } 4896 4897 //This function is called again when ttsPlay() has finished playback. 4898 //If the next chunk of text has not yet finished loading, ttsPlay() 4899 //will start polling until the next chunk is ready. 4900} 4901 4902// ttsNextChunkPhase2() 4903//______________________________________________________________________________ 4904// page flip animation has now completed 4905BookReader.prototype.ttsNextChunkPhase2 = function () { 4906 if (null == this.ttsChunks) { 4907 alert('error: ttsChunks is null?'); //TODO 4908 return; 4909 } 4910 4911 if (0 == this.ttsChunks.length) { 4912 if (soundManager.debugMode) console.log('ttsNextChunk2: ttsChunks.length is zero.. hacking...'); 4913 this.ttsStartCB(this.ttsChunks); 4914 return; 4915 } 4916 4917 if (soundManager.debugMode) console.log('next chunk is ' + this.tt
4917sPosition); 4918 4919 //prefetch next page of text 4920 if (0 == this.ttsPosition) { 4921 if (this.ttsIndex<(this.numLeafs-1)) { 4922 this.ttsGetText(this.ttsIndex+1, 'ttsNextPageCB'); 4923 } 4924 } 4925 4926 this.ttsPrefetchAudio(); 4927 4928 this.ttsPlay(); 4929} 4930 4931// ttsAdvance() 4932//______________________________________________________________________________ 4933// 1. advance ttsPosition 4934// 2. if necessary, advance ttsIndex, and copy ttsNextChunks to ttsChunks 4935// 3. if necessary, flip to current page, or scroll so chunk is visible 4936// 4. do something smart is ttsNextChunks has not yet finished preloading (TODO) 4937// 5. stop playing at end of book 4938 4939BookReader.prototype.ttsAdvance = function (starting) { 4940 this.ttsPosition++; 4941 4942 if (this.ttsPosition >= this.ttsChunks.length) { 4943 4944 if (this.ttsIndex == (this.numLeafs-1)) { 4945 if (soundManager.debugMode) console.log('tts stop'); 4946 return false; 4947 } else { 4948 if ((null != this.ttsNextChunks) || (starting)) { 4949 if (soundManager.debugMode) console.log('moving to next page!'); 4950 this.ttsIndex++; 4951 this.ttsPosition = 0; 4952 this.ttsChunks = this.ttsNextChunks; 4953 this.ttsNextChunks = null; 4954 4955 //A page flip might be necessary. This code is confusing since 4956 //ttsNextChunks might be null if we are starting on a blank page. 4957 if (2 == this.mode) { 4958 if ((this.ttsIndex != this.twoPage.currentIndexL) && (this.ttsIndex != this.twoPage.currentIndexR)) { 4959 if (!starting) { 4960 this.animationFinishedCallback = this.ttsNextChunkPhase2; 4961 this.next(); 4962 return false; 4963 } else { 4964 this.next(); 4965 return true; 4966 } 4967 } else { 4968 return true; 4969 } 4970 } 4971 } else { 4972 if (soundManager.debugMode) console.log('ttsAdvance: ttsNextChunks is null'); 4973 return false; 4974 } 4975 } 4976 } 4977 4978 return true; 4979} 4980 4981// ttsPrefetchAudio() 4982//______________________________________________________________________________ 4983BookReader.prototype.ttsPrefetchAudio = function () { 4984 4985 if(false != this.ttsBuffering) { 4986 alert('TTS Error: prefetch() called while content still buffering!'); 4987 return; 4988 } 4989 4990 //preload next chunk 4991 var nextPos = this.ttsPosition+1; 4992 if (nextPos < this.ttsChunks.length) { 4993 this.ttsLoadChunk(this.ttsIndex, nextPos, this.ttsChunks[nextPos][0]); 4994 } else { 4995 //for a short page, preload might nt have yet returned.. 4996 if (soundManager.debugMode) console.log('preloading chunk 0 from next page, index='+(this.ttsIndex+1)); 4997 if (null != this.ttsNextChunks) { 4998 if (0 != this.ttsNextChunks.length) { 4999 this.ttsLoadChunk(this.ttsIndex+1, 0, this.ttsNextChunks[0][0]); 5000 } else { 5001 if (soundManager.debugMode) console.log('prefetchAudio(): ttsNextChunks is zero length!'); 5002 } 5003 } else { 5004 if (soundManager.debugMode) console.log('ttsNextChunks is null, not preloading next page'); 5005 this.ttsBuffering = true; 5006 } 5007 } 5008 5009} 5010 5011// ttsPlay() 5012//______________________________________________________________________________ 5013BookReader.prototype.ttsPlay = function () { 5014 5015 var chunk = this.ttsChunks[this.ttsPosition]; 5016 if (soundManager.debugMode) {
5017 console.log('ttsPlay position = ' + this.ttsPosition); 5018 console.log('chunk = ' + chunk); 5019 console.log(this.ttsChunks); 5020 } 5021 5022 //add new hilights 5023 if (2 == this.mode) { 5024 this.ttsHilite2UP(chunk); 5025 } else { 5026 this.ttsHilite1UP(chunk); 5027 } 5028 5029 this.ttsScrollToChunk(chunk); 5030 5031 //play current chunk 5032 if (false == this.ttsBuffering) { 5033 soundManager.play('chunk'+this.ttsIndex+'-'+this.ttsPosition,{onfinish:function(){br.ttsNextChunk();}}); 5034 } else { 5035 soundManager.play('chunk'+this.ttsIndex+'-'+this.ttsPosition,{onfinish:function(){br.ttsStartPolling();}}); 5036 } 5037} 5038 5039// scrollToChunk() 5040//______________________________________________________________________________ 5041BookReader.prototype.ttsScrollToChunk = function(chunk) { 5042 if (this.constMode1up != this.mode) return; 5043 5044 var leafTop = 0; 5045 var h; 5046 var i; 5047 for (i=0; i<this.ttsIndex; i++) { 5048 h = parseInt(this._getPageHeight(i)/this.reduce); 5049 leafTop += h + this.padding; 5050 } 5051 5052 var chunkTop = chunk[1][3]; //coords are in l,b,r,t order 5053 var chunkBot = chunk[chunk.length-1][1]; 5054 5055 var topOfFirstChunk = leafTop + chunkTop/this.reduce; 5056 var botOfLastChunk = leafTop + chunkBot/this.reduce; 5057 5058 if (soundManager.debugMode) console.log('leafTop = ' + leafTop + ' topOfFirstChunk = ' + topOfFirstChunk + ' botOfLastChunk = ' + botOfLastChunk); 5059 5060 var containerTop = $('#BRcontainer').attr('scrollTop'); 5061 var containerBot = containerTop + $('#BRcontainer').height(); 5062 if (soundManager.debugMode) console.log('containerTop = ' + containerTop + ' containerBot = ' + containerBot); 5063 5064 if ((topOfFirstChunk < containerTop) || (botOfLastChunk > containerBot)) { 5065 //jumpToIndex scrolls so that chunkTop is centered.. we want chunkTop at the top 5066 //this.jumpToIndex(this.ttsIndex, null, chunkTop); 5067 $('#BRcontainer').animate({scrollTop: topOfFirstChunk},'fast'); 5068 } 5069} 5070 5071// ttsHilite1UP() 5072//______________________________________________________________________________ 5073BookReader.prototype.ttsHilite1UP = function(chunk) { 5074 var i; 5075 for (i=1; i<chunk.length; i++) { 5076 //each rect is an array of l,b,r,t coords (djvu.xml ordering...) 5077 var l = chunk[i][0]; 5078 var b = chunk[i][1]; 5079 var r = chunk[i][2]; 5080 var t = chunk[i][3]; 5081 5082 var div = document.createElement('div'); 5083 this.ttsHilites.push(div); 5084 $(div).attr('className', 'BookReaderSearchHilite').appendTo('#pagediv'+this.ttsIndex); 5085 5086 $(div).css({ 5087 width: (r-l)/this.reduce + 'px', 5088 height: (b-t)/this.reduce + 'px', 5089 left: l/this.reduce + 'px', 5090 top: t/this.reduce +'px' 5091 }); 5092 } 5093 5094} 5095 5096// ttsHilite2UP() 5097//______________________________________________________________________________ 5098BookReader.prototype.ttsHilite2UP = function (chunk) { 5099 var i; 5100 for (i=1; i<chunk.length; i++) { 5101 //each rect is an array of l,b,r,t coords (djvu.xml ordering...) 5102 var l = chunk[i][0]; 5103 var b = chunk[i][1]; 5104 var r = chunk[i][2]; 5105 var t = chunk[i][3]; 5106 5107 var div = document.createElement('div'); 5108 this.ttsHilites.push(div); 5109 $(div).attr('className', 'BookReaderSearchHilite').css('zIndex', 3).appendTo('#BRtwopageview'); 5110 this.setHilightCss2UP(div, this.ttsIndex, l, r, t, b); 5111 } 5112} 5113 5114// ttsRemoveHilites() 5115//______________________________________________________________________________ 5116BookReader.prototype.ttsRemoveHilites = function (chunk) { 5117 $(this.ttsHilites).remove(); 5118 this.ttsHilites = []; 5119} 5120 5121// ttsStartPolling() 5122//______________________________________________________________________________ 5123// Play of the current chunk has ended, but the next chunk has not yet been loaded. 5124// We need to wait for the text for the next page to be loaded, so we can 5125// load the next audio chunk 5126BookReader.prototype.ttsStartPolling = function () { 5127 if (soundManager.debugMode) console.log('Starting the TTS poller...'); 5128 var self = this; 5129 this.ttsPoller=setInterval(function(){ 5130 if (self.ttsBuffering) {return;} 5131 5132 if (soundManager.debugMode) console.log('TTS buffering finished!'); 5133 clearInterval(self.ttsPoller); 5134 self.ttsPoller = null; 5135 self.ttsPrefetchAudio(); 5136 self.ttsNextChunk(); 5137 },500); 5138} 5139 5140BookReader.prototype.buildShareDiv = function(jShareDiv) 5141{ 5142 var pageView = document.location + ''; 5143 var bookView = (pageView + '').replace(/#.*/,''); 5144 var self = this; 5145 5146 var jForm = $([ 5147 '<p>Copy and paste one of these options to share this book elsewhere.</p>', 5148 '<form method="post" action="">', 5149 '<fieldset>', 5150 '<label for="pageview">Link to this page view:</label>', 5151 '<input type="text" name="pageview" id="pageview" value="' + pageView + '"/>', 5152 '</fieldset>', 5153 '<fieldset>', 5154 '<label for="booklink">Link to the book:</label>', 5155 '<input type="text" name="booklink" id="booklink" value="' + bookView + '"/>', 5156 '</fieldset>', 5157 '<fieldset>', 5158 '<label for="iframe">Embed a mini Book Reader:</label>', 5159 '<fieldset class="sub">', 5160 '<label class="sub">', 5161 '<input type="radio" name="pages" value="' + this.constMode1up + '" checked="checked"/>', 5162 '1 page', 5163 '</label>', 5164 '<label class="sub">', 5165 '<input type="radio" name="pages" value="' + this.constMode2up + '"/>', 5166 '2 pages', 5167 '</label>', 5168 '<label class="sub">', 5169 '<input type="checkbox" name="thispage" value="thispage"/>', 5170 'Open to this page?', 5171 '</label>', 5172 '</fieldset>', 5173 '<textarea cols="30" rows="4" name="iframe" class="BRframeEmbed"></textarea>', 5174 '</fieldset>', 5175 '<fieldset class="center">', 5176 '<button type="button" onclick="$.fn.colorbox.close();">Finished</button>', 5177 '</fieldset>', 5178 '</form>'].join('\n')); 5179 5180 jForm.appendTo(jShareDiv); 5181 5182 jForm.find('input').bind('change', function() { 5183 var form = $(this).parents('form:first'); 5184 var params = {}; 5185 params.mode = $(form.find('input[name=pages]:checked')).val(); 5186 if (form.find('input[name=thispage]').attr('checked')) { 5187 params.page = self.getPageNum(self.currentIndex()); 5188 } 5189 5190 // $$$ changeable width/height to be added to share UI 5191 var frameWidth = "480px"; 5192 var frameHeight = "430px"; 5193 form.find('.BRframeEmbed').val(self.getEmbedCode(frameWidth, frameHeight, params)); 5194 }) 5195 jForm.find('input[name=thispage]').trigger('change'); 5196 jForm.find('input, textarea').bind('focus', function() { 5197 this.select(); 5198 }); 5199 5200 jForm.appendTo(jShareDiv); 5201 jForm = ''; // closure 5202 5203} 5204 5205// Should be overridden 5206BookReader.prototype.buildInfoDiv = function(jInfoDiv) 5207{ 5208 jInfoDiv.find('.BRfloatTitle a').attr({'href': this.bookUrl, 'alt': this.bookTitle}).text(this.bookTitle); 5209} 5210 5211// Can be overriden 5212BookReader.prototype.initUIStrings = function() 5213{ 5214 // Navigation handlers will be bound after all UI is in place -- makes moving icons between 5215 // the toolbar and nav bar easier 5216 5217 // Setup tooltips -- later we could load these from a file for i18n 5218 var titles = { '.logo': 'xraylibrary.ru', // $$$ update after getting OL record /* заголовок ÑайÑла в пÑоÑмоÑÑÑике */ 5219 '.zoom_in': 'Zoom in', 5220 '.zoom_out': 'Zoom out', 5221 '.onepg': 'One-page view', 5222 '.twopg': 'Two-page view', 5223 '.thumb': 'Thumbnail view', 5224 '.print': 'Print this page', 5225 '.embed': 'Embed BookReader', 5226 '.link': 'Link to this book (and page)', 5227 '.bookmark': 'Bookmark this page', 5228 '.read': 'Read this book aloud', 5229 '.share': 'Share this book', 5230 '.info': 'About this book', 5231 '.full': 'Show fullscreen', 5232 '.book_left': 'Flip left', 5233 '.book_right': 'Flip right', 5234 '.book_up': 'Page up', 5235 '.book_down': 'Page down', 5236 '.play': 'Play', 5237 '.pause': 'Pause', 5238 '.BRdn': 'Show/hide nav bar', // Would have to keep updating on state change to have just "Hide nav bar" 5239 '.BRup': 'Show/hide nav bar', 5240 '.book_top': 'First page', 5241 '.book_bottom': 'Last page' 5242 }; 5243 if ('rl' == this.pageProgression) { 5244 titles['.book_leftmost'] = 'Last page'; 5245 titles['.book_rightmost'] = 'First page'; 5246 } else { // LTR 5247 titles['.book_leftmost'] = 'First page'; 5248 titles['.book_rightmost'] = 'Last page'; 5249 } 5250 5251 for (var icon in titles) { 5252 if (titles.hasOwnProperty(icon)) { 5253 $('#BookReader').find(icon).attr('title', titles[icon]); 5254 } 5255 } 5256} 5257})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.