Ebe123 has uploaded a new change for review. ( 
https://gerrit.wikimedia.org/r/370209 )

Change subject: Output SVG in scores
......................................................................

Output SVG in scores

This change makes scores be outputted in SVG, and rendered in-
browser if supported. A .png fallback is also created through
the MediaHandler, for both files to be output in an <img tag
with scrset as the SVG, as discussed in T134455.

The SVG output uses the '-dcrop' option, of which is under code
review: https://codereview.appspot.com/326960043/ . This option
automatically places all the music on 1 file, of which is trimmed.

As the '-dcrop' option outputs a trimmed file, the $wgScoreTrim
global has been removed as it is now automatic. Further, the
multipage functionality has been removed since all is outputted
in 1 file.

DO NOT MERGE UNTIL -DCROP IS RELEASED UPSTREAM.

Change-Id: Ieb13455825053cb187e6240b238fa7b6bd5c18c1
Task: T49578
---
M README
M Score.body.php
M Score.hooks.php
M extension.json
M i18n/en.json
M i18n/qqq.json
6 files changed, 78 insertions(+), 110 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Score 
refs/changes/09/370209/1

diff --git a/README b/README
index bece82e..24c481c 100644
--- a/README
+++ b/README
@@ -4,8 +4,7 @@
 =============
 
 This extension uses LilyPond to render score images, so you need a working
-LilyPond installation. If you want the extension to trim the score files for
-you, you will also need ImageMagick.
+LilyPond installation.
 
 The extension is also capable of creating Ogg/Vorbis files from the MIDI files
 generated by LilyPond. If you want to make use of this functionality, you need
@@ -38,7 +37,6 @@
                                                           LilyPond conversion 
*/
    $wgScoreTimidty = '/path/to/your/timidty/executable'; /* if you want MIDI to
                                                             Vorbis conversion 
*/
-   $wgScoreTrim = true; /* Set to false if you don't want score trimming */
 
    to your LocalSettings.php file. If you get unexpected out-of-memory errors,
    you may also have to increase $wgMaxShellMemory (see
@@ -107,4 +105,3 @@
           provided you installed and configured the OggHandler extension.
           An Ogg/Vorbis player will be embedded in the HTML below the score
           image(s).
-
diff --git a/Score.body.php b/Score.body.php
index 6cb8304..d19b782 100644
--- a/Score.body.php
+++ b/Score.body.php
@@ -360,9 +360,9 @@
                                $existingFiles[$file] = true;
                        }
 
-                       /* Generate PNG and MIDI files if necessary */
-                       $imageFileName = "{$options['file_name_prefix']}.png";
-                       $multi1FileName = 
"{$options['file_name_prefix']}-page1.png";
+                       /* Generate SVG and MIDI files if necessary */
+                       $svgFileName = "{$options['file_name_prefix']}.svg";
+                       $pngFileName = "{$options['file_name_prefix']}.png";
                        $midiFileName = "{$options['file_name_prefix']}.midi";
                        $metaDataFileName = 
"{$options['file_name_prefix']}.json";
 
@@ -380,11 +380,11 @@
                        if (
                                !isset( $existingFiles[$metaDataFileName] )
                                || (
-                                       !isset( $existingFiles[$imageFileName] )
-                                       && !isset( 
$existingFiles[$multi1FileName] )
-                               )
+                                       !isset( $existingFiles[$svgFileName] )
+                                       && !isset( $existingFiles[$pngFileName] 
)
+                                       )
                                || !isset( $existingFiles[$midiFileName] ) ) {
-                               $existingFiles += self::generatePngAndMidi( 
$code, $options, $metaData );
+                               $existingFiles += self::generateImageAndMidi( 
$code, $options, $metaData );
                        }
 
                        /* Generate Ogg/Vorbis file if necessary */
@@ -420,34 +420,16 @@
                        }
 
                        /* return output link(s) */
