30use Wikimedia\AtEase\AtEase;
31use Wikimedia\ScopedCallback;
41 private const SVG_DEFAULT_RENDER_LANG =
'en';
47 private static $metaConversion = [
48 'originalwidth' =>
'ImageWidth',
49 'originalheight' =>
'ImageLength',
50 'description' =>
'ImageDescription',
51 'title' =>
'ObjectName',
55 $config = MediaWikiServices::getInstance()->getMainConfig();
56 $svgConverters = $config->get( MainConfigNames::SVGConverters );
57 $svgConverter = $config->get( MainConfigNames::SVGConverter );
58 if ( $config->get( MainConfigNames::SVGNativeRendering ) ===
true ) {
61 if ( !isset( $svgConverters[$svgConverter] ) ) {
62 wfDebug(
"\$wgSVGConverter is invalid, disabling SVG rendering." );
71 $svgNativeRendering = MediaWikiServices::getInstance()
72 ->getMainConfig()->get( MainConfigNames::SVGNativeRendering );
73 if ( $svgNativeRendering ===
true ) {
77 if ( $svgNativeRendering !==
'partial' ) {
81 $maxSVGFilesize = MediaWikiServices::getInstance()
82 ->getMainConfig()->get( MainConfigNames::SVGNativeRenderingSizeLimit );
86 && $file->getSize() <= $maxSVGFilesize;
102 # @todo Detect animated SVGs
104 if ( isset( $metadata[
'animated'] ) ) {
105 return $metadata[
'animated'];
126 if ( isset( $metadata[
'translations'] ) ) {
127 foreach ( $metadata[
'translations'] as $lang => $langType ) {
128 if ( $langType === SVGReader::LANG_FULL_MATCH ) {
129 $langList[] = strtolower( $lang );
133 return array_unique( $langList );
153 if ( $userPreferredLanguage ===
'und' ) {
156 foreach ( $svgLanguages as $svgLang ) {
157 if ( strcasecmp( $svgLang, $userPreferredLanguage ) === 0 ) {
160 $trimmedSvgLang = $svgLang;
161 while ( strpos( $trimmedSvgLang,
'-' ) !==
false ) {
162 $trimmedSvgLang = substr( $trimmedSvgLang, 0, strrpos( $trimmedSvgLang,
'-' ) );
163 if ( strcasecmp( $trimmedSvgLang, $userPreferredLanguage ) === 0 ) {
179 return $params[
'lang'] ?? $params[
'targetlang'] ?? self::SVG_DEFAULT_RENDER_LANG;
189 return self::SVG_DEFAULT_RENDER_LANG;
207 if ( parent::normaliseParams( $image, $params ) ) {
225 $svgMaxSize = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::SVGMaxSize );
227 # Don't make an image bigger than wgMaxSVGSize on the smaller side
228 if ( $params[
'physicalWidth'] <= $params[
'physicalHeight'] ) {
229 if ( $params[
'physicalWidth'] > $svgMaxSize ) {
230 $srcWidth = $image->getWidth( $params[
'page'] );
231 $srcHeight = $image->getHeight( $params[
'page'] );
232 $params[
'physicalWidth'] = $svgMaxSize;
233 $params[
'physicalHeight'] = File::scaleHeight( $srcWidth, $srcHeight, $svgMaxSize );
235 } elseif ( $params[
'physicalHeight'] > $svgMaxSize ) {
236 $srcWidth = $image->getWidth( $params[
'page'] );
237 $srcHeight = $image->getHeight( $params[
'page'] );
238 $params[
'physicalWidth'] = File::scaleHeight( $srcHeight, $srcWidth, $svgMaxSize );
239 $params[
'physicalHeight'] = $svgMaxSize;
243 if ( isset( $params[
'targetlang'] ) && !$image->getMatchedLanguage( $params[
'targetlang'] ) ) {
244 unset( $params[
'targetlang'] );
258 public function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
262 $clientWidth = $params[
'width'];
263 $clientHeight = $params[
'height'];
264 $physicalWidth = $params[
'physicalWidth'];
265 $physicalHeight = $params[
'physicalHeight'];
270 return new ThumbnailImage( $image, $image->getURL(),
false, $params );
273 if ( $flags & self::TRANSFORM_LATER ) {
278 if ( isset( $metadata[
'error'] ) ) {
279 $err =
wfMessage(
'svg-long-error', $metadata[
'error'][
'message'] );
286 wfMessage(
'thumbnail_dest_directory' ) );
289 $srcPath = $image->getLocalRefPath();
290 if ( $srcPath ===
false ) {
292 sprintf(
'Thumbnail failed on %s: could not get local copy of "%s"',
296 $params[
'width'], $params[
'height'],
305 $lnPath =
"$tmpDir/" . basename( $srcPath );
306 $ok = mkdir( $tmpDir, 0771 );
309 sprintf(
'Thumbnail failed on %s: could not create temporary directory %s',
312 $params[
'width'], $params[
'height'],
313 wfMessage(
'thumbnail-temp-create' )->text()
317 $ok = @symlink( $srcPath, $lnPath );
319 $cleaner =
new ScopedCallback(
static function () use ( $tmpDir, $lnPath ) {
320 AtEase::suppressWarnings();
323 AtEase::restoreWarnings();
327 $ok = copy( $srcPath, $lnPath );
331 sprintf(
'Thumbnail failed on %s: could not link %s to %s',
334 $params[
'width'], $params[
'height'],
339 $status = $this->
rasterize( $lnPath, $dstPath, $physicalWidth, $physicalHeight, $lang );
340 if ( $status ===
true ) {
357 public function rasterize( $srcPath, $dstPath, $width, $height, $lang =
false ) {
358 $mainConfig = MediaWikiServices::getInstance()->getMainConfig();
359 $svgConverters = $mainConfig->get( MainConfigNames::SVGConverters );
360 $svgConverter = $mainConfig->get( MainConfigNames::SVGConverter );
361 $svgConverterPath = $mainConfig->get( MainConfigNames::SVGConverterPath );
364 if ( isset( $svgConverters[$svgConverter] ) ) {
365 if ( is_array( $svgConverters[$svgConverter] ) ) {
367 $func = $svgConverters[$svgConverter][0];
368 if ( !is_callable( $func ) ) {
369 throw new UnexpectedValueException(
"$func is not callable" );
371 $err = $func( $srcPath,
376 ...array_slice( $svgConverters[$svgConverter], 1 )
378 $retval = (bool)$err;
381 $cmd = strtr( $svgConverters[$svgConverter], [
382 '$path/' => $svgConverterPath ? Shell::escape(
"$svgConverterPath/" ) :
'',
383 '$width' => (int)$width,
384 '$height' => (
int)$height,
385 '$input' => Shell::escape( $srcPath ),
386 '$output' => Shell::escape( $dstPath ),
390 if ( $lang !==
false ) {
391 $env[
'LANG'] = $lang;
394 wfDebug( __METHOD__ .
": $cmd" );
399 if ( $retval != 0 || $removed ) {
410 $im =
new Imagick( $srcPath );
411 $im->setBackgroundColor(
'transparent' );
412 $im->readImage( $srcPath );
413 $im->setImageFormat(
'png' );
414 $im->setImageDepth( 8 );
416 if ( !$im->thumbnailImage( (
int)$width, (
int)$height,
false ) ) {
417 return 'Could not resize image';
419 if ( !$im->writeImage( $dstPath ) ) {
420 return "Could not write to $dstPath";
425 return [
'png',
'image/png' ];
439 if ( isset( $metadata[
'error'] ) ) {
440 return wfMessage(
'svg-long-error', $metadata[
'error'][
'message'] )->text();
444 $msg =
wfMessage(
'svg-long-desc-animated' );
449 return $msg->numParams( $file->getWidth(), $file->getHeight() )->sizeParams( $file->getSize() )->parse();
458 $metadata = [
'version' => self::SVG_METADATA_VERSION ];
462 $metadata += $svgReader->getMetadata();
465 $metadata[
'error'] = [
466 'message' => $e->getMessage(),
467 'code' => $e->getCode()
469 wfDebug( __METHOD__ .
': ' . $e->getMessage() );
473 'width' => $metadata[
'width'] ?? 0,
474 'height' => $metadata[
'height'] ?? 0,
475 'metadata' => $metadata
480 if ( isset( $unser[
'version'] ) && $unser[
'version'] === self::SVG_METADATA_VERSION ) {
494 return self::METADATA_BAD;
496 if ( !isset( $meta[
'originalWidth'] ) ) {
498 return self::METADATA_COMPATIBLE;
501 return self::METADATA_GOOD;
505 return [
'objectname',
'imagedescription' ];
519 if ( !$metadata || isset( $metadata[
'error'] ) ) {
532 foreach ( $metadata as $name => $value ) {
533 $tag = strtolower( $name );
534 if ( isset( self::$metaConversion[$tag] ) ) {
535 $tag = strtolower( self::$metaConversion[$tag] );
541 self::addMeta( $result,
542 in_array( $tag, $visibleFields ) ?
'visible' :
'collapsed',
549 return $showMeta ? $result :
false;
558 if ( in_array( $name, [
'width',
'height' ] ) ) {
560 return ( $value > 0 );
562 if ( $name ===
'lang' ) {
565 || !LanguageCode::isWellFormedLanguageTag( $value )
584 if ( $code !== self::SVG_DEFAULT_RENDER_LANG ) {
585 $lang =
'lang' . strtolower( $code ) .
'-';
588 if ( isset( $params[
'physicalWidth'] ) && $params[
'physicalWidth'] ) {
589 return "$lang{$params['physicalWidth']}px";
592 if ( !isset( $params[
'width'] ) ) {
596 return "$lang{$params['width']}px";
602 if ( preg_match(
'/^lang([a-z]+(?:-[a-z]+)*)-(\d+)px$/', $str, $m ) ) {
603 if ( LanguageCode::isWellFormedLanguageTag( $m[1] ) ) {
604 return [
'width' => array_pop( $m ),
'lang' => $m[1] ];
606 return [
'width' => array_pop( $m ),
'lang' => self::SVG_DEFAULT_RENDER_LANG ];
608 if ( preg_match(
'/^(\d+)px$/', $str, $m ) ) {
609 return [
'width' => $m[1],
'lang' => self::SVG_DEFAULT_RENDER_LANG ];
615 return [
'img_lang' =>
'lang',
'img_width' =>
'width' ];
623 $scriptParams = [
'width' => $params[
'width'] ];
624 if ( isset( $params[
'lang'] ) ) {
625 $scriptParams[
'lang'] = $params[
'lang'];
628 return $scriptParams;
633 if ( !$metadata || isset( $metadata[
'error'] ) ) {
637 foreach ( $metadata as $name => $value ) {
638 $tag = strtolower( $name );
639 if ( $tag ===
'originalwidth' || $tag ===
'originalheight' ) {
644 if ( isset( self::$metaConversion[$tag] ) ) {
645 $tag = self::$metaConversion[$tag];
646 $stdMetadata[$tag] = $value;
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfTempDir()
Tries to get the system directory for temporary files.
wfRandomString( $length=32)
Get a random string containing a number of pseudo-random hex characters.
wfHostname()
Get host name of the current machine, for use in error reporting.
wfShellExecWithStderr( $cmd, &$retval=null, $environ=[], $limits=[])
Execute a shell command, returning both stdout and stderr.
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
wfMkdirParents( $dir, $mode=null, $caller=null)
Make directory, and make all parent directories if they don't exist.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Media handler abstract base class for images.
A class containing constants representing the names of configuration variables.
validateParam( $name, $value)
isVectorized( $file)
The material is vectorized and thus scaling is lossless.
normaliseParams( $image, &$params)
formatMetadata( $file, $context=false)
parseParamString( $str)
Parse a param string made with makeParamString back into an array.array|false Array of parameters or ...
mustRender( $file)
True if handled types cannot be displayed directly in a browser but can be rendered.
getScriptParams( $params)
makeParamString( $params)
getCommonMetaArray(File $file)
Get an array of standard (FormatMetadata type) metadata values.
doTransform( $image, $dstPath, $dstUrl, $params, $flags=0)
validateMetadata( $unser)
getLanguageFromParams(array $params)
Determines render language from image parameters This is a lowercase IETF language.
getAvailableLanguages(File $file)
Which languages (systemLanguage attribute) is supported.
getLongDesc( $file)
Subtitle for the image.
normaliseParamsInternal( $image, $params)
Code taken out of normaliseParams() for testability.
getMetadataType( $image)
Get a string describing the type of metadata, for display purposes.
getThumbType( $ext, $mime, $params=null)
Get the thumbnail extension and MIME type for a given source MIME type.
getDefaultRenderLanguage(File $file)
What language to render file in if none selected.
rasterize( $srcPath, $dstPath, $width, $height, $lang=false)
Transform an SVG file to PNG This function can be called outside of thumbnail contexts.
getSizeAndMetadata( $state, $filename)
isEnabled()
False if the handler is disabled for all files.
canAnimateThumbnail( $file)
We do not support making animated svg thumbnails.
visibleMetadataFields()
Get a list of metadata items which should be displayed when the metadata table is collapsed.
static rasterizeImagickExt( $srcPath, $dstPath, $width, $height)
isFileMetadataValid( $image)
Check if the metadata is valid for this handler.
getMatchedLanguage( $userPreferredLanguage, array $svgLanguages)
SVG's systemLanguage matching rules state: 'The systemLanguage attribute ... [e]valuates to "true" if...
const SVG_METADATA_VERSION
allowRenderingByUserAgent( $file)
getParamMap()
Get an associative array mapping magic word IDs to parameter names.Will be used by the parser to iden...
Media transform output for images.
Interface for objects which can provide a MediaWiki context on request.