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