Bug 9031 fixed: Misalignment when using xstring with a matrix
[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     /**
63      * Default constructor.
64      * @param colorMap the color map to use.
65      * @param textObject the scilab {@see Text} to draw.
66      */
67     public TextObjectSpriteDrawer(final ColorMap colorMap, final TextObject textObject) {
68         String[][] stringArray = computeTextData(textObject);
69         int columnNumber = -1;
70         for (String[] stringLine : stringArray) {
71             columnNumber = Math.max(stringLine.length, columnNumber);
72         }
73         int lineNumber = stringArray.length;
74
75         this.lineHeight = new int[lineNumber];
76         this.lineAscent = new float[lineNumber];
77         this.columnWidth = new int[columnNumber];
78         this.entities = new Object[columnNumber][lineNumber];
79
80         boolean fractionalFont = textObject.getFontFractional();
81         Color textColor = ColorFactory.createColor(colorMap, textObject.getFont().getColor());
82         Font font = computeFont(textObject);
83
84         fillEntityMatrix(stringArray, fractionalFont, textColor, font);
85
86         this.width  = sum(columnWidth) + HMARGIN * (columnNumber + 1) + 2 * thickness + SPACEWIDTH * (columnNumber - 1);
87         this.height = sum(lineHeight)  + VMARGIN * (lineNumber + 1) + 2 * thickness;
88     }
89
90     /**
91      * Constructor.
92      * Specifies a scale factor used to scale the text matrix.
93      * @param colorMap the color map to used.
94      * @param textObject the scilab {@see TextObject} to draw.
95      * @param scaleFactor the scale factor to apply.
96      */
97     public TextObjectSpriteDrawer(final ColorMap colorMap, final TextObject textObject, double scaleFactor) {
98         String[][] stringArray = computeTextData(textObject);
99         int columnNumber = -1;
100         for (String[] stringLine : stringArray) {
101             columnNumber = Math.max(stringLine.length, columnNumber);
102         }
103         int lineNumber = stringArray.length;
104
105         this.lineHeight = new int[lineNumber];
106         this.lineAscent = new float[lineNumber];
107         this.columnWidth = new int[columnNumber];
108         this.entities = new Object[columnNumber][lineNumber];
109
110         boolean fractionalFont = textObject.getFontFractional();
111         Color textColor = ColorFactory.createColor(colorMap, textObject.getFont().getColor());
112         Font font = computeFont(textObject, scaleFactor);
113
114         /* Fill the entity matrix */
115         fillEntityMatrix(stringArray, fractionalFont, textColor, font);
116
117         this.width  = (int)((double)sum(columnWidth) + scaleFactor * (double)(HMARGIN * (columnNumber + 1)) + 2 * thickness + scaleFactor * (double)(SPACEWIDTH * (columnNumber - 1)));
118         this.height = (int)((double)sum(lineHeight)  + scaleFactor * (double)(VMARGIN * (lineNumber + 1)) + 2 * thickness);
119     }
120
121     /**
122      * Fills the entity matrix
123      * @param stringArray the matrix of text strings used to fill the entity matrix.
124      * @param fractionalFont specifies whether a fractional font is used or not.
125      * @param textColor the text color.
126      * @param font the font to use.
127      */
128     protected void fillEntityMatrix(String[][] stringArray, boolean fractionalFont, Color textColor, Font font) {
129         int line = 0;
130         for (String[] textLine : stringArray) {
131             int column = 0;
132             for (String text : textLine) {
133                 if (text != null) {
134                     Dimension dimension = null;
135                     Icon icon = null;
136                     float ascent = 0;
137                     if (isLatex(text)) {
138                         LoadClassPath.loadOnUse("graphics_latex_textrendering");
139                         try {
140                             TeXFormula formula = new TeXFormula(text.substring(1, text.length() - 1));
141                             formula.setColor(textColor);
142                             icon = formula.createTeXIcon(TeXConstants.STYLE_DISPLAY, font.getSize());
143                             ascent = ((TeXIcon) icon).getIconHeight() - ((TeXIcon) icon).getIconDepth();
144                         } catch (Exception e) { }
145                     } else if (isMathML(text)) {
146                         LoadClassPath.loadOnUse("graphics_mathml_textrendering");
147                         try {
148                             icon = ScilabSpecialTextUtilities.compileMathMLExpression(text, font.getSize(), textColor);
149                             ScilabSpecialTextUtilities.SpecialIcon si = (ScilabSpecialTextUtilities.SpecialIcon) icon;
150                             ascent = si.getIconHeight() - si.getIconDepth();
151                         } catch (Exception e) { }
152                     }
153
154                     if (icon != null) {
155                         dimension = new Dimension(icon.getIconWidth(), icon.getIconHeight());
156                         entities[column][line] = icon;
157                     } else {
158                         TextEntity textEntity = new TextEntity(text);
159                         textEntity.setTextUseFractionalMetrics(fractionalFont);
160                         textEntity.setTextAntiAliased(true);
161                         textEntity.setTextColor(textColor);
162                         textEntity.setFont(font);
163                         entities[column][line] = textEntity;
164                         dimension = textEntity.getSize();
165                         ascent = textEntity.getLayout().getAscent();
166                     }
167
168                     lineAscent[line] = Math.max(lineAscent[line], ascent);
169
170                     if (dimension != null) {
171                         columnWidth[column] = Math.max(columnWidth[column], dimension.width);
172                         lineHeight[line] = Math.max(lineHeight[line], dimension.height);
173                     }
174                 }
175                 column++;
176             }
177             line++;
178         }
179     }
180
181     /**
182      * Return true if the given string represent a latex entity.
183      * @param string the given string.
184      * @return true if the given string represent a latex entity.
185      */
186     private boolean isLatex(String string) {
187         return (string.length() >= 2) && string.endsWith("$") && string.startsWith("$");
188     }
189
190     /**
191      * Return true if the given string represent a MathML entity.
192      * @param string the given string.
193      * @return true if the given string represent a MathML entity.
194      */
195     private boolean isMathML(String string) {
196         return (string.length() >= 2) && string.endsWith(">") && string.startsWith("<");
197     }
198
199     @Override
200     public void draw(TextureDrawingTools drawingTools) {
201         // Draw background.
202         if (appearance.getFillColor().getAlphaAsFloat() != 0) {
203             drawingTools.clear(appearance.getFillColor());
204         }
205
206         final int currentHMargin = getHMargin();
207         final int currentVMargin = getVMargin();
208         final int currentSpaceWidth = getSpaceWidth();
209
210         // Draw text.
211         int x = currentHMargin + thickness;
212         int column = 0;
213         for (Object[] entitiesLine : entities) {
214             int y = currentVMargin + thickness;
215             int line = 0;
216             for (Object entity : entitiesLine) {
217                 if (entity != null) {
218                     if (entity instanceof TextEntity) {
219                         TextEntity textEntity = (TextEntity) entity;
220                         TextLayout layout = textEntity.getLayout();
221                         double deltaX = alignmentFactor * (columnWidth[column] - textEntity.getSize().getWidth());
222                         drawingTools.draw(textEntity, (int) (x + deltaX), Math.round(y - layout.getAscent() + lineAscent[line]));
223                         y += lineHeight[line] + currentVMargin;
224                         line++;
225                     } else if (entity instanceof Icon) {
226                         Icon icon = (Icon) entity;
227                         double deltaX = alignmentFactor * (columnWidth[column] - icon.getIconWidth());
228                         if (icon instanceof TeXIcon) {
229                             TeXIcon tex = (TeXIcon) icon;
230                             float ascent = tex.getIconHeight() - tex.getIconDepth();
231                             drawingTools.draw(icon, (int) (x + deltaX), Math.round(y - ascent + lineAscent[line]));
232                         } else {
233                             // MathML
234                             ScilabSpecialTextUtilities.SpecialIcon si = (ScilabSpecialTextUtilities.SpecialIcon) icon;
235                             int ascent = si.getIconHeight() - si.getIconDepth();
236                             drawingTools.draw(icon, (int) (x + deltaX), y - ascent + Math.round(lineAscent[line]));
237                         }
238                         y += lineHeight[line] + currentVMargin;
239                         line++;
240                     }
241                 }
242             }
243             x += columnWidth[column] + currentHMargin + currentSpaceWidth;
244             column++;
245         }
246
247         // Draw border lines.
248         if (appearance.getLineWidth() > 0) {
249             float hlw = appearance.getLineWidth() / 2;
250             int x1 = (int) hlw;
251             int y1 = (int) hlw;
252             int x2 = (int) (width - hlw);
253             int y2 = (int) (height - hlw);
254             drawingTools.drawPolyline(new int[] {x1, y1, x2, y1, x2, y2, x1, y2, x1, y1}, appearance);
255         }
256     }
257
258     @Override
259     public OriginPosition getOriginPosition() {
260         return OriginPosition.UPPER_LEFT;
261     }
262
263     @Override
264     public Dimension getTextureSize() {
265         return new Dimension(width, height);
266     }
267
268     /**
269      * Return the sprite width needed by this drawer.
270      * @return the sprite width needed by this drawer.
271      */
272     public int getWidth() {
273         return width;
274     }
275
276     /**
277      * Return the sprite height needed by this drawer.
278      * @return the sprite height needed by this drawer.
279      */
280     public int getHeight() {
281         return height;
282     }
283
284     protected void setAlignmentFactor(float alignmentFactor) {
285         this.alignmentFactor = alignmentFactor;
286     }
287
288     protected void setAppearance(Appearance appearance) {
289         this.appearance = appearance;
290     }
291
292     protected void setThickness(int thickness) {
293         this.thickness = thickness;
294     }
295
296     public int getHMargin() {
297         return HMARGIN;
298     }
299
300     public int getVMargin() {
301         return VMARGIN;
302     }
303
304     public int getSpaceWidth() {
305         return SPACEWIDTH;
306     }
307
308     /**
309      * Compute and return the matrix of text string from the given {@see Text} object.
310      * @param text the given {@see Text} object.
311      * @return the matrix of text string from the given {@see Text} object.
312      */
313     protected String[][] computeTextData(final TextObject text) {
314         String[] textString = text.getTextStrings();
315         Integer[] dimensions = text.getTextArrayDimensions();
316         String[][] texts = new String[dimensions[0]][dimensions[1]];
317         int i = 0;
318         for (int c = 0; c < dimensions[1]; c++) {
319             for (int l = 0; l < dimensions[0]; l++) {
320                 texts[l][c] = textString[i];
321                 i++;
322             }
323         }
324         return texts;
325     }
326
327     /**
328      * Compute and return the {@see Font} adapted to the given scilab text.
329      * @param text the given scilab text.
330      * @return the {@see Font} adapted to the given scilab text.
331      */
332     private Font computeFont(final TextObject text) {
333         return FontManager.getSciFontManager().getFontFromIndex(text.getFontStyle(), text.getFontSize());
334     }
335
336     /**
337      * Computes and returns the {@link Font} adapted to the given Scilab text, taking into account the scale factor.
338      * It takes the size 1 Font to derive a new Font whose size is increased according to the scale factor.
339      * @param text the given {@see Text} object.
340      * @param scaleFactor the scale factor to apply.
341      * @return the {@see Font} adapted to the given Scilab text.
342      */
343     private Font computeFont(final TextObject text, double scaleFactor) {
344         Font font = FontManager.getSciFontManager().getFontFromIndex(text.getFontStyle(), 1.0);
345         return font.deriveFont(font.getSize2D() * (float)scaleFactor);
346     }
347
348     /**
349      * Compute and return the alignment factor corresponding to the given scilab text.
350      * @param text the given scilab text.
351      * @return the alignment factor corresponding to the given scilab text.
352      */
353     protected float computeAlignmentFactor(Text text) {
354         switch (text.getAlignmentAsEnum()) {
355             case LEFT:
356                 return 0f;
357             case CENTER:
358                 return 1f / 2f;
359             case RIGHT:
360                 return 1f;
361             default:
362                 return 0f;
363         }
364     }
365
366     /**
367      * Util function.
368      * Return sum of the element of the given array.
369      * @param values the given array.
370      * @return sum of the element of the given array.
371      */
372     private int sum(int[] values) {
373         int sum = 0;
374         for (int value : values) {
375             sum += value;
376         }
377         return sum;
378     }
379 }