Open main menu

Extension:Metapost

MediaWiki extensions manual
OOjs UI icon advanced.svg
Metapost
Release status: stable
Implementation Tag
Description Include METAPOST graphics on a Mediawiki page using the <metapost> tag
Author(s) LEE Sau Dan (李守敦)
Latest version 1.7 (2008-11-13T05:43:08)
MediaWiki 1.9 - 1.12
License No license specified
Download http://www.cs.hku.hk/~sdlee/Metapost/Metapost.php.gz

$wgMetaPostSettings->metapostCommand

$wgMetaPostSettings->convertCommand $wgMetaPostSettings->convertDPIDefault
Translate the Metapost extension if it is available at translatewiki.net
Check usage and version matrix.

Contents

What can this extension do?Edit

This extension adds a new tag <metapost> to allow wiki page authors to include METAPOST graphics into wiki pages.

The following software is required for this extension to work:

  1. METAPOST (version >= 0.993)
  2. ImageMagick (version >= 6.2)
  3. Ghostscript

UsageEdit

The following wiki page content will render a page like this one.

This is a demo:

<metapost>
input boxes;%

beginfig(0)
  path p[];
  p1 = fullcircle xscaled 30 yscaled 100 rotated 75;
  p2 = ((.2,0) .. (.5,.1) .. (.7,.4) .. (.9,.7)
        .. (.9,1) .. (.5,1) ..(.3, .7) .. (.3, .4) .. cycle)
      scaled 70 shifted (-50,-50);
  fill p1 withcolor .5[red, white];
  fill p2 withcolor .5[red+green, white];

  begingroup
    save t,u;
    (t1-0, u1) = subpath(0,2) of p1 intersectiontimes p2;
    (t2-2, u2) = subpath(2,4) of p1 intersectiontimes p2;
    (t3-4, u3) = subpath(4,6) of p1 intersectiontimes p2;
    (t4-6, u4) = subpath(6,8) of p1 intersectiontimes p2;

    p3 = subpath(t2, t3) of p1
         -- subpath(t1, 0) of p1 & subpath(8,t4) of p1
	 -- cycle;
  endgroup;

  draw p3;

  boxit.line1(btex MetaPost diagram etex);
  boxit.line2(btex containing \TeX\ symbols etex);
  boxit.line3(btex embedded in etex);
  boxit.line4(btex a Wikipedia page etex);

  (0,0) = line1.s - line2.n
        = line2.s - line3.n
        = line3.s - line4.n;

  drawoptions(withcolor .5blue);
  drawunboxed(line1);
  drawoptions(withcolor .5red);
  drawunboxed(line2);
  drawoptions(withcolor .5green);
  drawunboxed(line3);
  drawoptions(withcolor .5(red+blue));
  drawunboxed(line4);

  drawoptions(withcolor .5(blue+green));
  draw btex $\int_{-\infty}^{+\infty} e^{-{1\over2}z^2} dz
             = \sqrt{2\pi}$ etex
       rotated 15
       shifted(-30,0);
endfig;

</metapost>

That's all folks!

