Synchronize with archive.org CVS.
[bookreader.git] / GnuBook / GnuBook.js
1 /*
2 Copyright(c)2008 Internet Archive. Software license AGPL version 3.
3
4 This file is part of GnuBook.
5
6     GnuBook is free software: you can redistribute it and/or modify
7     it under the terms of the GNU Affero General Public License as published by
8     the Free Software Foundation, either version 3 of the License, or
9     (at your option) any later version.
10
11     GnuBook is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU Affero General Public License for more details.
15
16     You should have received a copy of the GNU Affero General Public License
17     along with GnuBook.  If not, see <http://www.gnu.org/licenses/>.
18     
19     The GnuBook source is hosted at http://github.com/openlibrary/bookreader/
20
21     archive.org cvs $Revision: 1.63 $ $Date: 2009-01-07 00:21:01 $
22 */
23
24 // GnuBook()
25 //______________________________________________________________________________
26 // After you instantiate this object, you must supply the following
27 // book-specific functions, before calling init():
28 //  - getPageWidth()
29 //  - getPageHeight()
30 //  - getPageURI()
31 // You must also add a numLeafs property before calling init().
32
33 function GnuBook() {
34     this.reduce  = 4;
35     this.padding = 10;
36     this.mode    = 1; //1 or 2
37     
38     this.displayedLeafs = [];   
39     //this.leafsToDisplay = [];
40     this.imgs = {};
41     this.prefetchedImgs = {}; //an object with numeric keys cooresponding to leafNum
42     
43     this.timer     = null;
44     this.animating = false;
45     this.auto      = false;
46     this.autoTimer = null;
47     this.flipSpeed = 'fast';
48
49     this.twoPagePopUp = null;
50     this.leafEdgeTmp  = null;
51     this.embedPopup = null;
52     
53     this.searchResults = {};
54     
55     this.firstIndex = null;
56     
57 };
58
59 // init()
60 //______________________________________________________________________________
61 GnuBook.prototype.init = function() {
62     var startLeaf = window.location.hash;
63     //console.log("startLeaf from location.hash: %s", startLeaf);
64     if ('' == startLeaf) {
65         if (this.titleLeaf) {
66             startLeaf = "#" + this.leafNumToIndex(this.titleLeaf);
67         }
68     }
69     var title = this.bookTitle.substr(0,50);
70     if (this.bookTitle.length>50) title += '...';
71     
72     // Ideally this would be set in the HTML/PHP for better search engine visibility but
73     // it takes some time to locate the item and retrieve the metadata
74     document.title = title;
75     
76     $("#GnuBook").empty();
77     $("#GnuBook").append("<div id='GBtoolbar'><span style='float:left;'><button class='GBicon' id='zoom_out' onclick='gb.zoom1up(-1); return false;'/> <button class='GBicon' id='zoom_in' onclick='gb.zoom1up(1); return false;'/> zoom: <span id='GBzoom'>25</span>% <button class='GBicon' id='script' onclick='gb.switchMode(1); return false;'/> <button class='GBicon' id='book_open' onclick='gb.switchMode(2); return false;'/>  &nbsp;&nbsp; <a href='"+this.bookUrl+"' target='_blank'>"+title+"</a></span></div>");
78     $("#GBtoolbar").append("<span id='GBtoolbarbuttons' style='float: right'><button class='GBicon' id='page_code' onclick='gb.showEmbedCode(); return false;'/><form class='GBpageform' action='javascript:' onsubmit='gb.jumpToPage(this.elements[0].value)'> page:<input id='GBpagenum' type='text' size='3' onfocus='gb.autoStop();'></input></form> <button class='GBicon' id='book_previous' onclick='gb.prev(); return false;'/> <button class='GBicon' id='book_next' onclick='gb.next(); return false;'/> <button class='GBicon play' id='autoImg' onclick='gb.autoToggle(); return false;'/></span>");
79     $("#GnuBook").append("<div id='GBcontainer'></div>");
80     $("#GBcontainer").append("<div id='GBpageview'></div>");
81
82     $("#GBcontainer").bind('scroll', this, function(e) {
83         e.data.loadLeafs();
84     });
85
86     this.setupKeyListeners();
87
88     $(window).bind('resize', this, function(e) {
89         //console.log('resize!');
90         if (1 == e.data.mode) {
91             //console.log('centering 1page view');
92             e.data.centerPageView();
93             $('#GBpageview').empty()
94             e.data.displayedLeafs = [];
95             e.data.updateSearchHilites(); //deletes hilights but does not call remove()            
96             e.data.loadLeafs();
97         } else {
98             //console.log('drawing 2 page view');
99             e.data.prepareTwoPageView();
100         }
101     });
102
103     if (1 == this.mode) {
104         this.resizePageView();
105     
106         if ('' != startLeaf) { // Jump to the leaf specified in the URL
107             this.jumpToIndex(parseInt(startLeaf.substr(1)));
108             //console.log('jump to ' + parseInt(startLeaf.substr(1)));
109         }
110     } else {
111         //this.resizePageView();
112         
113         this.displayedLeafs=[0];
114         if ('' != startLeaf) {
115             this.displayedLeafs = [parseInt(startLeaf.substr(1))];
116         }
117         //console.log('titleLeaf: %d', this.titleLeaf);
118         //console.log('displayedLeafs: %s', this.displayedLeafs);
119         this.prepareTwoPageView();
120         //if (this.auto) this.nextPage();
121     }
122 }
123
124 GnuBook.prototype.setupKeyListeners = function() {
125     var self = this;
126
127     var KEY_PGUP = 33;
128     var KEY_PGDOWN = 34;
129     var KEY_END = 35;
130     var KEY_HOME = 36;
131
132     var KEY_LEFT = 37;
133     var KEY_UP = 38;
134     var KEY_RIGHT = 39;
135     var KEY_DOWN = 40;
136
137     // We use document here instead of window to avoid a bug in jQuery on IE7
138     $(document).keydown(function(e) {
139         
140         // Keyboard navigation        
141         switch(e.keyCode) {
142             case KEY_PGUP:
143                 // In 1up mode page scrolling is handled by browser
144                 if (2 == self.mode) {
145                     self.prev();
146                 }
147                 break;
148             case KEY_PGDOWN:
149                 if (2 == self.mode) {
150                     self.next();
151                 }
152                 break;
153             case KEY_END:
154                 self.end();
155                 break;
156             case KEY_HOME:
157                 self.home();
158                 break;
159             case KEY_UP:
160             case KEY_LEFT:
161                 if (self.keyboardNavigationIsDisabled(e)) {
162                     break;
163                 }
164                 if (2 == self.mode) {
165                     self.prev();
166                 }
167                 break;
168             case KEY_DOWN:
169             case KEY_RIGHT:
170                 if (self.keyboardNavigationIsDisabled(e)) {
171                     break;
172                 }
173                 if (2 == self.mode) {
174                     self.next();
175                 }
176                 break;
177         }
178     });
179 }
180
181 // drawLeafs()
182 //______________________________________________________________________________
183 GnuBook.prototype.drawLeafs = function() {
184     if (1 == this.mode) {
185         this.drawLeafsOnePage();
186     } else {
187         this.drawLeafsTwoPage();
188     }
189 }
190
191
192 // drawLeafsOnePage()
193 //______________________________________________________________________________
194 GnuBook.prototype.drawLeafsOnePage = function() {
195     //alert('drawing leafs!');
196     this.timer = null;
197
198
199     var scrollTop = $('#GBcontainer').attr('scrollTop');
200     var scrollBottom = scrollTop + $('#GBcontainer').height();
201     //console.log('top=' + scrollTop + ' bottom='+scrollBottom);
202     
203     var leafsToDisplay = [];
204     
205     var i;
206     var leafTop = 0;
207     var leafBottom = 0;
208     for (i=0; i<this.numLeafs; i++) {
209         var height  = parseInt(this.getPageHeight(i)/this.reduce); 
210     
211         leafBottom += height;
212         //console.log('leafTop = '+leafTop+ ' pageH = ' + this.pageH[i] + 'leafTop>=scrollTop=' + (leafTop>=scrollTop));
213         var topInView    = (leafTop >= scrollTop) && (leafTop <= scrollBottom);
214         var bottomInView = (leafBottom >= scrollTop) && (leafBottom <= scrollBottom);
215         var middleInView = (leafTop <=scrollTop) && (leafBottom>=scrollBottom);
216         if (topInView | bottomInView | middleInView) {
217             //console.log('to display: ' + i);
218             leafsToDisplay.push(i);
219         }
220         leafTop += height +10;      
221         leafBottom += 10;
222     }
223
224     var firstLeafToDraw  = leafsToDisplay[0];
225     window.location.hash = firstLeafToDraw;    
226     this.firstIndex      = firstLeafToDraw;
227
228     if ((0 != firstLeafToDraw) && (1 < this.reduce)) {
229         firstLeafToDraw--;
230         leafsToDisplay.unshift(firstLeafToDraw);
231     }
232     
233     var lastLeafToDraw = leafsToDisplay[leafsToDisplay.length-1];
234     if ( ((this.numLeafs-1) != lastLeafToDraw) && (1 < this.reduce) ) {
235         leafsToDisplay.push(lastLeafToDraw+1);
236     }
237     
238     leafTop = 0;
239     var i;
240     for (i=0; i<firstLeafToDraw; i++) {
241         leafTop += parseInt(this.getPageHeight(i)/this.reduce) +10;
242     }
243
244     //var viewWidth = $('#GBpageview').width(); //includes scroll bar width
245     var viewWidth = $('#GBcontainer').attr('scrollWidth');
246
247
248     for (i=0; i<leafsToDisplay.length; i++) {
249         var leafNum = leafsToDisplay[i];    
250         var height  = parseInt(this.getPageHeight(leafNum)/this.reduce); 
251
252         if(-1 == jQuery.inArray(leafsToDisplay[i], this.displayedLeafs)) {            
253             var width   = parseInt(this.getPageWidth(leafNum)/this.reduce); 
254             //console.log("displaying leaf " + leafsToDisplay[i] + ' leafTop=' +leafTop);
255             var div = document.createElement("div");
256             div.className = 'GBpagediv1up';
257             div.id = 'pagediv'+leafNum;
258             div.style.position = "absolute";
259             $(div).css('top', leafTop + 'px');
260             var left = (viewWidth-width)>>1;
261             if (left<0) left = 0;
262             $(div).css('left', left+'px');
263             $(div).css('width', width+'px');
264             $(div).css('height', height+'px');
265             //$(div).text('loading...');
266             
267             $('#GBpageview').append(div);
268
269             var img = document.createElement("img");
270             img.src = this.getPageURI(leafNum);
271             $(img).css('width', width+'px');
272             $(img).css('height', height+'px');
273             $(div).append(img);
274
275         } else {
276             //console.log("not displaying " + leafsToDisplay[i] + ' score=' + jQuery.inArray(leafsToDisplay[i], this.displayedLeafs));            
277         }
278
279         leafTop += height +10;
280
281     }
282     
283     for (i=0; i<this.displayedLeafs.length; i++) {
284         if (-1 == jQuery.inArray(this.displayedLeafs[i], leafsToDisplay)) {
285             var leafNum = this.displayedLeafs[i];
286             //console.log('Removing leaf ' + leafNum);
287             //console.log('id='+'#pagediv'+leafNum+ ' top = ' +$('#pagediv'+leafNum).css('top'));
288             $('#pagediv'+leafNum).remove();
289         } else {
290             //console.log('NOT Removing leaf ' + this.displayedLeafs[i]);
291         }
292     }
293     
294     this.displayedLeafs = leafsToDisplay.slice();
295     this.updateSearchHilites();
296     
297     if (null != this.getPageNum(firstLeafToDraw))  {
298         $("#GBpagenum").val(this.getPageNum(firstLeafToDraw));
299     } else {
300         $("#GBpagenum").val('');
301     }
302 }
303
304 // drawLeafsTwoPage()
305 //______________________________________________________________________________
306 GnuBook.prototype.drawLeafsTwoPage = function() {
307     //alert('drawing two leafs!');
308
309     var scrollTop = $('#GBcontainer').attr('scrollTop');
310     var scrollBottom = scrollTop + $('#GBcontainer').height();
311     var leafNum = this.currentLeafL;
312     var height  = this.getPageHeight(leafNum); 
313     var width   = this.getPageWidth(leafNum);
314     var handSide= this.getPageSide(leafNum);
315
316     var leafEdgeWidthL = parseInt( (leafNum/this.numLeafs)*this.twoPageEdgeW );
317     var leafEdgeWidthR = this.twoPageEdgeW - leafEdgeWidthL;
318     var divWidth = this.twoPageW*2+20 + this.twoPageEdgeW;
319     var divLeft = ($('#GBcontainer').width() - divWidth) >> 1;
320     //console.log(leafEdgeWidthL);
321
322     var middle = ($('#GBcontainer').width() >> 1);            
323     var left = middle - this.twoPageW;
324     var top  = ($('#GBcontainer').height() - this.twoPageH) >> 1;                
325
326     var scaledW = parseInt(this.twoPageH*width/height);
327     left = 10+leafEdgeWidthL;
328     //var right = left+scaledW;
329     var right = $(this.twoPageDiv).width()-11-$(this.leafEdgeL).width()-scaledW;
330
331     var gutter = middle+parseInt((2*this.currentLeafL - this.numLeafs)*this.twoPageEdgeW/this.numLeafs/2);
332     
333     
334     this.prefetchImg(leafNum);
335     $(this.prefetchedImgs[leafNum]).css({
336         position: 'absolute',
337         /*right:   gutter+'px',*/
338         left: gutter-scaledW+'px',
339         top:    top+'px',
340         backgroundColor: 'rgb(234, 226, 205)',
341         height: this.twoPageH +'px',
342         width:  scaledW + 'px',
343         borderRight: '1px solid black',
344         zIndex: 2
345     }).appendTo('#GBcontainer');
346     //$('#GBcontainer').append(this.prefetchedImgs[leafNum]);
347
348
349     var leafNum = this.currentLeafR;
350     var height  = this.getPageHeight(leafNum); 
351     var width   = this.getPageWidth(leafNum);
352     //    var left = ($('#GBcontainer').width() >> 1);
353     left += scaledW;
354
355     var scaledW = this.twoPageH*width/height;
356     this.prefetchImg(leafNum);
357     $(this.prefetchedImgs[leafNum]).css({
358         position: 'absolute',
359         left:   gutter+'px',
360         top:    top+'px',
361         backgroundColor: 'rgb(234, 226, 205)',
362         height: this.twoPageH + 'px',
363         width:  scaledW + 'px',
364         borderLeft: '1px solid black',
365         zIndex: 2
366     }).appendTo('#GBcontainer');
367     //$('#GBcontainer').append(this.prefetchedImgs[leafNum]);
368         
369
370     this.displayedLeafs = [this.currentLeafL, this.currentLeafR];
371     this.setClickHandlers();
372
373     this.updatePageNumBox2UP();
374 }
375
376 // updatePageNumBox2UP
377 //______________________________________________________________________________
378 GnuBook.prototype.updatePageNumBox2UP = function() {
379     if (null != this.getPageNum(this.currentLeafL))  {
380         $("#GBpagenum").val(this.getPageNum(this.currentLeafL));
381     } else {
382         $("#GBpagenum").val('');
383     }
384     window.location.hash = this.currentLeafL; 
385 }
386
387 // loadLeafs()
388 //______________________________________________________________________________
389 GnuBook.prototype.loadLeafs = function() {
390
391
392     var self = this;
393     if (null == this.timer) {
394         this.timer=setTimeout(function(){self.drawLeafs()},250);
395     } else {
396         clearTimeout(this.timer);
397         this.timer=setTimeout(function(){self.drawLeafs()},250);    
398     }
399 }
400
401
402 // zoom1up()
403 //______________________________________________________________________________
404 GnuBook.prototype.zoom1up = function(dir) {
405     if (2 == this.mode) {     //can only zoom in 1-page mode
406         this.switchMode(1);
407         return;
408     }
409     
410     if (1 == dir) {
411         if (this.reduce <= 0.5) return;
412         this.reduce*=0.5;           //zoom in
413     } else {
414         if (this.reduce >= 8) return;
415         this.reduce*=2;             //zoom out
416     }
417     
418     this.resizePageView();
419
420     $('#GBpageview').empty()
421     this.displayedLeafs = [];
422     this.loadLeafs();
423     
424     $('#GBzoom').text(100/this.reduce);
425 }
426
427
428 // resizePageView()
429 //______________________________________________________________________________
430 GnuBook.prototype.resizePageView = function() {
431     var i;
432     var viewHeight = 0;
433     //var viewWidth  = $('#GBcontainer').width(); //includes scrollBar
434     var viewWidth  = $('#GBcontainer').attr('clientWidth');   
435
436     var oldScrollTop  = $('#GBcontainer').attr('scrollTop');
437     var oldViewHeight = $('#GBpageview').height();
438     if (0 != oldViewHeight) {
439         var scrollRatio = oldScrollTop / oldViewHeight;
440     } else {
441         var scrollRatio = 0;
442     }
443     
444     for (i=0; i<this.numLeafs; i++) {
445         viewHeight += parseInt(this.getPageHeight(i)/this.reduce) + this.padding; 
446         var width = parseInt(this.getPageWidth(i)/this.reduce);
447         if (width>viewWidth) viewWidth=width;
448     }
449     $('#GBpageview').height(viewHeight);
450     $('#GBpageview').width(viewWidth);    
451
452     $('#GBcontainer').attr('scrollTop', Math.floor(scrollRatio*viewHeight));
453     
454     this.centerPageView();
455     this.loadLeafs();
456     
457 }
458
459 // centerPageView()
460 //______________________________________________________________________________
461 GnuBook.prototype.centerPageView = function() {
462
463     var scrollWidth  = $('#GBcontainer').attr('scrollWidth');
464     var clientWidth  =  $('#GBcontainer').attr('clientWidth');
465     //console.log('sW='+scrollWidth+' cW='+clientWidth);
466     if (scrollWidth > clientWidth) {
467         $('#GBcontainer').attr('scrollLeft', (scrollWidth-clientWidth)/2);
468     }
469
470 }
471
472 // jumpToPage()
473 //______________________________________________________________________________
474 GnuBook.prototype.jumpToPage = function(pageNum) {
475     //if (2 == this.mode) return;
476     
477     var i;
478     var foundPage = false;
479     var foundLeaf = null;
480     for (i=0; i<this.numLeafs; i++) {
481         if (this.getPageNum(i) == pageNum) {
482             foundPage = true;
483             foundLeaf = i;
484             break;
485         }
486     }
487     
488     if (foundPage) {
489         var leafTop = 0;
490         var h;
491         this.jumpToIndex(foundLeaf);
492         $('#GBcontainer').attr('scrollTop', leafTop);
493     } else {
494         alert('Page not found. This book might not have pageNumbers in scandata.');
495     }
496 }
497
498 // jumpToIndex()
499 //______________________________________________________________________________
500 GnuBook.prototype.jumpToIndex = function(index) {
501     if (2 == this.mode) {
502         this.autoStop();
503         if (index<this.currentLeafL) {
504             if ('L' == this.getPageSide(index)) {
505                 this.flipBackToIndex(index);
506             } else {
507                 this.flipBackToIndex(index-1);
508             }
509         } else if (index>this.currentLeafR) {
510             if ('R' == this.getPageSide(index)) {
511                 this.flipFwdToIndex(index);
512             } else {
513                 this.flipFwdToIndex(index+1);
514             }        
515         }
516
517     } else {        
518         var i;
519         var leafTop = 0;
520         var h;
521         for (i=0; i<index; i++) {
522             h = parseInt(this.getPageHeight(i)/this.reduce); 
523             leafTop += h + this.padding;
524         }
525         //$('#GBcontainer').attr('scrollTop', leafTop);
526         $('#GBcontainer').animate({scrollTop: leafTop },'fast');    
527     }
528 }
529
530
531
532 // switchMode()
533 //______________________________________________________________________________
534 GnuBook.prototype.switchMode = function(mode) {
535     if (mode == this.mode) return;
536
537     this.autoStop();
538     this.removeSearchHilites();
539
540     this.mode = mode;
541     if (1 == mode) {
542         this.prepareOnePageView();
543     } else {
544         this.prepareTwoPageView();
545     }
546
547 }
548
549 //prepareOnePageView()
550 //______________________________________________________________________________
551 GnuBook.prototype.prepareOnePageView = function() {
552
553     var startLeaf = this.displayedLeafs[0];
554     
555     $('#GBcontainer').empty();
556     $('#GBcontainer').css({
557         overflowY: 'scroll',
558         overflowX: 'auto'
559     });
560     
561     $("#GBcontainer").append("<div id='GBpageview'></div>");
562     this.resizePageView();
563     this.jumpToIndex(startLeaf);
564     this.displayedLeafs = [];    
565     this.drawLeafsOnePage();
566     $('#GBzoom').text(100/this.reduce);    
567 }
568
569 // prepareTwoPageView()
570 //______________________________________________________________________________
571 GnuBook.prototype.prepareTwoPageView = function() {
572     $('#GBcontainer').empty();
573
574     var firstLeaf = this.displayedLeafs[0];
575     if ('R' == this.getPageSide(firstLeaf)) {
576         if (0 == firstLeaf) {
577             firstLeaf++;
578         } else {
579             firstLeaf--;
580         }
581     }
582
583     this.currentLeafL = null;
584     this.currentLeafR = null;
585     this.pruneUnusedImgs();
586     
587     this.currentLeafL = firstLeaf;
588     this.currentLeafR = firstLeaf + 1;
589     
590     this.calculateSpreadSize(); //sets this.twoPageW, twoPageH, and twoPageRatio
591
592     var middle = ($('#GBcontainer').width() >> 1);
593     var gutter = middle+parseInt((2*this.currentLeafL - this.numLeafs)*this.twoPageEdgeW/this.numLeafs/2);
594     var scaledWL = this.getPageWidth2UP(this.currentLeafL);
595     var scaledWR = this.getPageWidth2UP(this.currentLeafR);
596     var leafEdgeWidthL = parseInt( (firstLeaf/this.numLeafs)*this.twoPageEdgeW );
597     var leafEdgeWidthR = this.twoPageEdgeW - leafEdgeWidthL;
598
599     //console.log('idealWidth='+idealWidth+' idealHeight='+idealHeight);
600     //var divWidth = this.twoPageW*2+20 + this.twoPageEdgeW;
601     var divWidth = scaledWL + scaledWR + 20 + this.twoPageEdgeW;
602     var divHeight = this.twoPageH+20;
603     //var divLeft = ($('#GBcontainer').width() - divWidth) >> 1;
604     var divLeft = gutter-scaledWL-leafEdgeWidthL-10;
605     var divTop = ($('#GBcontainer').height() - divHeight) >> 1;
606     //console.log('divWidth='+divWidth+' divHeight='+divHeight+ ' divLeft='+divLeft+' divTop='+divTop);
607
608     this.twoPageDiv = document.createElement('div');
609     $(this.twoPageDiv).attr('id', 'book_div_1').css({
610         border: '1px solid rgb(68, 25, 17)',
611         width:  divWidth + 'px',
612         height: divHeight+'px',
613         visibility: 'visible',
614         position: 'absolute',
615         backgroundColor: 'rgb(136, 51, 34)',
616         left: divLeft + 'px',
617         top: divTop+'px',
618         MozBorderRadiusTopleft: '7px',
619         MozBorderRadiusTopright: '7px',
620         MozBorderRadiusBottomright: '7px',
621         MozBorderRadiusBottomleft: '7px'
622     }).appendTo('#GBcontainer');
623     //$('#GBcontainer').append('<div id="book_div_1" style="border: 1px solid rgb(68, 25, 17); width: ' + divWidth + 'px; height: '+divHeight+'px; visibility: visible; position: absolute; background-color: rgb(136, 51, 34); left: ' + divLeft + 'px; top: '+divTop+'px; -moz-border-radius-topleft: 7px; -moz-border-radius-topright: 7px; -moz-border-radius-bottomright: 7px; -moz-border-radius-bottomleft: 7px;"/>');
624
625
626     var height  = this.getPageHeight(this.currentLeafR); 
627     var width   = this.getPageWidth(this.currentLeafR);    
628     var scaledW = this.twoPageH*width/height;
629     
630     this.leafEdgeR = document.createElement('div');
631     $(this.leafEdgeR).css({
632         borderStyle: 'solid solid solid none',
633         borderColor: 'rgb(51, 51, 34)',
634         borderWidth: '1px 1px 1px 0px',
635         background: 'transparent url(http://www.us.archive.org/GnuBook/images/right-edges.png) repeat scroll 0% 0%',
636         width: leafEdgeWidthR + 'px',
637         height: this.twoPageH-1 + 'px',
638         /*right: '10px',*/
639         left: gutter+scaledW+'px',
640         top: divTop+10+'px',
641         position: 'absolute'
642     }).appendTo('#GBcontainer');
643     
644     this.leafEdgeL = document.createElement('div');
645     $(this.leafEdgeL).css({
646         borderStyle: 'solid none solid solid',
647         borderColor: 'rgb(51, 51, 34)',
648         borderWidth: '1px 0px 1px 1px',
649         background: 'transparent url(http://www.us.archive.org/GnuBook/images/left-edges.png) repeat scroll 0% 0%',
650         width: leafEdgeWidthL + 'px',
651         height: this.twoPageH-1 + 'px',
652         left: divLeft+10+'px',
653         top: divTop+10+'px',    
654         position: 'absolute'
655     }).appendTo('#GBcontainer');
656
657
658
659     divWidth = 30;
660     divHeight = this.twoPageH+20;
661     divLeft = ($('#GBcontainer').width() - divWidth) >> 1;
662     divTop = ($('#GBcontainer').height() - divHeight) >> 1;
663
664     div = document.createElement('div');
665     $(div).attr('id', 'book_div_2').css({
666         border:          '1px solid rgb(68, 25, 17)',
667         width:           divWidth+'px',
668         height:          divHeight+'px',
669         position:        'absolute',
670         backgroundColor: 'rgb(68, 25, 17)',
671         left:            divLeft+'px',
672         top:             divTop+'px'
673     }).appendTo('#GBcontainer');
674     //$('#GBcontainer').append('<div id="book_div_2" style="border: 1px solid rgb(68, 25, 17); width: '+divWidth+'px; height: '+divHeight+'px; visibility: visible; position: absolute; background-color: rgb(68, 25, 17); left: '+divLeft+'px; top: '+divTop+'px;"/>');
675
676     divWidth = this.twoPageW*2;
677     divHeight = this.twoPageH;
678     divLeft = ($('#GBcontainer').width() - divWidth) >> 1;
679     divTop = ($('#GBcontainer').height() - divHeight) >> 1;
680
681
682     this.prepareTwoPagePopUp();
683
684     this.displayedLeafs = [];
685     //this.leafsToDisplay=[firstLeaf, firstLeaf+1];
686     //console.log('leafsToDisplay: ' + this.leafsToDisplay[0] + ' ' + this.leafsToDisplay[1]);
687     this.drawLeafsTwoPage();
688     this.updateSearchHilites2UP();
689     
690     this.prefetch();
691     $('#GBzoom').text((100*this.twoPageH/this.getPageHeight(firstLeaf)).toString().substr(0,4));
692 }
693
694 // prepareTwoPagePopUp()
695 //______________________________________________________________________________
696 GnuBook.prototype.prepareTwoPagePopUp = function() {
697     this.twoPagePopUp = document.createElement('div');
698     $(this.twoPagePopUp).css({
699         border: '1px solid black',
700         padding: '2px 6px',
701         position: 'absolute',
702         fontFamily: 'sans-serif',
703         fontSize: '14px',
704         zIndex: '1000',
705         backgroundColor: 'rgb(255, 255, 238)',
706         opacity: 0.85
707     }).appendTo('#GBcontainer');
708     $(this.twoPagePopUp).hide();
709     
710     $(this.leafEdgeL).add(this.leafEdgeR).bind('mouseenter', this, function(e) {
711         $(e.data.twoPagePopUp).show();
712     });
713
714     $(this.leafEdgeL).add(this.leafEdgeR).bind('mouseleave', this, function(e) {
715         $(e.data.twoPagePopUp).hide();
716     });
717
718     $(this.leafEdgeL).bind('click', this, function(e) { 
719         e.data.autoStop();
720         var jumpIndex = e.data.currentLeafL - ($(e.data.leafEdgeL).offset().left + $(e.data.leafEdgeL).width() - e.pageX) * 10;
721         jumpIndex = Math.max(jumpIndex, 0);
722         e.data.flipBackToIndex(jumpIndex);
723     
724     });
725
726     $(this.leafEdgeR).bind('click', this, function(e) { 
727         e.data.autoStop();
728         var jumpIndex = e.data.currentLeafR + (e.pageX - $(e.data.leafEdgeR).offset().left) * 10;
729         jumpIndex = Math.max(jumpIndex, 0);
730         e.data.flipFwdToIndex(jumpIndex);
731     
732     });
733
734     $(this.leafEdgeR).bind('mousemove', this, function(e) {
735
736         var jumpLeaf = e.data.currentLeafR + (e.pageX - $(e.data.leafEdgeR).offset().left) * 10;
737         jumpLeaf = Math.min(jumpLeaf, e.data.numLeafs-1);        
738         $(e.data.twoPagePopUp).text('View Leaf '+jumpLeaf);
739         
740         $(e.data.twoPagePopUp).css({
741             left: e.pageX +5+ 'px',
742             top: e.pageY-$('#GBcontainer').offset().top+ 'px'
743         });
744     });
745
746     $(this.leafEdgeL).bind('mousemove', this, function(e) {
747         var jumpLeaf = e.data.currentLeafL - ($(e.data.leafEdgeL).offset().left + $(e.data.leafEdgeL).width() - e.pageX) * 10;
748         jumpLeaf = Math.max(jumpLeaf, 0);
749         $(e.data.twoPagePopUp).text('View Leaf '+jumpLeaf);
750         
751         $(e.data.twoPagePopUp).css({
752             left: e.pageX - $(e.data.twoPagePopUp).width() - 30 + 'px',
753             top: e.pageY-$('#GBcontainer').offset().top+ 'px'
754         });
755     });
756 }
757
758 // calculateSpreadSize()
759 //______________________________________________________________________________
760 // Calculates 2-page spread dimensions based on this.currentLeafL and
761 // this.currentLeafR
762 // This function sets this.twoPageH, twoPageW, and twoPageRatio
763
764 GnuBook.prototype.calculateSpreadSize = function() {
765     var firstLeaf  = this.currentLeafL;
766     var secondLeaf = this.currentLeafR;
767     //console.log('first page is ' + firstLeaf);
768
769     var canon5Dratio = 1.5;
770     
771     var firstLeafRatio  = this.getPageHeight(firstLeaf) / this.getPageWidth(firstLeaf);
772     var secondLeafRatio = this.getPageHeight(secondLeaf) / this.getPageWidth(secondLeaf);
773     //console.log('firstLeafRatio = ' + firstLeafRatio + ' secondLeafRatio = ' + secondLeafRatio);
774
775     var ratio;
776     if (Math.abs(firstLeafRatio - canon5Dratio) < Math.abs(secondLeafRatio - canon5Dratio)) {
777         ratio = firstLeafRatio;
778         //console.log('using firstLeafRatio ' + ratio);
779     } else {
780         ratio = secondLeafRatio;
781         //console.log('using secondLeafRatio ' + ratio);
782     }
783
784     var totalLeafEdgeWidth = parseInt(this.numLeafs * 0.1);
785     var maxLeafEdgeWidth   = parseInt($('#GBcontainer').width() * 0.1);
786     totalLeafEdgeWidth     = Math.min(totalLeafEdgeWidth, maxLeafEdgeWidth);
787     
788     $('#GBcontainer').css('overflow', 'hidden');
789
790     var idealWidth  = ($('#GBcontainer').width() - 30 - totalLeafEdgeWidth)>>1;
791     var idealHeight = $('#GBcontainer').height() - 30;
792     //console.log('init idealWidth='+idealWidth+' idealHeight='+idealHeight + ' ratio='+ratio);
793
794     if (idealHeight/ratio <= idealWidth) {
795         //use height
796         idealWidth = parseInt(idealHeight/ratio);
797     } else {
798         //use width
799         idealHeight = parseInt(idealWidth*ratio);
800     }
801
802     this.twoPageH     = idealHeight;
803     this.twoPageW     = idealWidth;
804     this.twoPageRatio = ratio;
805     this.twoPageEdgeW = totalLeafEdgeWidth;    
806
807 }
808
809 // next()
810 //______________________________________________________________________________
811 GnuBook.prototype.next = function() {
812     if (2 == this.mode) {
813         this.autoStop();
814         this.flipFwdToIndex(null);
815     } else {
816         if (this.firstIndex <= (this.numLeafs - 2)) {
817             this.jumpToIndex(this.firstIndex+1);
818         }
819     }
820 }
821
822 // prev()
823 //______________________________________________________________________________
824 GnuBook.prototype.prev = function() {
825     if (2 == this.mode) {
826         this.autoStop();
827         this.flipBackToIndex(null);
828     } else {
829         if (this.firstIndex >= 1) {
830             this.jumpToIndex(this.firstIndex-1);
831         }    
832     }
833 }
834
835 GnuBook.prototype.home = function() {
836     if (2 == this.mode) {
837         this.jumpToIndex(2);
838     }
839     else {
840         this.jumpToIndex(0);
841     }
842 }
843
844 GnuBook.prototype.end = function() {
845     if (2 == this.mode) {
846         this.jumpToIndex(this.numLeafs-5);
847     }
848     else {
849         this.jumpToIndex(this.numLeafs-1);
850     }
851 }
852
853 // flipBackToIndex()
854 //______________________________________________________________________________
855 // to flip back one spread, pass index=null
856 GnuBook.prototype.flipBackToIndex = function(index) {
857     if (1 == this.mode) return;
858
859     var leftIndex = this.currentLeafL;
860     if (leftIndex <= 2) return;
861     if (this.animating) return;
862
863     if (null != this.leafEdgeTmp) {
864         alert('error: leafEdgeTmp should be null!');
865         return;
866     }
867     
868     if (null == index) {
869         index = leftIndex-2;
870     }
871     if (index<0) return;
872
873     if ('L' !=  this.getPageSide(index)) {
874         alert('img with index ' + index + ' is not a left-hand page');
875         return;
876     }
877
878     this.animating = true;
879     
880     var prevL = index;
881     var prevR = index+1;
882
883     var gutter= this.prepareFlipBack(prevL, prevR);
884
885     var leftLeaf = this.currentLeafL;
886
887     var oldLeafEdgeWidthL = parseInt( (this.currentLeafL/this.numLeafs)*this.twoPageEdgeW );
888     var newLeafEdgeWidthL = parseInt( (index            /this.numLeafs)*this.twoPageEdgeW );    
889     var leafEdgeTmpW = oldLeafEdgeWidthL - newLeafEdgeWidthL;
890
891     var scaledWL = this.getPageWidth2UP(prevL);
892
893     var top  = ($('#GBcontainer').height() - this.twoPageH) >> 1;                
894
895     this.leafEdgeTmp = document.createElement('div');
896     $(this.leafEdgeTmp).css({
897         borderStyle: 'solid none solid solid',
898         borderColor: 'rgb(51, 51, 34)',
899         borderWidth: '1px 0px 1px 1px',
900         background: 'transparent url(http://www.us.archive.org/GnuBook/images/left-edges.png) repeat scroll 0% 0%',
901         width: leafEdgeTmpW + 'px',
902         height: this.twoPageH-1 + 'px',
903         left: gutter-scaledWL+10+newLeafEdgeWidthL+'px',
904         top: top+'px',    
905         position: 'absolute',
906         zIndex:1000
907     }).appendTo('#GBcontainer');
908     
909     //$(this.leafEdgeL).css('width', newLeafEdgeWidthL+'px');
910     $(this.leafEdgeL).css({
911         width: newLeafEdgeWidthL+'px', 
912         left: gutter-scaledWL-newLeafEdgeWidthL+'px'
913     });   
914
915     var left = $(this.prefetchedImgs[leftLeaf]).offset().left;
916     var right = $('#GBcontainer').width()-left-$(this.prefetchedImgs[leftLeaf]).width()+$('#GBcontainer').offset().left-2+'px';
917     $(this.prefetchedImgs[leftLeaf]).css({
918         right: right,
919         left: null
920     });
921
922      left = $(this.prefetchedImgs[leftLeaf]).offset().left - $('#book_div_1').offset().left;
923      right = left+$(this.prefetchedImgs[leftLeaf]).width()+'px';
924
925     $(this.leafEdgeTmp).animate({left: gutter}, this.flipSpeed, 'easeInSine');    
926     //$(this.prefetchedImgs[leftLeaf]).animate({width: '0px'}, 'slow', 'easeInSine');
927
928     var scaledWR = this.getPageWidth2UP(prevR);
929     
930     var self = this;
931
932     this.removeSearchHilites();
933
934     $(this.prefetchedImgs[leftLeaf]).animate({width: '0px'}, self.flipSpeed, 'easeInSine', function() {
935         $(self.leafEdgeTmp).animate({left: gutter+scaledWR+'px'}, self.flipSpeed, 'easeOutSine');    
936         $(self.prefetchedImgs[prevR]).animate({width: scaledWR+'px'}, self.flipSpeed, 'easeOutSine', function() {
937             $(self.prefetchedImgs[prevL]).css('zIndex', 2);
938
939             $(self.leafEdgeR).css({
940                 width: self.twoPageEdgeW-newLeafEdgeWidthL+'px',
941                 left:  gutter+scaledWR+'px'
942             });
943             
944             $(self.twoPageDiv).css({
945                 width: scaledWL+scaledWR+self.twoPageEdgeW+20+'px',
946                 left: gutter-scaledWL-newLeafEdgeWidthL-10+'px'
947             });
948             
949             $(self.leafEdgeTmp).remove();
950             self.leafEdgeTmp = null;
951             
952             self.currentLeafL = prevL;
953             self.currentLeafR = prevR;
954             self.displayedLeafs = [prevL, prevR];
955             self.setClickHandlers();
956             self.pruneUnusedImgs();
957             self.prefetch();
958             self.animating = false;
959             
960             self.updateSearchHilites2UP();
961             self.updatePageNumBox2UP();
962             //$('#GBzoom').text((self.twoPageH/self.getPageHeight(prevL)).toString().substr(0,4));            
963         });
964     });        
965     
966 }
967
968 // flipFwdToIndex()
969 //______________________________________________________________________________
970 // to flip forward one spread, pass index=null
971 GnuBook.prototype.flipFwdToIndex = function(index) {
972     var rightLeaf = this.currentLeafR;
973     if (rightLeaf >= this.numLeafs-3) return;
974
975     if (this.animating) return;
976
977     if (null != this.leafEdgeTmp) {
978         alert('error: leafEdgeTmp should be null!');
979         return;
980     }
981
982     
983     if (null == index) {
984         index = rightLeaf+2;
985     }
986     if (index>=this.numLeafs-3) return;
987
988     if ('R' !=  this.getPageSide(index)) {
989         alert('img with index ' + index + ' is not a right-hand page');
990         return;
991     }
992
993     this.animating = true;
994
995     var nextL = index-1;
996     var nextR = index;
997
998     var gutter= this.prepareFlipFwd(nextL, nextR);
999
1000     var oldLeafEdgeWidthL = parseInt( (this.currentLeafL/this.numLeafs)*this.twoPageEdgeW );
1001     var oldLeafEdgeWidthR = this.twoPageEdgeW-oldLeafEdgeWidthL;
1002     var newLeafEdgeWidthL = parseInt( (nextL            /this.numLeafs)*this.twoPageEdgeW );    
1003     var newLeafEdgeWidthR = this.twoPageEdgeW-newLeafEdgeWidthL;
1004
1005     var leafEdgeTmpW = oldLeafEdgeWidthR - newLeafEdgeWidthR;
1006
1007     var top  = ($('#GBcontainer').height() - this.twoPageH) >> 1;                
1008
1009     var height  = this.getPageHeight(rightLeaf); 
1010     var width   = this.getPageWidth(rightLeaf);    
1011     var scaledW = this.twoPageH*width/height;
1012
1013     var middle     = ($('#GBcontainer').width() >> 1);
1014     var currGutter = middle+parseInt((2*this.currentLeafL - this.numLeafs)*this.twoPageEdgeW/this.numLeafs/2);    
1015
1016     this.leafEdgeTmp = document.createElement('div');
1017     $(this.leafEdgeTmp).css({
1018         borderStyle: 'solid none solid solid',
1019         borderColor: 'rgb(51, 51, 34)',
1020         borderWidth: '1px 0px 1px 1px',
1021         background: 'transparent url(http://www.us.archive.org/GnuBook/images/left-edges.png) repeat scroll 0% 0%',
1022         width: leafEdgeTmpW + 'px',
1023         height: this.twoPageH-1 + 'px',
1024         left: currGutter+scaledW+'px',
1025         top: top+'px',    
1026         position: 'absolute',
1027         zIndex:1000
1028     }).appendTo('#GBcontainer');
1029
1030     var scaledWR = this.getPageWidth2UP(nextR);
1031     $(this.leafEdgeR).css({width: newLeafEdgeWidthR+'px', left: gutter+scaledWR+'px' });
1032
1033     var scaledWL = this.getPageWidth2UP(nextL);
1034     
1035     var self = this;
1036
1037     var speed = this.flipSpeed;
1038
1039     this.removeSearchHilites();
1040     
1041     $(this.leafEdgeTmp).animate({left: gutter}, speed, 'easeInSine');    
1042     $(this.prefetchedImgs[rightLeaf]).animate({width: '0px'}, speed, 'easeInSine', function() {
1043         $(self.leafEdgeTmp).animate({left: gutter-scaledWL-leafEdgeTmpW+'px'}, speed, 'easeOutSine');    
1044         $(self.prefetchedImgs[nextL]).animate({width: scaledWL+'px'}, speed, 'easeOutSine', function() {
1045             $(self.prefetchedImgs[nextR]).css('zIndex', 2);
1046
1047             $(self.leafEdgeL).css({
1048                 width: newLeafEdgeWidthL+'px', 
1049                 left: gutter-scaledWL-newLeafEdgeWidthL+'px'
1050             });
1051             
1052             $(self.twoPageDiv).css({
1053                 width: scaledWL+scaledWR+self.twoPageEdgeW+20+'px',
1054                 left: gutter-scaledWL-newLeafEdgeWidthL-10+'px'
1055             });
1056             
1057             $(self.leafEdgeTmp).remove();
1058             self.leafEdgeTmp = null;
1059             
1060             self.currentLeafL = nextL;
1061             self.currentLeafR = nextR;
1062             self.displayedLeafs = [nextL, nextR];
1063             self.setClickHandlers();            
1064             self.pruneUnusedImgs();
1065             self.prefetch();
1066             self.animating = false;
1067
1068             self.updateSearchHilites2UP();
1069             self.updatePageNumBox2UP();
1070             //$('#GBzoom').text((self.twoPageH/self.getPageHeight(nextL)).toString().substr(0,4));
1071         });
1072     });
1073     
1074 }
1075
1076 // setClickHandlers
1077 //______________________________________________________________________________
1078 GnuBook.prototype.setClickHandlers = function() {
1079     var self = this;
1080     $(this.prefetchedImgs[this.currentLeafL]).click(function() {
1081         //self.prevPage();
1082         self.autoStop();
1083         self.flipBackToIndex(null);
1084     });
1085     $(this.prefetchedImgs[this.currentLeafR]).click(function() {
1086         //self.nextPage();'
1087         self.autoStop();
1088         self.flipFwdToIndex(null);        
1089     });
1090 }
1091
1092 // prefetchImg()
1093 //______________________________________________________________________________
1094 GnuBook.prototype.prefetchImg = function(leafNum) {
1095     if (undefined == this.prefetchedImgs[leafNum]) {        
1096         var img = document.createElement("img");
1097         img.src = this.getPageURI(leafNum);
1098         this.prefetchedImgs[leafNum] = img;
1099     }
1100 }
1101
1102
1103 // prepareFlipBack()
1104 //______________________________________________________________________________
1105 GnuBook.prototype.prepareFlipBack = function(prevL, prevR) {
1106
1107     this.prefetchImg(prevL);
1108     this.prefetchImg(prevR);
1109     
1110     var height  = this.getPageHeight(prevL); 
1111     var width   = this.getPageWidth(prevL);    
1112     var middle = ($('#GBcontainer').width() >> 1);
1113     var top  = ($('#GBcontainer').height() - this.twoPageH) >> 1;                
1114     var scaledW = this.twoPageH*width/height;
1115
1116     var gutter = middle+parseInt((2*prevL - this.numLeafs)*this.twoPageEdgeW/this.numLeafs/2);    
1117
1118     $(this.prefetchedImgs[prevL]).css({
1119         position: 'absolute',
1120         /*right:   middle+'px',*/
1121         left: gutter-scaledW+'px',
1122         top:    top+'px',
1123         backgroundColor: 'rgb(234, 226, 205)',
1124         height: this.twoPageH,
1125         width:  scaledW+'px',
1126         borderRight: '1px solid black',
1127         zIndex: 1
1128     });
1129
1130     $('#GBcontainer').append(this.prefetchedImgs[prevL]);
1131
1132     $(this.prefetchedImgs[prevR]).css({
1133         position: 'absolute',
1134         left:   gutter+'px',
1135         top:    top+'px',
1136         backgroundColor: 'rgb(234, 226, 205)',
1137         height: this.twoPageH,
1138         width:  '0px',
1139         borderLeft: '1px solid black',
1140         zIndex: 2
1141     });
1142
1143     $('#GBcontainer').append(this.prefetchedImgs[prevR]);
1144
1145
1146     return gutter;
1147             
1148 }
1149
1150 // prepareFlipFwd()
1151 //______________________________________________________________________________
1152 GnuBook.prototype.prepareFlipFwd = function(nextL, nextR) {
1153
1154     this.prefetchImg(nextL);
1155     this.prefetchImg(nextR);
1156
1157     var height  = this.getPageHeight(nextR); 
1158     var width   = this.getPageWidth(nextR);    
1159     var middle = ($('#GBcontainer').width() >> 1);
1160     var top  = ($('#GBcontainer').height() - this.twoPageH) >> 1;                
1161     var scaledW = this.twoPageH*width/height;
1162
1163     var gutter = middle+parseInt((2*nextL - this.numLeafs)*this.twoPageEdgeW/this.numLeafs/2);    
1164     
1165     $(this.prefetchedImgs[nextR]).css({
1166         position: 'absolute',
1167         left:   gutter+'px',
1168         top:    top+'px',
1169         backgroundColor: 'rgb(234, 226, 205)',
1170         height: this.twoPageH,
1171         width:  scaledW+'px',
1172         borderLeft: '1px solid black',
1173         zIndex: 1
1174     });
1175
1176     $('#GBcontainer').append(this.prefetchedImgs[nextR]);
1177
1178     height  = this.getPageHeight(nextL); 
1179     width   = this.getPageWidth(nextL);      
1180     scaledW = this.twoPageH*width/height;
1181
1182     $(this.prefetchedImgs[nextL]).css({
1183         position: 'absolute',
1184         right:   $('#GBcontainer').width()-gutter+'px',
1185         top:    top+'px',
1186         backgroundColor: 'rgb(234, 226, 205)',
1187         height: this.twoPageH,
1188         width:  0+'px',
1189         borderRight: '1px solid black',
1190         zIndex: 2
1191     });
1192
1193     $('#GBcontainer').append(this.prefetchedImgs[nextL]);    
1194
1195     return gutter;
1196             
1197 }
1198
1199 // getNextLeafs()
1200 //______________________________________________________________________________
1201 GnuBook.prototype.getNextLeafs = function(o) {
1202     //TODO: we might have two left or two right leafs in a row (damaged book)
1203     //For now, assume that leafs are contiguous.
1204     
1205     //return [this.currentLeafL+2, this.currentLeafL+3];
1206     o.L = this.currentLeafL+2;
1207     o.R = this.currentLeafL+3;
1208 }
1209
1210 // getprevLeafs()
1211 //______________________________________________________________________________
1212 GnuBook.prototype.getPrevLeafs = function(o) {
1213     //TODO: we might have two left or two right leafs in a row (damaged book)
1214     //For now, assume that leafs are contiguous.
1215     
1216     //return [this.currentLeafL-2, this.currentLeafL-1];
1217     o.L = this.currentLeafL-2;
1218     o.R = this.currentLeafL-1;
1219 }
1220
1221 // pruneUnusedImgs()
1222 //______________________________________________________________________________
1223 GnuBook.prototype.pruneUnusedImgs = function() {
1224     //console.log('current: ' + this.currentLeafL + ' ' + this.currentLeafR);
1225     for (var key in this.prefetchedImgs) {
1226         //console.log('key is ' + key);
1227         if ((key != this.currentLeafL) && (key != this.currentLeafR)) {
1228             //console.log('removing key '+ key);
1229             $(this.prefetchedImgs[key]).remove();
1230         }
1231         if ((key < this.currentLeafL-4) || (key > this.currentLeafR+4)) {
1232             //console.log('deleting key '+ key);
1233             delete this.prefetchedImgs[key];
1234         }
1235     }
1236 }
1237
1238 // prefetch()
1239 //______________________________________________________________________________
1240 GnuBook.prototype.prefetch = function() {
1241     var lim = this.currentLeafL-4;
1242     var i;
1243     lim = Math.max(lim, 0);
1244     for (i = lim; i < this.currentLeafL; i++) {
1245         this.prefetchImg(i);
1246     }
1247     
1248     if (this.numLeafs > (this.currentLeafR+1)) {
1249         lim = Math.min(this.currentLeafR+4, this.numLeafs-1);
1250         for (i=this.currentLeafR+1; i<=lim; i++) {
1251             this.prefetchImg(i);
1252         }
1253     }
1254 }
1255
1256 // getPageWidth2UP()
1257 //______________________________________________________________________________
1258 GnuBook.prototype.getPageWidth2UP = function(index) {
1259     var height  = this.getPageHeight(index); 
1260     var width   = this.getPageWidth(index);    
1261     return Math.floor(this.twoPageH*width/height);
1262 }    
1263
1264 // search()
1265 //______________________________________________________________________________
1266 GnuBook.prototype.search = function(term) {
1267     $('#GnuBookSearchScript').remove();
1268         var script  = document.createElement("script");
1269         script.setAttribute('id', 'GnuBookSearchScript');
1270         script.setAttribute("type", "text/javascript");
1271         script.setAttribute("src", 'http://'+this.server+'/GnuBook/flipbook_search_gb.php?url='+escape(this.bookPath+'/'+this.bookId+'_djvu.xml')+'&term='+term+'&format=XML&callback=gb.GBSearchCallback');
1272         document.getElementsByTagName('head')[0].appendChild(script);
1273 }
1274
1275 // GBSearchCallback()
1276 //______________________________________________________________________________
1277 GnuBook.prototype.GBSearchCallback = function(txt) {
1278     //alert(txt);
1279     if (jQuery.browser.msie) {
1280         var dom=new ActiveXObject("Microsoft.XMLDOM");
1281         dom.async="false";
1282         dom.loadXML(txt);    
1283     } else {
1284         var parser = new DOMParser();
1285         var dom = parser.parseFromString(txt, "text/xml");    
1286     }
1287     
1288     $('#GnuBookSearchResults').empty();    
1289     $('#GnuBookSearchResults').append('<ul>');
1290     
1291     for (var key in this.searchResults) {
1292         if (null != this.searchResults[key].div) {
1293             $(this.searchResults[key].div).remove();
1294         }
1295         delete this.searchResults[key];
1296     }
1297     
1298     var pages = dom.getElementsByTagName('PAGE');
1299     for (var i = 0; i < pages.length; i++){
1300         //console.log(pages[i].getAttribute('file').substr(1) +'-'+ parseInt(pages[i].getAttribute('file').substr(1), 10));
1301
1302         
1303         var re = new RegExp (/_(\d{4})/);
1304         var reMatch = re.exec(pages[i].getAttribute('file'));
1305         var leafNum = parseInt(reMatch[1], 10);
1306         //var leafNum = parseInt(pages[i].getAttribute('file').substr(1), 10);
1307         
1308         var children = pages[i].childNodes;
1309         var context = '';
1310         for (var j=0; j<children.length; j++) {
1311             //console.log(j + ' - ' + children[j].nodeName);
1312             //console.log(children[j].firstChild.nodeValue);
1313             if ('CONTEXT' == children[j].nodeName) {
1314                 context += children[j].firstChild.nodeValue;
1315             } else if ('WORD' == children[j].nodeName) {
1316                 context += '<b>'+children[j].firstChild.nodeValue+'</b>';
1317                 
1318                 var index = this.leafNumToIndex(leafNum);
1319                 if (null != index) {
1320                     //coordinates are [left, bottom, right, top, [baseline]]
1321                     //we'll skip baseline for now...
1322                     var coords = children[j].getAttribute('coords').split(',',4);
1323                     if (4 == coords.length) {
1324                         this.searchResults[index] = {'l':coords[0], 'b':coords[1], 'r':coords[2], 't':coords[3], 'div':null};
1325                     }
1326                 }
1327             }
1328         }
1329         //TODO: remove hardcoded instance name
1330         $('#GnuBookSearchResults').append('<li><b><a href="javascript:gb.jumpToIndex('+index+');">Leaf ' + leafNum + '</a></b> - ' + context+'</li>');
1331     }
1332     $('#GnuBookSearchResults').append('</ul>');
1333
1334     this.updateSearchHilites();
1335 }
1336
1337 // updateSearchHilites()
1338 //______________________________________________________________________________
1339 GnuBook.prototype.updateSearchHilites = function() {
1340     if (2 == this.mode) {
1341         this.updateSearchHilites2UP();
1342     } else {
1343         this.updateSearchHilites1UP();
1344     }
1345 }
1346
1347 // showSearchHilites1UP()
1348 //______________________________________________________________________________
1349 GnuBook.prototype.updateSearchHilites1UP = function() {
1350
1351     for (var key in this.searchResults) {
1352         
1353         if (-1 != jQuery.inArray(parseInt(key), this.displayedLeafs)) {
1354             var result = this.searchResults[key];
1355             if(null == result.div) {
1356                 result.div = document.createElement('div');
1357                 $(result.div).attr('className', 'GnuBookSearchHilite').appendTo('#pagediv'+key);
1358                 //console.log('appending ' + key);
1359             }    
1360             $(result.div).css({
1361                 width:  (result.r-result.l)/this.reduce + 'px',
1362                 height: (result.b-result.t)/this.reduce + 'px',
1363                 left:   (result.l)/this.reduce + 'px',
1364                 top:    (result.t)/this.reduce +'px'
1365             });
1366
1367         } else {
1368             //console.log(key + ' not displayed');
1369             this.searchResults[key].div=null;
1370         }
1371     }
1372 }
1373
1374 // showSearchHilites2UP()
1375 //______________________________________________________________________________
1376 GnuBook.prototype.updateSearchHilites2UP = function() {
1377
1378     var middle = ($('#GBcontainer').width() >> 1);
1379
1380     for (var key in this.searchResults) {
1381         key = parseInt(key, 10);
1382         if (-1 != jQuery.inArray(key, this.displayedLeafs)) {
1383             var result = this.searchResults[key];
1384             if(null == result.div) {
1385                 result.div = document.createElement('div');
1386                 $(result.div).attr('className', 'GnuBookSearchHilite').css('zIndex', 3).appendTo('#GBcontainer');
1387                 //console.log('appending ' + key);
1388             }
1389
1390             var height = this.getPageHeight(key);
1391             var width  = this.getPageWidth(key)
1392             var reduce = this.twoPageH/height;
1393             var scaledW = parseInt(width*reduce);
1394             
1395             var gutter = middle+parseInt((2*this.currentLeafL - this.numLeafs)*this.twoPageEdgeW/this.numLeafs/2);
1396             
1397             if ('L' == this.getPageSide(key)) {
1398                 var pageL = gutter-scaledW;
1399             } else {
1400                 var pageL = gutter;
1401             }
1402             var pageT  = ($('#GBcontainer').height() - this.twoPageH) >> 1;                
1403                         
1404             $(result.div).css({
1405                 width:  (result.r-result.l)*reduce + 'px',
1406                 height: (result.b-result.t)*reduce + 'px',
1407                 left:   pageL+(result.l)*reduce + 'px',
1408                 top:    pageT+(result.t)*reduce +'px'
1409             });
1410
1411         } else {
1412             //console.log(key + ' not displayed');
1413             if (null != this.searchResults[key].div) {
1414                 //console.log('removing ' + key);
1415                 $(this.searchResults[key].div).remove();
1416             }
1417             this.searchResults[key].div=null;
1418         }
1419     }
1420 }
1421
1422 // removeSearchHilites()
1423 //______________________________________________________________________________
1424 GnuBook.prototype.removeSearchHilites = function() {
1425     for (var key in this.searchResults) {
1426         if (null != this.searchResults[key].div) {
1427             $(this.searchResults[key].div).remove();
1428             this.searchResults[key].div=null;
1429         }        
1430     }
1431 }
1432
1433 // showEmbedCode()
1434 //______________________________________________________________________________
1435 GnuBook.prototype.showEmbedCode = function() {
1436     if (null != this.embedPopup) { // check if already showing
1437         return;
1438     }
1439     this.autoStop();
1440     this.embedPopup = document.createElement("div");
1441     $(this.embedPopup).css({
1442         position: 'absolute',
1443         top:      '20px',
1444         left:     ($('#GBcontainer').width()-400)/2 + 'px',
1445         width:    '400px',
1446         padding:  "20px",
1447         border:   "3px double #999999",
1448         zIndex:   3,
1449         backgroundColor: "#fff"
1450     }).appendTo('#GnuBook');
1451
1452     htmlStr =  '<p style="text-align:center;"><b>Embed Bookreader in your blog!</b></p>';
1453     htmlStr += '<p><b>Note:</b> The bookreader is still in beta testing. URLs may change in the future, breaking embedded books. This feature is just for testing!</b></p>';
1454     htmlStr += '<p>The bookreader uses iframes for embedding. It will not work on web hosts that block iframes. The embed feature has been tested on blogspot.com blogs as well as self-hosted Wordpress blogs. This feature will NOT work on wordpress.com blogs.</p>';
1455     htmlStr += '<p>Embed Code: <input type="text" size="40" value="<iframe src=\'http://www.us.archive.org/GnuBook/GnuBookEmbed.php?id='+this.bookId+'\' width=\'430px\' height=\'430px\'></iframe>"></p>';
1456     htmlStr += '<p style="text-align:center;"><a href="" onclick="gb.embedPopup = null; $(this.parentNode.parentNode).remove(); return false">Close popup</a></p>';    
1457
1458     this.embedPopup.innerHTML = htmlStr;    
1459 }
1460
1461 // autoToggle()
1462 //______________________________________________________________________________
1463 GnuBook.prototype.autoToggle = function() {
1464     if (2 != this.mode) {
1465         this.switchMode(2);
1466     }
1467
1468     var self = this;
1469     if (null == this.autoTimer) {
1470         this.flipSpeed = 2000;
1471         this.flipFwdToIndex();
1472
1473         $('#autoImg').removeClass('play').addClass('pause');
1474         this.autoTimer=setInterval(function(){
1475             if (self.animating) {return;}
1476
1477             if (self.currentLeafR >= self.numLeafs-5) {
1478                 self.flipBackToIndex(1);
1479             } else {
1480                 self.flipFwdToIndex();
1481             }
1482         },5000);
1483     } else {
1484         this.autoStop();
1485     }
1486 }
1487
1488 // autoStop()
1489 //______________________________________________________________________________
1490 GnuBook.prototype.autoStop = function() {
1491     if (null != this.autoTimer) {
1492         clearInterval(this.autoTimer);
1493         this.flipSpeed = 'fast';
1494         $('#autoImg').removeClass('pause').addClass('play');
1495         this.autoTimer = null;
1496     }
1497 }
1498
1499 // keyboardNavigationIsDisabled(event)
1500 //   - returns true if keyboard navigation should be disabled for the event
1501 //______________________________________________________________________________
1502
1503 GnuBook.prototype.keyboardNavigationIsDisabled = function(event) {
1504     if (event.target.tagName == "INPUT") {
1505         return true;
1506     }   
1507     return false;
1508 }