Shortcut non latex/mathml text rendering
[scilab.git] / scilab / modules / console / src / java / org / scilab / modules / console / utils / ScilabSpecialTextUtilities.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2010 - Calixte DENIZET
4  *
5  * This file must be used under the terms of the CeCILL.
6  * This source file is licensed as described in the file COPYING, which
7  * you should have received as part of this distribution.  The terms
8  * are also available at
9  * http://www.cecill.info/licences/Licence_CeCILL_V2.1-en.txt
10  *
11  */
12
13 package org.scilab.modules.console.utils;
14
15 import java.awt.Color;
16 import java.awt.Component;
17 import java.awt.Graphics;
18 import java.awt.Graphics2D;
19 import java.awt.image.BufferedImage;
20 import java.io.IOException;
21 import java.lang.reflect.InvocationTargetException;
22 import java.lang.reflect.Method;
23
24 import javax.swing.Icon;
25 import javax.swing.ImageIcon;
26 import javax.swing.JComponent;
27 import javax.xml.parsers.ParserConfigurationException;
28
29 import net.sourceforge.jeuclid.MathMLParserSupport;
30 import net.sourceforge.jeuclid.context.LayoutContextImpl;
31 import net.sourceforge.jeuclid.context.Parameter;
32 import net.sourceforge.jeuclid.layout.JEuclidView;
33
34 import org.scilab.forge.jlatexmath.ParseException;
35 import org.scilab.forge.jlatexmath.TeXConstants;
36 import org.scilab.forge.jlatexmath.TeXFormula;
37 import org.scilab.modules.jvm.LoadClassPath;
38 import org.w3c.dom.Document;
39 import org.w3c.dom.Node;
40 import org.xml.sax.SAXException;
41
42 /**
43  * Utilities with JLaTeXMath.
44  * @author Calixte DENIZET
45  */
46 public final class ScilabSpecialTextUtilities {
47
48     private static boolean loadedLaTeX;
49     private static boolean loadedMathML;
50     private static Thread loadJLM;
51
52     /**
53      * @param component where to set a LaTeX icon
54      * @param text to use for the menu, if it's enclosed between '$' then it's interpreted as
55      * a LaTeX string and if enclosed between '<' and '>', then as MathML one.
56      */
57     public static boolean setText(JComponent component, String text) {
58         Icon icon = null;
59         if (text != null && text.length() > 1) {
60             if (text.startsWith("$") && text.endsWith("$")) {
61                 icon = compileLaTeXExpression(text.substring(1, text.length() - 1), component.getFont().getSize());
62             } else if (text.startsWith("<") && text.endsWith(">")) {
63                 icon = compileMathMLExpression(text, component.getFont().getSize());
64             }
65         }
66         
67         if (icon == null) {
68             // Shortcut when we are sure text is
69             // neither Latex nor MathML
70             return false;
71         }
72         
73         try {
74             setIcon(component, icon);
75         } catch (InvocationTargetException e) {
76             e.printStackTrace();
77         }
78
79         return icon != null;
80     }
81
82     /**
83      * Load, if necessary the jlatexmath package, and compile a LaTeX expression.
84      * @param exp the expression to compile
85      * @param fontSize the size of the font
86      * @return the Icon
87      */
88     public static Icon compileLaTeXExpression(String exp, int fontSize) {
89         if (!loadedLaTeX) {
90             LoadClassPath.loadOnUse("graphics_latex_textrendering");
91             loadedLaTeX = true;
92         }
93
94         return LaTeXCompiler.compile(exp, fontSize);
95     }
96
97     /**
98      * Load, if necessary the jlatexmath package, and compile a LaTeX expression or a valid subexpression.
99      * @param exp the expression to compile
100      * @param fontSize the size of the font
101      * @return the Icon
102      */
103     public static Icon compilePartialLaTeXExpression(String exp, int fontSize) {
104         if (!loadedLaTeX) {
105             if (loadJLM == null) {
106                 loadJLM = new Thread(new Runnable() {
107                     /* Create a thread in the background to avoid a lag in the loading of jar */
108                     public void run() {
109                         LoadClassPath.loadOnUse("graphics_latex_textrendering");
110                         LaTeXCompiler.compilePartial("", 0);
111                         loadedLaTeX = true;
112                         loadJLM = null;
113                     }
114                 });
115                 loadJLM.setPriority(Thread.MIN_PRIORITY);
116                 loadJLM.start();
117             }
118             return null;
119         } else {
120             return LaTeXCompiler.compilePartial(exp, fontSize);
121         }
122     }
123
124     /**
125      * Load, if necessary the jeuclid package, and compile a LaTeX expression.
126      * @param exp the expression to compile
127      * @param fontSize the size of the font
128      * @return the Icon
129      */
130     public static Icon compileMathMLExpression(String exp, int fontSize) {
131         return compileMathMLExpression(exp, fontSize, Color.BLACK);
132     }
133
134     /**
135      * Load, if necessary the jeuclid package, and compile a LaTeX expression.
136      * @param exp the expression to compile
137      * @param fontSize the size of the font
138      * @param fontColor the color of the font
139      * @return the Icon
140      */
141     public static Icon compileMathMLExpression(String exp, int fontSize, Color fontColor) {
142         if (!loadedMathML) {
143             LoadClassPath.loadOnUse("graphics_mathml_textrendering");
144             loadedMathML = true;
145         }
146
147         return MathMLCompiler.compile(exp, fontSize, fontColor);
148     }
149
150     /**
151      * @param component where to set a LaTeX icon
152      * @param icon to set
153      */
154     private static void setIcon(JComponent component, Icon icon) throws InvocationTargetException {
155         try {
156             Class clazz = component.getClass();
157             Method method = clazz.getMethod("getIcon", new Class[] {});
158             Object obj = method.invoke(component, new Object[] {});
159             if (icon != null || (obj != null && (obj instanceof SpecialIcon))) {
160                 method = clazz.getMethod("setIcon", new Class[] {Icon.class});
161                 method.invoke(component, new Object[] {icon});
162             }
163         } catch (NoSuchMethodException e) {
164             throw new InvocationTargetException(e, "No valid method setIcon");
165         } catch (IllegalAccessException e) {
166             throw new InvocationTargetException(e, "The method setIcon must be public");
167         } catch (InvocationTargetException e) {
168             throw new InvocationTargetException(e, "The method setIcon threw an exception");
169         }
170     }
171
172     /**
173      * Use an inner class rather than a single method to avoid the load of the jar of jlatex
174      */
175     private static final class LaTeXCompiler {
176
177         /**
178          * Compile the expression
179          * @param str the expression to compile
180          * @param fontSize the size of the font
181          * @return the Icon
182          */
183         static Icon compile(String str, int fontSize) {
184             TeXFormula.setDefaultDPI();
185             Icon icon = null;
186             try {
187                 TeXFormula formula = new TeXFormula(str);
188                 icon = formula.createTeXIcon(TeXConstants.STYLE_DISPLAY, fontSize);
189             } catch (ParseException e) { }
190
191             return new SpecialIcon(icon);
192         }
193
194         /**
195          * Compile the expression
196          * @param str the expression to compile
197          * @param fontSize the size of the font
198          * @return the Icon
199          */
200         static Icon compilePartial(String str, int fontSize) {
201             Icon icon = null;
202             try {
203                 TeXFormula formula = TeXFormula.getPartialTeXFormula(str);
204                 icon = formula.createTeXIcon(TeXConstants.STYLE_DISPLAY, fontSize);
205             } catch (ParseException e) { }
206
207             return icon;
208         }
209     }
210
211     /**
212      * Use an inner class rather than a single method to avoid the load of the jar of jeuclid
213      */
214     private static final class MathMLCompiler {
215
216         private static final Graphics2D TEMPGRAPHIC = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB).createGraphics();
217
218         /**
219          * Compile the expression
220          * @param str the expression to compile
221          * @param fontSize the size of the font
222          * @return the Icon
223          */
224         static Icon compile(String str, int fontSize, Color fontColor) {
225             LayoutContextImpl parameters = new LayoutContextImpl(LayoutContextImpl.getDefaultLayoutContext());
226             parameters.setParameter(Parameter.MATHSIZE, fontSize);
227             parameters.setParameter(Parameter.MATHCOLOR, fontColor);
228             if (!str.startsWith("<mathml>")) {
229                 str = "<mathml>" + str + "</mathml>";
230             }
231
232             Document doc = null;
233             try {
234                 doc = MathMLParserSupport.parseString(str);
235             } catch (final SAXException e) {
236                 return null;
237             } catch (final ParserConfigurationException e) {
238                 return null;
239             } catch (final IOException e) {
240                 return null;
241             }
242
243             JEuclidView jev = new JEuclidView((Node) doc, parameters, TEMPGRAPHIC);
244
245             int width = (int) Math.ceil(jev.getWidth());
246             int ascent = (int) Math.ceil(jev.getAscentHeight());
247             int height = (int) Math.ceil(jev.getDescentHeight()) + ascent;
248
249             if (width <= 0 || height <= 0) {
250                 return null;
251             }
252
253             BufferedImage bimg = new BufferedImage(width + 2, height, BufferedImage.TYPE_INT_ARGB);
254             Graphics2D g2d = bimg.createGraphics();
255             g2d.setColor(new Color(255, 255, 255, 0));
256             g2d.fillRect(0, 0, width, height);
257
258             jev.draw(g2d, 0, ascent);
259             g2d.dispose();
260
261             return new SpecialIcon(new ImageIcon(bimg));
262         }
263     }
264
265     /**
266      * Inner class to distinguish normal icons and icons coming from a LaTeX or a MathML compilation
267      */
268     private static class SpecialIcon implements Icon {
269
270         Icon icon;
271
272         /**
273          * @param icon the Icon to wrap
274          */
275         SpecialIcon(Icon icon) {
276             this.icon = icon;
277         }
278
279         /**
280          * {@inheritedDoc}
281          */
282         public int getIconHeight() {
283             return icon.getIconHeight();
284         }
285
286         /**
287          * {@inheritedDoc}
288          */
289         public int getIconWidth() {
290             return icon.getIconWidth();
291         }
292
293         /**
294          * {@inheritedDoc}
295          */
296         public void paintIcon(Component c, Graphics g, int x, int y) {
297             icon.paintIcon(c, g, x, y);
298         }
299     }
300 }