19use UnexpectedValueException;
20use Wikimedia\ScopedCallback;
30 private const SVG_DEFAULT_RENDER_LANG =
'en';
36 private static $metaConversion = [
37 'originalwidth' =>
'ImageWidth',
38 'originalheight' =>
'ImageLength',
39 'description' =>
'ImageDescription',
40 'title' =>
'ObjectName',
51 if ( !isset( $svgConverters[$svgConverter] ) ) {
52 wfDebug(
"\$wgSVGConverter is invalid, disabling SVG rendering." );
63 if ( $svgNativeRendering ===
false ) {
71 if ( $maxSVGFilesize && $file->
getSize() >= $maxSVGFilesize ) {
75 if ( $svgNativeRendering ===
true ) {
81 if ( $svgNativeRendering ===
'partial' ) {
89 return !$this->allowRenderingByUserAgent( $file );
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 ) {
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 ( str_contains( $trimmedSvgLang,
'-' ) ) {
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;
198 return $this->allowRenderingByUserAgent( $file );
207 if ( parent::normaliseParams( $image, $params ) ) {
208 $params = $this->normaliseParamsInternal( $image, $params );
227 $srcWidth = $image->getWidth( $params[
'page'] );
228 $srcHeight = $image->getHeight( $params[
'page'] );
229 $params[
'physicalWidth'] = $this->getSteppedThumbWidth(
230 $image, $params[
'physicalWidth'], $srcWidth, $srcHeight
232 $params[
'physicalHeight'] = File::scaleHeight( $srcWidth, $srcHeight, $params[
'physicalWidth'] );
234 # Don't make an image bigger than wgMaxSVGSize on the smaller side
235 if ( $params[
'physicalWidth'] <= $params[
'physicalHeight'] ) {
236 if ( $params[
'physicalWidth'] > $svgMaxSize ) {
237 $params[
'physicalWidth'] = $svgMaxSize;
238 $params[
'physicalHeight'] = File::scaleHeight( $srcWidth, $srcHeight, $svgMaxSize );
240 } elseif ( $params[
'physicalHeight'] > $svgMaxSize ) {
241 $params[
'physicalWidth'] = File::scaleHeight( $srcHeight, $srcWidth, $svgMaxSize );
242 $params[
'physicalHeight'] = $svgMaxSize;
246 if ( isset( $params[
'targetlang'] ) && !$image->getMatchedLanguage( $params[
'targetlang'] ) ) {
247 unset( $params[
'targetlang'] );
261 public function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
262 if ( !$this->normaliseParams( $image, $params ) ) {
265 $clientWidth = $params[
'width'];
266 $clientHeight = $params[
'height'];
267 $physicalWidth = $params[
'physicalWidth'];
268 $physicalHeight = $params[
'physicalHeight'];
269 $lang = $this->getLanguageFromParams( $params );
271 if ( $this->allowRenderingByUserAgent( $image ) ) {
272 return $this->getClientScalingThumbnailImage( $image, $params );
275 if ( $flags & self::TRANSFORM_LATER ) {
279 $metadata = $this->validateMetadata( $image->getMetadataArray() );
280 if ( isset( $metadata[
'error'] ) ) {
281 $err =
wfMessage(
'svg-long-error', $metadata[
'error'][
'message'] );
288 wfMessage(
'thumbnail_dest_directory' ) );
291 $srcPath = $image->getLocalRefPath();
292 if ( $srcPath ===
false ) {
294 sprintf(
'Thumbnail failed on %s: could not get local copy of "%s"',
298 $params[
'width'], $params[
'height'],
307 $lnPath =
"$tmpDir/" . basename( $srcPath );
308 $ok = mkdir( $tmpDir, 0771 );
311 sprintf(
'Thumbnail failed on %s: could not create temporary directory %s',
314 $params[
'width'], $params[
'height'],
315 wfMessage(
'thumbnail-temp-create' )->text()
319 $ok = @symlink( $srcPath, $lnPath );
321 $cleaner =
new ScopedCallback(
static function () use ( $tmpDir, $lnPath ) {
329 $ok = copy( $srcPath, $lnPath );
333 sprintf(
'Thumbnail failed on %s: could not link %s to %s',
336 $params[
'width'], $params[
'height'],
341 $status = $this->rasterize( $lnPath, $dstPath, $physicalWidth, $physicalHeight, $lang );
342 if ( $status ===
true ) {
359 public function rasterize( $srcPath, $dstPath, $width, $height, $lang =
false ) {
367 if ( isset( $svgConverters[$svgConverter] ) ) {
369 if ( is_array( $svgConverters[$svgConverter] ) ) {
371 $func = $svgConverters[$svgConverter][0];
372 if ( !is_callable( $func ) ) {
373 throw new UnexpectedValueException(
"$func is not callable" );
376 $err = $func( $srcPath,
381 ...array_slice( $svgConverters[$svgConverter], 1 )
383 $retval = is_string( $err ) ? 1 : 0;
384 $err = is_string( $err ) ? $err :
'';
385 $cmd =
'PHP Callback: ' . (string)$func;
388 $cmd = strtr( $svgConverters[$svgConverter], [
389 '$path/' => $svgConverterPath ? Shell::escape(
"$svgConverterPath/" ) :
'',
390 '$width' => (int)$width,
391 '$height' => (
int)$height,
392 '$input' => Shell::escape( $srcPath ),
393 '$output' => Shell::escape( $dstPath ),
397 if ( $lang !==
false ) {
398 $env[
'LANG'] = $lang;
401 wfDebug( __METHOD__ .
": $cmd" );
402 $err = Shell::command()->unsafeCommand( $cmd )->environment( $env )->execute();
403 $retval = $err->getExitCode();
404 $err = $err->getStderr();
405 $err = $err ===
null ?
'' : $err;
408 $removed = $this->removeBadFile( $dstPath, (
int)$retval );
409 if ( ( $retval != 0 || $removed ) && $retval !==
null ) {
410 $this->logErrorForExternalProcess( $retval, $err, $cmd );
425 $im =
new Imagick( $srcPath );
426 $im->setBackgroundColor(
'transparent' );
427 $im->readImage( $srcPath );
428 $im->setImageFormat(
'png' );
429 $im->setImageDepth( 8 );
431 if ( !$im->thumbnailImage( (
int)$width, (
int)$height,
false ) ) {
432 return 'Could not resize image';
434 if ( !$im->writeImage( $dstPath ) ) {
435 return "Could not write to $dstPath";
449 $url = $image->modifyClientThumbUrl( $image->getUrl(), $params );
455 return [
'png',
'image/png' ];
469 if ( isset( $metadata[
'error'] ) ) {
470 return wfMessage(
'svg-long-error', $metadata[
'error'][
'message'] )
471 ->inLanguage( $this->getLanguage() )->escaped();
474 if ( $this->isAnimatedImage( $file ) ) {
475 $msg =
wfMessage(
'svg-long-desc-animated' );
482 ->sizeParams( $file->
getSize() )
483 ->inLanguage( $this->getLanguage() )
493 $metadata = [
'version' => self::SVG_METADATA_VERSION ];
497 $metadata += $svgReader->getMetadata();
500 $metadata[
'error'] = [
501 'message' => $e->getMessage(),
502 'code' => $e->getCode()
504 wfDebug( __METHOD__ .
': ' . $e->getMessage() );
508 'width' => $metadata[
'width'] ?? 0,
509 'height' => $metadata[
'height'] ?? 0,
510 'metadata' => $metadata
519 if ( isset( $unser[
'version'] ) && $unser[
'version'] === self::SVG_METADATA_VERSION ) {
533 $meta = $this->validateMetadata( $image->getMetadataArray() );
535 return self::METADATA_BAD;
537 if ( !isset( $meta[
'originalWidth'] ) ) {
539 return self::METADATA_COMPATIBLE;
542 return self::METADATA_GOOD;
547 return [
'objectname',
'imagedescription' ];
561 if ( !$metadata || isset( $metadata[
'error'] ) ) {
571 $visibleFields = $this->visibleMetadataFields();
574 foreach ( $metadata as $name => $value ) {
575 $tag = strtolower( $name );
576 if ( isset( self::$metaConversion[$tag] ) ) {
577 $tag = strtolower( self::$metaConversion[$tag] );
583 self::addMeta( $result,
584 in_array( $tag, $visibleFields ) ?
'visible' :
'collapsed',
591 return $showMeta ? $result :
false;
600 if ( in_array( $name, [
'width',
'height' ] ) ) {
602 return (
int)$value > 0;
604 if ( $name ===
'lang' ) {
606 if ( !is_string( $value ) || $value ===
''
607 || !LanguageCode::isWellFormedLanguageTag( $value )
625 $code = $this->getLanguageFromParams( $params );
626 if ( $code !== self::SVG_DEFAULT_RENDER_LANG ) {
627 $lang =
'lang' . strtolower( $code ) .
'-';
630 if ( isset( $params[
'physicalWidth'] ) && $params[
'physicalWidth'] ) {
631 return "$lang{$params['physicalWidth']}px";
634 if ( !isset( $params[
'width'] ) ) {
638 return "$lang{$params['width']}px";
645 if ( preg_match(
'/^lang([a-z]+(?:-[a-z]+)*)-(\d+)px$/', $str, $m ) ) {
646 if ( LanguageCode::isWellFormedLanguageTag( $m[1] ) ) {
647 return [
'width' => array_pop( $m ),
'lang' => $m[1] ];
649 return [
'width' => array_pop( $m ),
'lang' => self::SVG_DEFAULT_RENDER_LANG ];
651 if ( preg_match(
'/^(\d+)px$/', $str, $m ) ) {
652 return [
'width' => $m[1],
'lang' => self::SVG_DEFAULT_RENDER_LANG ];
659 return [
'img_lang' =>
'lang',
'img_width' =>
'width' ];
667 $scriptParams = [
'width' => $params[
'width'] ];
668 if ( isset( $params[
'lang'] ) ) {
669 $scriptParams[
'lang'] = $params[
'lang'];
672 return $scriptParams;
678 if ( !$metadata || isset( $metadata[
'error'] ) ) {
682 foreach ( $metadata as $name => $value ) {
683 $tag = strtolower( $name );
684 if ( $tag ===
'originalwidth' || $tag ===
'originalheight' ) {
689 if ( isset( self::$metaConversion[$tag] ) ) {
690 $tag = self::$metaConversion[$tag];
691 $stdMetadata[$tag] = $value;
700class_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.
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.