]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/FlagNode.java
Fixed all line endings of the repository
[simantics/platform.git] / bundles / org.simantics.scenegraph / src / org / simantics / scenegraph / g2d / nodes / FlagNode.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management
3  * in Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.scenegraph.g2d.nodes;
13
14 import java.awt.BasicStroke;
15 import java.awt.Color;
16 import java.awt.Font;
17 import java.awt.FontMetrics;
18 import java.awt.Graphics2D;
19 import java.awt.RenderingHints;
20 import java.awt.Shape;
21 import java.awt.Stroke;
22 import java.awt.font.FontRenderContext;
23 import java.awt.font.TextLayout;
24 import java.awt.geom.AffineTransform;
25 import java.awt.geom.Point2D;
26 import java.awt.geom.Rectangle2D;
27 import java.util.Arrays;
28
29 import org.simantics.scenegraph.g2d.G2DNode;
30 import org.simantics.scenegraph.g2d.G2DPDFRenderingHints;
31 import org.simantics.scenegraph.utils.GeometryUtils;
32
33 public class FlagNode extends G2DNode {
34
35     private static final long          serialVersionUID = -1716729504104107151L;
36
37     private static final AffineTransform IDENTITY         = new AffineTransform();
38
39     private static final byte            LEADING          = 0;
40     private static final byte            TRAILING         = 1;
41     private static final byte            CENTER           = 2;
42
43     private static final boolean         DEBUG            = false;
44
45     private static final double          GLOBAL_SCALE     = 0.1;
46
47     private static final double          TEXT_MARGIN      = 5;
48
49     static transient final BasicStroke   STROKE           = new BasicStroke(0.25f, BasicStroke.CAP_BUTT,
50                                                                   BasicStroke.JOIN_MITER);
51
52     final transient Font                 FONT             = Font.decode("Arial 12");
53
54     protected boolean visible;
55
56     protected Shape flagShape;
57     protected String[] flagText;
58     protected Stroke stroke;
59     protected Color border;
60     protected Color fill;
61     protected Color textColor;
62     protected float width;
63     protected float height;
64     protected double direction; // in radians
65     protected float beakAngle;
66     protected Rectangle2D textArea;
67     protected byte hAlign;
68     protected byte vAlign;
69
70     private transient final Point2D      origin           = new Point2D.Double();
71     private transient final Point2D      xa               = new Point2D.Double();
72     private transient final Point2D      ya               = new Point2D.Double();
73
74     protected transient TextLayout[]     textLayout       = null;
75     protected transient Rectangle2D[]    rects            = null;
76     protected transient float            textHeight       = 0;
77     protected transient float            lastViewScale    = 0;
78
79     @SyncField("visible")
80     public void setVisible(boolean visible) {
81         this.visible = visible;
82     }
83
84     public boolean isVisible() {
85         return visible;
86     }
87
88     @SyncField({"visible", "flagShape", "flagText", "stroke", "border", "fill", "textColor", "width", "height", "direction", "beakAngle", "textSize", "hAlign", "vAlign"})
89     public void init(Shape flagShape, String[] flagText, Stroke stroke, Color border, Color fill, Color textColor, float width, float height, double direction, float beakAngle, Rectangle2D textArea, int hAlign, int vAlign) {
90         this.visible = true;
91         this.flagShape = flagShape;
92         this.flagText = flagText;
93         this.stroke = stroke;
94         this.border = border;
95         this.fill = fill;
96         this.textColor = textColor;
97         this.width = width;
98         this.height = height;
99         this.direction = direction;
100         this.beakAngle = beakAngle;
101         this.textArea = textArea;
102         this.hAlign =  (byte) hAlign;
103         this.vAlign = (byte) vAlign;
104
105         resetCaches();
106     }
107
108     private void resetCaches() {
109         textLayout = null;
110         rects = null;
111     }
112
113     @Override
114     public void render(Graphics2D g) {
115         if (!visible)
116             return;
117
118         if (DEBUG) {
119             System.out.println("FlagNode.render:");
120             System.out.println("\tflagShape:       " + flagShape);
121             System.out.println("\tflagText:     " + Arrays.toString(flagText));
122             System.out.println("\tstroke:       " + stroke);
123             System.out.println("\tborder:       " + border);
124             System.out.println("\tfill:         " + fill);
125             System.out.println("\ttextColor:    " + textColor);
126             System.out.println("\twidth:        " + width);
127             System.out.println("\theight:       " + height);
128             System.out.println("\tdirection:    " + direction);
129             System.out.println("\tbeakAngle:    " + beakAngle);
130             System.out.println("\ttextArea:     " + textArea);
131             System.out.println("\thAlign:       " + hAlign);
132             System.out.println("\tvAlign:       " + vAlign);
133             System.out.println("\tdraw:         " + visible);
134         }
135
136         AffineTransform ot = g.getTransform();
137         g.transform(transform);
138
139         try {
140             Object renderingHint = g.getRenderingHint(RenderingHints.KEY_RENDERING);
141
142             //g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
143
144             // Paint flag shape
145             g.setColor(fill);
146             g.fill(flagShape);
147             g.setStroke(stroke);
148             g.setColor(border);
149             g.draw(flagShape);
150
151             // Speed rendering optimization: don't draw text that is too small to read
152             if (renderingHint != RenderingHints.VALUE_RENDER_QUALITY) {
153                 double viewScale = GeometryUtils.getScale(ot);
154                 viewScale *= GeometryUtils.getScale(transform);
155                 if (viewScale < 4.0)
156                     return;
157             }
158
159             if (flagText == null || flagText.length == 0)
160                 return;
161
162             if (DEBUG) {
163                 g.setColor(Color.RED);
164                 g.draw(textArea);
165             }
166
167             // Paint flag text
168             Font f = FONT;
169             g.setFont(f);
170             g.setColor(textColor);
171
172             AffineTransform orig = g.getTransform();
173
174             double det = orig.getDeterminant();
175             if (DEBUG)
176                 System.out.println("DETERMINANT: " + det);
177
178             if (det < 0) {
179                 // Invert the Y-axis if the symbol is "flipped" either vertically xor horizontally
180                 origin.setLocation(textArea.getMinX(), textArea.getMaxY());
181                 xa.setLocation(textArea.getMaxX(), textArea.getMaxY());
182                 ya.setLocation(textArea.getMinX(), textArea.getMinY());
183             } else {
184                 origin.setLocation(textArea.getMinX(), textArea.getMinY());
185                 xa.setLocation(textArea.getMaxX(), textArea.getMinY());
186                 ya.setLocation(textArea.getMinX(), textArea.getMaxY());
187             }
188
189             orig.transform(origin, origin);
190             orig.transform(xa, xa);
191             orig.transform(ya, ya);
192
193             double xAxisX = xa.getX() - origin.getX();
194             double xAxisY = xa.getY() - origin.getY();
195             double yAxisX = ya.getX() - origin.getX();
196             double yAxisY = ya.getY() - origin.getY();
197
198             boolean needToFlip = xAxisX < 0 || yAxisY < 0;
199             if (DEBUG)
200                 System.out.println("TEXT NEEDS FLIPPING: " + needToFlip);
201
202             byte horizAlign = hAlign;
203
204             if (needToFlip) {
205                 // Okay, the text would be upside-down if rendered directly with these axes.
206                 // Let's flip the origin to the diagonal point and
207                 // invert both x & y axis of the text area to get
208                 // the text the right way around. Also, horizontal alignment
209                 // needs to be switched unless it's centered.
210                 origin.setLocation(origin.getX() + xAxisX + yAxisX, origin.getY() + xAxisY + yAxisY);
211                 xAxisX = -xAxisX;
212                 xAxisY = -xAxisY;
213                 yAxisX = -yAxisX;
214                 yAxisY = -yAxisY;
215
216                 // Must flip horizontal alignment to keep text visually at the same
217                 // end as before.
218                 if (horizAlign == LEADING)
219                     horizAlign = TRAILING;
220                 else if (horizAlign == TRAILING)
221                     horizAlign = LEADING;
222             }
223
224             final double gScale = GLOBAL_SCALE;
225             final double gScaleRecip = 1.0 / gScale;
226             final double scale = GeometryUtils.getMaxScale(orig) * gScale;
227             final double rotation = Math.atan2(xAxisY, xAxisX);
228             g.setTransform(IDENTITY);
229             g.translate(origin.getX(), origin.getY());
230             g.rotate(rotation);
231             g.scale(scale, scale);
232
233             if (DEBUG) {
234                 System.out.println("ORIGIN: " + origin);
235                 System.out.println("X-AXIS: (" + xAxisX + "," + xAxisY + ")");
236                 System.out.println("Y-AXIS: (" + yAxisX + "," + yAxisY + ")");
237                 System.out.println("rotation: " + Math.toDegrees(rotation));
238                 System.out.println("scale: " + scale);
239                 System.out.println("ORIG transform: " + orig);
240                 System.out.println("transform: " + g.getTransform());
241             }
242
243             FontMetrics fm = g.getFontMetrics(f);
244             double fontHeight = fm.getHeight();
245
246             if (textLayout == null || (float) scale != lastViewScale)
247             {
248                 lastViewScale = (float) scale;
249                 FontRenderContext frc = g.getFontRenderContext();
250                 if (textLayout == null)
251                     textLayout = new TextLayout[flagText.length];
252                 if (rects == null)
253                     rects = new Rectangle2D[flagText.length];
254                 textHeight = 0;
255                 for (int i = 0; i < flagText.length; ++i) {
256                     String txt = flagText[i].isEmpty() ? " " : flagText[i]; 
257                     textLayout[i] = new TextLayout(txt, f, frc);
258                     rects[i] = textLayout[i].getBounds();
259
260                     // If the bb height is not overridden with the font height
261                     // text lines will not be drawn in the correct Y location.
262                     rects[i].setRect(rects[i].getX(), rects[i].getY(), rects[i].getWidth(), fontHeight);
263
264                     textHeight += rects[i].getHeight() * gScale;
265                     if (DEBUG)
266                         System.out.println("  bounding rectangle for line " + i + " '" + flagText[i] + "': " + rects[i]);
267                 }
268             }
269
270             double leftoverHeight = textArea.getHeight() - textHeight;
271             if (leftoverHeight < 0)
272                 leftoverHeight = 0;
273
274             if (DEBUG) {
275                 System.out.println("text area height: " + textArea.getHeight());
276                 System.out.println("total text height: " + textHeight);
277                 System.out.println("leftover height: " + leftoverHeight);
278             }
279
280             double lineDist = 0;
281             double startY = 0;
282
283             switch (vAlign) {
284                 case LEADING:
285                     if (DEBUG)
286                         System.out.println("VERTICAL LEADING");
287                     lineDist = leftoverHeight / flagText.length;
288                     startY = fm.getMaxAscent();
289                     break;
290                 case TRAILING:
291                     if (DEBUG)
292                         System.out.println("VERTICAL TRAILING");
293                     lineDist = leftoverHeight / flagText.length;
294                     startY = fm.getMaxAscent() + lineDist * gScaleRecip;
295                     break;
296                 case CENTER:
297                     if (DEBUG)
298                         System.out.println("VERTICAL CENTER");
299                     lineDist = leftoverHeight / (flagText.length + 1);
300                     startY = fm.getMaxAscent() + lineDist * gScaleRecip;
301                     break;
302             }
303
304             if (DEBUG) {
305                 System.out.println("lineDist: " + lineDist);
306                 System.out.println("startY: " + startY);
307             }
308
309             lineDist *= gScaleRecip;
310             double y = startY;
311             double textAreaWidth = textArea.getWidth() * gScaleRecip;
312             boolean isRenderingPdf = g.getRenderingHint(G2DPDFRenderingHints.KEY_PDF_WRITER) != null;
313
314             for (int i = 0; i < flagText.length; ++i) {
315                 //String line = flagText[i];
316                 Rectangle2D rect = rects[i];
317
318                 double x = 0;
319
320                 switch (horizAlign) {
321                     case LEADING:
322                         if (DEBUG)
323                             System.out.println("HORIZ LEADING: " + rect);
324                         x = TEXT_MARGIN;
325                         break;
326                     case TRAILING:
327                         if (DEBUG)
328                             System.out.println("HORIZ TRAILING: " + rect);
329                         x = textAreaWidth - rect.getWidth() - TEXT_MARGIN;;
330                         break;
331                     case CENTER:
332                         if (DEBUG)
333                             System.out.println("HORIZ CENTER: " + rect);
334                         x = textAreaWidth * 0.5 - rect.getWidth()*0.5;
335                         break;
336                 }
337
338                 if (DEBUG)
339                     System.out.println("  X, Y: " + x + ", " + y);
340
341                 if (DEBUG)
342                     System.out.println(" DRAW: '" + flagText[i] + "' with " + g.getTransform());
343
344                 // #6459: render as text in PDF and paths on screen
345                 if (isRenderingPdf)
346                     g.drawString(flagText[i], (float) x, (float) y);
347                 else
348                     textLayout[i].draw(g, (float) x, (float) y);
349
350                 y += lineDist;
351                 y += rect.getHeight();
352             }
353
354         } finally {
355             g.setTransform(ot);
356         }
357     }
358
359     public static double getBeakLength(double height, double beakAngle) {
360         beakAngle = Math.min(180, Math.max(10, beakAngle));
361         return height / (2*Math.tan(Math.toRadians(beakAngle) / 2));
362     }
363
364     @Override
365     public Rectangle2D getBoundsInLocal() {
366         if (flagShape == null)
367             return null;
368         return flagShape.getBounds2D();
369     }
370
371 }