-                       if ( isset( $existingFiles[$imageFileName] ) ) {
-                               list( $width, $height ) = 
$metaData[$imageFileName]['size'];
+                       if ( isset( $existingFiles[$svgFileName] ) ) {
+                               list( $width, $height ) = 
$metaData[$svgFileName]['size'];
                                $link = Html::rawElement( 'img', [
-                                       'src' => 
"{$options['dest_url']}/$imageFileName",
+                                       'src' => 
"{$options['dest_url']}/$pngFileName",
+                                       'srcset' => 
"{$options['dest_url']}/$svgFileName 1x",
                                        'width' => $width,
                                        'height' => $height,
                                        'alt' => $code,
+                                       'class' => 'ScoreSVG',
                                ] );
-                       } elseif ( isset( $existingFiles[$multi1FileName] ) ) {
-                               $link = '';
-                               for ( $i = 1; ; ++$i ) {
-                                       $fileName = 
"{$options['file_name_prefix']}-page$i.png";
-                                       if ( !isset( $existingFiles[$fileName] 
) ) {
-                                               break;
-                                       }
-                                       $pageNumb = wfMessage( 'score-page' )
-                                               ->inContentLanguage()
-                                               ->numParams( $i )
-                                               ->plain();
-                                       list( $width, $height ) = 
$metaData[$fileName]['size'];
-                                       $link .= Html::rawElement( 'img', [
-                                               'src' => 
"{$options['dest_url']}/$fileName",
-                                               'width' => $width,
-                                               'height' => $height,
-                                               'alt' => $pageNumb,
-                                               'title' => $pageNumb
-                                       ] );
-                               }
                        } else {
                                /* No images; this may happen in raw mode or 
when the user omits the score code */
                                throw new ScoreException( wfMessage( 
'score-noimages' ) );
@@ -504,7 +486,7 @@
        }
 
        /**
-        * Generates score PNG file(s) and a MIDI file.
+        * Generates score SVG, PNG, and MIDI files.
         *
         * @param $code string Score code.
         * @param $options array Rendering options. They are the same as for
@@ -516,8 +498,8 @@
         *
         * @throws ScoreException on error.
         */
-       private static function generatePngAndMidi( $code, $options, &$metaData 
) {
-               global $wgScoreLilyPond, $wgScoreTrim;
+       private static function generateImageAndMidi( $code, $options, 
&$metaData ) {
+               global $wgScoreLilyPond;
 
                if ( !is_executable( $wgScoreLilyPond ) ) {
                        throw new ScoreException( wfMessage( 
'score-notexecutable', $wgScoreLilyPond ) );
@@ -528,8 +510,9 @@
                self::createDirectory( $factoryDirectory, 0700 );
                $factoryLy = "$factoryDirectory/file.ly";
                $factoryMidi = "$factoryDirectory/file.midi";
-               $factoryImage = "$factoryDirectory/file.png";
-               $factoryImageTrimmed = "$factoryDirectory/file-trimmed.png";
+               $factoryPng = "$factoryDirectory/file.png";
+               $factorySvg = "$factoryDirectory/file.cropped.svg";
+               $factorySvgProcessed = "$factoryDirectory/file.svg";
 
                /* Generate LilyPond input file */
                if ( $options['lang'] == 'lilypond' ) {
@@ -565,8 +548,8 @@
                $env = [ 'LILYPOND_GC_YIELD' => '25' ];
 
                $cmd = wfEscapeShellArg( $wgScoreLilyPond )
-                       . ' ' . wfEscapeShellArg( '-dsafe=#t' )
-                       . ' -dbackend=ps --png --header=texidoc '
+                       . ' -dsafe=#t -dbackend=svg -dno-point-and-click 
-dno-print-pages'
+                       . ' -dcrop --header=texidoc '
                        . wfEscapeShellArg( $factoryLy )
                        . ' 2>&1';
                $output = wfShellExec( $cmd, $rc2, $env );
@@ -589,21 +572,11 @@
                        }
                }
 
-               /* trim output images if wanted */
-               if ( $wgScoreTrim ) {
-                       if ( file_exists( $factoryImage ) ) {
-                               self::trimImage( $factoryImage, 
$factoryImageTrimmed );
-                       } else {
-                               for ( $i = 1; ; ++$i ) {
-                                       $src = 
"$factoryDirectory/file-page$i.png";
-                                       if ( !file_exists( $src ) ) {
-                                               break;
-                                       }
-                                       $dest = 
"$factoryDirectory/file-page$i-trimmed.png";
-                                       self::trimImage( $src, $dest );
-                               }
-                       }
-               }
+               // Process SVG file
+               self::processSvg( $factorySvg, $factorySvgProcessed );
+
+               // Create PNG fallback
+               self::createPngFallback( $factorySvg, $factoryPng );
 
                // Create the destination directory if it doesn't exist
                $backend = self::getBackend();
@@ -629,43 +602,26 @@
                        }
                }
 
-               // Add the PNGs
-               if ( $wgScoreTrim ) {
-                       $src = $factoryImageTrimmed;
-               } else {
-                       $src = $factoryImage;
-               }
-               if ( file_exists( $src ) ) {
+               // Add the SVG
+               if ( file_exists( $factorySvgProcessed ) ) {
+                       $dstFileName = "{$options['file_name_prefix']}.svg";
+                       $ops[] = [
+                               'op' => 'store',
+                               'src' => $factorySvgProcessed,
+                               'dst' => 
"{$options['dest_storage_path']}/$dstFileName" ];
+
+                       list( $width, $height ) = self::imageSize( 
$factorySvgProcessed );
+                       $metaData[$dstFileName]['size'] = [ $width, $height ];
+                       $newFiles[$dstFileName] = true;
+
+                       // Now for the PNG
                        $dstFileName = "{$options['file_name_prefix']}.png";
                        $ops[] = [
                                'op' => 'store',
-                               'src' => $src,
+                               'src' => $factoryPng,
                                'dst' => 
"{$options['dest_storage_path']}/$dstFileName" ];
 
-                       list( $width, $height ) = self::imageSize( $src );
-                       $metaData[$dstFileName]['size'] = [ $width, $height ];
                        $newFiles[$dstFileName] = true;
-               } else {
-                       for ( $i = 1; ; ++$i ) {
-                               if ( $wgScoreTrim ) {
-                                       $src = 
"$factoryDirectory/file-page$i-trimmed.png";
-                               } else {
-                                       $src = 
"$factoryDirectory/file-page$i.png";
-                               }
-                               if ( !file_exists( $src ) ) {
-                                       break;
-                               }
-                               $dstFileName = 
"{$options['file_name_prefix']}-page$i.png";
-                               $dest = 
"{$options['dest_storage_path']}/$dstFileName";
-                               $ops[] = [
-                                       'op' => 'store',
-                                       'src' => $src,
-                                       'dst' => $dest ];
-
-                               list( $width, $height ) = self::imageSize( $src 
);
-                               $metaData[$dstFileName]['size'] = [ $width, 
$height ];
-                               $newFiles[$dstFileName] = true;
-                       }
                }
 
                $dstFileName = "{$options['file_name_prefix']}.json";
@@ -692,8 +648,34 @@
         * @return array of ints (width, height)
         */
        private static function imageSize( $filename ) {
-               list( $width, $height ) = getimagesize( $filename );
+               $svg = simplexml_load_file( $filename );
+
+               $width = intval($svg['width']);
+               $height = intval($svg['height']);
                return [ $width, $height ];
+       }
+
+       /**
+        * Convert height and width from SVG file
+        *
+        * @param string $source
+        * @param string $dest
+        */
+       private static function processSvg( $source, $dest ) {
+               $svg = simplexml_load_file( $source );
+
+               // Remove mm
+               $width = floatval( strstr( $svg['width'], 'mm', true ) );
+               $height = floatval( strstr( $svg['height'], 'mm', true ) );
+
+               // Convert to px
+               $mmToPxFactor = 3.543307;
+               $width = intval( ceil( $width * $mmToPxFactor ) );
+               $height = intval( ceil( $height * $mmToPxFactor ) );
+
+               $svg['width'] = $width;
+               $svg['height'] = $height;
+               file_put_contents( $dest, $svg->asXML() );
        }
 
        /**
@@ -890,24 +872,19 @@
        }
 
        /**
-        * Trims an image with ImageMagick.
+        * Use convert to create png of given SVG file
         *
-        * @param $source string Local path to the source image.
-        * @param $dest string Local path to the target (trimmed) image.
+        * @param $source string Local path to the source svg image.
+        * @param $dest string Local path to the target png image.
         *
         * @throws ScoreException on error.
         */
-       private static function trimImage( $source, $dest ) {
-               global $wgImageMagickConvertCommand;
-
-               $cmd = wfEscapeShellArg( $wgImageMagickConvertCommand )
-                       . ' -trim -transparent white '
-                       . wfEscapeShellArg( $source ) . ' '
-                       . wfEscapeShellArg( $dest )
-                       . ' 2>&1';
-               $output = wfShellExec( $cmd, $rc );
-               if ( $rc != 0 ) {
-                       self::throwCallException( wfMessage( 'score-trimerr' ), 
$output );
+       private static function createPngFallback( $source, $dest ) {
+               list( $width, $height ) = self::imageSize( $source );
+               $handler = MediaHandler::getHandler( 'image/svg' );
+               $status = $handler->rasterize( $source, $dest, $width, $height 
);
+               if ( $status !== true ) {
+                       self::throwCallException( wfMessage( 'score-pngerr' ), 
$status->toText() );
                }
        }
 
diff --git a/Score.hooks.php b/Score.hooks.php
index 855a7b4..859f25d 100644
--- a/Score.hooks.php
+++ b/Score.hooks.php
@@ -6,11 +6,6 @@
          * @return bool Returns true
          */
        public static function onParserFirstCallInit( Parser &$parser ) {
-               global $wgUseImageMagick, $wgScoreTrim;
-               if ( $wgScoreTrim === null ) {
-                       // Default to if we use Image Magick, since it requires 
Image Magick.
-                       $wgScoreTrim = $wgUseImageMagick;
-               }
                $parser->setHook( 'score', 'Score::render' );
                return true;
        }
diff --git a/extension.json b/extension.json
index f585433..ce7b601 100644
--- a/extension.json
+++ b/extension.json
@@ -67,7 +67,6 @@
                ]
        },
        "config": {
-               "ScoreTrim": null,
                "ScoreLilyPond": "/usr/bin/lilypond",
                "ScoreAbc2Ly": "/usr/bin/abc2ly",
                "ScoreTimidity": "/usr/bin/timidity",
diff --git a/i18n/en.json b/i18n/en.json
index 263e931..d4c6552 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -26,11 +26,11 @@
        "score-oggconversionerr": "Unable to convert MIDI to Ogg/Vorbis:\n$1",
        "score-oggoverridenotfound": "The file \"<nowiki>$1</nowiki>\" you 
specified with override_ogg does not exist.",
        "score-page": "Page $1",
+       "score-pngerr": "SVG could not be converted to PNG:\n$1",
        "score-pregreplaceerr": "PCRE regular expression replacement failed",
        "score-readerr": "Unable to read file $1.",
        "score-timiditynotexecutable": "TiMidity++ could not be executed: $1 is 
not an executable file. Make sure <code>$wgScoreTimidity</code> is set 
correctly.",
        "score-renameerr": "Error moving score files to upload directory.",
-       "score-trimerr": "Image could not be trimmed:\n$1\nSet 
<code>$wgScoreTrim=false</code> if this problem persists.",
        "score-versionerr": "Unable to obtain LilyPond version:\n$1",
        "score-visualeditor-mwscoreinspector-card-advanced": "Advanced",
        "score-visualeditor-mwscoreinspector-card-audio": "Audio",
diff --git a/i18n/qqq.json b/i18n/qqq.json
index bcbc759..ba5d20c 100644
--- a/i18n/qqq.json
+++ b/i18n/qqq.json
@@ -32,11 +32,11 @@
        "score-oggconversionerr": "Displayed if the MIDI to Ogg/Vorbis 
conversion failed. $1 is the error (generally big block of text in a pre tag)",
        "score-oggoverridenotfound": "Displayed if the file specified with the 
override_ogg=\"…\" attribute could not be found. $1 is the value of the 
override_ogg attribute.",
        "score-page": "The word \"Page\" as used in pagination. Parameters:\n* 
$1 - the page number\n{{Identical|Page}}",
+       "score-pngerr": "Displayed if the extension failed to convert SVG to 
PNG. $1 is the error (generally big block of text in a pre tag)",
        "score-pregreplaceerr": "Displayed if a PCRE regular expression 
replacement failed.",
        "score-readerr": "Displayed if the extension could not read a file. $1 
is the path to the file that could not be read.",
        "score-timiditynotexecutable": "Displayed if TiMidity++ could not be 
executed. $1 is the path to the TiMidity++ binary.",
        "score-renameerr": "Displayed if moving the resultant files from the 
working environment to the upload directory fails.",
-       "score-trimerr": "Displayed if the extension failed to trim an output 
image. $1 is the error (generally big block of text in a pre tag)",
        "score-versionerr": "Displayed if the extension failed to obtain the 
version string of LilyPond. $1 is the LilyPond stdout output generated by the 
attempt.",
        "score-visualeditor-mwscoreinspector-card-advanced": "Label for the 
advanced card of the score inspector\n{{Identical|Advanced}}",
        "score-visualeditor-mwscoreinspector-card-audio": "Label for the audio 
card of the score inspector\n{{Identical|Audio}}",

-- 
To view, visit https://gerrit.wikimedia.org/r/370209
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ieb13455825053cb187e6240b238fa7b6bd5c18c1
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/Score
Gerrit-Branch: master
Gerrit-Owner: Ebe123 <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to