* (bug 7684) Obey watchcreated preference for Special:Upload watch checkbox
[lhc/web/wiklou.git] / includes / DjVuImage.php
1 <?php
2 /**
3 * Support for detecting/validating DjVu image files and getting
4 * some basic file metadata (resolution etc)
5 *
6 * File format docs are available in source package for DjVuLibre:
7 * http://djvulibre.djvuzone.org/
8 *
9 *
10 * Copyright (C) 2006 Brion Vibber <brion@pobox.com>
11 * http://www.mediawiki.org/
12 *
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 2 of the License, or
16 * (at your option) any later version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License along
24 * with this program; if not, write to the Free Software Foundation, Inc.,
25 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
26 * http://www.gnu.org/copyleft/gpl.html
27 *
28 * @package MediaWiki
29 */
30
31 class DjVuImage {
32 function __construct( $filename ) {
33 $this->mFilename = $filename;
34 }
35
36 /**
37 * Check if the given file is indeed a valid DjVu image file
38 * @return bool
39 */
40 public function isValid() {
41 $info = $this->getInfo();
42 return $info !== false;
43 }
44
45
46 /**
47 * Return data in the style of getimagesize()
48 * @return array or false on failure
49 */
50 public function getImageSize() {
51 $data = $this->getInfo();
52
53 if( $data !== false ) {
54 $width = $data['width'];
55 $height = $data['height'];
56
57 return array( $width, $height, 'DjVu',
58 "width=\"$width\" height=\"$height\"" );
59 }
60 return false;
61 }
62
63 // ---------
64
65 /**
66 * For debugging; dump the IFF chunk structure
67 */
68 function dump() {
69 $file = fopen( $this->mFilename, 'rb' );
70 $header = fread( $file, 12 );
71 extract( unpack( 'a4magic/a4chunk/NchunkLength', $header ) );
72 echo "$chunk $chunkLength\n";
73 $this->dumpForm( $file, $chunkLength, 1 );
74 fclose( $file );
75 }
76
77 private function dumpForm( $file, $length, $indent ) {
78 $start = ftell( $file );
79 $secondary = fread( $file, 4 );
80 echo str_repeat( ' ', $indent * 4 ) . "($secondary)\n";
81 while( ftell( $file ) - $start < $length ) {
82 $chunkHeader = fread( $file, 8 );
83 if( $chunkHeader == '' ) {
84 break;
85 }
86 extract( unpack( 'a4chunk/NchunkLength', $chunkHeader ) );
87 echo str_repeat( ' ', $indent * 4 ) . "$chunk $chunkLength\n";
88
89 if( $chunk == 'FORM' ) {
90 $this->dumpForm( $file, $chunkLength, $indent + 1 );
91 } else {
92 fseek( $file, $chunkLength, SEEK_CUR );
93 if( $chunkLength & 1 == 1 ) {
94 // Padding byte between chunks
95 fseek( $file, 1, SEEK_CUR );
96 }
97 }
98 }
99 }
100
101 function getInfo() {
102 $file = fopen( $this->mFilename, 'rb' );
103 if( $file === false ) {
104 wfDebug( __METHOD__ . ": missing or failed file read\n" );
105 return false;
106 }
107
108 $header = fread( $file, 16 );
109 $info = false;
110
111 if( strlen( $header ) < 16 ) {
112 wfDebug( __METHOD__ . ": too short file header\n" );
113 } else {
114 extract( unpack( 'a4magic/a4form/NformLength/a4subtype', $header ) );
115
116 if( $magic != 'AT&T' ) {
117 wfDebug( __METHOD__ . ": not a DjVu file\n" );
118 } elseif( $subtype == 'DJVU' ) {
119 // Single-page document
120 $info = $this->getPageInfo( $file, $formLength );
121 } elseif( $subtype == 'DJVM' ) {
122 // Multi-page document
123 $info = $this->getMultiPageInfo( $file, $formLength );
124 } else {
125 wfDebug( __METHOD__ . ": unrecognized DJVU file type '$formType'\n" );
126 }
127 }
128 fclose( $file );
129 return $info;
130 }
131
132 private function readChunk( $file ) {
133 $header = fread( $file, 8 );
134 if( strlen( $header ) < 8 ) {
135 return array( false, 0 );
136 } else {
137 extract( unpack( 'a4chunk/Nlength', $header ) );
138 return array( $chunk, $length );
139 }
140 }
141
142 private function skipChunk( $file, $chunkLength ) {
143 fseek( $file, $chunkLength, SEEK_CUR );
144
145 if( $chunkLength & 0x01 == 1 && !feof( $file ) ) {
146 // padding byte
147 fseek( $file, 1, SEEK_CUR );
148 }
149 }
150
151 private function getMultiPageInfo( $file, $formLength ) {
152 // For now, we'll just look for the first page in the file
153 // and report its information, hoping others are the same size.
154 $start = ftell( $file );
155 do {
156 list( $chunk, $length ) = $this->readChunk( $file );
157 if( !$chunk ) {
158 break;
159 }
160
161 if( $chunk == 'FORM' ) {
162 $subtype = fread( $file, 4 );
163 if( $subtype == 'DJVU' ) {
164 wfDebug( __METHOD__ . ": found first subpage\n" );
165 return $this->getPageInfo( $file, $length );
166 }
167 $this->skipChunk( $file, $length - 4 );
168 } else {
169 wfDebug( __METHOD__ . ": skipping '$chunk' chunk\n" );
170 $this->skipChunk( $file, $length );
171 }
172 } while( $length != 0 && !feof( $file ) && ftell( $file ) - $start < $formLength );
173
174 wfDebug( __METHOD__ . ": multi-page DJVU file contained no pages\n" );
175 return false;
176 }
177
178 private function getPageInfo( $file, $formLength ) {
179 list( $chunk, $length ) = $this->readChunk( $file );
180 if( $chunk != 'INFO' ) {
181 wfDebug( __METHOD__ . ": expected INFO chunk, got '$chunk'\n" );
182 return false;
183 }
184
185 if( $length < 9 ) {
186 wfDebug( __METHOD__ . ": INFO should be 9 or 10 bytes, found $length\n" );
187 return false;
188 }
189 $data = fread( $file, $length );
190 if( strlen( $data ) < $length ) {
191 wfDebug( __METHOD__ . ": INFO chunk cut off\n" );
192 return false;
193 }
194
195 extract( unpack(
196 'nwidth/' .
197 'nheight/' .
198 'Cminor/' .
199 'Cmajor/' .
200 'vresolution/' .
201 'Cgamma', $data ) );
202 # Newer files have rotation info in byte 10, but we don't use it yet.
203
204 return array(
205 'width' => $width,
206 'height' => $height,
207 'version' => "$major.$minor",
208 'resolution' => $resolution,
209 'gamma' => $gamma / 10.0 );
210 }
211
212 /**
213 * Return an XML string describing the DjVu image
214 * @return string
215 */
216 function retrieveMetaData() {
217 global $wgDjvuToXML;
218 if ( isset( $wgDjvuToXML ) ) {
219 $cmd = $wgDjvuToXML . ' --without-anno --without-text ' . $this->mFilename;
220 $xml = wfShellExec( $cmd, $retval );
221 } else {
222 $xml = null;
223 }
224 return $xml;
225 }
226
227 }
228
229
230 ?>