summaryrefslogtreecommitdiff
path: root/includes/DjVuImage.php
diff options
context:
space:
mode:
Diffstat (limited to 'includes/DjVuImage.php')
-rw-r--r--includes/DjVuImage.php214
1 files changed, 214 insertions, 0 deletions
diff --git a/includes/DjVuImage.php b/includes/DjVuImage.php
new file mode 100644
index 00000000..b857fa66
--- /dev/null
+++ b/includes/DjVuImage.php
@@ -0,0 +1,214 @@
+<?php
+/**
+ * Support for detecting/validating DjVu image files and getting
+ * some basic file metadata (resolution etc)
+ *
+ * File format docs are available in source package for DjVuLibre:
+ * http://djvulibre.djvuzone.org/
+ *
+ *
+ * Copyright (C) 2006 Brion Vibber <brion@pobox.com>
+ * http://www.mediawiki.org/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @package MediaWiki
+ */
+
+class DjVuImage {
+ function __construct( $filename ) {
+ $this->mFilename = $filename;
+ }
+
+ /**
+ * Check if the given file is indeed a valid DjVu image file
+ * @return bool
+ */
+ public function isValid() {
+ $info = $this->getInfo();
+ return $info !== false;
+ }
+
+
+ /**
+ * Return data in the style of getimagesize()
+ * @return array or false on failure
+ */
+ public function getImageSize() {
+ $data = $this->getInfo();
+
+ if( $data !== false ) {
+ $width = $data['width'];
+ $height = $data['height'];
+
+ return array( $width, $height, 'DjVu',
+ "width=\"$width\" height=\"$height\"" );
+ }
+ return false;
+ }
+
+ // ---------
+
+ /**
+ * For debugging; dump the IFF chunk structure
+ */
+ function dump() {
+ $file = fopen( $this->mFilename, 'rb' );
+ $header = fread( $file, 12 );
+ extract( unpack( 'a4magic/a4chunk/NchunkLength', $header ) );
+ echo "$chunk $chunkLength\n";
+ $this->dumpForm( $file, $chunkLength, 1 );
+ fclose( $file );
+ }
+
+ private function dumpForm( $file, $length, $indent ) {
+ $start = ftell( $file );
+ $secondary = fread( $file, 4 );
+ echo str_repeat( ' ', $indent * 4 ) . "($secondary)\n";
+ while( ftell( $file ) - $start < $length ) {
+ $chunkHeader = fread( $file, 8 );
+ if( $chunkHeader == '' ) {
+ break;
+ }
+ extract( unpack( 'a4chunk/NchunkLength', $chunkHeader ) );
+ echo str_repeat( ' ', $indent * 4 ) . "$chunk $chunkLength\n";
+
+ if( $chunk == 'FORM' ) {
+ $this->dumpForm( $file, $chunkLength, $indent + 1 );
+ } else {
+ fseek( $file, $chunkLength, SEEK_CUR );
+ if( $chunkLength & 1 == 1 ) {
+ // Padding byte between chunks
+ fseek( $file, 1, SEEK_CUR );
+ }
+ }
+ }
+ }
+
+ function getInfo() {
+ $file = fopen( $this->mFilename, 'rb' );
+ if( $file === false ) {
+ wfDebug( __METHOD__ . ": missing or failed file read\n" );
+ return false;
+ }
+
+ $header = fread( $file, 16 );
+ $info = false;
+
+ if( strlen( $header ) < 16 ) {
+ wfDebug( __METHOD__ . ": too short file header\n" );
+ } else {
+ extract( unpack( 'a4magic/a4form/NformLength/a4subtype', $header ) );
+
+ if( $magic != 'AT&T' ) {
+ wfDebug( __METHOD__ . ": not a DjVu file\n" );
+ } elseif( $subtype == 'DJVU' ) {
+ // Single-page document
+ $info = $this->getPageInfo( $file, $formLength );
+ } elseif( $subtype == 'DJVM' ) {
+ // Multi-page document
+ $info = $this->getMultiPageInfo( $file, $formLength );
+ } else {
+ wfDebug( __METHOD__ . ": unrecognized DJVU file type '$formType'\n" );
+ }
+ }
+ fclose( $file );
+ return $info;
+ }
+
+ private function readChunk( $file ) {
+ $header = fread( $file, 8 );
+ if( strlen( $header ) < 8 ) {
+ return array( false, 0 );
+ } else {
+ extract( unpack( 'a4chunk/Nlength', $header ) );
+ return array( $chunk, $length );
+ }
+ }
+
+ private function skipChunk( $file, $chunkLength ) {
+ fseek( $file, $chunkLength, SEEK_CUR );
+
+ if( $chunkLength & 0x01 == 1 && !feof( $file ) ) {
+ // padding byte
+ fseek( $file, 1, SEEK_CUR );
+ }
+ }
+
+ private function getMultiPageInfo( $file, $formLength ) {
+ // For now, we'll just look for the first page in the file
+ // and report its information, hoping others are the same size.
+ $start = ftell( $file );
+ do {
+ list( $chunk, $length ) = $this->readChunk( $file );
+ if( !$chunk ) {
+ break;
+ }
+
+ if( $chunk == 'FORM' ) {
+ $subtype = fread( $file, 4 );
+ if( $subtype == 'DJVU' ) {
+ wfDebug( __METHOD__ . ": found first subpage\n" );
+ return $this->getPageInfo( $file, $length );
+ }
+ $this->skipChunk( $file, $length - 4 );
+ } else {
+ wfDebug( __METHOD__ . ": skipping '$chunk' chunk\n" );
+ $this->skipChunk( $file, $length );
+ }
+ } while( $length != 0 && !feof( $file ) && ftell( $file ) - $start < $formLength );
+
+ wfDebug( __METHOD__ . ": multi-page DJVU file contained no pages\n" );
+ return false;
+ }
+
+ private function getPageInfo( $file, $formLength ) {
+ list( $chunk, $length ) = $this->readChunk( $file );
+ if( $chunk != 'INFO' ) {
+ wfDebug( __METHOD__ . ": expected INFO chunk, got '$chunk'\n" );
+ return false;
+ }
+
+ if( $length < 9 ) {
+ wfDebug( __METHOD__ . ": INFO should be 9 or 10 bytes, found $length\n" );
+ return false;
+ }
+ $data = fread( $file, $length );
+ if( strlen( $data ) < $length ) {
+ wfDebug( __METHOD__ . ": INFO chunk cut off\n" );
+ return false;
+ }
+
+ extract( unpack(
+ 'nwidth/' .
+ 'nheight/' .
+ 'Cminor/' .
+ 'Cmajor/' .
+ 'vresolution/' .
+ 'Cgamma', $data ) );
+ # Newer files have rotation info in byte 10, but we don't use it yet.
+
+ return array(
+ 'width' => $width,
+ 'height' => $height,
+ 'version' => "$major.$minor",
+ 'resolution' => $resolution,
+ 'gamma' => $gamma / 10.0 );
+ }
+}
+
+
+?> \ No newline at end of file