function process ($fo, $out) {
		if (!file_exists($fo))
			die("file '$fo' not found");
		$format = file_extension($out);
		if ($format != 'ps' && $format != 'pdf')
			$format = 'pdf';
		RunTool('fo2rtf', "FO=$fo OUT=$out", 'pipe-callback', array('FO2RTF', 'messageCallback'));
	}
	function createMO ($format, $attr, $showMessages=false) {
		$fname = file_strip_extension($this->fname).".$format";
		$mo = MediaObjectFactory::createMediaObject($this->pagename, $fname);
		if ($this->mustReconvert($mo)) {
			$in  = $this->path();
			$out = $mo->path();
			RunTool('pec2img', "IN=$in OUT=$out");
			if ($format != 'eps' && $format != 'pdf' && isset($attr['scale'])) 
				RunTool('mogrify', "SCALE=$attr[scale] FILE=".$mo->path());
//			$mo->scale($this->attribs->getAttrib('scale'));
		}
		return $mo;
	}
	function createMO ($format, $attr, $showMessages=false) {
		//      Example:
		//      set terminal gif
		//      set output "test.gif"
		//      plot x**2

		$fname = file_strip_extension($this->fname).".$format";
		$mo = MediaObjectFactory::createMediaObject($this->pagename, $fname);
		if ($this->mustReconvert($mo)) {
			if ($showMessages)
				message("creating gnuplot image in ".strtoupper($format)." format", 'start');
			$scripthead = "cd '".$mo->dir()."'\nset output '$fname'\n";
			switch ($format) {
				case "gif":	$scripthead .= "set term gif transparent crop";	break;
				case "png":	$scripthead .= "set term png truecolor crop";	break;
				case 'eps': $scripthead .= "set term postscript eps color"; break;
				case 'pdf': $scripthead .= "set term pdf color"; break;
			}
			$script = file_get_contents($this->path());
			$script = preg_replace('/^\s*set?\s+((o)|(ou\w*))/', '', $script); // remove "set output" statements
			$script = preg_replace('/^\s*set?\s+t\w/', '', $script);           // remove "set terminal" statements
			$script = "$scripthead\n$script";
			$tmpfile = $this->path().".tmp";
			$f = fopen($tmpfile, "w");
			fputs($f, $script);
			fclose($f);
			RunTool('gnuplot', "SCRIPT=$tmpfile");
//			unlink($tmpfile);
			if ($format == 'pdf') {
				// the gnuplot generated bounding box is too wide, so we
				// compute and assign a minimal box
				if ($showMessages)
					message("adapting bounding box");
//				assignMinimalBoundingBoxToPDF($mo->path());
			}
			// @@ the current gnuplot version doesn't handle transparency correctly, so we apply it seperately
			// @@ can hopefully be removed again
			elseif ($format == 'png')
				RunTool('convert', "IN=".$mo->path()." OUT=".$mo->path()." TRANSPARENT=white");

			if ($format != 'eps' && $format != 'pdf' && isset($attr['scale'])) 
				RunTool('mogrify', "SCALE=$attr[scale] FILE=".$mo->path());
			if ($showMessages)
				message("", 'end');
		}
		return $mo;
	}
	function convert ($format, $attr) {
		$currentFormat = $this->format();
		$format = strtolower(trim($format));
		if ($format == '')
			return false;
		if ($currentFormat == $format)
			return $this;
			
		$name = file_strip_extension($this->fname);  // remove file extension
		$newMO = MediaObjectFactory::createMediaObject($this->pagename, "$name.$format");
		if (!$newMO->exists() || $newMO->olderThan($this)) {
			if ($showMessages)
				message("creating FIG image in ".strtoupper($format)." format", 'start');
			RunTool('fig2dev', "IN=".$this->path()." OUT=".$mo->path()." FORMAT=$format");
		}
		return $newMO;
	}
	function getHTML ($attr) {
		if ($this->fname == '')
			return '';
		$ext = strtolower(file_extension($this->fname));
		$appletdir = $this->dir()."/".file_strip_extension($this->fname);
		$appleturl = dirname($this->url())."/".file_strip_extension($this->fname);
		recursive_mkdir($appletdir);
		if ($ext == 'jar' || $ext == 'zip') {
			$cwd = getcwd();
			chdir($appletdir);
			if ($ext == 'zip')
				RunTool('unzip', "IN=".$this->path(), 'pipe');
			else
				RunTool('unzip', "IN=".$this->path()." FILE=index.html", 'pipe');
			$ok = $this->readHTMLFile('index.html');
			chdir($cwd);
			if (!$ok)
				return '';
		}
		if (!isset($this->attribs['archive']))
			return '';

		// generate html file containing the applet
		$ret = "<html><body>\n";
		$ret.= '<div style="margin:0;padding:0"><applet';
		foreach ($this->attribs as $k=>$v)
			$ret .= " $k=\"$v\"";
		$ret .= ">\n";
		foreach ($this->params as $k=>$v)
			$ret .= "<param name=\"$k\" value=\"$v\"/>\n";
		$ret .= "</applet></div>";
		$ret .= "</body></html>";
		$f = fopen("$appletdir/applet.html", "w");
		fputs($f, $ret);
		fclose($f);

		// return iframe with above applet page
		$ret = "<iframe src='$appleturl/applet.html'";
		$ret.= " scrolling='no' frameborder='0' marginwidth='0' marginheight='0'";
		$ret.= "	width='{$this->attribs['width']}' height='{$this->attribs['height']}'>\n";
		$ret.= "<p>Your browser doesn't support embedded frames</p>\n";
		$ret.= "</iframe>";
		return $ret;
	}
	function size () {
		$output = RunTool('ffmpeg', "IN=".$this->path(), 'pipe');
		if (preg_match('/\n\s*Stream\s+.*?Video:.*?(\d+)x(\d+)/', $output, $m)) 
			return array('width'=>$m[1], 'height'=>$m[2]);
		return false;
	}
	function process ($xsl, $xml, $out) {
		if (!file_exists($xsl))
			die("stylesheet file '$xsl' not found");
		if (!file_exists($xml))
			die("XML file '$xml' not found");
//		global $XALAN;
		RunTool('xalan', "XSL=$xsl XML=$xml OUT=$out");
//		passthru("$XALAN -IN $xml -XSL $xsl -OUT $out");
	}
	function fileInfo () {
		if (!$this->exists())
			return false;
		$infolines = RunTool('identify', "OPT=-verbose IN=".$this->path(), 'exec');
		// collect output of identify (not really reliable yet)
		foreach ($infolines as $line)
			if (preg_match('/^  ([ a-zA-Z]+):(.+)$/', $line, $m))
				$info[trim($m[1])] = trim($m[2]);
		// add info about image width and height
		preg_match('/(\d+)x(\d+)/', $info['Geometry'], $m);
		$info['Width'] = $m[1];
		$info['Height'] = $m[2];
		preg_match('/(\d+)x(\d+)/', $info['Resolution'], $m);		
		$dpi=96;
		if($m[1] > 0){
	   	$m[2] = $dpi;
	    	$m[1] = $dpi;
		}

		if ($m[1] > 0)
			$info['AbsWidth'] = str_replace(',', '.', ($info['Width']/$m[1])."in"); // TODO: distinguish between units stored in image file
		if ($m[2] > 0)
			$info['AbsHeight'] = str_replace(',', '.', ($info['Height']/$m[2])."in");
		return $info;
	}
	function process ($fo, $out) {
		if (!file_exists($fo))
			return false;
		$format = file_extension($out);
		if ($format != 'ps' && $format != 'pdf')
			$format = 'pdf';
		RunTool('xep', "FO=$fo FORMAT=$format OUT=$out", 'pipe-callback', array('XepFOProcessor', 'messageCallback'));
		return true;
	}
	function tex2format ($texfile, $format) {
		if ($format == 'eps')
			return $this->tex2eps($texfile);
		if ($format == 'pdf')
			return $this->tex2pdf($texfile);

		$resfile = file_strip_extension($texfile).".$format";
		if (file_exists($resfile))
			return $resfile;

		$epsfile = $this->tex2eps($texfile);
		if ($epsfile) {
			RunTool('convert', "DENSITY=110x110 TRANSPARENT=white IN=$epsfile OUT=$resfile");
			return file_exists($resfile) ? $resfile : false;
		}
		return false;
	}
	function convertWiki ($pageinfo) {
		global $M2MDir;
		
//		$this->createOptionStylesheet();
		message("creating XML document", 'start');
		// add entity references
		$xml = $this->convertPagesToXML($pageinfo);			
		$dtd = "<!DOCTYPE wikixml [\n";
		$dtd.= "<!ENTITY % HTMLlat1 PUBLIC \"-//W3C//ENTITIES Latin 1 for XHTML//EN\" \"$M2MDir/xml/xhtml-lat1.ent\">\n";
		$dtd.= "<!ENTITY % HTMLsymbol PUBLIC \"-//W3C//ENTITIES Symbols for XHTML//EN\" \"$M2MDir/xml/xhtml-symbol.ent\">\n";
		$dtd.= "<!ENTITY % HTMLspecial PUBLIC \"-//W3C//ENTITIES Special for XHTML//EN\" \"$M2MDir/xml/xhtml-special.ent\">\n";
		$dtd.= "%HTMLlat1;\n";
		$dtd.= "%HTMLsymbol;\n";
		$dtd.= "%HTMLspecial;\n";
		$dtd.= "]>\n";
		$xml = '<?xml version="1.0" encoding="iso-8859-15"?>'."\n$dtd$xml";

		// check well-formedness and fix corresponding errors
		message("checking well-formedness", 'start');
		$outputDir = $this->outputDir();
		$fname = "$outputDir/source.xml";
		@unlink($fname);
		$f = fopen("$fname.1", "w");
		fputs($f, $xml);
		fclose($f);
		unset($xml);
/*		$wf = new WellFormer();
		$wf->processFile("$fname.1", $fname);
		$msg = array();
		if ($wf->numNestingErrors > 0)
			$msg[] = sprintf('%d nesting error%s fixed', $wf->numNestingErrors, $wf->numNestingErrors>1?'s':'');
		if ($wf->numCloseErrors > 0)
			$msg[] = sprintf('%d open element%s closed', $wf->numCloseErrors, $wf->numCloseErrors>1?'s':'');
		$msg = implode(' and ', $msg);
		if ($msg)
			message($msg);*/
		RunTool('wellformer', "IN=$fname.1 OUT=$fname");
		message('', 'end'); // well-formedness check
		message('', 'end'); // XML creation
		
		chmod($fname, 0644);

		if ($this->convertXML($fname) && file_exists("$outputDir/".$this->getOutputFile()))
			return "$outputDir/".$this->getOutputFile();
		return false;
	}	
