Index: trunk/extensions/LilyPond/LilyPond.php |
— | — | @@ -0,0 +1,335 @@ |
| 2 | +<?php |
| 3 | +/** |
| 4 | + * MediaWiki extension: LilyPond |
| 5 | + * ============================= |
| 6 | + * |
| 7 | + * To activate, edit your LocalSettings.php, add |
| 8 | + * require_once("$IP/extensions/LilyPond.php"); |
| 9 | + * and make sure that the images/ directory is writable. |
| 10 | + * |
| 11 | + * Example wiki code: <lilypond>\relative c' { c d e f g }</lilypond> |
| 12 | + * If you want to typeset a fragment with clickable midi, use |
| 13 | + * <lilymidi>...</lilymidi> |
| 14 | + * If you want write a complete lilypond file, use |
| 15 | + * <lilybook>...</lilybook> |
| 16 | + * |
| 17 | + * Tested with Lilypond version 2.12.2. |
| 18 | + * |
| 19 | + * @file |
| 20 | + * @ingroup Extensions |
| 21 | + * @version 0.01 |
| 22 | + * @author Johannes E. Schindelin |
| 23 | + * @link http://www.mediawiki.org/wiki/Extension:LilyPond |
| 24 | + */ |
| 25 | + |
| 26 | +# User Settings |
| 27 | + |
| 28 | +# The following variables can be set in LocalSettings.php |
| 29 | +# before the line: |
| 30 | +# require_once("$IP/extensions/LilyPond.php"); |
| 31 | + |
| 32 | +# You can set the variable $wgLilypond if you want/need to override the |
| 33 | +# path to the Lilypond executable. For example: |
| 34 | +# $wgLilypond = "/home/username/bin/lilypond"; |
| 35 | + |
| 36 | +# Add a text link to prompt user to listen to midi, before and/or after |
| 37 | +# the image. Remember line breaks |
| 38 | +# $wgLilypondPreMidi = "Listen<br>"; |
| 39 | +# $wgLilypondPostMidi = "<br>Listen"; |
| 40 | + |
| 41 | +# If you want to avoid trimming the resulting image, set $wgLilypondTrim |
| 42 | +# to false. |
| 43 | +# $wgLilypondTrim = false; |
| 44 | + |
| 45 | +# You can put a white border around the image if you like. |
| 46 | +# $wgLilypondBorderX = 10; |
| 47 | +# $wgLilypondBorderY = 0; |
| 48 | + |
| 49 | +# End User Settings |
| 50 | + |
| 51 | +# Defaulting of user settings |
| 52 | +if( !isset( $wgLilypond ) ) |
| 53 | + $wgLilypond = "PATH=\$PATH:/usr/local/bin /usr/local/bin/lilypond"; |
| 54 | + |
| 55 | +if( !isset( $wgLilypondPreMidi ) ) |
| 56 | + $wgLilypondPreMidi = ""; |
| 57 | + |
| 58 | +if( !isset( $wgLilypondPostMidi ) ) |
| 59 | + $wgLilypondPostMidi = ""; |
| 60 | + |
| 61 | +if( !isset( $wgLilypondTrim ) ) { |
| 62 | + $wgLilypondTrim = true; |
| 63 | +} |
| 64 | + |
| 65 | +if( !isset( $wgLilypondBorderX ) ) { |
| 66 | + $wgLilypondBorderX = 0; |
| 67 | +} |
| 68 | + |
| 69 | +if( !isset( $wgLilypondBorderY ) ) { |
| 70 | + $wgLilypondBorderY = 0; |
| 71 | +} |
| 72 | + |
| 73 | +$wgExtensionFunctions[] = "wfLilyPondExtension"; |
| 74 | + |
| 75 | +function wfLilyPondExtension() { |
| 76 | + global $wgParser; |
| 77 | + $wgParser->setHook( "lilypond", "renderLilyPondFragment" ); |
| 78 | + $wgParser->setHook( "lilymidi", "renderLilyPondMidiFragment" ); |
| 79 | + $wgParser->setHook( "lilybook", "renderLilyPond" ); |
| 80 | +} |
| 81 | + |
| 82 | +function renderLilyPondMidiFragment( $lilypond_code ) |
| 83 | +{ |
| 84 | + return renderLilyPondFragment( $lilypond_code, true ); |
| 85 | +} |
| 86 | + |
| 87 | +function renderLilyPondFragment( $lilypond_code, $midi=false ) |
| 88 | +{ |
| 89 | + return renderLilyPond( "\\header {\n" |
| 90 | + . "\ttagline = ##f\n" |
| 91 | + . "}\n" |
| 92 | + . "\\paper {\n" |
| 93 | + . "\traggedright = ##t\n" |
| 94 | + . "\traggedbottom = ##t\n" |
| 95 | + . "\tindent = 0\mm\n" |
| 96 | + . "}\n" |
| 97 | + . "\\score {\n" |
| 98 | + . $lilypond_code |
| 99 | + . "\t\\layout { }\n" |
| 100 | + . ($midi?"\t\\midi { }\n":"") |
| 101 | + . "}\n", $lilypond_code ); |
| 102 | +} |
| 103 | + |
| 104 | +function renderLilyPond( $lilypond_code, $short_code=false ) |
| 105 | +{ |
| 106 | + global $wgMathPath, $wgMathDirectory, $wgTmpDirectory, $wgLilypond, $wgLilypondPreMidi, $wgLilypondPostMidii, $wgLilypondTrim, $wgLilypondBorderX, $wgLilypondBorderY; |
| 107 | + |
| 108 | + $mf = wfMsg( "math_failure" ); |
| 109 | + $munk = wfMsg( "math_unknown_error" ); |
| 110 | + |
| 111 | + $fname = "renderMusic"; |
| 112 | + |
| 113 | + $md5 = md5($lilypond_code); |
| 114 | + |
| 115 | + if( file_exists( $wgMathDirectory."/".$md5.".midi" ) ) { |
| 116 | + $pre = "<a href=\"".$wgMathPath."/".$md5.".midi\"> " . $wgLilypondPreMidi; |
| 117 | + $post = $wgLilypondPostMidi . " </a>"; |
| 118 | + } else { |
| 119 | + $pre = ""; |
| 120 | + $post = ""; |
| 121 | + } |
| 122 | + |
| 123 | + # if short_code is supplied, this is a fragment |
| 124 | + if( $short_code ) { |
| 125 | + $link = "<img src=\"".$wgMathPath."/".$md5.".png\" alt=\"" |
| 126 | + .htmlspecialchars( $short_code )."\">"; |
| 127 | + |
| 128 | + if( file_exists( "$wgMathDirectory/$md5.png" ) ) { |
| 129 | + return $pre.$link.$post; |
| 130 | + } |
| 131 | + } else { |
| 132 | + if( file_exists( "$wgMathDirectory/$md5-1.png" ) ) { |
| 133 | + $link=""; |
| 134 | + for($i=1; file_exists( $wgMathDirectory . "/" . |
| 135 | + $md5 . "-" . $i . ".png" ); |
| 136 | + $i++) { |
| 137 | + |
| 138 | + $link .= "<img src=\"" . $wgMathPath . "/" . |
| 139 | + $md5 . "-" . $i . ".png\" alt=\"" . |
| 140 | + htmlspecialchars( "page ".$i )."\">"; |
| 141 | + } |
| 142 | + return $pre.$link.$post; |
| 143 | + } |
| 144 | + } |
| 145 | + |
| 146 | + # Ensure that the temp and output dirs are available before continuing. |
| 147 | + if( !file_exists( $wgMathDirectory ) ) { |
| 148 | + if( !@mkdir( $wgMathDirectory ) ) { |
| 149 | + return "<b>$mf (" . wfMsg( "math_bad_output" ) . |
| 150 | + $wgMathDirectory . ")</b>"; |
| 151 | + } |
| 152 | + } elseif( !is_dir( $wgMathDirectory ) || |
| 153 | + !is_writable( $wgMathDirectory ) ) { |
| 154 | + return "<b>$mf (" . wfMsg( "math_bad_output" ) . ")</b>"; |
| 155 | + } |
| 156 | + if( !file_exists( $wgTmpDirectory ) ) { |
| 157 | + if( !@mkdir( $wgTmpDirectory ) ) { |
| 158 | + return "<b>$mf (" . wfMsg( "math_bad_tmpdir" ) |
| 159 | + . ")</b>"; |
| 160 | + } |
| 161 | + } elseif( !is_dir( $wgTmpDirectory ) || |
| 162 | + !is_writable( $wgTmpDirectory ) ) { |
| 163 | + return "<b>$mf (" . wfMsg( "math_bad_tmpdir" ) . ")</b>"; |
| 164 | + } |
| 165 | + |
| 166 | + $lyFile = $md5.".ly"; |
| 167 | + $out = fopen( $wgTmpDirectory."/".$lyFile, "w" ); |
| 168 | + if( $out === false ) { |
| 169 | + return "<b>$mf (" . wfMsg( "math_bad_tmpdir" ) . ")</b>"; |
| 170 | + } |
| 171 | + fwrite( $out, $lilypond_code ); |
| 172 | + fclose( $out ); |
| 173 | + |
| 174 | + $cmd = $wgLilypond . |
| 175 | + " -dsafe='#t' -dbackend=eps --png --header=texidoc " . |
| 176 | + escapeshellarg($lyFile) . " 2>&1"; |
| 177 | + |
| 178 | + wfDebug( "Lilypond: $cmd\n" ); |
| 179 | + $oldcwd = getcwd(); |
| 180 | + chdir( $wgTmpDirectory ); |
| 181 | + $contents = exec( $cmd, $output, $ret ); |
| 182 | + chdir( $oldcwd ); |
| 183 | + |
| 184 | + if( $ret != 0 ) { |
| 185 | + return "<br><b>LilyPond error:</b><br><i>" |
| 186 | + . str_replace( array( $md5, " " ), |
| 187 | + array( "<b>your code</b>", " " ), |
| 188 | + nl2br( htmlentities( join( "\n", $output ) ) ) ) |
| 189 | + . "</i><br>"; |
| 190 | + } |
| 191 | + |
| 192 | + if($short_code) { |
| 193 | + $outputFile = $wgTmpDirectory."/".$md5.".png"; |
| 194 | + |
| 195 | + if( !file_exists( $outputFile ) ) { |
| 196 | + return "<b>$mf (" . wfMsg( "math_image_error" ) |
| 197 | + . ")</b>"; |
| 198 | + } |
| 199 | + |
| 200 | + rename( $outputFile, $wgMathDirectory."/".$md5.".png"); |
| 201 | + } |
| 202 | + |
| 203 | + # remove all temporary files |
| 204 | + $files = opendir( $wgTmpDirectory ); |
| 205 | + $last_page = 0; |
| 206 | + |
| 207 | + while( false !== ($file = readdir( $files ))) { |
| 208 | + if( substr( $file, 0, 32 ) != $md5 ) |
| 209 | + continue; |
| 210 | + |
| 211 | + $file_absolute = $wgTmpDirectory . "/" . $file; |
| 212 | + if( !$short_code && preg_match( '/-page(\d+)\.png$/', |
| 213 | + $file, $matches ) ) { |
| 214 | + if($matches[1]>$last_page) |
| 215 | + $last_page = $matches[1]; |
| 216 | + rename( $file_absolute, $wgMathDirectory . "/" . |
| 217 | + $md5 . "-" . $matches[1] . ".png" ); |
| 218 | + continue; |
| 219 | + } |
| 220 | + |
| 221 | + if( preg_match( '/.png$/', $file ) ) { |
| 222 | + rename( $file_absolute, $wgMathDirectory."/".$md5.".png" ); |
| 223 | + continue; |
| 224 | + } |
| 225 | + |
| 226 | + if( preg_match( '/.midi$/', $file ) ) { |
| 227 | + rename( $file_absolute, $wgMathDirectory . "/" . |
| 228 | + $md5 . ".midi" ); |
| 229 | + $pre = "<a href=\"".$wgMathPath."/".$md5.".midi\"> " . $wgLilypondPreMidi; |
| 230 | + $post = $wgLilypondPostMidi . " </a>"; |
| 231 | + continue; |
| 232 | + } |
| 233 | + |
| 234 | + if( !is_file( $file_absolute ) ) |
| 235 | + continue; |
| 236 | + unlink( $file_absolute ); |
| 237 | + } |
| 238 | + closedir( $files ); |
| 239 | + |
| 240 | + if( $short_code ) { |
| 241 | + if( !file_exists( $wgMathDirectory."/".$md5.".png" ) ) { |
| 242 | + $errmsg = wfMsg( "math_image_error" ); |
| 243 | + return "<h3>$mf ($errmsg): " . |
| 244 | + htmlspecialchars($lilypond_code) . "</h3>"; |
| 245 | + } |
| 246 | + } else { |
| 247 | + $link .= "<img src=\"".$wgMathPath."/".$md5.".png\" alt=\"" |
| 248 | + . htmlspecialchars( "page " )."\">"; |
| 249 | + } |
| 250 | + |
| 251 | + if( $wgLilypondTrim ) { |
| 252 | + $imgFile = $wgMathDirectory ."/" .$md5 . ".png"; |
| 253 | + trimImage( $imgFile, $imgFile, 0xFFFFFF ); |
| 254 | + }; |
| 255 | + |
| 256 | + if( $wgLilypondBorderX > 0 || $wgLilypondBorderY > 0 ) { |
| 257 | + $imgFile = $wgMathDirectory ."/" .$md5 . ".png"; |
| 258 | + frameImage( $imgFile, $imgFile, 0xFFFFFF, $wgLilypondBorderX, $wgLilypondBorderY ); |
| 259 | + }; |
| 260 | + |
| 261 | + return $pre . $link . $post; |
| 262 | +} |
| 263 | + |
| 264 | +function trimImage( $source, $dest, $bgColour ) |
| 265 | +{ |
| 266 | + $srcImage = imagecreatefrompng( $source ); |
| 267 | + $width = imagesx( $srcImage ); |
| 268 | + $height = imagesy( $srcImage ); |
| 269 | + |
| 270 | + $xmin = 0; |
| 271 | + $found = false; |
| 272 | + for( $x = 0; $x < $width && !$found; $x++ ) { |
| 273 | + for( $y = 0; $y < $height && !$found; $y++ ) { |
| 274 | + $rgb = imagecolorat( $srcImage, $x, $y ); |
| 275 | + if( $rgb != $bgColour ) { |
| 276 | + $xmin = $x; |
| 277 | + $found = true; |
| 278 | + } |
| 279 | + } |
| 280 | + } |
| 281 | + |
| 282 | + $xmax = $xmin; |
| 283 | + $found = false; |
| 284 | + for( $x = $width-1; $x > $xmin && !$found; $x-- ) { |
| 285 | + for( $y = 0; $y < $height && !$found; $y++ ) { |
| 286 | + $rgb = imagecolorat( $srcImage, $x, $y ); |
| 287 | + if( $rgb != $bgColour ) { |
| 288 | + $xmax = $x; |
| 289 | + $found = true; |
| 290 | + } |
| 291 | + } |
| 292 | + } |
| 293 | + |
| 294 | + $ymin = 0; |
| 295 | + $found = false; |
| 296 | + for( $y = 0; $y < $height && !$found; $y++ ) { |
| 297 | + for( $x = 0; $x < $width && !$found; $x++ ) { |
| 298 | + $rgb = imagecolorat( $srcImage, $x, $y ); |
| 299 | + if( $rgb != $bgColour ) { |
| 300 | + $ymin = $y; |
| 301 | + $found = true; |
| 302 | + } |
| 303 | + } |
| 304 | + } |
| 305 | + |
| 306 | + $ymax = $ymin; |
| 307 | + $found = false; |
| 308 | + for( $y = $height-1; $y > $ymin && !$found; $y-- ) { |
| 309 | + for( $x = 0; $x < $width && !$found; $x++ ) { |
| 310 | + $rgb = imagecolorat( $srcImage, $x, $y ); |
| 311 | + if( $rgb != $bgColour ) { |
| 312 | + $ymax = $y; |
| 313 | + $found = true; |
| 314 | + } |
| 315 | + } |
| 316 | + } |
| 317 | + |
| 318 | + $newWidth = $xmax - $xmin + 1; |
| 319 | + $newHeight = $ymax - $ymin + 1; |
| 320 | + |
| 321 | + $dstImage = imagecreatetruecolor( $newWidth, $newHeight ); |
| 322 | + imagecopy( $dstImage, $srcImage, 0, 0, $xmin, $ymin, $newWidth, $newHeight ); |
| 323 | + imagepng( $dstImage, $dest ); |
| 324 | +} |
| 325 | + |
| 326 | +function frameImage( $source, $dest, $bgColour, $borderWidth, $borderHeight ) |
| 327 | +{ |
| 328 | + $srcImage = imagecreatefrompng( $source ); |
| 329 | + $width = imagesx( $srcImage ); |
| 330 | + $height = imagesy( $srcImage ); |
| 331 | + $dstImage = imagecreatetruecolor( $width + 2*$borderWidth, $height + 2*$borderHeight ); |
| 332 | + $allocatedBgColour = imagecolorallocate( $dstImage, ($bgColour >> 16) & 0xFF, ($bgColour >> 8) & 0xFF, $bgColour & 0xFF); |
| 333 | + imagefill( $dstImage, 0, 0, $allocatedBgColour ); |
| 334 | + imagecopy( $dstImage, $srcImage, $borderWidth, $borderHeight, 0, 0, $width, $height ); |
| 335 | + imagepng( $dstImage, $dest ); |
| 336 | +} |
Property changes on: trunk/extensions/LilyPond/LilyPond.php |
___________________________________________________________________ |
Added: svn:eol-syle |
1 | 337 | + native |