Merge branch 'read_aloud' into newui
authorrajbot <raj@archive.org>
Tue, 12 Oct 2010 20:47:59 +0000 (20:47 +0000)
committerrajbot <raj@archive.org>
Tue, 12 Oct 2010 20:47:59 +0000 (20:47 +0000)
Conflicts:
BookReader/BookReader.css
BookReader/BookReader.js
BookReader/images/read_aloud.png
BookReaderIA/datanode/BookReaderGetText.py
BookReaderIA/datanode/BookReaderGetTextWrapper.php
BookReaderIA/inc/BookReader.inc

1  2 
BookReader/BookReader.css
BookReader/BookReader.js
BookReaderIA/datanode/BookReaderJSIA.php
BookReaderIA/inc/BookReader.inc

@@@ -368,288 -252,20 +368,296 @@@ a.BRgrey:visited   { color: #666; 
  }
  
  .BRtwoPagePopUp {
 -    border: 1px solid black;
 -    padding: 2px 6px;
 +    padding: 6px;
 +    position: absolute;
 +    font-family: Arial, sans-serif;
 +    font-size: 11px;
 +    color: white;
 +    background-color: #939598;
 +    opacity: 0.85,
 +    -webkit-border-radius: 4px;
 +    -moz-border-radius: 4px;
 +    border-radius: 4px;
 +    white-space: nowrap;
 +}
 +
 +/* COLORBOX POP-UP */
 +
 +#colorbox, #cboxOverlay, #cboxWrapper{position:absolute; top:0; left:0; z-index:9999;}
 +#cboxOverlay{position:fixed; width:100%; height:100%;background:#000;opacity:0.75;filter:Alpha(Opacity=75);}
 +#cboxMiddleLeft, #cboxBottomLeft{clear:left;}
 +#cboxContent{position:relative;}
 +#cboxLoadedContent{overflow:visible!important;}
 +#cboxLoadedContent iframe{display:block;border:0;}
 +#cboxTitle{margin:0;display:none!important;}
 +#cboxLoadingOverlay, #cboxLoadingGraphic{position:absolute; top:25px; left:25px; width:100%;}
 +#cboxPrevious, #cboxNext, #cboxClose, #cboxSlideshow{cursor:pointer;}
 +#cboxClose{display:none!important;}
 +
 +#colorBox{}
 +    #cboxContent{background:#fff;padding:0;border:10px solid #615132;-webkit-border-radius:12px;-moz-border-radius:12px;border-radius:12px;-moz-box-shadow: 1px 3px 10px #000;-webkit-box-shadow: 1px 3px 10px #000;box-shadow: 1px 3px 10px #000;}
 +        #cboxLoadedContent{background:#fff;margin:0;}
 +        #cboxLoadingOverlay{background:transparent;}
 +        /* XXXmang where is icon_close? */
 +        #cboxClose{position:absolute;top:20px;right:20px;display:block;width:32px;height:32px;background-image:url(/images/icons/icon_close-pop.png);background-position:0 0;background-repeat:no-repeat;}
 +        #cboxClose:hover{background-position:0 -32px;}
 +
 +div#BRpage {
 +    float: right;
 +    width: 80px;
 +    padding-left:12px;
 +    text-align: right;
 +}
 +div#BRnav {
 +    position: fixed;
 +    bottom: 0;
 +    left: 0;
 +    width: 100%;
 +    height: 40px;
 +    overflow: visible;
 +    z-index: 100;
 +    background-color: #e2dcc5;    
 +}
 +div#BRnavpos {
 +    position: relative;
 +    margin-right: 80px;
 +    height: 40px;
 +}
 +div#BRpager {
 +    position: relative;
 +    margin-left: -10px;
 +    height: 40px;
 +}
 +div#BRslider {
 +    position: absolute;
 +    top: 13px;
 +    height: 27px;
 +}
 +div#BRfiller {
 +    position: absolute;
 +    height: 40px;
 +    width: 10px;
 +    background-color: #e2dcc5;
 +    top: 0;
 +    left: 0;
 +    z-index: 102;
 +}
 +div#slider {
 +    position: absolute;
 +    width: 2500px;
 +    height: 27px;
 +    top: 0;
 +    left: -2478px;
 +    background-color: #000;
 +    opacity: .1;
 +    z-index: 101;
 +}
 +div#pager {
 +    position: absolute;
 +    width: 23px;
 +    height: 27px;
 +    top: 0;
 +    left: 8px;
 +    background: url(images/slider.png);
 +    z-index: 103;
 +}
 +div#pagenum {
 +    display: none;
 +    position: absolute;
 +    left: 24px;
 +    top: 4px;
 +    color: #999;
 +    font-size: 11px;
 +    line-height: 19px;
 +    font-weight: 700;
 +    padding: 0 5px;
 +    width: 80px;
 +    text-align: right;
 +    background-color: #000;
 +    font-family: "Lucida Grande", "Arial", sans-serif;
 +}
 +div#pagenum span {
 +    color: #ffa337;
 +    font-style: italic;
 +}
 +div#BRnavline {
 +    position: relative;
 +    height: 2px;
 +    width: auto;
 +    background-color: #000;
 +    top: -29px;
 +    margin: 0 10px;
 +}
 +.BRnavend {
 +    position: absolute;
 +    top: -2px;
 +    width: 1px;
 +    height: 6px;
 +    background-color: #000;
 +}
 +#BRnavleft {
 +    left: 0;
 +}
 +#BRnavright {
 +    right: 0;
 +}
 +div.chapter {
 +    position: absolute;
 +    top: -13px;
 +    width: 18px;
 +    height: 27px;
 +    background: transparent url(images/marker_chap.png) no-repeat;
 +    cursor: pointer;
 +}
 +div.chapter div.title {
 +    display: none;
 +}
 +div.title span {
 +    color: #666;
 +    padding: 0 5px;
 +}
 +div.search {
 +    position: absolute;
 +    top: -13px;
 +    width: 18px;
 +    height: 27px;
 +    background-color: transparent;
 +    background-image: url(images/marker_srch-off.png);
 +    background-repeat: no-repeat;
 +    cursor: pointer;
 +}
 +div.search.front {
 +    background: transparent url(images/marker_srch-on.png) no-repeat;
 +}
 +div.search div.query,div.searchChap div.query {
 +    display: none;
 +}
 +div.query {
 +    position: relative;
 +}
 +div.query strong {
 +    color: #000;
 +    font-weight: 700;
 +}
 +div.query span {
 +    font-size: 10px;
 +    color: #666;
 +    font-style: italic;
 +}
 +div.query div.queryChap {
 +    position: absolute;
 +    top: -40px;
 +    left: -13px;
 +    width: 256px;
 +    overflow: hidden;
 +    text-align: center;
 +    background: #000;
 +    padding: 5px 10px;
 +    color: #fff;
 +    font-weight: 700;
 +    font-size: 11px;
 +}
 +div.query div.queryChap span {
 +    color: #666;
 +    padding: 0 5px;
 +    font-style: normal;
 +}
 +div.search div.pointer {
 +    position: absolute;
 +    left: 121px;
 +    bottom: -14px;
 +    width: 18px;
 +    height: 27px;
 +    background: transparent url(images/marker_srch-on.png) no-repeat;
 +}
 +div.searchChap {
 +    position: absolute;
 +    top: -13px;
 +    width: 18px;
 +    height: 27px;
 +    background-color: transparent;
 +    background-image: url(images/marker_srchchap-off.png);
 +    background-repeat: no-repeat;
 +    cursor: pointer;
 +}
 +div.searchChap.front {
 +    background-image: url(images/marker_srchchap-on.png);
 +}
 +#BRnav .front {
 +    z-index: 10001;
 +}
 +div#BRzoomer {
 +    position: fixed;
 +    bottom: 40px;
 +    right: 0;
 +    width: 26px;
 +    height: 190px;
 +    z-index: 100;
 +}
 +div#BRzoompos {
 +    position: relative;
 +    width: 26px;
 +    height: 190px;
 +    top: 0;
 +    left: 0;
 +}
 +div#BRzoomer button {
 +    position: absolute;
 +    left: 0;
 +    background-color: #e2dcc5;
 +    width: 26px;
 +}
 +div#BRzoomer button:hover {
 +    background-color: #000;
 +}
 +div#BRzoomer .zoom_out {
 +    top: 0;
 +    -webkit-border-top-left-radius: 6px;
 +    -webkit-border-bottom-left-radius: 6px;
 +    -moz-border-radius-topleft: 6px;
 +    -moz-border-radius-bottomleft: 6px;
 +    border-top-left-radius: 6px;
 +    border-bottom-left-radius: 6px;
 +    -webkit-box-shadow: 2px 2px 2px #333;
 +    -moz-box-shadow: 2px 2px 2px #333;
 +    box-shadow: 2px 2px 2px #333;
 +}
 +div#BRzoomer .zoom_in {
 +    bottom: 0;
 +    -webkit-border-top-left-radius: 6px;
 +    -moz-border-radius-topleft: 6px;
 +    border-top-left-radius: 6px;
 +}
 +div#BRzoomcontrol {
 +    position: relative;
 +    top: 40px;
 +    left:3px;
 +    width: 23px;
 +    height: 110px;    
 +}
 +div#BRzoomstrip {
 +    position: absolute;
 +    top: 0;
 +    left: 0;
 +    width: 23px;
 +    height: 110px;
 +    background-color: #000;
 +    opacity: .1;
 +}
 +div#BRzoombtn {
      position: absolute;
 -    font-family: sans-serif;
 -    font-size: 14px;
 -    background-color: rgb(255, 255, 238);
 -    opacity: 0.85;
 +    width: 23px;
 +    height: 23px;
 +    top: 0;
 +    left: 0;
 +    background: url("images/icon_zoomer.png");
  }
  