function assignMinimalBoundingBoxToPDF ($pdffile) {
	if (!file_exists($pdffile))
		return false;
	// compute new bounding box
	$bbox = RunTool('gs', "DEVICE=bbox IN=$pdffile", 'pipe');
	if (!preg_match('/^%%BoundingBox/', $bbox))  // ghostscript call failed?
		return false;
	$bbox = preg_replace('/^.*%%HiResBoundingBox:\s*(.*?)\n$/s', '$1', $bbox);

	// adapt pdf file
	// This is a first implementation that should work with most pdf files but 
	// can't be considered as 100% reliable.
	$f = fopen($pdffile, 'rb');
	$g = fopen("$pdffile.new", 'wb');
	$count = 0;
	$inobj = $inpageobj = $nocount = false;
	while (!feof($f)) {
		$line = fgets($f);
		if (!$inobj && preg_match('/^\d+\s+\d+\s+obj$/', $line)) // start of new object?
			$inobj = true;
		elseif ($inobj && preg_match('/\bendobj\n/', $line))     // end of current object?
			$inobj = $inpageobj = false;
		elseif ($inobj && preg_match('#/Type\s*/Page\b#', $line))// in page object?
			$inpageobj = true;
		elseif ($line == "xref\n") {  // xref section reached?
			fputs($g, $line);
			// adapt xref pointers
			while (($line = fgets($f)) != "trailer\n") {
				if (preg_match('/^(\d{10}) (\d{5} \w )/', $line, $m) && $m[1]>=$checkoffs)
					$line = sprintf("%010d %s\n", $m[1]+$diff, $m[2]);
				fputs($g, $line);
			}
			$nocount = true;  // stop byte counting
		}
		elseif ($line == "startxref\n") {
			// adapt pointer to start of xref section
			fputs($g, "startxref\n$count\n%%EOF");
			break;
		}

		if ($inpageobj && preg_match('#^(.*?/MediaBox\s*)\[(.*?)\](.*?)$#', $line, $m)) {
			// adapt bounding box
			$checkoffs = $count+strlen($line);
			$line = "$m[1][$bbox]$m[3]\n";
			$diff = strlen($bbox)-strlen($m[2]);
		}
		if (!$nocount)
			$count += strlen($line);
		fputs($g, $line);
	}
	fclose($g);
	fclose($f);
	if (file_exists("$pdffile.new")) {
		unlink($pdffile);
		rename("$pdffile.new", $pdffile);
		return file_exists($pdffile);
	}
	return false;
}
	function createMathML ($inlined) {
		$mathmark = $inlined ? '$' : '$$';  // latex source is expected to be enclosed by $...$ or $$...$$
		$source = $mathmark.trim($this->code).$mathmark;

		// header code taken from tex4ht script 'mzlatex'
		$head  =	'\makeatletter'."\n";
		$head .= '\def\HCode{\futurelet\HCode\HChar}'."\n";
		$head .= '\def\HChar{\ifx"\HCode\def\HCode"##1"{\Link##1}\expandafter\HCode\else\expandafter\Link\fi}'."\n";
		$head .= '\def\Link#1.a.b.c.{%'."\n";
		$head .= '  \g@addto@macro\@documentclasshook{\RequirePackage[#1,xhtml,mozilla]{tex4ht}}'."\n";
		$head .= '  \let\HCode\documentstyle'."\n";
		$head .= '  \def\documentstyle{%'."\n";
		$head .= '	  \let\documentstyle\HCode'."\n";
		$head .= '    \expandafter\def\csname tex4ht\endcsname{#1,xhtml,mozilla}'."\n";
		$head .= '	  \def\HCode####1{\documentstyle[tex4ht,}'."\n";
		$head .= '	  \@ifnextchar[{\HCode}{\documentstyle[tex4ht]}}}'."\n";
		$head .= '\makeatother' ."\n";
		$head .= '\HCode .a.b.c.'."\n";
		$head .= '\documentclass{article}';

		$cwd = chdir($this->outputDir);
		$fname = md5($source);
		$f = fopen("$fname.tex", 'w');
		fputs($f, "$head\n\\begin{document}\n$source\n\\end{document}\n");
		fclose($f);

		RunTool('latex', "TEX=$fname");
		RunTool('tex4ht', "TEX=$fname");
		RunTool('t4ht', "TEX=$fname");
//		execute("$LATEX $fname");
//		execute("$TEX4HT -f$fname -i$TEX4HTDIR/texmf/tex4ht/ht-fonts -cmozhtf");
//		execute("$T4HT -f$fname -cvalidate ##");

		$mml = file_get_contents("$fname.xml");
		$mml = preg_replace('#^.*(<math.*>.*</math>).*$#Usi', '\1', $mml);  // pick out MathML
		$mml = preg_replace('/<!--.*?-->/', '', $mml);  // remove comments
//		$mml = preg_replace('/\s*\n\s*(.*?)>/', '\1>', $mml);  // compact output
		// remove temporary files
		$ext = array('4ct', '4tc', 'aux', 'css', 'dvi', 'html', 'idv', 'lg', 'log', 'tex', 'tmp', 'xml', 'xref');
		foreach ($ext as $e)
			@unlink("{$this->outputDir}/$fname.$e");
		return $mml;
	}