MediaWiki  REL1_23
SVG.php
Go to the documentation of this file.
00001 <?php
00029 class SvgHandler extends ImageHandler {
00030     const SVG_METADATA_VERSION = 2;
00031 
00036     private static $metaConversion = array(
00037         'originalwidth' => 'ImageWidth',
00038         'originalheight' => 'ImageLength',
00039         'description' => 'ImageDescription',
00040         'title' => 'ObjectName',
00041     );
00042 
00043     function isEnabled() {
00044         global $wgSVGConverters, $wgSVGConverter;
00045         if ( !isset( $wgSVGConverters[$wgSVGConverter] ) ) {
00046             wfDebug( "\$wgSVGConverter is invalid, disabling SVG rendering.\n" );
00047 
00048             return false;
00049         } else {
00050             return true;
00051         }
00052     }
00053 
00054     function mustRender( $file ) {
00055         return true;
00056     }
00057 
00058     function isVectorized( $file ) {
00059         return true;
00060     }
00061 
00066     function isAnimatedImage( $file ) {
00067         # @todo Detect animated SVGs
00068         $metadata = $file->getMetadata();
00069         if ( $metadata ) {
00070             $metadata = $this->unpackMetadata( $metadata );
00071             if ( isset( $metadata['animated'] ) ) {
00072                 return $metadata['animated'];
00073             }
00074         }
00075 
00076         return false;
00077     }
00078 
00091     public function getAvailableLanguages( File $file ) {
00092         $metadata = $file->getMetadata();
00093         $langList = array();
00094         if ( $metadata ) {
00095             $metadata = $this->unpackMetadata( $metadata );
00096             if ( isset( $metadata['translations'] ) ) {
00097                 foreach ( $metadata['translations'] as $lang => $langType ) {
00098                     if ( $langType === SvgReader::LANG_FULL_MATCH ) {
00099                         $langList[] = $lang;
00100                     }
00101                 }
00102             }
00103         }
00104         return $langList;
00105     }
00106 
00112     public function getDefaultRenderLanguage( File $file ) {
00113         return 'en';
00114     }
00115 
00119     function canAnimateThumb( $file ) {
00120         return false;
00121     }
00122 
00128     function normaliseParams( $image, &$params ) {
00129         global $wgSVGMaxSize;
00130         if ( !parent::normaliseParams( $image, $params ) ) {
00131             return false;
00132         }
00133         # Don't make an image bigger than wgMaxSVGSize on the smaller side
00134         if ( $params['physicalWidth'] <= $params['physicalHeight'] ) {
00135             if ( $params['physicalWidth'] > $wgSVGMaxSize ) {
00136                 $srcWidth = $image->getWidth( $params['page'] );
00137                 $srcHeight = $image->getHeight( $params['page'] );
00138                 $params['physicalWidth'] = $wgSVGMaxSize;
00139                 $params['physicalHeight'] = File::scaleHeight( $srcWidth, $srcHeight, $wgSVGMaxSize );
00140             }
00141         } else {
00142             if ( $params['physicalHeight'] > $wgSVGMaxSize ) {
00143                 $srcWidth = $image->getWidth( $params['page'] );
00144                 $srcHeight = $image->getHeight( $params['page'] );
00145                 $params['physicalWidth'] = File::scaleHeight( $srcHeight, $srcWidth, $wgSVGMaxSize );
00146                 $params['physicalHeight'] = $wgSVGMaxSize;
00147             }
00148         }
00149 
00150         return true;
00151     }
00152 
00161     function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
00162         if ( !$this->normaliseParams( $image, $params ) ) {
00163             return new TransformParameterError( $params );
00164         }
00165         $clientWidth = $params['width'];
00166         $clientHeight = $params['height'];
00167         $physicalWidth = $params['physicalWidth'];
00168         $physicalHeight = $params['physicalHeight'];
00169         $lang = isset( $params['lang'] ) ? $params['lang'] : $this->getDefaultRenderLanguage( $image );
00170 
00171         if ( $flags & self::TRANSFORM_LATER ) {
00172             return new ThumbnailImage( $image, $dstUrl, $dstPath, $params );
00173         }
00174 
00175         $metadata = $this->unpackMetadata( $image->getMetadata() );
00176         if ( isset( $metadata['error'] ) ) { // sanity check
00177             $err = wfMessage( 'svg-long-error', $metadata['error']['message'] )->text();
00178 
00179             return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, $err );
00180         }
00181 
00182         if ( !wfMkdirParents( dirname( $dstPath ), null, __METHOD__ ) ) {
00183             return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight,
00184                 wfMessage( 'thumbnail_dest_directory' )->text() );
00185         }
00186 
00187         $srcPath = $image->getLocalRefPath();
00188         $status = $this->rasterize( $srcPath, $dstPath, $physicalWidth, $physicalHeight, $lang );
00189         if ( $status === true ) {
00190             return new ThumbnailImage( $image, $dstUrl, $dstPath, $params );
00191         } else {
00192             return $status; // MediaTransformError
00193         }
00194     }
00195 
00207     public function rasterize( $srcPath, $dstPath, $width, $height, $lang = false ) {
00208         global $wgSVGConverters, $wgSVGConverter, $wgSVGConverterPath;
00209         $err = false;
00210         $retval = '';
00211         if ( isset( $wgSVGConverters[$wgSVGConverter] ) ) {
00212             if ( is_array( $wgSVGConverters[$wgSVGConverter] ) ) {
00213                 // This is a PHP callable
00214                 $func = $wgSVGConverters[$wgSVGConverter][0];
00215                 $args = array_merge( array( $srcPath, $dstPath, $width, $height, $lang ),
00216                     array_slice( $wgSVGConverters[$wgSVGConverter], 1 ) );
00217                 if ( !is_callable( $func ) ) {
00218                     throw new MWException( "$func is not callable" );
00219                 }
00220                 $err = call_user_func_array( $func, $args );
00221                 $retval = (bool)$err;
00222             } else {
00223                 // External command
00224                 $cmd = str_replace(
00225                     array( '$path/', '$width', '$height', '$input', '$output' ),
00226                     array( $wgSVGConverterPath ? wfEscapeShellArg( "$wgSVGConverterPath/" ) : "",
00227                         intval( $width ),
00228                         intval( $height ),
00229                         wfEscapeShellArg( $srcPath ),
00230                         wfEscapeShellArg( $dstPath ) ),
00231                     $wgSVGConverters[$wgSVGConverter]
00232                 );
00233 
00234                 $env = array();
00235                 if ( $lang !== false ) {
00236                     $env['LANG'] = $lang;
00237                 }
00238 
00239                 wfProfileIn( 'rsvg' );
00240                 wfDebug( __METHOD__ . ": $cmd\n" );
00241                 $err = wfShellExecWithStderr( $cmd, $retval, $env );
00242                 wfProfileOut( 'rsvg' );
00243             }
00244         }
00245         $removed = $this->removeBadFile( $dstPath, $retval );
00246         if ( $retval != 0 || $removed ) {
00247             $this->logErrorForExternalProcess( $retval, $err, $cmd );
00248             return new MediaTransformError( 'thumbnail_error', $width, $height, $err );
00249         }
00250 
00251         return true;
00252     }
00253 
00254     public static function rasterizeImagickExt( $srcPath, $dstPath, $width, $height ) {
00255         $im = new Imagick( $srcPath );
00256         $im->setImageFormat( 'png' );
00257         $im->setBackgroundColor( 'transparent' );
00258         $im->setImageDepth( 8 );
00259 
00260         if ( !$im->thumbnailImage( intval( $width ), intval( $height ), /* fit */ false ) ) {
00261             return 'Could not resize image';
00262         }
00263         if ( !$im->writeImage( $dstPath ) ) {
00264             return "Could not write to $dstPath";
00265         }
00266     }
00267 
00274     function getImageSize( $file, $path, $metadata = false ) {
00275         if ( $metadata === false ) {
00276             $metadata = $file->getMetaData();
00277         }
00278         $metadata = $this->unpackMetaData( $metadata );
00279 
00280         if ( isset( $metadata['width'] ) && isset( $metadata['height'] ) ) {
00281             return array( $metadata['width'], $metadata['height'], 'SVG',
00282                 "width=\"{$metadata['width']}\" height=\"{$metadata['height']}\"" );
00283         } else { // error
00284             return array( 0, 0, 'SVG', "width=\"0\" height=\"0\"" );
00285         }
00286     }
00287 
00288     function getThumbType( $ext, $mime, $params = null ) {
00289         return array( 'png', 'image/png' );
00290     }
00291 
00301     function getLongDesc( $file ) {
00302         global $wgLang;
00303 
00304         $metadata = $this->unpackMetadata( $file->getMetadata() );
00305         if ( isset( $metadata['error'] ) ) {
00306             return wfMessage( 'svg-long-error', $metadata['error']['message'] )->text();
00307         }
00308 
00309         $size = $wgLang->formatSize( $file->getSize() );
00310 
00311         if ( $this->isAnimatedImage( $file ) ) {
00312             $msg = wfMessage( 'svg-long-desc-animated' );
00313         } else {
00314             $msg = wfMessage( 'svg-long-desc' );
00315         }
00316 
00317         $msg->numParams( $file->getWidth(), $file->getHeight() )->params( $size );
00318 
00319         return $msg->parse();
00320     }
00321 
00327     function getMetadata( $file, $filename ) {
00328         $metadata = array( 'version' => self::SVG_METADATA_VERSION );
00329         try {
00330             $metadata += SVGMetadataExtractor::getMetadata( $filename );
00331         } catch ( MWException $e ) { // @todo SVG specific exceptions
00332             // File not found, broken, etc.
00333             $metadata['error'] = array(
00334                 'message' => $e->getMessage(),
00335                 'code' => $e->getCode()
00336             );
00337             wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
00338         }
00339 
00340         return serialize( $metadata );
00341     }
00342 
00343     function unpackMetadata( $metadata ) {
00344         wfSuppressWarnings();
00345         $unser = unserialize( $metadata );
00346         wfRestoreWarnings();
00347         if ( isset( $unser['version'] ) && $unser['version'] == self::SVG_METADATA_VERSION ) {
00348             return $unser;
00349         } else {
00350             return false;
00351         }
00352     }
00353 
00354     function getMetadataType( $image ) {
00355         return 'parsed-svg';
00356     }
00357 
00358     function isMetadataValid( $image, $metadata ) {
00359         $meta = $this->unpackMetadata( $metadata );
00360         if ( $meta === false ) {
00361             return self::METADATA_BAD;
00362         }
00363         if ( !isset( $meta['originalWidth'] ) ) {
00364             // Old but compatible
00365             return self::METADATA_COMPATIBLE;
00366         }
00367 
00368         return self::METADATA_GOOD;
00369     }
00370 
00371     protected function visibleMetadataFields() {
00372         $fields = array( 'objectname', 'imagedescription' );
00373 
00374         return $fields;
00375     }
00376 
00381     function formatMetadata( $file ) {
00382         $result = array(
00383             'visible' => array(),
00384             'collapsed' => array()
00385         );
00386         $metadata = $file->getMetadata();
00387         if ( !$metadata ) {
00388             return false;
00389         }
00390         $metadata = $this->unpackMetadata( $metadata );
00391         if ( !$metadata || isset( $metadata['error'] ) ) {
00392             return false;
00393         }
00394 
00395         /* @todo Add a formatter
00396         $format = new FormatSVG( $metadata );
00397         $formatted = $format->getFormattedData();
00398         */
00399 
00400         // Sort fields into visible and collapsed
00401         $visibleFields = $this->visibleMetadataFields();
00402 
00403         $showMeta = false;
00404         foreach ( $metadata as $name => $value ) {
00405             $tag = strtolower( $name );
00406             if ( isset( self::$metaConversion[$tag] ) ) {
00407                 $tag = strtolower( self::$metaConversion[$tag] );
00408             } else {
00409                 // Do not output other metadata not in list
00410                 continue;
00411             }
00412             $showMeta = true;
00413             self::addMeta( $result,
00414                 in_array( $tag, $visibleFields ) ? 'visible' : 'collapsed',
00415                 'exif',
00416                 $tag,
00417                 $value
00418             );
00419         }
00420 
00421         return $showMeta ? $result : false;
00422     }
00423 
00429     function validateParam( $name, $value ) {
00430         if ( in_array( $name, array( 'width', 'height' ) ) ) {
00431             // Reject negative heights, widths
00432             return ( $value > 0 );
00433         } elseif ( $name == 'lang' ) {
00434             // Validate $code
00435             if ( $value === '' || !Language::isValidBuiltinCode( $value ) ) {
00436                 wfDebug( "Invalid user language code\n" );
00437 
00438                 return false;
00439             }
00440 
00441             return true;
00442         }
00443 
00444         // Only lang, width and height are acceptable keys
00445         return false;
00446     }
00447 
00452     function makeParamString( $params ) {
00453         $lang = '';
00454         if ( isset( $params['lang'] ) && $params['lang'] !== 'en' ) {
00455             $params['lang'] = mb_strtolower( $params['lang'] );
00456             $lang = "lang{$params['lang']}-";
00457         }
00458         if ( !isset( $params['width'] ) ) {
00459             return false;
00460         }
00461 
00462         return "$lang{$params['width']}px";
00463     }
00464 
00465     function parseParamString( $str ) {
00466         $m = false;
00467         if ( preg_match( '/^lang([a-z]+(?:-[a-z]+)*)-(\d+)px$/', $str, $m ) ) {
00468             return array( 'width' => array_pop( $m ), 'lang' => $m[1] );
00469         } elseif ( preg_match( '/^(\d+)px$/', $str, $m ) ) {
00470             return array( 'width' => $m[1], 'lang' => 'en' );
00471         } else {
00472             return false;
00473         }
00474     }
00475 
00476     function getParamMap() {
00477         return array( 'img_lang' => 'lang', 'img_width' => 'width' );
00478     }
00479 
00484     function getScriptParams( $params ) {
00485         $scriptParams = array( 'width' => $params['width'] );
00486         if ( isset( $params['lang'] ) ) {
00487             $scriptParams['lang'] = $params['lang'];
00488         }
00489 
00490         return $scriptParams;
00491     }
00492 
00493     public function getCommonMetaArray( File $file ) {
00494         $metadata = $file->getMetadata();
00495         if ( !$metadata ) {
00496             return array();
00497         }
00498         $metadata = $this->unpackMetadata( $metadata );
00499         if ( !$metadata || isset( $metadata['error'] ) ) {
00500             return array();
00501         }
00502         $stdMetadata = array();
00503         foreach ( $metadata as $name => $value ) {
00504             $tag = strtolower( $name );
00505             if ( $tag === 'originalwidth' || $tag === 'originalheight' ) {
00506                 // Skip these. In the exif metadata stuff, it is assumed these
00507                 // are measured in px, which is not the case here.
00508                 continue;
00509             }
00510             if ( isset( self::$metaConversion[$tag] ) ) {
00511                 $tag = self::$metaConversion[$tag];
00512                 $stdMetadata[$tag] = $value;
00513             }
00514         }
00515 
00516         return $stdMetadata;
00517     }
00518 }