30use Wikimedia\AtEase\AtEase;
31use Wikimedia\ScopedCallback;
41privateconst SVG_DEFAULT_RENDER_LANG =
'en';
47privatestatic $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 );
58if ( $config->get( MainConfigNames::SVGNativeRendering ) ===
true ) {
61if ( !isset( $svgConverters[$svgConverter] ) ) {
62wfDebug(
"\$wgSVGConverter is invalid, disabling SVG rendering." );
75 $svgNativeRendering = MediaWikiServices::getInstance()
76 ->getMainConfig()->get( MainConfigNames::SVGNativeRendering );
77if ( $svgNativeRendering ===
true ) {
78// Don't do any transform for any SVG. 81if ( $svgNativeRendering !==
'partial' ) {
82// SVG images are always rasterized to PNG 85 $maxSVGFilesize = MediaWikiServices::getInstance()
86 ->getMainConfig()->get( MainConfigNames::SVGNativeRenderingSizeLimit );
87// Browsers don't really support SVG translations, so always render them to PNG 88// Files bigger than the limit are also rendered as PNG, as big files might be a tax on the user agent 90 && $file->getSize() <= $maxSVGFilesize;
106 # @todo Detect animated SVGs 108if ( isset( $metadata[
'animated'] ) ) {
109return $metadata[
'animated'];
130if ( isset( $metadata[
'translations'] ) ) {
131foreach ( $metadata[
'translations'] as $lang => $langType ) {
132if ( $langType === SVGReader::LANG_FULL_MATCH ) {
133 $langList[] = strtolower( $lang );
137return array_unique( $langList );
156// Explicitly requested undetermined language (text without svg systemLanguage attribute) 157if ( $userPreferredLanguage ===
'und' ) {
160foreach ( $svgLanguages as $svgLang ) {
161if ( strcasecmp( $svgLang, $userPreferredLanguage ) === 0 ) {
164 $trimmedSvgLang = $svgLang;
165while ( strpos( $trimmedSvgLang,
'-' ) !==
false ) {
166 $trimmedSvgLang = substr( $trimmedSvgLang, 0, strrpos( $trimmedSvgLang,
'-' ) );
167if ( strcasecmp( $trimmedSvgLang, $userPreferredLanguage ) === 0 ) {
183return $params[
'lang'] ?? $params[
'targetlang'] ?? self::SVG_DEFAULT_RENDER_LANG;
193return self::SVG_DEFAULT_RENDER_LANG;
211if ( parent::normaliseParams( $image, $params ) ) {
229 $svgMaxSize = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::SVGMaxSize );
231 # Don't make an image bigger than wgMaxSVGSize on the smaller side 232if ( $params[
'physicalWidth'] <= $params[
'physicalHeight'] ) {
233if ( $params[
'physicalWidth'] > $svgMaxSize ) {
234 $srcWidth = $image->getWidth( $params[
'page'] );
235 $srcHeight = $image->getHeight( $params[
'page'] );
236 $params[
'physicalWidth'] = $svgMaxSize;
237 $params[
'physicalHeight'] = File::scaleHeight( $srcWidth, $srcHeight, $svgMaxSize );
239 } elseif ( $params[
'physicalHeight'] > $svgMaxSize ) {
240 $srcWidth = $image->getWidth( $params[
'page'] );
241 $srcHeight = $image->getHeight( $params[
'page'] );
242 $params[
'physicalWidth'] = File::scaleHeight( $srcHeight, $srcWidth, $svgMaxSize );
243 $params[
'physicalHeight'] = $svgMaxSize;
245// To prevent the proliferation of thumbnails in languages not present in SVGs, unless 246// explicitly forced by user. 247if ( isset( $params[
'targetlang'] ) && !$image->getMatchedLanguage( $params[
'targetlang'] ) ) {
248 unset( $params[
'targetlang'] );
262publicfunctiondoTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
266 $clientWidth = $params[
'width'];
267 $clientHeight = $params[
'height'];
268 $physicalWidth = $params[
'physicalWidth'];
269 $physicalHeight = $params[
'physicalHeight'];
273// No transformation required for native rendering 274returnnewThumbnailImage( $image, $image->getURL(),
false, $params );
277if ( $flags & self::TRANSFORM_LATER ) {
282if ( isset( $metadata[
'error'] ) ) {
283 $err =
wfMessage(
'svg-long-error', $metadata[
'error'][
'message'] );
293 $srcPath = $image->getLocalRefPath();
294if ( $srcPath ===
false ) {
// Failed to get local copy 296 sprintf(
'Thumbnail failed on %s: could not get local copy of "%s"',
300 $params[
'width'], $params[
'height'],
305// Make a temp dir with a symlink to the local copy in it. 306// This plays well with rsvg-convert policy for external entities. 307// https://git.gnome.org/browse/librsvg/commit/?id=f01aded72c38f0e18bc7ff67dee800e380251c8e 309 $lnPath =
"$tmpDir/" . basename( $srcPath );
310 $ok = mkdir( $tmpDir, 0771 );
313 sprintf(
'Thumbnail failed on %s: could not create temporary directory %s',
316 $params[
'width'], $params[
'height'],
317wfMessage(
'thumbnail-temp-create' )->text()
320// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged 321 $ok = @symlink( $srcPath, $lnPath );
323 $cleaner =
new ScopedCallback(
staticfunction () use ( $tmpDir, $lnPath ) {
324 AtEase::suppressWarnings();
327 AtEase::restoreWarnings();
330// Fallback because symlink often fails on Windows 331 $ok = copy( $srcPath, $lnPath );
335 sprintf(
'Thumbnail failed on %s: could not link %s to %s',
338 $params[
'width'], $params[
'height'],
343 $status = $this->
rasterize( $lnPath, $dstPath, $physicalWidth, $physicalHeight, $lang );
344if ( $status ===
true ) {
348return $status;
// MediaTransformError 361publicfunctionrasterize( $srcPath, $dstPath, $width, $height, $lang =
false ) {
362 $mainConfig = MediaWikiServices::getInstance()->getMainConfig();
363 $svgConverters = $mainConfig->get( MainConfigNames::SVGConverters );
364 $svgConverter = $mainConfig->get( MainConfigNames::SVGConverter );
365 $svgConverterPath = $mainConfig->get( MainConfigNames::SVGConverterPath );
368if ( isset( $svgConverters[$svgConverter] ) ) {
369if ( is_array( $svgConverters[$svgConverter] ) ) {
370// This is a PHP callable 371 $func = $svgConverters[$svgConverter][0];
372if ( !is_callable( $func ) ) {
373thrownew UnexpectedValueException(
"$func is not callable" );
375 $err = $func( $srcPath,
380 ...array_slice( $svgConverters[$svgConverter], 1 )
382 $retval = (bool)$err;
385 $cmd = strtr( $svgConverters[$svgConverter], [
386'$path/' => $svgConverterPath ? Shell::escape(
"$svgConverterPath/" ) :
'',
387'$width' => (int)$width,
388'$height' => (
int)$height,
389'$input' => Shell::escape( $srcPath ),
390'$output' => Shell::escape( $dstPath ),
394if ( $lang !==
false ) {
395 $env[
'LANG'] = $lang;
398wfDebug( __METHOD__ .
": $cmd" );
403if ( $retval != 0 || $removed ) {
404// @phan-suppress-next-next-line PhanPossiblyUndeclaredVariable cmd is set when used 405// @phan-suppress-next-line PhanTypeMismatchArgumentNullable cmd is set when used 421 $im =
new Imagick( $srcPath );
422 $im->setBackgroundColor(
'transparent' );
423 $im->readImage( $srcPath );
424 $im->setImageFormat(
'png' );
425 $im->setImageDepth( 8 );
427if ( !$im->thumbnailImage( (
int)$width, (
int)$height,
/* fit */false ) ) {
428return'Could not resize image';
430if ( !$im->writeImage( $dstPath ) ) {
431return"Could not write to $dstPath";
436return [
'png',
'image/png' ];
450if ( isset( $metadata[
'error'] ) ) {
451returnwfMessage(
'svg-long-error', $metadata[
'error'][
'message'] )->escaped();
455 $msg =
wfMessage(
'svg-long-desc-animated' );
461 ->numParams( $file->getWidth(), $file->getHeight() )
462 ->sizeParams( $file->getSize() )
472 $metadata = [
'version' => self::SVG_METADATA_VERSION ];
476 $metadata += $svgReader->getMetadata();
478// File not found, broken, etc. 479 $metadata[
'error'] = [
480'message' => $e->getMessage(),
481'code' => $e->getCode()
483wfDebug( __METHOD__ .
': ' . $e->getMessage() );
487'width' => $metadata[
'width'] ?? 0,
488'height' => $metadata[
'height'] ?? 0,
489'metadata' => $metadata
494if ( isset( $unser[
'version'] ) && $unser[
'version'] === self::SVG_METADATA_VERSION ) {
508return self::METADATA_BAD;
510if ( !isset( $meta[
'originalWidth'] ) ) {
512return self::METADATA_COMPATIBLE;
515return self::METADATA_GOOD;
519return [
'objectname',
'imagedescription' ];
533if ( !$metadata || isset( $metadata[
'error'] ) ) {
537/* @todo Add a formatter 538 $format = new FormatSVG( $metadata ); 539 $formatted = $format->getFormattedData(); 542// Sort fields into visible and collapsed 546foreach ( $metadata as $name => $value ) {
547 $tag = strtolower( $name );
548if ( isset( self::$metaConversion[$tag] ) ) {
549 $tag = strtolower( self::$metaConversion[$tag] );
551// Do not output other metadata not in list 555 self::addMeta( $result,
556 in_array( $tag, $visibleFields ) ?
'visible' :
'collapsed',
563return $showMeta ? $result :
false;
572if ( in_array( $name, [
'width',
'height' ] ) ) {
573// Reject negative heights, widths 574return ( $value > 0 );
576if ( $name ===
'lang' ) {
579 || !LanguageCode::isWellFormedLanguageTag( $value )
587// Only lang, width and height are acceptable keys 598if ( $code !== self::SVG_DEFAULT_RENDER_LANG ) {
599 $lang =
'lang' . strtolower( $code ) .
'-';
602if ( isset( $params[
'physicalWidth'] ) && $params[
'physicalWidth'] ) {
603return"$lang{$params['physicalWidth']}px";
606if ( !isset( $params[
'width'] ) ) {
610return"$lang{$params['width']}px";
615// Language codes are supposed to be lowercase 616if ( preg_match(
'/^lang([a-z]+(?:-[a-z]+)*)-(\d+)px$/', $str, $m ) ) {
617if ( LanguageCode::isWellFormedLanguageTag( $m[1] ) ) {
618return [
'width' => array_pop( $m ),
'lang' => $m[1] ];
620return [
'width' => array_pop( $m ),
'lang' => self::SVG_DEFAULT_RENDER_LANG ];
622if ( preg_match(
'/^(\d+)px$/', $str, $m ) ) {
623return [
'width' => $m[1],
'lang' => self::SVG_DEFAULT_RENDER_LANG ];
629return [
'img_lang' =>
'lang',
'img_width' =>
'width' ];
637 $scriptParams = [
'width' => $params[
'width'] ];
638if ( isset( $params[
'lang'] ) ) {
639 $scriptParams[
'lang'] = $params[
'lang'];
647if ( !$metadata || isset( $metadata[
'error'] ) ) {
651foreach ( $metadata as $name => $value ) {
652 $tag = strtolower( $name );
653if ( $tag ===
'originalwidth' || $tag ===
'originalheight' ) {
654// Skip these. In the exif metadata stuff, it is assumed these 655// are measured in px, which is not the case here. 658if ( isset( self::$metaConversion[$tag] ) ) {
659 $tag = self::$metaConversion[$tag];
660 $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.
logErrorForExternalProcess( $retval, $err, $cmd)
Log an error that occurred in an external process.
removeBadFile( $dstPath, $retval=0)
Check for zero-sized thumbnails.
Basic media transform error class.
Implements some public methods and some protected utility functions which are required by multiple ch...
getMetadataArray()
Get the unserialized handler-specific metadata STUB.
Methods for dealing with language codes.
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
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.
Shortcut class for parameter validation errors.
Interface for objects which can provide a MediaWiki context on request.