+ .BRttsPopUp {
+     position: absolute;
+     background-color: #E6E4E1;
+     background-image: url(images/progressbar.gif);
+     background-repeat:no-repeat;
+     font-size: 0.8em; 
+     z-index: 3;    
+ }
@@@ -113,16 -104,13 +113,24 @@@ function BookReader() 
          autofit: 'auto'
      };
      
 +    // This object/dictionary controls which optional features are enabled
 +    // XXXmang in progress
 +    this.features = {
 +        // search
 +        // read aloud
 +        // open library entry
 +        // table of contents
 +        // embed/share ui
 +        // info ui
 +    };
++
+     // Text-to-Speech params
+     this.ttsPlaying     = false;
+     this.ttsIndex       = null;  //leaf index
+     this.ttsPosition    = -1;    //chunk (paragraph) number
+     this.ttsBuffering   = false;
+     this.ttsPoller      = null;
+     this.ttsFormat      = null;
      
      return this;
  };
@@@ -2963,6 -2946,11 +2976,12 @@@ BookReader.prototype.updatePrintFrame 
  // showEmbedCode()
  //______________________________________________________________________________
  BookReader.prototype.showEmbedCode = function() {
+     if (null != this.embedPopup) { // check if already showing
+         return;
+     }
+     this.autoStop();
+     this.ttsStop();
++
      this.embedPopup = document.createElement("div");
      $(this.embedPopup).css({
          position: 'absolute',
@@@ -3191,286 -3134,17 +3210,286 @@@ BookReader.prototype.jumpIndexForRightE
      }
  }
  
 +// initNavbar
 +//______________________________________________________________________________
 +// Initialize the navigation bar.
 +// $$$ this could also add the base elements to the DOM, so disabling the nav bar
 +//     could be as simple as not calling this function
 +BookReader.prototype.initNavbar = function() {
 +    // Setup nav / chapter / search results bar
 +    
 +    // $$$ should make this work inside the BookReader div (self-contained), rather than after
 +    $('#BookReader').after(
 +        '<div id="BRnav">'
 +        +     '<div id="BRpage">'
 +        +         '<button class="BRicon book_left"></button>'
 +        +         '<button class="BRicon book_right"></button>'
 +        +     '</div>'
 +        +     '<div id="BRnavpos">'
 +        +         '<div id="BRfiller"></div>'
 +        +         '<div id="BRpager">'
 +        +             '<div id="BRslider">'
 +        +                 '<div id="slider"></div>'
 +        +                 '<div id="pager"></div>'
 +        // XXXmang update code to update pagenum
 +        +                 '<div id="pagenum"><span>n141</span> / 325</div>'
 +        +             '</div>'
 +        +         '</div>'       
 +        +         '<div id="BRnavline">'
 +        +             '<div class="BRnavend" id="BRnavleft"></div>'
 +        +             '<div class="BRnavend" id="BRnavright"></div>'
 +        +         '</div>'     
 +        +     '</div>'
 +        + '</div>'
 +    );
 +    
 +/*
 +        <div class="searchChap" style="left:49%;" title="Search result">
 +            <div class="query">
 +            A related distinction is between the emotion and the results of the emotion, principally behaviors and <strong><a href="">emotional</a></strong> expressions. People often behave in certain ways as a direct result of their <strong><a href="">emotional</a></strong> state, such as crying, fighting or fleeing. <span>Page 163</span>
 +                <div class="queryChap">IV. The Witch <span>|</span> Page 163</div>
 +            </div>
 +        </div>
 +*/
 +    
 +    /* $$$mang search results and chapters should automatically coalesce
 +    $('.searchChap').bt({
 +        contentSelector: '$(this).find(".query")',
 +        trigger: 'click',
 +        closeWhenOthersOpen: true,
 +        cssStyles: {
 +            width: '250px',
 +            padding: '10px 10px 15px',
 +            backgroundColor: '#fff',
 +            border: '3px solid #e2dcc5',
 +            borderBottom: 'none',
 +            fontFamily: '"Lucida Grande","Arial",sans-serif',
 +            fontSize: '12px',
 +            lineHeight: '18px',
 +            color: '#615132'
 +        },
 +        shrinkToFit: false,
 +        width: '230px',
 +        padding: 0,
 +        spikeGirth: 0,
 +        spikeLength: 0,
 +        overlap: '10px',
 +        overlay: false,
 +        killTitle: true, 
 +        textzIndex: 9999,
 +        boxzIndex: 9998,
 +        wrapperzIndex: 9997,
 +        offsetParent: null,
 +        positions: ['top'],
 +        fill: 'white',
 +        windowMargin: 10,
 +        strokeWidth: 3,
 +        strokeStyle: '#e2dcc5',
 +        cornerRadius: 0,
 +        centerPointX: 0,
 +        centerPointY: 0,
 +        shadow: false
 +    });
 +    $('.searchChap').each(function(){
 +        $(this).hover(function(){
 +            $(this).addClass('front');
 +        },function(){
 +            $(this).removeClass('front');
 +        });
 +    });
 +    */
 +}
 +
 +BookReader.prototype.addSearchResult = function(queryString, pageNumber, pageIndex) {
 +    var uiStringSearch = "Search result"; // i18n
 +    var uiStringPage = "Page"; // i18n
 +    
 +    var percentThrough = BookReader.util.cssPercentage(pageIndex, this.numLeafs);
 +    
 +    // $$$mang add click-through to page
 +    $('<div class="search" style="left:' + percentThrough + ';" title="' + uiStringSearch + '"><div class="query">'
 +        + queryString + '<span>' + uiStringPage + ' ' + pageNumber + '</span></div>')
 +    .appendTo('#BRnavpos').bt({
 +        contentSelector: '$(this).find(".query")',
 +        trigger: 'click',
 +        closeWhenOthersOpen: true,
 +        cssStyles: {
 +            padding: '10px 10px 15px',
 +            backgroundColor: '#fff',
 +            border: '3px solid #e2dcc5',
 +            borderBottom: 'none',
 +            fontFamily: '"Lucida Grande","Arial",sans-serif',
 +            fontSize: '12px',
 +            lineHeight: '18px',
 +            color: '#615132'
 +        },
 +        shrinkToFit: false,
 +        width: '230px',
 +        padding: 0,
 +        spikeGirth: 0,
 +        spikeLength: 0,
 +        overlap: '10px',
 +        overlay: false,
 +        killTitle: false, 
 +        textzIndex: 9999,
 +        boxzIndex: 9998,
 +        wrapperzIndex: 9997,
 +        offsetParent: null,
 +        positions: ['top'],
 +        fill: 'white',
 +        windowMargin: 10,
 +        strokeWidth: 3,
 +        strokeStyle: '#e2dcc5',
 +        cornerRadius: 0,
 +        centerPointX: 0,
 +        centerPointY: 0,
 +        shadow: false
 +    })
 +    .hover(function(){
 +              $(this).addClass('front');
 +          },function(){
 +              $(this).removeClass('front');
 +          }
 +    );
 +}
 +
 +BookReader.prototype.removeSearchResults = function() {
 +    $('#BRnavpos .search').remove();
 +}
 +
 +BookReader.prototype.addChapter = function(chapterTitle, pageNumber, pageIndex) {
 +    var uiStringPage = 'Page'; // i18n
 +
 +    var percentThrough = BookReader.util.cssPercentage(pageIndex, this.numLeafs);
 +    
 +    $('<div class="chapter" style="left:' + percentThrough + ';"><div class="title">'
 +        + chapterTitle + '<span>|</span> ' + uiStringPage + ' ' + pageNumber + '</div></div>')
 +    .appendTo('#BRnavpos')
 +    .data({'self': this, 'pageIndex': pageIndex })
 +    .bt({
 +        contentSelector: '$(this).find(".title")',
 +        trigger: 'hover',
 +        closeWhenOthersOpen: true,
 +        cssStyles: {
 +            backgroundColor: '#000',
 +            border: '2px solid #e2dcc5',
 +            borderBottom: 'none',
 +            padding: '5px 10px',
 +            fontFamily: '"Arial", sans-serif',
 +            fontSize: '11px',
 +            fontWeight: '700',
 +            color: '#fff',
 +            whiteSpace: 'nowrap'
 +        },
 +        shrinkToFit: true,
 +        width: '200px',
 +        padding: 0,
 +        spikeGirth: 0,
 +        spikeLength: 0,
 +        overlap: '16px',
 +        overlay: false,
 +        killTitle: true, 
 +        textzIndex: 9999,
 +        boxzIndex: 9998,
 +        wrapperzIndex: 9997,
 +        offsetParent: null,
 +        positions: ['top'],
 +        fill: 'black',
 +        windowMargin: 10,
 +        strokeWidth: 0,
 +        cornerRadius: 0,
 +        centerPointX: 0,
 +        centerPointY: 0,
 +        shadow: false
 +    })
 +    .hover( function() {
 +                $(this).addClass('front');
 +            }, function() {
 +                $(this).removeClass('front');
 +            }
 +    )
 +    .bind('click', function() {
 +        $(this).data('self').jumpToIndex($(this).data('pageIndex'));
 +    });
 +}
 +
 +/*
 + * Remove all chapters.
 + */
 +BookReader.prototype.removeChapters = function() {
 +    $('#BRnavpos .chapter').remove();
 +}
 +
 +/*
 + * Update the table of contents based on array of TOC entries.
 + */
 +BookReader.prototype.updateTOC = function(tocEntries) {
 +    this.removeChapters();
 +    for (var i = 0; i < tocEntries.length; i++) {
 +        this.addChapterFromEntry(tocEntries[i]);
 +    }
 +}
 +
 +/*
 + *   Example table of contents entry - this format is defined by Open Library
 + *   {
 + *       "pagenum": "17",
 + *       "level": 1,
 + *       "label": "CHAPTER I",
 + *       "type": {"key": "/type/toc_item"},
 + *       "title": "THE COUNTRY AND THE MISSION"
 + *   }
 + */
 +BookReader.prototype.addChapterFromEntry = function(tocEntryObject) {
 +    console.log(tocEntryObject);
 +    var pageIndex = this.getPageIndex(tocEntryObject['pagenum']);
 +    // Only add if we know where it is
 +    if (pageIndex) {
 +        this.addChapter(tocEntryObject['title'], tocEntryObject['pagenum'], pageIndex);
 +    }
 +    $('.chapter').each(function(){
 +        $(this).hover(function(){
 +            $(this).addClass('front');
 +        },function(){
 +            $(this).removeClass('front');
 +        });
 +    });
 +    $('.search').each(function(){
 +        $(this).hover(function(){
 +            $(this).addClass('front');
 +        },function(){
 +            $(this).removeClass('front');
 +        });
 +    });
 +    $('.searchChap').each(function(){
 +        $(this).hover(function(){
 +            $(this).addClass('front');
 +        },function(){
 +            $(this).removeClass('front');
 +        });
 +    });
 +    $("#BRslider").draggable({axis:'x',containment:'parent'});
 +    $("#BRzoombtn").draggable({axis:'y',containment:'parent'});
 +    $("#BRslider").hover(
 +        function(){
 +            $("#pagenum").show();
 +        },function(){
 +            $("#pagenum").hide();
 +        });
 +}
 +
  BookReader.prototype.initToolbar = function(mode, ui) {
  
 -    $("#BookReader").append("<div id='BRtoolbar'>"
 -        + "<span id='BRtoolbarbuttons' style='float: right'>"
 -        +   "<button class='BRicon rollover read_aloud' onclick='br.ttsToggle(); return false;'/>"
 -        +   "<button class='BRicon print rollover' /> <button class='BRicon rollover embed' />"
 -        +   "<form class='BRpageform' action='javascript:' onsubmit='br.jumpToPage(this.elements[0].value)'> <span class='label'>Page:<input id='BRpagenum' type='text' size='3' onfocus='br.autoStop();'></input></span></form>"
 -        +   "<div class='BRtoolbarmode2' style='display: none'><button class='BRicon rollover book_leftmost' /><button class='BRicon rollover book_left' /><button class='BRicon rollover book_right' /><button class='BRicon rollover book_rightmost' /></div>"
 -        +   "<div class='BRtoolbarmode1' style='display: none'><button class='BRicon rollover book_top' /><button class='BRicon rollover book_up' /> <button class='BRicon rollover book_down' /><button class='BRicon rollover book_bottom' /></div>"
 -        +   "<div class='BRtoolbarmode3' style='display: none'><button class='BRicon rollover book_top' /><button class='BRicon rollover book_up' /> <button class='BRicon rollover book_down' /><button class='BRicon rollover book_bottom' /></div>"
 -        +   "<button class='BRicon rollover play' /><button class='BRicon rollover pause' style='display: none' />"
 +    // $$$mang should be contained within the BookReader div instead of body
 +    $("body").append("<div id='BRtoolbar'>"
 +        + "<span id='BRtoolbarbuttons'>"
 +        /* XXXmang integrate search */
 +        +   "<form method='get' id='booksearch'><input type='search' id='textSrch' name='textSrch' val='' placeholder='Search inside'/><button type='submit' id='btnSrch' name='btnSrch'>GO</button></form>"
 +        // XXXmang icons incorrect or handlers wrong
 +        +   "<button class='BRicon info' onclick='br.switchMode(3); return false;'></button>"
 +        +   "<button class='BRicon share' onclick='br.switchMode(2); return false;'></button>"
-         +   "<button class='BRicon read modal'></button>"
++        +   "<button class='BRicon read modal' onclick='br.ttsToggle(); return false;'></button>"
 +        +   "<button class='BRicon full'></button>"
          + "</span>"
          
          + "<span>"
@@@ -4233,3 -3800,413 +4254,413 @@@ BookReader.util = 
      }
      // The final property here must NOT have a comma after it - IE7
  }
 -}
+ // ttsToggle()
+ //______________________________________________________________________________
+ BookReader.prototype.ttsToggle = function () {
+     if (false == this.ttsPlaying) {        
+         if(soundManager.supported()) {
+             this.ttsStart();            
+         } else {               
+             soundManager.onready(function(oStatus) {
+               if (oStatus.success) {                
+                 this.ttsStart();
+               } else {
+                 alert('Could not load soundManger2, possibly due to FlashBlock. Audio playback is disabled');
+               }
+             }, this);        
+         }
+     } else {
+         this.ttsStop();
+     }
+ }
+ // ttsStart()
+ //______________________________________________________________________________
+ BookReader.prototype.ttsStart = function () {
+     if (soundManager.debugMode) console.log('starting readAloud');
+     if (this.constModeThumb == this.mode) this.switchMode(this.constMode1up);
+     
+     this.ttsPlaying = true;
+     this.ttsIndex = this.currentIndex();
+     this.ttsFormat = 'mp3';
+     if ($.browser.mozilla) {
+         this.ttsFormat = 'ogg';
+     }
+     this.ttsGetText(this.ttsIndex, 'ttsStartCB');    
+ }
+ // ttsStop()
+ //______________________________________________________________________________
+ BookReader.prototype.ttsStop = function () {
+     if (false == this.ttsPlaying) return;
+     
+     if (soundManager.debugMode) console.log('stopping readaloud');
+     soundManager.stopAll();
+     soundManager.destroySound('chunk'+this.ttsIndex+'-'+this.ttsPosition);
+     this.ttsRemoveHilites();
+     this.ttsRemovePopup();
+     this.ttsPlaying     = false;
+     this.ttsIndex       = null;  //leaf index
+     this.ttsPosition    = -1;    //chunk (paragraph) number
+     this.ttsBuffering   = false;
+     this.ttsPoller      = null;
+ }
+ // ttsGetText()
+ //______________________________________________________________________________
+ BookReader.prototype.ttsGetText = function(index, callback) {
+     var url = 'http://'+this.server+'/BookReader/BookReaderGetTextWrapper.php?path='+this.bookPath+'_djvu.xml&page='+index;    
+     this.ttsAjax = $.ajax({url:url, dataType:'jsonp', jsonpCallback:callback});
+ }
+ // ttsStartCB(): text-to-speech callback
+ //______________________________________________________________________________
+ BookReader.prototype.ttsStartCB = function (data) {
+     if (soundManager.debugMode)  console.log('ttsStartCB got data: ' + data);
+     this.ttsChunks = data;
+     this.ttsHilites = [];
+     
+     //deal with the page being blank
+     if (0 == data.length) {
+         if (soundManager.debugMode) console.log('first page is blank!');
+         if(this.ttsAdvance(true)) {
+             this.ttsGetText(this.ttsIndex, 'ttsStartCB');            
+         }
+         return;
+     }
+     
+     this.ttsShowPopup();
+     
+     ///// whileloading: broken on safari
+     ///// onload fires on safari, but *after* the sound starts playing..
+     this.ttsPosition = -1;    
+     var snd = soundManager.createSound({
+      id: 'chunk'+this.ttsIndex+'-0',
+      //url: 'http://home.us.archive.org/~rkumar/arctic.ogg',
+      url: 'http://'+this.server+'/BookReader/BookReaderGetTTS.php?string=' + escape(data[0][0]) + '&format=.'+this.ttsFormat, //the .ogg is to trick SoundManager2 to use the HTML5 audio player
+      whileloading: function(){if (this.bytesLoaded == this.bytesTotal) this.br.ttsRemovePopup();}, //onload never fires in FF...
+      onload: function(){this.br.ttsRemovePopup();} //whileloading never fires in safari...
+     });    
+     snd.br = this;
+     snd.load();
+     this.ttsNextChunk();
+ }
+ // ttsShowPopup
+ //______________________________________________________________________________
+ BookReader.prototype.ttsShowPopup = function() {
+     if (soundManager.debugMode) console.log('ttsShowPopup index='+this.ttsIndex+' pos='+this.ttsPosition);
+     
+     this.popup = document.createElement("div");
+     $(this.popup).css({
+         top:      $('#BRtoolbar').height() + 'px',
+         left:     $('.read_aloud').position().left + 'px',
+         width:    $('#BRtoolbar').width()-$('.read_aloud').position().left + 'px',
+         height:   '20px',
+     }).attr('className', 'BRttsPopUp').appendTo('#BookReader');
+     htmlStr =  '&nbsp;';
+     this.popup.innerHTML = htmlStr;
+ }
+ // ttsRemovePopup
+ //______________________________________________________________________________
+ BookReader.prototype.ttsRemovePopup = function() {
+     $(this.popup).remove(); 
+     this.popup=null;
+ }
+ // ttsNextPageCB
+ //______________________________________________________________________________
+ BookReader.prototype.ttsNextPageCB = function (data) {
+     this.ttsNextChunks = data;
+     if (soundManager.debugMode) console.log('preloaded next chunks.. data is ' + data);
+     
+     if (true == this.ttsBuffering) {
+         if (soundManager.debugMode) console.log('ttsNextPageCB: ttsBuffering is true');
+         this.ttsBuffering = false;
+     }
+ }
+ // ttsLoadChunk
+ //______________________________________________________________________________
+ BookReader.prototype.ttsLoadChunk = function (page, pos, string) {
+     var snd = soundManager.createSound({
+      id: 'chunk'+page+'-'+pos,
+      url: 'http://'+this.server+'/BookReader/BookReaderGetTTS.php?string=' + escape(string) + '&format=.'+this.ttsFormat //the .ogg is to trick SoundManager2 to use the HTML5 audio player
+     });
+     snd.br = this;
+     snd.load()
+ }
+ // ttsNextChunk()
+ //______________________________________________________________________________
+ // This function into two parts: ttsNextChunk gets run before page flip animation
+ // and ttsNextChunkPhase2 get run after page flip animation.
+ // If a page flip is necessary, ttsAdvance() will return false so Phase2 isn't
+ // called. Instead, this.animationFinishedCallback is set, so that Phase2
+ // continues after animation is finished.
+ BookReader.prototype.ttsNextChunk = function () {
+     if (soundManager.debugMode) console.log('nextchunk pos=' + this.ttsPosition);
+     
+     if (-1 != this.ttsPosition) {
+         soundManager.destroySound('chunk'+this.ttsIndex+'-'+this.ttsPosition);
+     }
+     this.ttsRemoveHilites(); //remove old hilights
+         
+     var moreToPlay = this.ttsAdvance();
+     
+     if (moreToPlay) {
+         this.ttsNextChunkPhase2();
+     }    
+     
+     //This function is called again when ttsPlay() has finished playback.
+     //If the next chunk of text has not yet finished loading, ttsPlay()
+     //will start polling until the next chunk is ready.
+ }
+ // ttsNextChunkPhase2()
+ //______________________________________________________________________________
+ // page flip animation has now completed
+ BookReader.prototype.ttsNextChunkPhase2 = function () {
+     if (null == this.ttsChunks) {
+         alert('error: ttsChunks is null?'); //TODO
+         return;
+     }
+     
+     if (0 == this.ttsChunks.length) {
+         if (soundManager.debugMode) console.log('ttsNextChunk2: ttsChunks.length is zero.. hacking...');
+         this.ttsStartCB(this.ttsChunks);
+         return;
+     }
+     
+     if (soundManager.debugMode) console.log('next chunk is ' + this.ttsPosition);  
+     //prefetch next page of text
+     if (0 == this.ttsPosition) {
+         if (this.ttsIndex<(this.numLeafs-1)) {
+             this.ttsGetText(this.ttsIndex+1, 'ttsNextPageCB');
+         }
+     }
+     
+     this.ttsPrefetchAudio();
+     
+     this.ttsPlay();
+ }
+ // ttsAdvance()
+ //______________________________________________________________________________
+ // 1. advance ttsPosition
+ // 2. if necessary, advance ttsIndex, and copy ttsNextChunks to ttsChunks
+ // 3. if necessary, flip to current page, or scroll so chunk is visible
+ // 4. do something smart is ttsNextChunks has not yet finished preloading (TODO)
+ // 5. stop playing at end of book
+ BookReader.prototype.ttsAdvance = function (starting) {
+     this.ttsPosition++;
+     if (this.ttsPosition >= this.ttsChunks.length) {
+         
+         if (this.ttsIndex == (this.numLeafs-1)) {
+             if (soundManager.debugMode) console.log('tts stop');
+             return false;
+         } else {
+             if ((null != this.ttsNextChunks) || (starting)) {
+                 if (soundManager.debugMode) console.log('moving to next page!');
+                 this.ttsIndex++;
+                 this.ttsPosition = 0;
+                 this.ttsChunks = this.ttsNextChunks;
+                 this.ttsNextChunks = null;
+                 //A page flip might be necessary. This code is confusing since
+                 //ttsNextChunks might be null if we are starting on a blank page.
+                 if (2 == this.mode) {
+                     if ((this.ttsIndex != this.twoPage.currentIndexL) && (this.ttsIndex != this.twoPage.currentIndexR)) {
+                         if (!starting) {
+                             this.animationFinishedCallback = this.ttsNextChunkPhase2;
+                             this.next();
+                             return false;
+                         } else {
+                             this.next();
+                             return true;
+                         }
+                     } else {
+                         return true;
+                     }
+                 }
+             } else {
+                 if (soundManager.debugMode) console.log('ttsAdvance: ttsNextChunks is null');
+                 return false; 
+             }
+         }
+     }
+         
+     return true;
+ }
+ // ttsPrefetchAudio()
+ //______________________________________________________________________________
+ BookReader.prototype.ttsPrefetchAudio = function () {
+     if(false != this.ttsBuffering) {
+         alert('TTS Error: prefetch() called while content still buffering!');
+         return;
+     }    
+     //preload next chunk
+     var nextPos = this.ttsPosition+1;
+     if (nextPos < this.ttsChunks.length) {     
+         this.ttsLoadChunk(this.ttsIndex, nextPos, this.ttsChunks[nextPos][0]);
+     } else {
+         //for a short page, preload might nt have yet returned..
+         if (soundManager.debugMode) console.log('preloading chunk 0 from next page, index='+(this.ttsIndex+1));
+         if (null != this.ttsNextChunks) {
+             if (0 != this.ttsNextChunks.length) {
+                 this.ttsLoadChunk(this.ttsIndex+1, 0, this.ttsNextChunks[0][0]);        
+             } else {
+                 if (soundManager.debugMode) console.log('prefetchAudio(): ttsNextChunks is zero length!');
+             }
+         } else {
+             if (soundManager.debugMode) console.log('ttsNextChunks is null, not preloading next page');
+             this.ttsBuffering = true;
+         }
+     }
+ }
+ // ttsPlay()
+ //______________________________________________________________________________
+ BookReader.prototype.ttsPlay = function () {
+         
+     var chunk = this.ttsChunks[this.ttsPosition];
+     if (soundManager.debugMode) {
+         console.log('ttsPlay position = ' + this.ttsPosition);
+         console.log('chunk = ' + chunk);
+         console.log(this.ttsChunks);
+     }
+     
+     //add new hilights
+     if (2 == this.mode) {
+         this.ttsHilite2UP(chunk);
+     } else {
+         this.ttsHilite1UP(chunk);
+     }
+     
+     this.ttsScrollToChunk(chunk);
+         
+     //play current chunk
+     if (false == this.ttsBuffering) {        
+         soundManager.play('chunk'+this.ttsIndex+'-'+this.ttsPosition,{onfinish:function(){br.ttsNextChunk();}});
+     } else {
+         soundManager.play('chunk'+this.ttsIndex+'-'+this.ttsPosition,{onfinish:function(){br.ttsStartPolling();}});
+     }
+ }
+ // scrollToChunk()
+ //______________________________________________________________________________
+ BookReader.prototype.ttsScrollToChunk = function(chunk) {
+     if (this.constMode1up != this.mode) return;
+     var leafTop = 0;
+     var h;
+     var i;
+     for (i=0; i<this.ttsIndex; i++) {
+         h = parseInt(this._getPageHeight(i)/this.reduce); 
+         leafTop += h + this.padding;
+     }
+     
+     var chunkTop = chunk[1][3]; //coords are in l,b,r,t order
+     var chunkBot = chunk[chunk.length-1][1];
+     
+     var topOfFirstChunk = leafTop + chunkTop/this.reduce;
+     var botOfLastChunk  = leafTop + chunkBot/this.reduce;
+     
+     if (soundManager.debugMode) console.log('leafTop = ' + leafTop + ' topOfFirstChunk = ' + topOfFirstChunk + ' botOfLastChunk = ' + botOfLastChunk);
+     var containerTop = $('#BRcontainer').attr('scrollTop');
+     var containerBot = containerTop + $('#BRcontainer').height();
+     if (soundManager.debugMode) console.log('containerTop = ' + containerTop + ' containerBot = ' + containerBot);
+     if ((topOfFirstChunk < containerTop) || (botOfLastChunk > containerBot)) {
+         //jumpToIndex scrolls so that chunkTop is centered.. we want chunkTop at the top
+         //this.jumpToIndex(this.ttsIndex, null, chunkTop);
+         $('#BRcontainer').animate({scrollTop: topOfFirstChunk},'fast');            
+     }    
+ }
+ // ttsHilite1UP()
+ //______________________________________________________________________________
+ BookReader.prototype.ttsHilite1UP = function(chunk) {
+     var i;
+     for (i=1; i<chunk.length; i++) {
+         //each rect is an array of l,b,r,t coords (djvu.xml ordering...)       
+         var l = chunk[i][0];
+         var b = chunk[i][1];
+         var r = chunk[i][2];
+         var t = chunk[i][3];
+         
+         var div = document.createElement('div');
+         this.ttsHilites.push(div);        
+         $(div).attr('className', 'BookReaderSearchHilite').appendTo('#pagediv'+this.ttsIndex);
+         $(div).css({
+             width:  (r-l)/this.reduce + 'px',
+             height: (b-t)/this.reduce + 'px',
+             left:   l/this.reduce + 'px',
+             top:    t/this.reduce +'px'
+         });
+     }
+ }
+ // ttsHilite2UP()
+ //______________________________________________________________________________
+ BookReader.prototype.ttsHilite2UP = function (chunk) {
+     var i;
+     for (i=1; i<chunk.length; i++) {
+         //each rect is an array of l,b,r,t coords (djvu.xml ordering...)       
+         var l = chunk[i][0];
+         var b = chunk[i][1];
+         var r = chunk[i][2];
+         var t = chunk[i][3];
+         
+         var div = document.createElement('div');
+         this.ttsHilites.push(div);        
+         $(div).attr('className', 'BookReaderSearchHilite').css('zIndex', 3).appendTo('#BRtwopageview');
+         this.setHilightCss2UP(div, this.ttsIndex, l, r, t, b);        
+     }
+ }
+ // ttsRemoveHilites()
+ //______________________________________________________________________________
+ BookReader.prototype.ttsRemoveHilites = function (chunk) {
+     $(this.ttsHilites).remove();
+     this.ttsHilites = [];
+ }
+ // ttsStartPolling()
+ //______________________________________________________________________________
+ // Play of the current chunk has ended, but the next chunk has not yet been loaded.
+ // We need to wait for the text for the next page to be loaded, so we can
+ // load the next audio chunk
+ BookReader.prototype.ttsStartPolling = function () {
+     if (soundManager.debugMode) console.log('Starting the TTS poller...');
+     var self = this;
+     this.ttsPoller=setInterval(function(){
+         if (self.ttsBuffering) {return;}
+         
+         if (soundManager.debugMode) console.log('TTS buffering finished!');
+         clearInterval(self.ttsPoller);
+         self.ttsPoller = null;
+         self.ttsPrefetchAudio();
+         self.ttsNextChunk();
+     },500);    
++}
@@@ -130,30 -128,24 +130,37 @@@ class BookReade
      <link rel="stylesheet" type="text/css" href="/bookreader/touch/BookReaderTouch.css?v=<? echo($version); ?>">
  <? } /* uiMode */ ?>
      <script src="/includes/jquery-1.4.2.min.js" type="text/javascript"></script>
 -    <script type="text/javascript" src="/bookreader/jquery-ui-1.8.1.custom.min.js?v=<? echo($version); ?>"></script>
 +    <script type="text/javascript" src="/bookreader/jquery-ui-1.8.5.custom.min.js?v=<? echo($version); ?>"></script>
 +    <script type="text/javascript" src="http://www.archive.org/includes/analytics.js?v=2"></script>
      <script type="text/javascript" src="/bookreader/dragscrollable.js?v=<? echo($version); ?>"></script>
 +    <script type="text/javascript" src="/bookreader/jquery.colorbox-min.js"></script>
 +     <!-- THIS ALLOWS BEAUTYTIPS TO WORK ON IE -->
 +        <!--[if lt IE 9]>
 +        <script type="text/javascript" src="excanvas.compiled.js"></script>
 +        <![endif]-->
 +    <script type="text/javascript" src="/bookreader/jquery.bt.min.js"></script>
      <script type="text/javascript" src="/bookreader/BookReader.js?v=<? echo($version); ?>"></script>
