19use UnexpectedValueException;
20use Wikimedia\AtEase\AtEase;
21use Wikimedia\ScopedCallback;
31 private const SVG_DEFAULT_RENDER_LANG =
'en';
37 private static $metaConversion = [
38 'originalwidth' =>
'ImageWidth',
39 'originalheight' =>
'ImageLength',
40 'description' =>
'ImageDescription',
41 'title' =>
'ObjectName',
52 if ( !isset( $svgConverters[$svgConverter] ) ) {
53 wfDebug(
"\$wgSVGConverter is invalid, disabling SVG rendering." );
68 if ( $svgNativeRendering ===
true ) {
72 if ( $svgNativeRendering !==
'partial' ) {
81 && $file->getSize() <= $maxSVGFilesize;
99 # @todo Detect animated SVGs
101 if ( isset( $metadata[
'animated'] ) ) {
102 return $metadata[
'animated'];
123 if ( isset( $metadata[
'translations'] ) ) {
124 foreach ( $metadata[
'translations'] as $lang => $langType ) {
126 $langList[] = strtolower( $lang );
130 return array_unique( $langList );
150 if ( $userPreferredLanguage ===
'und' ) {
153 foreach ( $svgLanguages as $svgLang ) {
154 if ( strcasecmp( $svgLang, $userPreferredLanguage ) === 0 ) {
157 $trimmedSvgLang = $svgLang;
158 while ( str_contains( $trimmedSvgLang,
'-' ) ) {
159 $trimmedSvgLang = substr( $trimmedSvgLang, 0, strrpos( $trimmedSvgLang,
'-' ) );
160 if ( strcasecmp( $trimmedSvgLang, $userPreferredLanguage ) === 0 ) {
176 return $params[
'lang'] ?? $params[
'targetlang'] ?? self::SVG_DEFAULT_RENDER_LANG;
186 return self::SVG_DEFAULT_RENDER_LANG;
204 if ( parent::normaliseParams( $image, $params ) ) {
224 # Don't make an image bigger than wgMaxSVGSize on the smaller side
225 if ( $params[
'physicalWidth'] <= $params[
'physicalHeight'] ) {
226 if ( $params[
'physicalWidth'] > $svgMaxSize ) {
227 $srcWidth = $image->getWidth( $params[
'page'] );
228 $srcHeight = $image->getHeight( $params[
'page'] );
229 $params[
'physicalWidth'] = $svgMaxSize;
230 $params[
'physicalHeight'] = File::scaleHeight( $srcWidth, $srcHeight, $svgMaxSize );
232 } elseif ( $params[
'physicalHeight'] > $svgMaxSize ) {
233 $srcWidth = $image->getWidth( $params[
'page'] );
234 $srcHeight = $image->getHeight( $params[
'page'] );
235 $params[
'physicalWidth'] = File::scaleHeight( $srcHeight, $srcWidth, $svgMaxSize );
236 $params[
'physicalHeight'] = $svgMaxSize;
240 if ( isset( $params[
'targetlang'] ) && !$image->getMatchedLanguage( $params[
'targetlang'] ) ) {
241 unset( $params[
'targetlang'] );
255 public function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
259 $clientWidth = $params[
'width'];
260 $clientHeight = $params[
'height'];
261 $physicalWidth = $params[
'physicalWidth'];
262 $physicalHeight = $params[
'physicalHeight'];
267 return new ThumbnailImage( $image, $image->getURL(),
false, $params );
270 if ( $flags & self::TRANSFORM_LATER ) {
275 if ( isset( $metadata[
'error'] ) ) {
276 $err =
wfMessage(
'svg-long-error', $metadata[
'error'][
'message'] );
283 wfMessage(
'thumbnail_dest_directory' ) );
286 $srcPath = $image->getLocalRefPath();
287 if ( $srcPath ===
false ) {
289 sprintf(
'Thumbnail failed on %s: could not get local copy of "%s"',
293 $params[
'width'], $params[
'height'],
302 $lnPath =
"$tmpDir/" . basename( $srcPath );
303 $ok = mkdir( $tmpDir, 0771 );
306 sprintf(
'Thumbnail failed on %s: could not create temporary directory %s',
309 $params[
'width'], $params[
'height'],
310 wfMessage(
'thumbnail-temp-create' )->text()
314 $ok = @symlink( $srcPath, $lnPath );
316 $cleaner =
new ScopedCallback(
static function () use ( $tmpDir, $lnPath ) {
317 AtEase::suppressWarnings();
320 AtEase::restoreWarnings();
324 $ok = copy( $srcPath, $lnPath );
328 sprintf(
'Thumbnail failed on %s: could not link %s to %s',
331 $params[
'width'], $params[
'height'],
336 $status = $this->
rasterize( $lnPath, $dstPath, $physicalWidth, $physicalHeight, $lang );
337 if ( $status ===
true ) {
354 public function rasterize( $srcPath, $dstPath, $width, $height, $lang =
false ) {
361 if ( isset( $svgConverters[$svgConverter] ) ) {
362 if ( is_array( $svgConverters[$svgConverter] ) ) {
364 $func = $svgConverters[$svgConverter][0];
365 if ( !is_callable( $func ) ) {
366 throw new UnexpectedValueException(
"$func is not callable" );
368 $err = $func( $srcPath,
373 ...array_slice( $svgConverters[$svgConverter], 1 )
375 $retval = (bool)$err;
378 $cmd = strtr( $svgConverters[$svgConverter], [
379 '$path/' => $svgConverterPath ? Shell::escape(
"$svgConverterPath/" ) :
'',
380 '$width' => (int)$width,
381 '$height' => (
int)$height,
382 '$input' => Shell::escape( $srcPath ),
383 '$output' => Shell::escape( $dstPath ),
387 if ( $lang !==
false ) {
388 $env[
'LANG'] = $lang;
391 wfDebug( __METHOD__ .
": $cmd" );
396 if ( $retval != 0 || $removed ) {
414 $im =
new Imagick( $srcPath );
415 $im->setBackgroundColor(
'transparent' );
416 $im->readImage( $srcPath );
417 $im->setImageFormat(
'png' );
418 $im->setImageDepth( 8 );
420 if ( !$im->thumbnailImage( (
int)$width, (
int)$height,
false ) ) {
421 return 'Could not resize image';
423 if ( !$im->writeImage( $dstPath ) ) {
424 return "Could not write to $dstPath";
430 return [
'png',
'image/png' ];
444 if ( isset( $metadata[
'error'] ) ) {
445 return wfMessage(
'svg-long-error', $metadata[
'error'][
'message'] )->escaped();
449 $msg =
wfMessage(
'svg-long-desc-animated' );
455 ->numParams( $file->getWidth(), $file->getHeight() )
456 ->sizeParams( $file->getSize() )
470 $metadata += $svgReader->getMetadata();
473 $metadata[
'error'] = [
474 'message' => $e->getMessage(),
475 'code' => $e->getCode()
477 wfDebug( __METHOD__ .
': ' . $e->getMessage() );
481 'width' => $metadata[
'width'] ?? 0,
482 'height' => $metadata[
'height'] ?? 0,
483 'metadata' => $metadata
489 if ( isset( $unser[
'version'] ) && $unser[
'version'] === self::SVG_METADATA_VERSION ) {
507 if ( !isset( $meta[
'originalWidth'] ) ) {
517 return [
'objectname',
'imagedescription' ];
531 if ( !$metadata || isset( $metadata[
'error'] ) ) {
544 foreach ( $metadata as $name => $value ) {
545 $tag = strtolower( $name );
546 if ( isset( self::$metaConversion[$tag] ) ) {
547 $tag = strtolower( self::$metaConversion[$tag] );
554 in_array( $tag, $visibleFields ) ?
'visible' :
'collapsed',
561 return $showMeta ? $result :
false;
570 if ( in_array( $name, [
'width',
'height' ] ) ) {
572 return ( $value > 0 );
574 if ( $name ===
'lang' ) {
577 || !LanguageCode::isWellFormedLanguageTag( $value )
596 if ( $code !== self::SVG_DEFAULT_RENDER_LANG ) {
597 $lang =
'lang' . strtolower( $code ) .
'-';
600 if ( isset( $params[
'physicalWidth'] ) && $params[
'physicalWidth'] ) {
601 return "$lang{$params['physicalWidth']}px";
604 if ( !isset( $params[
'width'] ) ) {
608 return "$lang{$params['width']}px";
615 if ( preg_match(
'/^lang([a-z]+(?:-[a-z]+)*)-(\d+)px$/', $str, $m ) ) {
616 if ( LanguageCode::isWellFormedLanguageTag( $m[1] ) ) {
617 return [
'width' => array_pop( $m ),
'lang' => $m[1] ];
619 return [
'width' => array_pop( $m ),
'lang' => self::SVG_DEFAULT_RENDER_LANG ];
621 if ( preg_match(
'/^(\d+)px$/', $str, $m ) ) {
622 return [
'width' => $m[1],
'lang' => self::SVG_DEFAULT_RENDER_LANG ];
629 return [
'img_lang' =>
'lang',
'img_width' =>
'width' ];
637 $scriptParams = [
'width' => $params[
'width'] ];
638 if ( isset( $params[
'lang'] ) ) {
639 $scriptParams[
'lang'] = $params[
'lang'];
642 return $scriptParams;
648 if ( !$metadata || isset( $metadata[
'error'] ) ) {
652 foreach ( $metadata as $name => $value ) {
653 $tag = strtolower( $name );
654 if ( $tag ===
'originalwidth' || $tag ===
'originalheight' ) {
659 if ( isset( self::$metaConversion[$tag] ) ) {
660 $tag = self::$metaConversion[$tag];
661 $stdMetadata[$tag] = $value;
670class_alias( SvgHandler::class,
'SvgHandler' );
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.
A class containing constants representing the names of configuration variables.
const SVGConverter
Name constant for the SVGConverter setting, for use with Config::get()
const SVGConverters
Name constant for the SVGConverters setting, for use with Config::get()
const SVGNativeRenderingSizeLimit
Name constant for the SVGNativeRenderingSizeLimit setting, for use with Config::get()
const SVGMaxSize
Name constant for the SVGMaxSize setting, for use with Config::get()
const SVGConverterPath
Name constant for the SVGConverterPath setting, for use with Config::get()
const SVGNativeRendering
Name constant for the SVGNativeRendering setting, for use with Config::get()
Interface for objects which can provide a MediaWiki context on request.