MediaWiki  REL1_22
SVG.php
Go to the documentation of this file.
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 }