1 /*******************************************************************************
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
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
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.scenegraph.g2d.nodes;
14 import java.awt.BasicStroke;
15 import java.awt.Color;
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;
29 import org.simantics.scenegraph.g2d.G2DNode;
30 import org.simantics.scenegraph.g2d.G2DPDFRenderingHints;
31 import org.simantics.scenegraph.g2d.G2DPDFRenderingHints.TextRenderingMode;
32 import org.simantics.scenegraph.utils.GeometryUtils;
34 import com.lowagie.text.pdf.PdfWriter;
36 public class FlagNode extends G2DNode {
38 private static final long serialVersionUID = -1716729504104107151L;
40 private static final AffineTransform IDENTITY = new AffineTransform();
42 private static final byte LEADING = 0;
43 private static final byte TRAILING = 1;
44 private static final byte CENTER = 2;
46 private static final boolean DEBUG = false;
48 private static final double GLOBAL_SCALE = 0.1;
50 private static final double TEXT_MARGIN = 5;
52 static transient final BasicStroke STROKE = new BasicStroke(0.25f, BasicStroke.CAP_BUTT,
53 BasicStroke.JOIN_MITER);
55 public final static Font DEFAULT_FONT = Font.decode("Arial 12");
57 protected boolean visible;
59 protected Shape flagShape;
60 protected String[] flagText;
61 protected Stroke stroke;
62 protected Color border;
64 protected Color textColor;
65 protected float width;
66 protected float height;
67 protected double direction; // in radians
68 protected float beakAngle;
69 protected Rectangle2D textArea;
70 protected byte hAlign;
71 protected byte vAlign;
72 protected Font font = DEFAULT_FONT;
74 private transient final Point2D origin = new Point2D.Double();
75 private transient final Point2D xa = new Point2D.Double();
76 private transient final Point2D ya = new Point2D.Double();
78 protected transient TextLayout[] textLayout = null;
79 protected transient Rectangle2D[] rects = null;
80 protected transient float textHeight = 0;
81 protected transient float lastViewScale = 0;
84 public void setVisible(boolean visible) {
85 this.visible = visible;
88 public boolean isVisible() {
92 @SyncField({"visible", "flagShape", "flagText", "stroke", "border", "fill", "textColor", "width", "height", "direction", "beakAngle", "textSize", "hAlign", "vAlign", "font"})
93 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, Font font) {
95 this.flagShape = flagShape;
96 this.flagText = flagText;
100 this.textColor = textColor;
102 this.height = height;
103 this.direction = direction;
104 this.beakAngle = beakAngle;
105 this.textArea = textArea;
106 this.hAlign = (byte) hAlign;
107 this.vAlign = (byte) vAlign;
113 private void resetCaches() {
119 public void render(Graphics2D g) {
124 System.out.println("FlagNode.render:");
125 System.out.println("\tflagShape: " + flagShape);
126 System.out.println("\tflagText: " + Arrays.toString(flagText));
127 System.out.println("\tstroke: " + stroke);
128 System.out.println("\tborder: " + border);
129 System.out.println("\tfill: " + fill);
130 System.out.println("\ttextColor: " + textColor);
131 System.out.println("\twidth: " + width);
132 System.out.println("\theight: " + height);
133 System.out.println("\tdirection: " + direction);
134 System.out.println("\tbeakAngle: " + beakAngle);
135 System.out.println("\ttextArea: " + textArea);
136 System.out.println("\thAlign: " + hAlign);
137 System.out.println("\tvAlign: " + vAlign);
138 System.out.println("\tdraw: " + visible);
141 AffineTransform ot = g.getTransform();
142 g.transform(transform);
145 Object renderingHint = g.getRenderingHint(RenderingHints.KEY_RENDERING);
147 //g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
156 // Speed rendering optimization: don't draw text that is too small to read
157 if (renderingHint != RenderingHints.VALUE_RENDER_QUALITY) {
158 double viewScale = GeometryUtils.getScale(ot);
159 viewScale *= GeometryUtils.getScale(transform);
164 if (flagText == null || flagText.length == 0)
168 g.setColor(Color.RED);
174 g.setColor(textColor);
176 AffineTransform orig = g.getTransform();
178 double det = orig.getDeterminant();
180 System.out.println("DETERMINANT: " + det);
183 // Invert the Y-axis if the symbol is "flipped" either vertically xor horizontally
184 origin.setLocation(textArea.getMinX(), textArea.getMaxY());
185 xa.setLocation(textArea.getMaxX(), textArea.getMaxY());
186 ya.setLocation(textArea.getMinX(), textArea.getMinY());
188 origin.setLocation(textArea.getMinX(), textArea.getMinY());
189 xa.setLocation(textArea.getMaxX(), textArea.getMinY());
190 ya.setLocation(textArea.getMinX(), textArea.getMaxY());
193 orig.transform(origin, origin);
194 orig.transform(xa, xa);
195 orig.transform(ya, ya);
197 double xAxisX = xa.getX() - origin.getX();
198 double xAxisY = xa.getY() - origin.getY();
199 double yAxisX = ya.getX() - origin.getX();
200 double yAxisY = ya.getY() - origin.getY();
202 boolean needToFlip = xAxisX < 0 || yAxisY < 0;
204 System.out.println("TEXT NEEDS FLIPPING: " + needToFlip);
206 byte horizAlign = hAlign;
209 // Okay, the text would be upside-down if rendered directly with these axes.
210 // Let's flip the origin to the diagonal point and
211 // invert both x & y axis of the text area to get
212 // the text the right way around. Also, horizontal alignment
213 // needs to be switched unless it's centered.
214 origin.setLocation(origin.getX() + xAxisX + yAxisX, origin.getY() + xAxisY + yAxisY);
220 // Must flip horizontal alignment to keep text visually at the same
222 if (horizAlign == LEADING)
223 horizAlign = TRAILING;
224 else if (horizAlign == TRAILING)
225 horizAlign = LEADING;
228 final double gScale = GLOBAL_SCALE;
229 final double gScaleRecip = 1.0 / gScale;
230 final double scale = GeometryUtils.getMaxScale(orig) * gScale;
231 final double rotation = Math.atan2(xAxisY, xAxisX);
232 g.setTransform(IDENTITY);
233 g.translate(origin.getX(), origin.getY());
235 g.scale(scale, scale);
238 System.out.println("ORIGIN: " + origin);
239 System.out.println("X-AXIS: (" + xAxisX + "," + xAxisY + ")");
240 System.out.println("Y-AXIS: (" + yAxisX + "," + yAxisY + ")");
241 System.out.println("rotation: " + Math.toDegrees(rotation));
242 System.out.println("scale: " + scale);
243 System.out.println("ORIG transform: " + orig);
244 System.out.println("transform: " + g.getTransform());
247 FontMetrics fm = g.getFontMetrics(font);
248 double fontHeight = fm.getHeight();
250 if (textLayout == null || (float) scale != lastViewScale)
252 lastViewScale = (float) scale;
253 FontRenderContext frc = g.getFontRenderContext();
254 if (textLayout == null)
255 textLayout = new TextLayout[flagText.length];
257 rects = new Rectangle2D[flagText.length];
259 for (int i = 0; i < flagText.length; ++i) {
260 String txt = flagText[i].isEmpty() ? " " : flagText[i];
261 textLayout[i] = new TextLayout(txt, font, frc);
262 rects[i] = textLayout[i].getBounds();
264 // If the bb height is not overridden with the font height
265 // text lines will not be drawn in the correct Y location.
266 rects[i].setRect(rects[i].getX(), rects[i].getY(), rects[i].getWidth(), fontHeight);
268 textHeight += rects[i].getHeight() * gScale;
270 System.out.println(" bounding rectangle for line " + i + " '" + flagText[i] + "': " + rects[i]);
274 double leftoverHeight = textArea.getHeight() - textHeight;
275 if (leftoverHeight < 0)
279 System.out.println("text area height: " + textArea.getHeight());
280 System.out.println("total text height: " + textHeight);
281 System.out.println("leftover height: " + leftoverHeight);
290 System.out.println("VERTICAL LEADING");
291 lineDist = leftoverHeight / flagText.length;
292 startY = fm.getMaxAscent();
296 System.out.println("VERTICAL TRAILING");
297 lineDist = leftoverHeight / flagText.length;
298 startY = fm.getMaxAscent() + lineDist * gScaleRecip;
302 System.out.println("VERTICAL CENTER");
303 lineDist = leftoverHeight / (flagText.length + 1);
304 startY = fm.getMaxAscent() + lineDist * gScaleRecip;
309 System.out.println("lineDist: " + lineDist);
310 System.out.println("startY: " + startY);
313 lineDist *= gScaleRecip;
315 double textAreaWidth = textArea.getWidth() * gScaleRecip;
316 boolean renderAsText = g.getRenderingHint(G2DPDFRenderingHints.KEY_PDF_WRITER) != null
317 || g.getRenderingHint(G2DPDFRenderingHints.KEY_TEXT_RENDERING_MODE) == TextRenderingMode.AS_TEXT;
319 for (int i = 0; i < flagText.length; ++i) {
320 //String line = flagText[i];
321 Rectangle2D rect = rects[i];
325 switch (horizAlign) {
328 System.out.println("HORIZ LEADING: " + rect);
333 System.out.println("HORIZ TRAILING: " + rect);
334 x = textAreaWidth - rect.getWidth() - TEXT_MARGIN;;
338 System.out.println("HORIZ CENTER: " + rect);
339 x = textAreaWidth * 0.5 - rect.getWidth()*0.5;
344 System.out.println(" X, Y: " + x + ", " + y);
347 System.out.println(" DRAW: '" + flagText[i] + "' with " + g.getTransform());
349 // #6459: render as text in PDF and paths on screen
351 g.drawString(flagText[i], (float) x, (float) y);
353 textLayout[i].draw(g, (float) x, (float) y);
356 y += rect.getHeight();
364 public static double getBeakLength(double height, double beakAngle) {
365 beakAngle = Math.min(180, Math.max(10, beakAngle));
366 return height / (2*Math.tan(Math.toRadians(beakAngle) / 2));
370 public Rectangle2D getBoundsInLocal() {
371 if (flagShape == null)
373 return flagShape.getBounds2D();