Merged changes upstream
[bookreader.git] / BookReaderIA / datanode / BookReaderJSIA.php
1 <?
2 /*
3 Copyright(c)2008 Internet Archive. Software license AGPL version 3.
4
5 This file is part of BookReader.
6
7     BookReader is free software: you can redistribute it and/or modify
8     it under the terms of the GNU Affero General Public License as published by
9     the Free Software Foundation, either version 3 of the License, or
10     (at your option) any later version.
11
12     BookReader is distributed in the hope that it will be useful,
13     but WITHOUT ANY WARRANTY; without even the implied warranty of
14     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15     GNU Affero General Public License for more details.
16
17     You should have received a copy of the GNU Affero General Public License
18     along with BookReader.  If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 $id = $_REQUEST['id'];
22 $itemPath = $_REQUEST['itemPath'];
23 $subPrefix = $_REQUEST['subPrefix'];
24 $server = $_REQUEST['server'];
25
26 // Check if we're on a dev vhost and point to JSIA in the user's public_html on the datanode
27 // $$$ TODO consolidate this logic
28 if (strpos($_SERVER["REQUEST_URI"], "/~mang") === 0) { // Serving out of home dir
29     $server .= ':80/~mang';
30 } else if (strpos($_SERVER["REQUEST_URI"], "/~testflip") === 0) { // Serving out of home dir
31     $server .= ':80/~testflip';
32 }
33
34 if (! $subPrefix) {
35     $subPrefix = $id;
36 }
37 $subItemPath = $itemPath . '/' . $subPrefix;
38
39 if ("" == $id) {
40     BRFatal("No identifier specified!");
41 }
42
43 if ("" == $itemPath) {
44     BRFatal("No itemPath specified!");
45 }
46
47 if ("" == $server) {
48     BRFatal("No server specified!");
49 }
50
51 if (!preg_match("|^/\d+/items/{$id}$|", $itemPath)) {
52     BRFatal("Bad id!");
53 }
54
55 // XXX check here that subitem is okay
56
57 $filesDataFile = "$itemPath/${id}_files.xml";
58
59 if (file_exists($filesDataFile)) {
60     $filesData = simplexml_load_file("$itemPath/${id}_files.xml");
61 } else {
62     BRfatal("File metadata not found!");
63 }
64
65 $imageStackInfo = findImageStack($subPrefix, $filesData);
66 if ($imageStackInfo['imageFormat'] == 'unknown') {
67     BRfatal('Couldn\'t find image stack');
68 }
69
70 $imageFormat = $imageStackInfo['imageFormat'];
71 $archiveFormat = $imageStackInfo['archiveFormat'];
72 $imageStackFile = $itemPath . "/" . $imageStackInfo['imageStackFile'];
73
74 if ("unknown" == $imageFormat) {
75   BRfatal("Unknown image format");
76 }
77
78 if ("unknown" == $archiveFormat) {
79   BRfatal("Unknown archive format");
80 }
81
82
83 $scanDataFile = "${subItemPath}_scandata.xml";
84 $scanDataZip  = "$itemPath/scandata.zip";
85 if (file_exists($scanDataFile)) {
86     $scanData = simplexml_load_file($scanDataFile);
87 } else if (file_exists($scanDataZip)) {
88     $cmd  = 'unzip -p ' . escapeshellarg($scanDataZip) . ' scandata.xml';
89     exec($cmd, $output, $retval);
90     if ($retval != 0) BRFatal("Could not unzip ScanData!");
91     
92     $dump = join("\n", $output);
93     $scanData = simplexml_load_string($dump);
94 } else if (file_exists("$itemPath/scandata.xml")) {
95     // For e.g. Scribe v.0 books!
96     $scanData = simplexml_load_file("$itemPath/scandata.xml");
97 } else {
98     BRFatal("ScanData file not found!");
99 }
100
101 $metaDataFile = "$itemPath/{$id}_meta.xml";
102 if (!file_exists($metaDataFile)) {
103     BRFatal("MetaData file not found!");
104 }
105
106
107 $metaData = simplexml_load_file($metaDataFile);
108
109 //$firstLeaf = $scanData->pageData->page[0]['leafNum'];
110 ?>
111
112 br = new BookReader();
113
114 <?
115 /* Output title leaf if marked */
116 $titleLeaf = '';
117 foreach ($scanData->pageData->page as $page) {
118     if (("Title Page" == $page->pageType) || ("Title" == $page->pageType)) {
119         $titleLeaf = "{$page['leafNum']}";
120         break;
121     }
122 }
123     
124 if ('' != $titleLeaf) {
125     printf("br.titleLeaf = %d;\n", $titleLeaf);
126 }
127 ?>
128
129 br.getPageWidth = function(index) {
130     return this.pageW[index];
131 }
132
133 br.getPageHeight = function(index) {
134     return this.pageH[index];
135 }
136
137 // Returns true if page image is available rotated
138 br.canRotatePage = function(index) {
139     return 'jp2' == this.imageFormat; // Assume single format for now
140 }
141
142 // reduce defaults to 1 (no reduction)
143 // rotate defaults to 0 (no rotation)
144 br.getPageURI = function(index, reduce, rotate) {
145     var _reduce;
146     var _rotate;
147
148     if ('undefined' == typeof(reduce)) {
149         _reduce = 1;
150     } else {
151         _reduce = reduce;
152     }
153     if ('undefined' == typeof(rotate)) {
154         _rotate = 0;
155     } else {
156         _rotate = rotate;
157     }
158     
159     var file = this._getPageFile(index);
160         
161     // $$$ add more image stack formats here
162     return 'http://'+this.server+'/BookReader/BookReaderImages.php?zip='+this.zip+'&file='+file+'&scale='+_reduce+'&rotate='+_rotate;
163 }
164
165 br._getPageFile = function(index) {
166     var leafStr = '0000';
167     var imgStr = this.leafMap[index].toString();
168     var re = new RegExp("0{"+imgStr.length+"}$");
169     
170     var insideZipPrefix = this.subPrefix.match('[^/]+$');
171     var file = insideZipPrefix + '_' + this.imageFormat + '/' + insideZipPrefix + '_' + leafStr.replace(re, imgStr) + '.' + this.imageFormat;
172     
173     return file;
174 }
175
176 br.getPageSide = function(index) {
177     //assume the book starts with a cover (right-hand leaf)
178     //we should really get handside from scandata.xml
179     
180     <? // Use special function if we should infer the page sides based off the title page index
181     if (preg_match('/goog$/', $id) && ('' != $titleLeaf)) {
182     ?>
183     // assume page side based on title pagex
184     var titleIndex = br.leafNumToIndex(br.titleLeaf);
185     // assume title page is RHS
186     var delta = titleIndex - index;
187     if (0 == (delta & 0x1)) {
188         // even delta
189         return 'R';
190     } else {
191         return 'L';
192     }
193     <?
194     }
195     ?>
196     
197     // $$$ we should get this from scandata instead of assuming the accessible
198     //     leafs are contiguous
199     if ('rl' != this.pageProgression) {
200         // If pageProgression is not set RTL we assume it is LTR
201         if (0 == (index & 0x1)) {
202             // Even-numbered page
203             return 'R';
204         } else {
205             // Odd-numbered page
206             return 'L';
207         }
208     } else {
209         // RTL
210         if (0 == (index & 0x1)) {
211             return 'L';
212         } else {
213             return 'R';
214         }
215     }
216 }
217
218 br.getPageNum = function(index) {
219     var pageNum = this.pageNums[index];
220     if (pageNum) {
221         return pageNum;
222     } else {
223         return 'n' + index;
224     }
225 }
226
227 br.leafNumToIndex = function(leafNum) {
228     for (var index = 0; index < this.leafMap.length; index++) {
229         if (this.leafMap[index] == leafNum) {
230             return index;
231         }
232     }
233     
234     return null;
235 }
236
237 // This function returns the left and right indices for the user-visible
238 // spread that contains the given index.  The return values may be
239 // null if there is no facing page or the index is invalid.
240 br.getSpreadIndices = function(pindex) {
241     // $$$ we could make a separate function for the RTL case and
242     //      only bind it if necessary instead of always checking
243     // $$$ we currently assume there are no gaps
244     
245     var spreadIndices = [null, null]; 
246     if ('rl' == this.pageProgression) {
247         // Right to Left
248         if (this.getPageSide(pindex) == 'R') {
249             spreadIndices[1] = pindex;
250             spreadIndices[0] = pindex + 1;
251         } else {
252             // Given index was LHS
253             spreadIndices[0] = pindex;
254             spreadIndices[1] = pindex - 1;
255         }
256     } else {
257         // Left to right
258         if (this.getPageSide(pindex) == 'L') {
259             spreadIndices[0] = pindex;
260             spreadIndices[1] = pindex + 1;
261         } else {
262             // Given index was RHS
263             spreadIndices[1] = pindex;
264             spreadIndices[0] = pindex - 1;
265         }
266     }
267     
268     //console.log("   index %d mapped to spread %d,%d", pindex, spreadIndices[0], spreadIndices[1]);
269     
270     return spreadIndices;
271 }
272
273 // Remove the page number assertions for all but the highest index page with
274 // a given assertion.  Ensures there is only a single page "{pagenum}"
275 // e.g. the last page asserted as page 5 retains that assertion.
276 br.uniquifyPageNums = function() {
277     var seen = {};
278     
279     for (var i = br.pageNums.length - 1; i--; i >= 0) {
280         var pageNum = br.pageNums[i];
281         if ( !seen[pageNum] ) {
282             seen[pageNum] = true;
283         } else {
284             br.pageNums[i] = null;
285         }
286     }
287
288 }
289
290 br.cleanupMetadata = function() {
291     br.uniquifyPageNums();
292 }
293
294 // getEmbedURL
295 //________
296 // Returns a URL for an embedded version of the current book
297 br.getEmbedURL = function() {
298     // We could generate a URL hash fragment here but for now we just leave at defaults
299     var url = 'http://' + window.location.host + '/stream/'+this.bookId;
300     if (this.subPrefix != this.bookId) { // Only include if needed
301         url += '/' + this.subPrefix;
302     }
303     url += '?ui=embed';
304     return url;
305 }
306
307 // getEmbedCode
308 //________
309 // Returns the embed code HTML fragment suitable for copy and paste
310 br.getEmbedCode = function() {
311     return "<iframe src='" + this.getEmbedURL() + "' width='480px' height='430px'></iframe>";
312 }
313
314 br.pageW =  [
315             <?
316             $i=0;
317             foreach ($scanData->pageData->page as $page) {
318                 if (shouldAddPage($page)) {
319                     if(0 != $i) echo ",";   //stupid IE
320                     echo "{$page->cropBox->w}";
321                     $i++;
322                 }
323             }
324             ?>
325             ];
326
327 br.pageH =  [
328             <?
329             $totalHeight = 0;
330             $i=0;            
331             foreach ($scanData->pageData->page as $page) {
332                 if (shouldAddPage($page)) {
333                     if(0 != $i) echo ",";   //stupid IE                
334                     echo "{$page->cropBox->h}";
335                     $totalHeight += intval($page->cropBox->h/4) + 10;
336                     $i++;
337                 }
338             }
339             ?>
340             ];
341 br.leafMap = [
342             <?
343             $i=0;
344             foreach ($scanData->pageData->page as $page) {
345                 if (shouldAddPage($page)) {
346                     if(0 != $i) echo ",";   //stupid IE
347                     echo "{$page['leafNum']}";
348                     $i++;
349                 }
350             }
351             ?>    
352             ];
353
354 br.pageNums = [
355             <?
356             $i=0;
357             foreach ($scanData->pageData->page as $page) {
358                 if (shouldAddPage($page)) {
359                     if(0 != $i) echo ",";   //stupid IE                
360                     if (array_key_exists('pageNumber', $page) && ('' != $page->pageNumber)) {
361                         echo "'{$page->pageNumber}'";
362                     } else {
363                         echo "null";
364                     }
365                     $i++;
366                 }
367             }
368             ?>    
369             ];
370             
371       
372 br.numLeafs = br.pageW.length;
373
374 br.bookId   = '<?echo $id;?>';
375 br.zip      = '<?echo $imageStackFile;?>';
376 br.subPrefix = '<?echo $subPrefix;?>';
377 br.server   = '<?echo $server;?>';
378 br.bookTitle= '<?echo preg_replace("/\'/", "\\'", $metaData->title);?>';
379 br.bookPath = '<?echo $subItemPath;?>';
380 br.bookUrl  = '<?echo "http://www.archive.org/details/$id";?>';
381 br.imageFormat = '<?echo $imageFormat;?>';
382 br.archiveFormat = '<?echo $archiveFormat;?>';
383
384 <?
385
386 # Load some values from meta.xml
387 if ('' != $metaData->{'page-progression'}) {
388   echo "br.pageProgression = '" . $metaData->{"page-progression"} . "';";
389 } else {
390   // Assume page progression is Left To Right
391   echo "br.pageProgression = 'lr';";
392 }
393
394 # Special cases
395 if ('bandersnatchhsye00scarrich' == $id) {
396     echo "br.mode     = 2;\n";
397     echo "br.auto     = true;\n";
398 }
399
400 ?>
401
402 // Check for config object
403 // $$$ change this to use the newer params object
404 if (typeof(brConfig) != 'undefined') {
405     if (typeof(brConfig["ui"]) != 'undefined') {
406         br.ui = brConfig["ui"];
407     }
408
409     if (brConfig['mode'] == 1) {
410         br.mode = 1;
411         if (typeof(brConfig['reduce'] != 'undefined')) {
412             br.reduce = brConfig['reduce'];
413         }
414     } else if (brConfig['mode'] == 2) {
415         br.mode = 2;
416       
417 <?
418         //$$$mang hack to override request for 2up for books with attribution page
419         //   as first page until we can display that page in 2up
420         $needle = 'goog';
421         if (strrpos($id, $needle) === strlen($id)-strlen($needle)) {
422             print "// override for books with attribution page\n";
423             print "br.mode = 1;\n";
424         }
425 ?>
426     }
427 } // brConfig
428
429 br.cleanupMetadata();
430 br.init();
431
432 <?
433
434
435 function BRFatal($string) {
436     // $$$ TODO log error
437     echo "alert('$string')\n";
438     die(-1);
439 }
440
441 // Returns true if a page should be added based on it's information in
442 // the metadata
443 function shouldAddPage($page) {
444     // Return false only if the page is marked addToAccessFormats false.
445     // If there is no assertion we assume it should be added.
446     if (isset($page->addToAccessFormats)) {
447         if ("false" == strtolower(trim($page->addToAccessFormats))) {
448             return false;
449         }
450     }
451     
452     return true;
453 }
454
455 // Returns { 'imageFormat' => , 'archiveFormat' => '} given a sub-item prefix and loaded xml data
456 function findImageStack($subPrefix, $filesData) {
457
458     // $$$ The order of the image formats determines which will be returned first
459     $imageFormats = array('JP2' => 'jp2', 'TIFF' => 'tif', 'JPEG' => 'jpg');
460     $archiveFormats = array('ZIP' => 'zip', 'Tar' => 'tar');
461     $imageGroup = implode('|', array_keys($imageFormats));
462     $archiveGroup = implode('|', array_keys($archiveFormats));
463     // $$$ Currently only return processed images
464     $imageStackRegex = "/Single Page (Processed) (${imageGroup}) (${archiveGroup})/";
465         
466     foreach ($filesData->file as $file) {        
467         if (strpos($file['name'], $subPrefix) === 0) { // subprefix matches beginning
468             if (preg_match($imageStackRegex, $file->format, $matches)) {
469             
470                 // Make sure we have a regular image stack
471                 $imageFormat = $imageFormats[$matches[2]];
472                 if (strpos($file['name'], $subPrefix . '_' . $imageFormat) === 0) {            
473                     return array('imageFormat' => $imageFormat,
474                                  'archiveFormat' => $archiveFormats[$matches[3]],
475                                  'imageStackFile' => $file['name']);
476                 }
477             }
478         }
479     }
480     
481     return array('imageFormat' => 'unknown', 'archiveFormat' => 'unknown', 'imageStackFile' => 'unknown');
482         
483 }
484
485 ?>