3b4f16ae70c61403cf4703fe7746e2651dcd594e
[scilab.git] / scilab / modules / helptools / src / java / org / scilab / modules / helptools / CopyConvert.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2008 - INRIA - Hussein SHAFIE
4  * Copyright (C) 2010 - DIGITEO - Sylvestre LEDRU
5  *
6  * Copyright (C) 2012 - 2016 - Scilab Enterprises
7  *
8  * This file is hereby licensed under the terms of the GNU GPL v2.0,
9  * pursuant to article 5.3.4 of the CeCILL v.2.1.
10  * This file was originally licensed under the terms of the CeCILL v2.1,
11  * and continues to be available under such terms.
12  * For more information, see the COPYING file which you should have received
13  * along with this program.
14  *
15  */
16 package org.scilab.modules.helptools;
17
18 import java.io.IOException;
19 import java.io.File;
20 import java.io.InputStream;
21 import java.io.OutputStream;
22 import java.io.FileInputStream;
23 import java.io.FileOutputStream;
24 import java.io.OutputStreamWriter;
25 import java.io.PrintWriter;
26 import java.net.URISyntaxException;
27 import java.net.MalformedURLException;
28 import java.net.URL;
29 import java.util.Map;
30 import java.util.Iterator;
31 import java.util.HashMap;
32 import java.util.ArrayList;
33 import java.util.zip.GZIPInputStream;
34 import javax.xml.parsers.SAXParser;
35 import javax.xml.parsers.SAXParserFactory;
36 import org.xml.sax.SAXParseException;
37 import org.xml.sax.SAXException;
38 import org.xml.sax.Locator;
39 import org.xml.sax.Attributes;
40 import org.xml.sax.helpers.DefaultHandler;
41
42 import org.apache.batik.transcoder.TranscoderException;
43 import org.apache.batik.transcoder.ErrorHandler;
44 import org.apache.batik.transcoder.TranscoderInput;
45 import org.apache.batik.transcoder.TranscoderOutput;
46 import org.apache.batik.transcoder.Transcoder;
47 import org.apache.batik.transcoder.XMLAbstractTranscoder;
48 import org.apache.batik.transcoder.image.ImageTranscoder;
49 import org.apache.batik.transcoder.image.PNGTranscoder;
50
51 import net.sourceforge.jeuclid.context.Parameter;
52 import net.sourceforge.jeuclid.MutableLayoutContext;
53 import net.sourceforge.jeuclid.context.LayoutContextImpl;
54 import net.sourceforge.jeuclid.converter.Converter;
55
56 import org.scilab.forge.jlatexmath.TeXConstants;
57 import org.scilab.forge.jlatexmath.TeXFormula;
58 import org.scilab.forge.jlatexmath.ParseException;
59 import java.awt.Color;
60
61 /**
62  * Preprocess before building the documentation
63  */
64 public class CopyConvert extends DefaultHandler implements ErrorHandler {
65
66     private static final String MATHML_NS =
67         "http://www.w3.org/1998/Math/MathML";
68
69     /**
70      * The tmp directory
71      */
72     public static final File TMPDIR = new File(System.getenv("TMPDIR"));
73
74     private boolean verbose;
75     private String printFormat;
76
77     private File outDir;
78     private PrintWriter out;
79     private SAXParser parser;
80     private Locator locator;
81
82     private ArrayList<String[]> inScopePrefixes;
83     private PrintWriter mainOut;
84     private int graphicsCounter;
85     private int embeddedGraphicsIsMathML;
86
87     private int initTeX;
88     private File latex;
89     private File dvips;
90     private File gs;
91     private File ps2pdf;
92
93     private LaTeXElement latexElem;
94     private boolean isLatexConverted = true;
95
96     /**
97      * Enables the verbose mode
98      *
99      * @param verbose true if enable
100      */
101     public void setVerbose(boolean verbose) {
102         this.verbose = verbose;
103     }
104
105     /**
106      * Set the print format
107      *
108      * @param printFormat  The print format
109      */
110     public void setPrintFormat(String printFormat) {
111         this.printFormat = printFormat;
112     }
113
114
115
116     /**
117      * Set if the LaTeX label are converted in PNG or not to use with
118      * the jlatexmath-fop plugin.
119      * @param isLatexConverted true if LaTeX label are converted in PNG
120      */
121     public void setLatexConverted(boolean isLatexConverted) {
122         this.isLatexConverted = isLatexConverted;
123     }
124
125     // -----------------------------------------------------------------------
126
127     /**
128      * Run the copy/convert process
129      *
130      * @param inFile Input file
131      * @param outputFile Output file
132      */
133     public void run(File inFile, File outputFile)
134     throws SAXParseException, SAXException, IOException {
135         File outFile = outputFile.getCanonicalFile();
136         outDir = outFile.getParentFile();
137         if (!outDir.isDirectory() && !outDir.mkdirs()) {
138             throw new IOException("Cannot create directory '" + outDir + "'");
139         }
140
141         try {
142             SAXParserFactory factory = SAXParserFactory.newInstance();
143             factory.setNamespaceAware(true);
144             // We need qNames and xmlns*.
145             // FIXME: xmlns:db prefix is not handled by the thirdparty's stylesheet (not the right version)
146             //            factory.setFeature(
147             //                "http://xml.org/sax/features/namespace-prefixes", true);
148             factory.setValidating(false);
149             //factory.setXIncludeAware(false);
150
151             parser = factory.newSAXParser();
152         } catch (Exception e) {
153             throw new SAXParseException(
154                 "Cannot create a properly configured SAX parser: " + Helpers.reason(e), locator);
155         }
156
157         inScopePrefixes = new ArrayList<String[]>();
158         mainOut = null;
159         graphicsCounter = 0;
160         embeddedGraphicsIsMathML = -1;
161         initTeX = -1;
162
163         out = new PrintWriter(
164             new OutputStreamWriter(new FileOutputStream(outFile), "UTF-8"));
165         try {
166             parser.parse(inFile, this);
167             out.flush();
168             if (out.checkError()) {
169                 throw new IOException("Error writing '" + outFile + "'");
170             }
171         } catch (SAXException e) {
172             if (locator != null) {
173                 throw new SAXParseException("Cannot parse " + inFile + " " + Helpers.reason(e), locator);
174             } else {
175                 throw new SAXException("Cannot parse " + inFile + " " + Helpers.reason(e));
176             }
177         } finally {
178             out.close();
179         }
180     }
181
182     // -----------------------------------------------------------------------
183     // ContentHandler
184     // -----------------------------------------------------------------------
185
186     /**
187      * Set the document Locator
188      *
189      * @param locator  The locator
190      */
191     public void setDocumentLocator(Locator locator) {
192         this.locator = locator;
193     }
194
195     public void startDocument() throws SAXException {
196         out.write("<?xml version='1.0' encoding='UTF-8'?>\n");
197     }
198
199     public void endDocument() throws SAXException {
200     }
201
202
203     public void startPrefixMapping(String prefix, String uri)
204     throws SAXException {
205         inScopePrefixes.add(new String[] {prefix, uri });
206     }
207
208     public void endPrefixMapping(String prefix)
209     throws SAXException {
210         int count = inScopePrefixes.size();
211         for (int i = count - 1; i >= 0; --i) {
212             String[] pair = inScopePrefixes.get(i);
213             if (pair[0].equals(prefix)) {
214                 inScopePrefixes.remove(i);
215                 break;
216             }
217         }
218     }
219
220     public void startElement(String uri, String localName, String qName,
221                              Attributes atts)
222     throws SAXException {
223         out.write('<');
224
225         if ("latex".equals(localName)) {
226             latexElem = new LaTeXElement(atts, isLatexConverted);
227             return;
228         }
229
230         out.write(qName);
231
232         boolean isImage = "imagedata".equals(localName);
233         boolean isGraphicsFile = false;
234
235         int count = atts.getLength();
236         for (int i = 0; i < count; ++i) {
237             String attValue = atts.getValue(i);
238
239             if (isImage && "fileref".equals(atts.getLocalName(i))) {
240                 isGraphicsFile = true;
241
242                 URL url = null;
243
244                 try {
245                     URL base = new URL(locator.getSystemId());
246                     url = new URL(base, attValue);
247                 } catch (MalformedURLException e) {
248                     reportError("Malformed URL '" + attValue + "'");
249                 }
250
251                 if (url != null) {
252                     File graphicsFile = null;
253
254                     try {
255                         graphicsFile = new File(url.toURI());
256                     } catch (URISyntaxException e) {
257                         reportError("Malformed URI '" + url + "'");
258                     } catch (IllegalArgumentException e) {
259                         reportError("'" + url + "', not a 'file:' URL");
260                     }
261
262                     if (graphicsFile != null) {
263                         String converted =
264                             copyConvertGraphics(graphicsFile);
265                         if (converted != null) {
266                             // Substitute the basename of the converted file
267                             // to attValue.
268                             attValue = converted;
269                         }
270                     }
271                 }
272             }
273
274             out.write(' ');
275             out.write(atts.getQName(i));
276             out.write("='");
277             Helpers.escapeXML(attValue, out);
278             out.write('\'');
279         }
280
281         if (mainOut != null) {
282             if (embeddedGraphicsIsMathML < 0) {
283                 // Is embedded graphics SVG or MathML?
284                 embeddedGraphicsIsMathML = MATHML_NS.equals(uri) ? 1 : 0;
285             }
286
287             // Declare namespace prefixes in the extracted file.
288             declarePrefixes(atts);
289         }
290
291         if (!isImage || isGraphicsFile) {
292             out.write('>');
293         } else {
294             File extractedFile =
295                 new File(outDir, "graphics-" + (++graphicsCounter) + ".tmp");
296             try {
297                 PrintWriter extracted = new PrintWriter(
298                     new OutputStreamWriter(new FileOutputStream(extractedFile),
299                                            "UTF-8"));
300
301                 out.flush();
302                 mainOut = out;
303
304                 out = extracted;
305                 out.write("<?xml version='1.0' encoding='UTF-8'?>\n");
306
307                 embeddedGraphicsIsMathML = -1;
308             } catch (IOException e) {
309                 reportError("Cannot create file '" + extractedFile + "': "
310                             + Helpers.reason(e));
311                 // Keep embedded graphics.
312                 out.write('>');
313             }
314         }
315     }
316
317     private void declarePrefixes(Attributes atts) {
318         HashMap<String, String> uniquePrefixes = new HashMap<String, String>();
319
320         int count = inScopePrefixes.size();
321         for (int i = 0; i < count; ++i) {
322             String[] pair = inScopePrefixes.get(i);
323
324             uniquePrefixes.put(pair[0], pair[1]);
325         }
326
327         Iterator<Map.Entry<String, String>> iter =
328             uniquePrefixes.entrySet().iterator();
329         while (iter.hasNext()) {
330             Map.Entry<String, String> e = iter.next();
331
332             String prefix = e.getKey();
333             String namespace = e.getValue();
334
335             String xmlns;
336             if (prefix.length() == 0) {
337                 xmlns = "xmlns";
338             } else {
339                 xmlns = "xmlns:" + prefix;
340             }
341
342             if (atts.getValue(xmlns) == null) { // If not already declared.
343                 out.write(' ');
344                 out.write(xmlns);
345                 out.write("='");
346                 Helpers.escapeXML(namespace, out);
347                 out.write('\'');
348             }
349         }
350     }
351
352     public void endElement(String uri, String localName, String qName)
353     throws SAXException {
354         if ("latex".equals(localName)) {
355             latexElem.generate(locator);
356             latexElem = null;
357             return;
358         }
359
360         if ("imagedata".equals(localName) && mainOut != null) {
361             String rootName = "graphics-" + graphicsCounter;
362             String baseName = rootName + ".tmp";
363             File extractedFile = new File(outDir, baseName);
364
365             out.flush();
366             if (out.checkError()) {
367                 reportError("Error writing '" + extractedFile + "'");
368                 out.close();
369                 extractedFile = null;
370             } else {
371                 out.close(); // Close now otherwise rename fails on Windows
372
373                 File extractedFile2 = new File(
374                     outDir,
375                     rootName + ((embeddedGraphicsIsMathML == 1) ? ".mml" : ".svg"));
376                 if (!extractedFile.renameTo(extractedFile2)) {
377                     reportError("Cannot rename '" + extractedFile + "' to '"
378                                 + extractedFile2 + "'");
379                     extractedFile = null;
380                 } else {
381                     extractedFile = extractedFile2;
382                 }
383             }
384
385             out = mainOut;
386             mainOut = null;
387
388             if (extractedFile != null) {
389                 reportInfo("Extracted embedded graphics from '" + extractedFile + "' to '" + extractedFile + "'.");
390
391                 String converted = copyConvertGraphics(extractedFile);
392                 if (converted != null) {
393                     baseName = converted;
394
395                     if (!extractedFile.delete()) {
396                         reportError("Cannot delete '" + extractedFile + "'");
397                     }
398                 }
399             }
400
401             out.write(" fileref='");
402             Helpers.escapeXML(baseName, out);
403             out.write("'/>");
404         } else {
405             out.write("</");
406             out.write(qName);
407             out.write('>');
408         }
409     }
410
411     public void characters(char[] ch, int start, int length)
412     throws SAXException {
413         if (latexElem != null) {
414             latexElem.setLaTeX(new String(ch, start, length));
415             return;
416         }
417
418         Helpers.escapeXML(ch, start, length, out);
419     }
420
421     public void processingInstruction(String target, String data)
422     throws SAXException {
423         // The DocBook XSL style sheets make use of processing-instructions.
424         out.write("<?");
425         out.write(target);
426         out.write(' ');
427         out.write(data);
428         out.write("?>");
429     }
430
431     public void ignorableWhitespace(char[] ch, int start, int length)
432     throws SAXException { }
433
434     public void skippedEntity(String name)
435     throws SAXException { }
436
437     // -----------------------------------------------------------------------
438     // Copy & convert graphics file
439     // -----------------------------------------------------------------------
440
441     private String copyConvertGraphics(File graphicsFile) {
442         if (!graphicsFile.isFile()) {
443             reportError("'" + graphicsFile + "' not found");
444             return null;
445         }
446
447         String baseName = graphicsFile.getName();
448         String rootName = Helpers.setFileExtension(baseName, null);
449         String ext = Helpers.getFileExtension(baseName);
450
451         if ("tex".equalsIgnoreCase(ext)) {
452             if ("ps".equalsIgnoreCase(printFormat)) {
453                 baseName = rootName + "_tex.eps";
454             } else if ("pdf".equalsIgnoreCase(printFormat)) {
455                 baseName = rootName + "_tex.pdf";
456             } else {
457                 baseName = rootName + "_tex.png";
458             }
459             File convertedFile = new File(outDir, baseName);
460
461             if (!convertedFile.exists() || convertedFile.lastModified() < graphicsFile.lastModified()) {
462                 reportInfo("Converting TeX '" + graphicsFile + "' to '"
463                            + convertedFile + "'...");
464
465                 if (!convertTeX(graphicsFile, convertedFile)) {
466                     baseName = null;
467                 }
468             }
469         } else if ("mml".equalsIgnoreCase(ext)) {
470             baseName = rootName + "_mml.png";
471             File convertedFile = new File(outDir, baseName);
472
473             if (!convertedFile.exists() || convertedFile.lastModified() < graphicsFile.lastModified()) {
474                 reportInfo("Converting MathML '" + graphicsFile + "' to '"
475                            + convertedFile + "'...");
476
477                 if (!convertMathML(graphicsFile, convertedFile)) {
478                     baseName = null;
479                 }
480             }
481         } else if ("svg".equalsIgnoreCase(ext) || "svgz".equalsIgnoreCase(ext)) {
482             baseName = rootName + "_svg.png";
483             File convertedFile = new File(outDir, baseName);
484
485             if (!convertedFile.exists() || convertedFile.lastModified() < graphicsFile.lastModified()) {
486                 reportInfo("Converting SVG '" + graphicsFile + "' to '"
487                            + convertedFile + "'...");
488
489                 if (!convertSVG("svgz".equalsIgnoreCase(ext), graphicsFile, convertedFile)) {
490                     baseName = null;
491                 }
492             }
493         } else {
494             // Just copy the file ---
495
496             File outFile = new File(outDir, baseName);
497             if (!outFile.exists() || outFile.lastModified() < graphicsFile.lastModified()) {
498                 reportInfo("Copying '" + graphicsFile + "' to '"
499                            + outFile + "'...");
500
501                 try {
502                     Helpers.copyFile(graphicsFile, outFile);
503                 } catch (IOException e) {
504                     reportError("Cannot copy '" + graphicsFile + "' to '"
505                                 + outFile + "': " + Helpers.reason(e));
506                 }
507             }
508         }
509
510         return baseName;
511     }
512
513     private boolean convertTeX(File inFile, File outFile) {
514         if (!initTeX()) {
515             return false;
516         }
517
518         try {
519             doConvertTeX(inFile, outFile);
520             return true;
521         } catch (Exception e) {
522             reportError("Cannot convert '" + inFile + "' to '"
523                         + outFile + "': " + Helpers.reason(e));
524             return false;
525         }
526     }
527
528     private boolean initTeX() {
529         if (initTeX == -1) {
530             latex = Helpers.findInPath("latex");
531             if (latex == null) {
532                 reportError("Don't find executable 'latex' in PATH");
533             }
534
535             dvips = Helpers.findInPath("dvips");
536             if (dvips == null) {
537                 reportError("Don't find executable 'dvips' in PATH");
538             }
539
540             String appName = Helpers.IS_WINDOWS ? "gswin32c" : "gs";
541             gs = Helpers.findInPath(appName);
542             if (gs == null) {
543                 reportError("Don't find executable '" + appName + "' in PATH");
544             }
545
546             ps2pdf = Helpers.findInPath("ps2pdf");
547             if (ps2pdf == null) {
548                 reportError("Don't find executable 'ps2pdf' in PATH");
549             }
550
551             if (latex == null || dvips == null || gs == null || ps2pdf == null) {
552                 initTeX = 0;
553             } else {
554                 initTeX = 1;
555             }
556         }
557         return (initTeX == 1);
558     }
559
560     private void doConvertTeX(File inFile, File outFile)
561     throws IOException, InterruptedException {
562         File latexFile = wrapTeX(inFile);
563
564         File tmpDir = latexFile.getParentFile();
565
566         String latexBaseName = latexFile.getName();
567         String latexRootName = Helpers.setFileExtension(latexBaseName, null);
568
569         File auxFile = new File(tmpDir, latexRootName + ".aux");
570         File logFile = new File(tmpDir, latexRootName + ".log");
571         File dviFile = new File(tmpDir, latexRootName + ".dvi");
572         File epsFile = new File(tmpDir, latexRootName + ".eps");
573
574         try {
575             StringBuilder cmd = new StringBuilder();
576             cmd.append('\"');
577             cmd.append(latex);
578             cmd.append("\" --interaction batchmode ");
579             cmd.append(latexBaseName);
580
581             shellExec(cmd.toString(), tmpDir);
582
583             if (!dviFile.isFile()) {
584                 throw new IOException("'" + inFile + "' has TeX errors");
585             }
586
587             cmd = new StringBuilder();
588             cmd.append('\"');
589             cmd.append(dvips);
590             cmd.append("\" -E -o \"");
591             cmd.append(epsFile);
592             cmd.append("\" \"");
593             cmd.append(dviFile);
594             cmd.append("\"");
595
596             shellExec(cmd.toString(), tmpDir);
597
598             if ("ps".equalsIgnoreCase(printFormat)) {
599                 Helpers.copyFile(epsFile, outFile);
600             } else if ("pdf".equalsIgnoreCase(printFormat)) {
601                 cmd = new StringBuilder();
602                 cmd.append('\"');
603                 cmd.append(ps2pdf);
604                 cmd.append("\" -dEPSCrop \"");
605                 cmd.append(epsFile);
606                 cmd.append("\" \"");
607                 cmd.append(outFile);
608                 cmd.append("\"");
609
610                 shellExec(cmd.toString(), tmpDir);
611             } else {
612                 cmd = new StringBuilder();
613                 cmd.append('\"');
614                 cmd.append(gs);
615                 cmd.append("\" -q -dBATCH -dNOPAUSE -sDEVICE=png16m");
616                 cmd.append(" -r96 -dTextAlphaBits=4 -dGraphicsAlphaBits=4");
617                 cmd.append(" -dEPSCrop \"-sOutputFile=");
618                 cmd.append(outFile);
619                 cmd.append("\" \"");
620                 cmd.append(epsFile);
621                 cmd.append("\"");
622
623                 shellExec(cmd.toString(), tmpDir);
624             }
625         } finally {
626             // Delete all temporary files ---
627             latexFile.delete();
628             if (auxFile.isFile()) {
629                 auxFile.delete();
630             }
631             if (logFile.isFile()) {
632                 logFile.delete();
633             }
634             if (dviFile.isFile()) {
635                 dviFile.delete();
636             }
637             if (epsFile.isFile()) {
638                 epsFile.delete();
639             }
640         }
641     }
642
643     private File wrapTeX(File inFile)
644     throws IOException {
645         String tex = Helpers.loadString(inFile, "ISO-8859-1");
646
647         File latexFile = File.createTempFile("CopyConvert", ".tex", TMPDIR);
648
649         StringBuilder buffer = new StringBuilder();
650         buffer.append("\\documentclass[12pt]{article}\n");
651         buffer.append("\\usepackage[latin1]{inputenc}\n");
652         buffer.append("\\pagestyle{empty}\n");
653         buffer.append("\\begin{document}\n");
654         buffer.append(tex);
655         buffer.append('\n');
656         buffer.append("\\end{document}\n");
657
658         Helpers.saveString(buffer.toString(), latexFile, "ISO-8859-1");
659
660         return latexFile;
661     }
662
663     private void shellExec(String command, File workDir)
664     throws IOException, InterruptedException {
665         int status =
666             Helpers.shellExec(command, /*envp*/ null, workDir, verbose);
667         if (status != 0) {
668             throw new RuntimeException("command '" + command
669                                        + "' has exited with non-zero status "
670                                        + status);
671         }
672     }
673
674     private boolean convertMathML(File inFile, File outFile) {
675         MutableLayoutContext context = new LayoutContextImpl(LayoutContextImpl
676                 .getDefaultLayoutContext());
677         context.setParameter(Parameter.ANTIALIAS, "true");
678         // Workaround a XEP problem. FOP 1 is OK.
679         context.setParameter(Parameter.MATHBACKGROUND, "#FFFFFF");
680         context.setParameter(Parameter.MATHSIZE, "18");
681
682         try {
683             Converter.getInstance().convert(inFile, outFile, "image/png", context);
684
685             return true;
686         } catch (IOException e) {
687             reportError("Cannot convert '" + inFile + "' to '" + outFile + "': " + Helpers.reason(e));
688             return false;
689         }
690     }
691
692     private boolean convertSVG(boolean gunzip, File inFile, File outFile) {
693         Transcoder transcoder = new PNGTranscoder();
694         transcoder.addTranscodingHint(
695             ImageTranscoder.KEY_FORCE_TRANSPARENT_WHITE,
696             Boolean.TRUE);
697
698         try {
699             InputStream in = new FileInputStream(inFile);
700             if (gunzip) {
701                 in = new GZIPInputStream(in);
702             }
703
704             try {
705                 TranscoderInput input = new TranscoderInput(in);
706                 input.setURI(inFile.toURI().toASCIIString());
707
708                 // Failing to do so causes Batik to report this error: SAX2
709                 // driver class org.apache.xerces.parsers.SAXParser not found
710                 // which is normal because we don't want to bundle a copy of
711                 // Xerces.
712
713                 transcoder.addTranscodingHint(
714                     XMLAbstractTranscoder.KEY_XML_PARSER_CLASSNAME,
715                     parser.getXMLReader().getClass().getName());
716                 transcoder.addTranscodingHint(
717                     XMLAbstractTranscoder.KEY_XML_PARSER_VALIDATING,
718                     Boolean.FALSE);
719
720                 OutputStream outf = new FileOutputStream(outFile);
721
722                 try {
723                     TranscoderOutput output = new TranscoderOutput(outf);
724                     transcoder.transcode(input, output);
725                     outf.flush();
726                 } finally {
727                     outf.close();
728                 }
729             } finally {
730                 in.close();
731             }
732             return true;
733         } catch (Exception e) {
734             reportError("Cannot convert '" + inFile + "' to '"
735                         + outFile + "': " + Helpers.reason(e));
736             return false;
737         }
738     }
739
740     // -----------------------------------
741     // Batik's ErrorHandler
742     // -----------------------------------
743
744     public void warning(TranscoderException e)
745     throws TranscoderException {
746         String msg = "SVG transcoder warning: " + Helpers.reason(e);
747         reportWarning(msg);
748     }
749
750     public void error(TranscoderException e)
751     throws TranscoderException {
752         String msg = "SVG transcoder error: " + Helpers.reason(e);
753         reportError(msg);
754     }
755
756     public void fatalError(TranscoderException e)
757     throws TranscoderException {
758         String msg = "SVG transcoder fatal error: " + Helpers.reason(e);
759         reportError(msg);
760     }
761
762     // -----------------------------------------------------------------------
763     // Utilities
764     // -----------------------------------------------------------------------
765
766     private void reportInfo(String message) {
767         if (verbose) {
768             System.out.println(message);
769         }
770     }
771
772     private static void reportWarning(String message) {
773         System.err.println("warning: " + message);
774     }
775
776     private static void reportError(String message) {
777         System.err.println("*** error: " + message);
778     }
779
780     // -----------------------------------------------------------------------
781     // Main
782     // -----------------------------------------------------------------------
783
784     public static int main(String[] args) {
785         boolean verbose = false;
786         String printFormat = null;
787         boolean usage = false;
788         int l = 0;
789
790         for (; l < args.length; ++l) {
791             String arg = args[l];
792
793             if ("-v".equalsIgnoreCase(arg)) {
794                 verbose = true;
795             } else if ("-ps".equalsIgnoreCase(arg)) {
796                 printFormat = "ps";
797             } else if ("-pdf".equalsIgnoreCase(arg)) {
798                 printFormat = "pdf";
799             } else {
800                 if (arg.startsWith("-")) {
801                     usage = true;
802                 }
803                 break;
804             }
805         }
806
807         if (!usage && l + 2 != args.length) {
808             usage = true;
809         }
810
811         if (usage) {
812             System.err.println(
813                 "Usage: java org.scilab.modules.helptools.CopyConvert"
814                 + "  {-ps|-pdf}? {-v}? in_xml_file out_xml_file\n"
815                 + "Creates out_xml_file as a ``flattened'' copy of\n"
816                 + "in_xml_file.\n"
817                 + "All graphics files referenced by <imagedata fileref='XXX'/>\n"
818                 + "elements are copied, possibly after being converted to PNG,\n"
819                 + "to the directory containing in_xml_file.\n"
820                 + "(This directory is assumed to be a temporary \n"
821                 + "working directory.)\n"
822                 + "Options are:\n"
823                 + "  -ps  Target format is PostScript rather than HTML.\n"
824                 + "  -pdf Target format is PDF rather than HTML.\n"
825                 + "  -v   Verbose.");
826             return 1;
827         }
828
829         File inFile = new File(args[l]);
830         File outFile = new File(args[l + 1]);
831
832         CopyConvert copyConvert = new CopyConvert();
833         copyConvert.verbose = verbose;
834         copyConvert.printFormat = printFormat;
835
836         try {
837             copyConvert.run(inFile, outFile);
838         } catch (Exception e) {
839             reportError("Cannot copy/convert '" + inFile + "' to '" + outFile
840                         + "': " + Helpers.reason(e));
841             return 2;
842         }
843         return 0;
844     }
845
846     protected class LaTeXElement {
847         int size = 18;
848         Color fg = null, bg = null;
849         int disp = TeXConstants.STYLE_DISPLAY;
850         String code = "mediaobject><imageobject><imagedata";
851         String LaTeX = "";
852         boolean exported;
853         String attribs, align = "";
854
855         protected LaTeXElement(Attributes attrs, boolean exported) {
856             this.exported = exported;
857             int n = attrs.getLength();
858             String fgS = "", bgS = "";
859             String dispS = "display";
860
861             for (int i = 0; i < n; i++) {
862                 String attr = attrs.getValue(i);
863                 String name = attrs.getLocalName(i);
864                 if ("align".equals(name)) {
865                     code += " align=\'" + attr + "\'";
866                     align = attr;
867                 } else if ("style".equals(name)) {
868                     if ("text".equals(attr)) {
869                         disp = TeXConstants.STYLE_TEXT;
870                     } else if ("script".equals(attr)) {
871                         disp = TeXConstants.STYLE_SCRIPT;
872                     } else if ("script_script".equals(attr)) {
873                         disp = TeXConstants.STYLE_SCRIPT_SCRIPT;
874                     }
875                     dispS = attr;
876                 } else if ("size".equals(name)) {
877                     try {
878                         size = Integer.parseInt(attr);
879                     } catch (NumberFormatException e) {
880                         size = 16;
881                     }
882                 } else if ("fg".equals(name)) {
883                     fg = Color.decode(attr);
884                     fgS = attr;
885                 } else if ("bg".equals(name)) {
886                     bg = Color.decode(attr);
887                     bgS = attr;
888                 }
889             }
890
891             attribs = " align='" + align + "'";
892             attribs += " size='" + size + "'";
893             attribs += " style='" + dispS + "'";
894             if (bg != null) {
895                 attribs += " bg='" + bgS + "'";
896             }
897             if (fg != null) {
898                 attribs += " fg='" + fgS + "'";
899             }
900         }
901
902         protected void setLaTeX(String str) {
903             LaTeX += str;
904         }
905
906         protected void generate(Locator loc) throws SAXParseException {
907             if (exported) {
908                 generatePNG(loc);
909                 return;
910             }
911             out.write("latex" + attribs  + " xmlns=\"http://forge.scilab.org/p/jlatexmath\"><![CDATA[" + LaTeX + "]]></latex>");
912         }
913
914         protected void generatePNG(Locator loc) throws SAXParseException {
915             TeXFormula tf;
916             try {
917                 tf = new TeXFormula(LaTeX);
918             } catch (ParseException e) {
919                 throw new SAXParseException(
920                     "\nThere was a problem in parsing the LaTeX' formula : \n"
921                     + e.getMessage(), locator);
922             }
923             File f = new File(outDir, "graphics-" + (++graphicsCounter)
924                               + "_latex.png");
925             reportInfo("Converting LaTeX formula to " + f + "'...");
926             tf.createPNG(disp, size, f.getPath(), bg, fg);
927
928             String end = "";
929             if (align.length() == 0) {
930                 code = "inline" + code;
931                 end = "inline";
932             }
933
934             out.write(code + " fileref='graphics-" + graphicsCounter
935                       + "_latex.png'/>" + "</imageobject></" + end
936                       + "mediaobject>");
937         }
938     }
939
940 }