+     <script type="text/javascript" src="/bookreader/soundmanager/soundmanager2.js?v=<? echo($version); ?>"></script>
+     <script>
+         soundManager.debugMode = false;
+         soundManager.url = '/bookreader/soundmanager/swf/';       
+         soundManager.useHTML5Audio = true;
+         soundManager.flashVersion = 9; //flash 8 version of swf is buggy when calling play() on a sound that is still loading
+     </script>
  </head>
 -<body style="background-color: #FFFFFF;">
 +<body style="background-color: ##939598;">
  
 -<? if ($uiMode == 'full') { ?>
 -<div id="BookReader" style="left:10px; right:200px; top:10px; bottom:2em;">Internet Archive BookReader <noscript>requires JavaScript to be enabled.</noscript></div>
 -<? } else { ?>
 -<div id="BookReader" style="left:0; right:0; top:0; bottom:0; border:0">Internet Archive Bookreader <noscript>requires JavaScript to be enabled.</noscript></div>
 -<? } /* uiMode*/ ?>
 +<?
 +/*
 +// <? if ($uiMode == 'full') { ?>
 +// <div id="BookReader" style="left:10px; right:200px; top:10px; bottom:2em;">Internet Archive BookReader <noscript>requires JavaScript to be enabled.</noscript></div>
 +// <? } else { ?>
 +// <div id="BookReader" style="left:0; right:0; top:0; bottom:0; border:0">Internet Archive Bookreader <noscript>requires JavaScript to be enabled.</noscript></div>
 +// <? } ?>
 +*/
 +?>
 +
 +<div id="BookReader">Internet Archive BookReader <noscript>requires JavaScript to be enabled.</noscript></div>
  
  <script type="text/javascript">
    // Set some config variables -- $$$ NB: Config object format has not been finalized