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.utils.GeometryUtils;
33 public class FlagNode extends G2DNode {
35 private static final long serialVersionUID = -1716729504104107151L;
37 private static final AffineTransform IDENTITY = new AffineTransform();
39 private static final byte LEADING = 0;
40 private static final byte TRAILING = 1;
41 private static final byte CENTER = 2;
43 private static final boolean DEBUG = false;
45 private static final double GLOBAL_SCALE = 0.1;
47 private static final double TEXT_MARGIN = 5;
49 static transient final BasicStroke STROKE = new BasicStroke(0.25f, BasicStroke.CAP_BUTT,
50 BasicStroke.JOIN_MITER);
52 final transient Font FONT = Font.decode("Arial 12");
54 protected boolean visible;
56 protected Shape flagShape;
57 protected String[] flagText;
58 protected Stroke stroke;
59 protected Color border;
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;
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();
74 protected transient TextLayout[] textLayout = null;
75 protected transient Rectangle2D[] rects = null;
76 protected transient float textHeight = 0;
77 protected transient float lastViewScale = 0;
80 public void setVisible(boolean visible) {
81 this.visible = visible;
84 public boolean isVisible() {
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) {
91 this.flagShape = flagShape;
92 this.flagText = flagText;
96 this.textColor = textColor;
99 this.direction = direction;
100 this.beakAngle = beakAngle;
101 this.textArea = textArea;
102 this.hAlign = (byte) hAlign;
103 this.vAlign = (byte) vAlign;
108 private void resetCaches() {
114 public void render(Graphics2D g) {
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);
136 AffineTransform ot = g.getTransform();
137 g.transform(transform);
140 Object renderingHint = g.getRenderingHint(RenderingHints.KEY_RENDERING);
142 //g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
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);
159 if (flagText == null || flagText.length == 0)
163 g.setColor(Color.RED);
170 g.setColor(textColor);
172 AffineTransform orig = g.getTransform();
174 double det = orig.getDeterminant();
176 System.out.println("DETERMINANT: " + det);
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());
184 origin.setLocation(textArea.getMinX(), textArea.getMinY());
185 xa.setLocation(textArea.getMaxX(), textArea.getMinY());
186 ya.setLocation(textArea.getMinX(), textArea.getMaxY());
189 orig.transform(origin, origin);
190 orig.transform(xa, xa);
191 orig.transform(ya, ya);
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();
198 boolean needToFlip = xAxisX < 0 || yAxisY < 0;
200 System.out.println("TEXT NEEDS FLIPPING: " + needToFlip);
202 byte horizAlign = hAlign;
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);
216 // Must flip horizontal alignment to keep text visually at the same
218 if (horizAlign == LEADING)
219 horizAlign = TRAILING;
220 else if (horizAlign == TRAILING)
221 horizAlign = LEADING;
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());
231 g.scale(scale, scale);
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());
243 FontMetrics fm = g.getFontMetrics(f);
244 double fontHeight = fm.getHeight();
246 if (textLayout == null || (float) scale != lastViewScale)
248 lastViewScale = (float) scale;
249 FontRenderContext frc = g.getFontRenderContext();
250 if (textLayout == null)
251 textLayout = new TextLayout[flagText.length];
253 rects = new Rectangle2D[flagText.length];
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();
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);
264 textHeight += rects[i].getHeight() * gScale;
266 System.out.println(" bounding rectangle for line " + i + " '" + flagText[i] + "': " + rects[i]);
270 double leftoverHeight = textArea.getHeight() - textHeight;
271 if (leftoverHeight < 0)
275 System.out.println("text area height: " + textArea.getHeight());
276 System.out.println("total text height: " + textHeight);
277 System.out.println("leftover height: " + leftoverHeight);
286 System.out.println("VERTICAL LEADING");
287 lineDist = leftoverHeight / flagText.length;
288 startY = fm.getMaxAscent();
292 System.out.println("VERTICAL TRAILING");
293 lineDist = leftoverHeight / flagText.length;
294 startY = fm.getMaxAscent() + lineDist * gScaleRecip;
298 System.out.println("VERTICAL CENTER");
299 lineDist = leftoverHeight / (flagText.length + 1);
300 startY = fm.getMaxAscent() + lineDist * gScaleRecip;
305 System.out.println("lineDist: " + lineDist);
306 System.out.println("startY: " + startY);
309 lineDist *= gScaleRecip;
311 double textAreaWidth = textArea.getWidth() * gScaleRecip;
312 boolean isRenderingPdf = g.getRenderingHint(G2DPDFRenderingHints.KEY_PDF_WRITER) != null;
314 for (int i = 0; i < flagText.length; ++i) {
315 //String line = flagText[i];
316 Rectangle2D rect = rects[i];
320 switch (horizAlign) {
323 System.out.println("HORIZ LEADING: " + rect);
328 System.out.println("HORIZ TRAILING: " + rect);
329 x = textAreaWidth - rect.getWidth() - TEXT_MARGIN;;
333 System.out.println("HORIZ CENTER: " + rect);
334 x = textAreaWidth * 0.5 - rect.getWidth()*0.5;
339 System.out.println(" X, Y: " + x + ", " + y);
342 System.out.println(" DRAW: '" + flagText[i] + "' with " + g.getTransform());
344 // #6459: render as text in PDF and paths on screen
346 g.drawString(flagText[i], (float) x, (float) y);
348 textLayout[i].draw(g, (float) x, (float) y);
351 y += rect.getHeight();
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));
365 public Rectangle2D getBoundsInLocal() {
366 if (flagShape == null)
368 return flagShape.getBounds2D();