InstallationEdit

  1. Download the gzipped source code
  2. Decompress the file and rename it to Metapost.php
  3. Put Metapost.php into your MediaWiki extensions directory.
  4. Change LocalSettings.php to load the extension. (See #Changes to LocalSettings.php)

ParametersEdit

These parameters can be set in LocalSettings.php after Metapost.php is loaded:

Parameter Description Default Value
$wgMetaPostSettings->metapostCommand Command to invoke METAPOST "/usr/bin/mpost"
$wgMetaPostSettings->convertCommand Command to invoke ImageMagick's convert command "/usr/bin/convert"
$wgMetaPostSettings->convertDPIDefault DPI (dots-per-inch) to use to render the image. "144"


Changes to LocalSettings.phpEdit

require_once("$IP/extensions/Metapost.php");

CodeEdit

Please fetch it from http://www.cs.hku.hk/~sdlee/Metapost/Metapost.php.gz

New version of this extensionEdit

As the above link leads nowhere and the implementation is nowhere to be found, I endeavored to write a new version of this extension, as following:

<?php
/**
 * Parser hook extension adds a <metapost> tag to wiki markup for rendering
 * Metapost diagrams within a wiki page.
 * Adapted from MetaUML extension.
 *
 * Parameters:
 *   density=width               passed to ImageMagic to set rendering density.
 *   input=pkg_1,pkg_2,...pkg_n  Metapost packages to load in prologue.
 *   redraw                      Force redraw.
 *   format                      Target format to use (PNG/SVG), the latter is
 *                               only supported by metapost-1.2 or later
 *
 */

// Make sure we are being called properly
if( !defined( 'MEDIAWIKI' ) ) {
    echo( "This file is an extension to the MediaWiki software and cannot be used standalone.\n" );
    die( -1 );
}

class MetapostSettings {
    var $metapostCommand, $convertCommand;
    var $density, $format, $inputs, $redraw;

    function __construct() {
      global $wgImageMagickConvertCommand;

      $this->metapostCommand = "/usr/bin/mpost";
      $this->convertCommand = $wgImageMagickConvertCommand;
      $this->density = "100%";
      $this->format = "png";
      $this->inputs = array();
      $this->redraw = false;
    }
}

$wgMetapostSettings = new MetapostSettings();

//Avoid unstubbing $wgParser too early on modern (1.12+) MW versions, as per r35980
if (defined('MW_SUPPORTS_PARSERFIRSTCALLINIT')) {
        $wgHooks['ParserFirstCallInit'][] = 'wfMetapostExtension';
} else {
        $wgExtensionFunctions[] = 'wfMetapostExtension';
}

$wgExtensionCredits['parserhook'][] = array(
        'name' => 'Metapost',
        'version' => '0.3',
        'author' => 'Alex Dubov; SVG by Glen Prideaux',
        'url' => 'http://www.mediawiki.org/wiki/Extension:Metapost',
        'description' => 'Renders a Metapost diagram.'
);


function wfMetapostExtension() {
  global $wgParser;
  $wgParser->setHook('metapost', 'renderMetapost');
  return true;
}

class MetapostRenderer {
  var $settings;
  var $src, $hash;
  var $src_path, $ltx_src_path, $dst_path, $dst_url;
  var $old_cwd, $cwd;

 function get_hash_path() {
    $path = '/' . $this->hash[0]
          . '/' . $this->hash[1]
          . '/' . $this->hash[2];
    return $path;
  }

  function __construct($src) {
    global $wgMathDirectory, $wgMathPath, $wgTmpDirectory;
    global $wgMetapostSettings;
 
    $this->settings = $wgMetapostSettings;
    $this->old_cwd = getcwd();
    $this->src = $src;
    $this->hash = hash('md5', $src);

    wfDebugLog('Metapost', "Calculated src hash: " . $this->hash);

    $dest_dir = $wgMathDirectory . $this->get_hash_path();

    if (!is_dir($dest_dir)) {
      mkdir($dest_dir, 0755, true);
    }

    if (!is_dir($wgTmpDirectory)) {
      mkdir($wgTmpDirectory, 0755);
    }

    $this->src_path = $wgTmpDirectory . '/' . $this->hash;
    $this->ltx_src_path = $wgTmpDirectory . "/ltx-" . $this->hash;
    $this->dst_path = $dest_dir . '/' . $this->hash . '.'
                      . $this->settings->format;
    $this->dst_url = $wgMathPath . $this->get_hash_path() . '/' . $this->hash
                     . '.' . $this->settings->format;
    chdir($wgTmpDirectory);
    wfDebugLog('Metapost', "src_path: " . $this->src_path);
    wfDebugLog('Metapost', "dst_path: " . $this->dst_path);
    wfDebugLog('Metapost', "dst_url: " . $this->dst_url);
  }

  function __destruct() {
    $er = error_reporting(E_ALL ^ E_WARNING);
    unlink($this->src_path . ".mp");
    unlink($this->src_path . ".mpx");
    unlink($this->ltx_src_path . ".mpx");
    unlink($this->ltx_src_path . ".tmp");
    unlink($this->src_path . ".1");
    unlink($this->src_path . ".log");
    chdir($this->old_cwd);
    error_reporting($er);
  }

  function create_metapost() {
    $fd = fopen($this->src_path . ".mp", "wb");
    if ($this->settings->format == 'svg') {
        fwrite($fd, "outputformat := \"svg\";\n");
    }
    fwrite($fd, "prologues := 3;\nnonstopmode;\n\n");
    foreach ($this->settings->inputs as $inp) {
      fwrite($fd, "input " . $inp . ";\n");
    }
    fwrite($fd, "\nbeginfig(1);\npicture diag;\ndiag:=image(\n");
    fwrite($fd, $this->src);
    fwrite($fd, ");");
    // scale svg output to translate points to pixels according to the setting of density
    // (in png output this is handled by the convert phase)
    if($this->settings->format == 'svg') {
      fwrite($fd,"draw diag scaled ".$this->settings->density/72 .";\n");
    } else {
      fwrite($fd,"draw diag;\n");
    }
    fwrite($fd, "\nendfig;\n\nend\n");
    fclose($fd);
  }

  function create_image() {
    $mpost_cmd = wfEscapeShellArg($this->settings->metapostCommand)
                 . " " . $this->src_path . ".mp";
    wfDebugLog('Metapost', "mpost stage: " . $mpost_cmd);
    exec($mpost_cmd);
    if (!file_exists($this->src_path . ".1")) {
      return false;
    }

    if (file_exists($this->ltx_src_path . ".tmp")) {
        wfDebugLog('Metapost', "mpost stage ltx: " . $mpost_cmd);
        exec($mpost_cmd);
    }
    if (!file_exists($this->src_path . ".1")) {
      return false;
    }

    if($this->settings->format == 'svg') {
      rename($this->src_path . ".1", $this->dst_path);
    } else { 
      $conv_cmd = wfEscapeShellArg($this->settings->convertCommand)
                  . " -density " . wfEscapeShellArg($this->settings->density)
                  . " -trim -transparent \"#FFFFFF\" "
                  . wfEscapeShellArg($this->src_path . ".1")
                  . " " . wfEscapeShellArg($this->dst_path);
      wfDebugLog('Metapost', "convert stage: " . $conv_cmd);
      exec($conv_cmd);
    }
    if (!file_exists($this->dst_path)) {
      return false;
    }

    return true;
  }

  function get_image_url() {
    if (is_file($this->dst_path) && ($this->settings->redraw == true)) {
      unlink($this->dst_path);
    }

    if (is_file($this->dst_path)) {
      return $this->dst_url;
    } else {
      $this->create_metapost();
      if ($this->create_image()) {
        return $this->dst_url;
      } else {
        return false;
      }
    }
  }
}

# The callback function for converting the input text to HTML output
function renderMetapost($src, $argv) {
  $renderer = new MetapostRenderer($src);
  global $wgMetapostSettings;

  if (array_key_exists('density', $argv)) {
    $renderer->settings->density = $argv['density'];
  }

  if (array_key_exists('redraw', $argv)) {
    $renderer->settings->redraw = true;
  }

  if (array_key_exists('input', $argv)) {
    $c_inp = explode(',', $argv['input']);
    $renderer->settings->inputs = array_unique(array_merge($wgMetapostSettings->inputs,
                                                           $c_inp));
  }

  if (array_key_exists('format', $argv)) {
    if ($argv['format'] != "png" && $argv['format'] != "svg") {
      return "[Formats other than PNG or SVG are not supported]";
    } else {
      $renderer->settings->format = $argv['format'];
    }
  }

  $url = $renderer->get_image_url();

  if ($url == false) {
    $text = "[An error occurred in Metapost extension]";
  } else {
    $text = "<img src='$url'>";
  }

  return $text;
}

MikTex on WindowsEdit

Getting this new version to work with MikTex on a windows installation requires these steps:

  • MikTex/bin is in the path (and the machine is rebooted so that Apache picks up the change)
  • Rather than passing the the full path to the mp file in create_image we have to chdir and use the file name only:
  function create_image() {
	global $wgTmpDirectory;
    $mpost_cmd = wfEscapeShellArg($this->settings->metapostCommand)
                 . " " . $this->hash . ".mp";
    wfDebugLog('Metapost', "mpost stage: " . $mpost_cmd);
    chdir($wgTmpDirectory);
    exec($mpost_cmd);
    if (!file_exists($this->hash . ".0")) {
      return false;
    }
  • The $wgTmpDirectory global has to be pointed to a path with no spaces
  • The $wgImageMagickConvertCommand global needed to point to the convert function
  • References to files ending in .1 should be changed to .0 (for some reason this is the terminus that MikTex uses)

See alsoEdit

Extension:GraphViz
Another cool extension for embedding graphics. Indeed, the source code of the Metapost extension is branched off from the source code of the GraphiViz extension!
Gnuplot extension
Useful for plotting graphs against a function or a set of data