MediaWiki
REL1_22
|
00001 <?php 00029 class SvgHandler extends ImageHandler { 00030 const SVG_METADATA_VERSION = 2; 00031 00032 function isEnabled() { 00033 global $wgSVGConverters, $wgSVGConverter; 00034 if ( !isset( $wgSVGConverters[$wgSVGConverter] ) ) { 00035 wfDebug( "\$wgSVGConverter is invalid, disabling SVG rendering.\n" ); 00036 return false; 00037 } else { 00038 return true; 00039 } 00040 } 00041 00042 function mustRender( $file ) { 00043 return true; 00044 } 00045 00046 function isVectorized( $file ) { 00047 return true; 00048 } 00049 00054 function isAnimatedImage( $file ) { 00055 # TODO: detect animated SVGs 00056 $metadata = $file->getMetadata(); 00057 if ( $metadata ) { 00058 $metadata = $this->unpackMetadata( $metadata ); 00059 if ( isset( $metadata['animated'] ) ) { 00060 return $metadata['animated']; 00061 } 00062 } 00063 return false; 00064 } 00065 00069 function canAnimateThumb( $file ) { 00070 return false; 00071 } 00072 00078 function normaliseParams( $image, &$params ) { 00079 global $wgSVGMaxSize; 00080 if ( !parent::normaliseParams( $image, $params ) ) { 00081 return false; 00082 } 00083 # Don't make an image bigger than wgMaxSVGSize on the smaller side 00084 if ( $params['physicalWidth'] <= $params['physicalHeight'] ) { 00085 if ( $params['physicalWidth'] > $wgSVGMaxSize ) { 00086 $srcWidth = $image->getWidth( $params['page'] ); 00087 $srcHeight = $image->getHeight( $params['page'] ); 00088 $params['physicalWidth'] = $wgSVGMaxSize; 00089 $params['physicalHeight'] = File::scaleHeight( $srcWidth, $srcHeight, $wgSVGMaxSize ); 00090 } 00091 } else { 00092 if ( $params['physicalHeight'] > $wgSVGMaxSize ) { 00093 $srcWidth = $image->getWidth( $params['page'] ); 00094 $srcHeight = $image->getHeight( $params['page'] ); 00095 $params['physicalWidth'] = File::scaleHeight( $srcHeight, $srcWidth, $wgSVGMaxSize ); 00096 $params['physicalHeight'] = $wgSVGMaxSize; 00097 } 00098 } 00099 return true; 00100 } 00101 00110 function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) { 00111 if ( !$this->normaliseParams( $image, $params ) ) { 00112 return new TransformParameterError( $params ); 00113 } 00114 $clientWidth = $params['width']; 00115 $clientHeight = $params['height']; 00116 $physicalWidth = $params['physicalWidth']; 00117 $physicalHeight = $params['physicalHeight']; 00118 $lang = isset( $params['lang'] ) ? $params['lang'] : 'en'; 00119 00120 if ( $flags & self::TRANSFORM_LATER ) { 00121 return new ThumbnailImage( $image, $dstUrl, $dstPath, $params ); 00122 } 00123 00124 $metadata = $this->unpackMetadata( $image->getMetadata() ); 00125 if ( isset( $metadata['error'] ) ) { // sanity check 00126 $err = wfMessage( 'svg-long-error', $metadata['error']['message'] )->text(); 00127 return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, $err ); 00128 } 00129 00130 if ( !wfMkdirParents( dirname( $dstPath ), null, __METHOD__ ) ) { 00131 return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, 00132 wfMessage( 'thumbnail_dest_directory' )->text() ); 00133 } 00134 00135 $srcPath = $image->getLocalRefPath(); 00136 $status = $this->rasterize( $srcPath, $dstPath, $physicalWidth, $physicalHeight, $lang ); 00137 if ( $status === true ) { 00138 return new ThumbnailImage( $image, $dstUrl, $dstPath, $params ); 00139 } else { 00140 return $status; // MediaTransformError 00141 } 00142 } 00143 00155 public function rasterize( $srcPath, $dstPath, $width, $height, $lang = false ) { 00156 global $wgSVGConverters, $wgSVGConverter, $wgSVGConverterPath; 00157 $err = false; 00158 $retval = ''; 00159 if ( isset( $wgSVGConverters[$wgSVGConverter] ) ) { 00160 if ( is_array( $wgSVGConverters[$wgSVGConverter] ) ) { 00161 // This is a PHP callable 00162 $func = $wgSVGConverters[$wgSVGConverter][0]; 00163 $args = array_merge( array( $srcPath, $dstPath, $width, $height, $lang ), 00164 array_slice( $wgSVGConverters[$wgSVGConverter], 1 ) ); 00165 if ( !is_callable( $func ) ) { 00166 throw new MWException( "$func is not callable" ); 00167 } 00168 $err = call_user_func_array( $func, $args ); 00169 $retval = (bool)$err; 00170 } else { 00171 // External command 00172 $cmd = str_replace( 00173 array( '$path/', '$width', '$height', '$input', '$output' ), 00174 array( $wgSVGConverterPath ? wfEscapeShellArg( "$wgSVGConverterPath/" ) : "", 00175 intval( $width ), 00176 intval( $height ), 00177 wfEscapeShellArg( $srcPath ), 00178 wfEscapeShellArg( $dstPath ) ), 00179 $wgSVGConverters[$wgSVGConverter] 00180 ); 00181 00182 $env = array(); 00183 if ( $lang !== false ) { 00184 $env['LANG'] = $lang; 00185 } 00186 00187 wfProfileIn( 'rsvg' ); 00188 wfDebug( __METHOD__ . ": $cmd\n" ); 00189 $err = wfShellExecWithStderr( $cmd, $retval, $env ); 00190 wfProfileOut( 'rsvg' ); 00191 } 00192 } 00193 $removed = $this->removeBadFile( $dstPath, $retval ); 00194 if ( $retval != 0 || $removed ) { 00195 wfDebugLog( 'thumbnail', sprintf( 'thumbnail failed on %s: error %d "%s" from "%s"', 00196 wfHostname(), $retval, trim( $err ), $cmd ) ); 00197 return new MediaTransformError( 'thumbnail_error', $width, $height, $err ); 00198 } 00199 return true; 00200 } 00201 00202 public static function rasterizeImagickExt( $srcPath, $dstPath, $width, $height ) { 00203 $im = new Imagick( $srcPath ); 00204 $im->setImageFormat( 'png' ); 00205 $im->setBackgroundColor( 'transparent' ); 00206 $im->setImageDepth( 8 ); 00207 00208 if ( !$im->thumbnailImage( intval( $width ), intval( $height ), /* fit */ false ) ) { 00209 return 'Could not resize image'; 00210 } 00211 if ( !$im->writeImage( $dstPath ) ) { 00212 return "Could not write to $dstPath"; 00213 } 00214 } 00215 00222 function getImageSize( $file, $path, $metadata = false ) { 00223 if ( $metadata === false ) { 00224 $metadata = $file->getMetaData(); 00225 } 00226 $metadata = $this->unpackMetaData( $metadata ); 00227 00228 if ( isset( $metadata['width'] ) && isset( $metadata['height'] ) ) { 00229 return array( $metadata['width'], $metadata['height'], 'SVG', 00230 "width=\"{$metadata['width']}\" height=\"{$metadata['height']}\"" ); 00231 } else { // error 00232 return array( 0, 0, 'SVG', "width=\"0\" height=\"0\"" ); 00233 } 00234 } 00235 00236 function getThumbType( $ext, $mime, $params = null ) { 00237 return array( 'png', 'image/png' ); 00238 } 00239 00249 function getLongDesc( $file ) { 00250 global $wgLang; 00251 00252 $metadata = $this->unpackMetadata( $file->getMetadata() ); 00253 if ( isset( $metadata['error'] ) ) { 00254 return wfMessage( 'svg-long-error', $metadata['error']['message'] )->text(); 00255 } 00256 00257 $size = $wgLang->formatSize( $file->getSize() ); 00258 00259 if ( $this->isAnimatedImage( $file ) ) { 00260 $msg = wfMessage( 'svg-long-desc-animated' ); 00261 } else { 00262 $msg = wfMessage( 'svg-long-desc' ); 00263 } 00264 00265 $msg->numParams( $file->getWidth(), $file->getHeight() )->params( $size ); 00266 00267 return $msg->parse(); 00268 } 00269 00270 function getMetadata( $file, $filename ) { 00271 $metadata = array( 'version' => self::SVG_METADATA_VERSION ); 00272 try { 00273 $metadata += SVGMetadataExtractor::getMetadata( $filename ); 00274 } catch ( MWException $e ) { // @todo SVG specific exceptions 00275 // File not found, broken, etc. 00276 $metadata['error'] = array( 00277 'message' => $e->getMessage(), 00278 'code' => $e->getCode() 00279 ); 00280 wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" ); 00281 } 00282 return serialize( $metadata ); 00283 } 00284 00285 function unpackMetadata( $metadata ) { 00286 wfSuppressWarnings(); 00287 $unser = unserialize( $metadata ); 00288 wfRestoreWarnings(); 00289 if ( isset( $unser['version'] ) && $unser['version'] == self::SVG_METADATA_VERSION ) { 00290 return $unser; 00291 } else { 00292 return false; 00293 } 00294 } 00295 00296 function getMetadataType( $image ) { 00297 return 'parsed-svg'; 00298 } 00299 00300 function isMetadataValid( $image, $metadata ) { 00301 $meta = $this->unpackMetadata( $metadata ); 00302 if ( $meta === false ) { 00303 return self::METADATA_BAD; 00304 } 00305 if ( !isset( $meta['originalWidth'] ) ) { 00306 // Old but compatible 00307 return self::METADATA_COMPATIBLE; 00308 } 00309 return self::METADATA_GOOD; 00310 } 00311 00312 function visibleMetadataFields() { 00313 $fields = array( 'objectname', 'imagedescription' ); 00314 return $fields; 00315 } 00316 00321 function formatMetadata( $file ) { 00322 $result = array( 00323 'visible' => array(), 00324 'collapsed' => array() 00325 ); 00326 $metadata = $file->getMetadata(); 00327 if ( !$metadata ) { 00328 return false; 00329 } 00330 $metadata = $this->unpackMetadata( $metadata ); 00331 if ( !$metadata || isset( $metadata['error'] ) ) { 00332 return false; 00333 } 00334 00335 /* TODO: add a formatter 00336 $format = new FormatSVG( $metadata ); 00337 $formatted = $format->getFormattedData(); 00338 */ 00339 00340 // Sort fields into visible and collapsed 00341 $visibleFields = $this->visibleMetadataFields(); 00342 00343 // Rename fields to be compatible with exif, so that 00344 // the labels for these fields work and reuse existing messages. 00345 $conversion = array( 00346 'originalwidth' => 'imagewidth', 00347 'originalheight' => 'imagelength', 00348 'description' => 'imagedescription', 00349 'title' => 'objectname', 00350 ); 00351 $showMeta = false; 00352 foreach ( $metadata as $name => $value ) { 00353 $tag = strtolower( $name ); 00354 if ( isset( $conversion[$tag] ) ) { 00355 $tag = $conversion[$tag]; 00356 } else { 00357 // Do not output other metadata not in list 00358 continue; 00359 } 00360 $showMeta = true; 00361 self::addMeta( $result, 00362 in_array( $tag, $visibleFields ) ? 'visible' : 'collapsed', 00363 'exif', 00364 $tag, 00365 $value 00366 ); 00367 } 00368 return $showMeta ? $result : false; 00369 } 00370 00371 00377 function validateParam( $name, $value ) { 00378 if ( in_array( $name, array( 'width', 'height' ) ) ) { 00379 // Reject negative heights, widths 00380 return ( $value > 0 ); 00381 } elseif ( $name == 'lang' ) { 00382 // Validate $code 00383 if ( !Language::isValidBuiltinCode( $value ) ) { 00384 wfDebug( "Invalid user language code\n" ); 00385 return false; 00386 } 00387 return true; 00388 } 00389 // Only lang, width and height are acceptable keys 00390 return false; 00391 } 00392 00397 function makeParamString( $params ) { 00398 $lang = ''; 00399 if ( isset( $params['lang'] ) && $params['lang'] !== 'en' ) { 00400 $params['lang'] = mb_strtolower( $params['lang'] ); 00401 $lang = "lang{$params['lang']}-"; 00402 } 00403 if ( !isset( $params['width'] ) ) { 00404 return false; 00405 } 00406 return "$lang{$params['width']}px"; 00407 } 00408 00409 function parseParamString( $str ) { 00410 $m = false; 00411 if ( preg_match( '/^lang([a-z]+(?:-[a-z]+)*)-(\d+)px$/', $str, $m ) ) { 00412 return array( 'width' => array_pop( $m ), 'lang' => $m[1] ); 00413 } elseif ( preg_match( '/^(\d+)px$/', $str, $m ) ) { 00414 return array( 'width' => $m[1], 'lang' => 'en' ); 00415 } else { 00416 return false; 00417 } 00418 } 00419 00420 function getParamMap() { 00421 return array( 'img_lang' => 'lang', 'img_width' => 'width' ); 00422 } 00423 00428 function getScriptParams( $params ) { 00429 return array( 00430 'width' => $params['width'], 00431 'lang' => $params['lang'], 00432 ); 00433 } 00434 }