Bug 13351 fixed: xstringb failed with LaTeX code
[scilab.git] / scilab / modules / renderer / src / java / org / scilab / modules / renderer / JoGLView / util / TextObjectSpriteDrawer.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2009-2010 - DIGITEO - Pierre Lando
4  * Copyright (C) 2012 - Scilab Enterprises - Bruno JOFRET
5  *
6  * This file must be used under the terms of the CeCILL.
7  * This source file is licensed as described in the file COPYING, which
8  * you should have received as part of this distribution.  The terms
9  * are also available at
10  * http://www.cecill.info/licences/Licence_CeCILL_V2.1-en.txt
11  */
12
13 package org.scilab.modules.renderer.JoGLView.util;
14
15 import java.awt.Dimension;
16 import java.awt.Font;
17 import java.awt.font.TextLayout;
18 import java.awt.geom.Rectangle2D;
19
20 import javax.swing.Icon;
21
22 import org.scilab.forge.jlatexmath.TeXConstants;
23 import org.scilab.forge.jlatexmath.TeXFormula;
24 import org.scilab.forge.jlatexmath.TeXIcon;
25 import org.scilab.forge.scirenderer.shapes.appearance.Appearance;
26 import org.scilab.forge.scirenderer.shapes.appearance.Color;
27 import org.scilab.forge.scirenderer.texture.TextEntity;
28 import org.scilab.forge.scirenderer.texture.TextureDrawer;
29 import org.scilab.forge.scirenderer.texture.TextureDrawingTools;
30 import org.scilab.modules.console.utils.ScilabSpecialTextUtilities;
31 import org.scilab.modules.graphic_objects.figure.ColorMap;
32 import org.scilab.modules.graphic_objects.textObject.Text;
33 import org.scilab.modules.graphic_objects.textObject.TextObject;
34 import org.scilab.modules.jvm.LoadClassPath;
35 import org.scilab.modules.renderer.utils.textRendering.FontManager;
36
37 /**
38  * A {@see SpriteDrawer} who draw a Scilab {@see Text} object.
39  * @author Pierre Lando
40  */
41 public class TextObjectSpriteDrawer implements TextureDrawer {
42
43     /**
44      * Scilab text margin.
45      */
46     private static final int HMARGIN = 2;
47     private static final int VMARGIN = 2;
48     private static final int SPACEWIDTH = (int) Math.ceil(new TextEntity("_").getSize().getWidth()) - 2;
49
50     private Appearance appearance;
51     private int thickness;
52     private final Object[][] entities;
53     private float alignmentFactor;
54
55     private final int[] lineHeight;
56     private final int[] columnWidth;
57     private final float[] lineAscent;
58
59     private final int width;
60     private final int height;
61
62     private boolean latexSet = false;
63     private boolean mathmlSet = false;
64
65     /**
66      * Default constructor.
67      * @param colorMap the color map to use.
68      * @param textObject the scilab {@see Text} to draw.
69      */
70     public TextObjectSpriteDrawer(final ColorMap colorMap, final TextObject textObject) {
71         String[][] stringArray = computeTextData(textObject);
72         int columnNumber = -1;
73         for (String[] stringLine : stringArray) {
74             columnNumber = Math.max(stringLine.length, columnNumber);
75         }
76         int lineNumber = stringArray.length;
77
78         this.lineHeight = new int[lineNumber];
79         this.lineAscent = new float[lineNumber];
80         this.columnWidth = new int[columnNumber];
81         this.entities = new Object[columnNumber][lineNumber];
82
83         boolean fractionalFont = textObject.getFontFractional();
84         Color textColor = ColorFactory.createColor(colorMap, textObject.getFont().getColor());
85         Font font = computeFont(textObject);
86
87         loadDeps(stringArray);
88         fillEntityMatrix(stringArray, fractionalFont, textColor, font);
89
90         this.width  = sum(columnWidth) + HMARGIN * (columnNumber + 1) + 2 * thickness + SPACEWIDTH * (columnNumber - 1);
91         this.height = sum(lineHeight)  + VMARGIN * (lineNumber + 1) + 2 * thickness;
92     }
93
94     /**
95      * Constructor.
96      * Specifies a scale factor used to scale the text matrix.
97      * @param colorMap the color map to used.
98      * @param textObject the scilab {@see TextObject} to draw.
99      * @param scaleFactor the scale factor to apply.
100      */
101     public TextObjectSpriteDrawer(final ColorMap colorMap, final TextObject textObject, double scaleFactor) {
102         String[][] stringArray = computeTextData(textObject);
103         int columnNumber = -1;
104         for (String[] stringLine : stringArray) {
105             columnNumber = Math.max(stringLine.length, columnNumber);
106         }
107         int lineNumber = stringArray.length;
108
109         this.lineHeight = new int[lineNumber];
110         this.lineAscent = new float[lineNumber];
111         this.columnWidth = new int[columnNumber];
112         this.entities = new Object[columnNumber][lineNumber];
113
114         boolean fractionalFont = textObject.getFontFractional();
115         Color textColor = ColorFactory.createColor(colorMap, textObject.getFont().getColor());
116         Font font = computeFont(textObject, scaleFactor);
117
118         /* Fill the entity matrix */
119         loadDeps(stringArray);
120         fillEntityMatrix(stringArray, fractionalFont, textColor, font);
121
122         this.width  = (int)((double)sum(columnWidth) + scaleFactor * (double)(HMARGIN * (columnNumber + 1)) + 2 * thickness + scaleFactor * (double)(SPACEWIDTH * (columnNumber - 1)));
123         this.height = (int)((double)sum(lineHeight)  + scaleFactor * (double)(VMARGIN * (lineNumber + 1)) + 2 * thickness);
124     }
125
126     /**
127      * Load JLaTeXMath or JEuclid if mandatory
128      * @param stringArray the text to check
129      */
130     protected void loadDeps(String[][] stringArray) {
131     loop: {
132             for (String[] textLine : stringArray) {
133                 for (String text : textLine) {
134                     if (text != null) {
135                         if (!latexSet && isLatex(text)) {
136                             latexSet = true;
137                             LoadClassPath.loadOnUse("graphics_latex_textrendering");
138                         } else if (!mathmlSet && isMathML(text)) {
139                             LoadClassPath.loadOnUse("graphics_mathml_textrendering");
140                         } else if (latexSet && mathmlSet) {
141                             break loop;
142                         }
143                     }
144                 }
145             }
146         }
147     }
148
149     /**
150      * Fills the entity matrix
151      * @param stringArray the matrix of text strings used to fill the entity matrix.
152      * @param fractionalFont specifies whether a fractional font is used or not.
153      * @param textColor the text color.
154      * @param font the font to use.
155      */
156     protected void fillEntityMatrix(String[][] stringArray, boolean fractionalFont, Color textColor, Font font) {
157         int line = 0;
158         for (String[] textLine : stringArray) {
159             int column = 0;
160             for (String text : textLine) {
161                 if (text != null) {
162                     Dimension dimension = null;
163                     Icon icon = null;
164                     float ascent = 0;
165                     if (isLatex(text)) {
166                         try {
167                             TeXFormula formula = new TeXFormula(text.substring(1, text.length() - 1));
168                             formula.setColor(textColor);
169                             icon = formula.createTeXIcon(TeXConstants.STYLE_DISPLAY, font.getSize());
170                             ascent = ((TeXIcon) icon).getIconHeight() - ((TeXIcon) icon).getIconDepth();
171                         } catch (Exception e) { }
172                     } else if (isMathML(text)) {
173                         try {
174                             icon = ScilabSpecialTextUtilities.compileMathMLExpression(text, font.getSize(), textColor);
175                             ScilabSpecialTextUtilities.SpecialIcon si = (ScilabSpecialTextUtilities.SpecialIcon) icon;
176                             ascent = si.getIconHeight() - si.getIconDepth();
177                         } catch (Exception e) { }
178                     }
179
180                     if (icon != null) {
181                         dimension = new Dimension(icon.getIconWidth(), icon.getIconHeight());
182                         entities[column][line] = icon;
183                     } else {
184                         TextEntity textEntity = new TextEntity(text);
185                         textEntity.setTextUseFractionalMetrics(fractionalFont);
186                         textEntity.setTextAntiAliased(true);
187                         textEntity.setTextColor(textColor);
188                         textEntity.setFont(font);
189                         entities[column][line] = textEntity;
190                         dimension = textEntity.getSize();
191                         ascent = textEntity.isValid() ? textEntity.getLayout().getAscent() : 0;
192                     }
193
194                     lineAscent[line] = Math.max(lineAscent[line], ascent);
195
196                     if (dimension != null) {
197                         columnWidth[column] = Math.max(columnWidth[column], dimension.width);
198                         lineHeight[line] = Math.max(lineHeight[line], dimension.height);
199                     }
200                 }
201                 column++;
202             }
203             line++;
204         }
205     }
206
207     /**
208      * Return true if the given string represent a latex entity.
209      * @param string the given string.
210      * @return true if the given string represent a latex entity.
211      */
212     private boolean isLatex(String string) {
213         return (string.length() >= 2) && string.endsWith("$") && string.startsWith("$");
214     }
215
216     /**
217      * Return true if the given string represent a MathML entity.
218      * @param string the given string.
219      * @return true if the given string represent a MathML entity.
220      */
221     private boolean isMathML(String string) {
222         return (string.length() >= 2) && string.endsWith(">") && string.startsWith("<");
223     }
224
225     @Override
226     public void draw(TextureDrawingTools drawingTools) {
227         // Draw background.
228         if (appearance.getFillColor().getAlphaAsFloat() != 0) {
229             drawingTools.clear(appearance.getFillColor());
230         }
231
232         final int currentHMargin = getHMargin();
233         final int currentVMargin = getVMargin();
234         final int currentSpaceWidth = getSpaceWidth();
235
236         // Draw text.
237         int x = currentHMargin + thickness;
238         int column = 0;
239         for (Object[] entitiesLine : entities) {
240             int y = currentVMargin + thickness;
241             int line = 0;
242             for (Object entity : entitiesLine) {
243                 if (entity != null) {
244                     if (entity instanceof TextEntity) {
245                         TextEntity textEntity = (TextEntity) entity;
246                         float ascent = textEntity.isValid() ? textEntity.getLayout().getAscent() : 0.f;
247                         double deltaX = alignmentFactor * (columnWidth[column] - textEntity.getSize().getWidth());
248                         drawingTools.draw(textEntity, (int) (x + deltaX), Math.round(y - ascent + lineAscent[line]));
249                         y += lineHeight[line] + currentVMargin;
250                         line++;
251                     } else if (entity instanceof Icon) {
252                         Icon icon = (Icon) entity;
253                         double deltaX = alignmentFactor * (columnWidth[column] - icon.getIconWidth());
254                         if (latexSet && (icon instanceof TeXIcon)) {
255                             TeXIcon tex = (TeXIcon) icon;
256                             float ascent = tex.getIconHeight() - tex.getIconDepth();
257                             drawingTools.draw(icon, (int) (x + deltaX), Math.round(y - ascent + lineAscent[line]));
258                         } else {
259                             // MathML
260                             ScilabSpecialTextUtilities.SpecialIcon si = (ScilabSpecialTextUtilities.SpecialIcon) icon;
261                             int ascent = si.getIconHeight() - si.getIconDepth();
262                             drawingTools.draw(icon, (int) (x + deltaX), y - ascent + Math.round(lineAscent[line]));
263                         }
264                         y += lineHeight[line] + currentVMargin;
265                         line++;
266                     }
267                 }
268             }
269             x += columnWidth[column] + currentHMargin + currentSpaceWidth;
270             column++;
271         }
272
273         // Draw border lines.
274         if (appearance.getLineWidth() > 0) {
275             float hlw = appearance.getLineWidth() / 2;
276             int x1 = (int) hlw;
277             int y1 = (int) hlw;
278             int x2 = (int) (width - hlw);
279             int y2 = (int) (height - hlw);
280             drawingTools.drawPolyline(new int[] {x1, y1, x2, y1, x2, y2, x1, y2, x1, y1}, appearance);
281         }
282     }
283
284     @Override
285     public OriginPosition getOriginPosition() {
286         return OriginPosition.UPPER_LEFT;
287     }
288
289     @Override
290     public Dimension getTextureSize() {
291         return new Dimension(width, height);
292     }
293
294     /**
295      * Return the sprite width needed by this drawer.
296      * @return the sprite width needed by this drawer.
297      */
298     public int getWidth() {
299         return width;
300     }
301
302     /**
303      * Return the sprite height needed by this drawer.
304      * @return the sprite height needed by this drawer.
305      */
306     public int getHeight() {
307         return height;
308     }
309
310     protected void setAlignmentFactor(float alignmentFactor) {
311         this.alignmentFactor = alignmentFactor;
312     }
313
314     protected void setAppearance(Appearance appearance) {
315         this.appearance = appearance;
316     }
317
318     protected void setThickness(int thickness) {
319         this.thickness = thickness;
320     }
321
322     public int getHMargin() {
323         return HMARGIN;
324     }
325
326     public int getVMargin() {
327         return VMARGIN;
328     }
329
330     public int getSpaceWidth() {
331         return SPACEWIDTH;
332     }
333
334     /**
335      * Compute and return the matrix of text string from the given {@see Text} object.
336      * @param text the given {@see Text} object.
337      * @return the matrix of text string from the given {@see Text} object.
338      */
339     protected String[][] computeTextData(final TextObject text) {
340         String[] textString = text.getTextStrings();
341         Integer[] dimensions = text.getTextArrayDimensions();
342         String[][] texts = new String[dimensions[0]][dimensions[1]];
343         int i = 0;
344         for (int c = 0; c < dimensions[1]; c++) {
345             for (int l = 0; l < dimensions[0]; l++) {
346                 texts[l][c] = textString[i];
347                 i++;
348             }
349         }
350         return texts;
351     }
352
353     /**
354      * Compute and return the {@see Font} adapted to the given scilab text.
355      * @param text the given scilab text.
356      * @return the {@see Font} adapted to the given scilab text.
357      */
358     private Font computeFont(final TextObject text) {
359         return FontManager.getSciFontManager().getFontFromIndex(text.getFontStyle(), text.getFontSize());
360     }
361
362     /**
363      * Computes and returns the {@link Font} adapted to the given Scilab text, taking into account the scale factor.
364      * It takes the size 1 Font to derive a new Font whose size is increased according to the scale factor.
365      * @param text the given {@see Text} object.
366      * @param scaleFactor the scale factor to apply.
367      * @return the {@see Font} adapted to the given Scilab text.
368      */
369     private Font computeFont(final TextObject text, double scaleFactor) {
370         Font font = FontManager.getSciFontManager().getFontFromIndex(text.getFontStyle(), 1.0);
371         return font.deriveFont(font.getSize2D() * (float)scaleFactor);
372     }
373
374     /**
375      * Compute and return the alignment factor corresponding to the given scilab text.
376      * @param text the given scilab text.
377      * @return the alignment factor corresponding to the given scilab text.
378      */
379     protected float computeAlignmentFactor(Text text) {
380         switch (text.getAlignmentAsEnum()) {
381         case LEFT:
382             return 0f;
383         case CENTER:
384             return 1f / 2f;
385         case RIGHT:
386             return 1f;
387         default:
388             return 0f;
389         }
390     }
391
392     /**
393      * Util function.
394      * Return sum of the element of the given array.
395      * @param values the given array.
396      * @return sum of the element of the given array.
397      */
398     private int sum(int[] values) {
399         int sum = 0;
400         for (int value : values) {
401             sum += value;
402         }
403         return sum;
404     }
405 }