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 public final static Font DEFAULT_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;
69 protected Font font = DEFAULT_FONT;
71 private transient final Point2D origin = new Point2D.Double();
72 private transient final Point2D xa = new Point2D.Double();
73 private transient final Point2D ya = new Point2D.Double();
75 protected transient TextLayout[] textLayout = null;
76 protected transient Rectangle2D[] rects = null;
77 protected transient float textHeight = 0;
78 protected transient float lastViewScale = 0;
81 public void setVisible(boolean visible) {
82 this.visible = visible;
85 public boolean isVisible() {
89 @SyncField({"visible", "flagShape", "flagText", "stroke", "border", "fill", "textColor", "width", "height", "direction", "beakAngle", "textSize", "hAlign", "vAlign", "font"})
90 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) {
92 this.flagShape = flagShape;
93 this.flagText = flagText;
97 this.textColor = textColor;
100 this.direction = direction;
101 this.beakAngle = beakAngle;
102 this.textArea = textArea;
103 this.hAlign = (byte) hAlign;
104 this.vAlign = (byte) vAlign;
110 private void resetCaches() {
116 public void render(Graphics2D g) {
121 System.out.println("FlagNode.render:");
122 System.out.println("\tflagShape: " + flagShape);
123 System.out.println("\tflagText: " + Arrays.toString(flagText));
124 System.out.println("\tstroke: " + stroke);
125 System.out.println("\tborder: " + border);
126 System.out.println("\tfill: " + fill);
127 System.out.println("\ttextColor: " + textColor);
128 System.out.println("\twidth: " + width);
129 System.out.println("\theight: " + height);
130 System.out.println("\tdirection: " + direction);
131 System.out.println("\tbeakAngle: " + beakAngle);
132 System.out.println("\ttextArea: " + textArea);
133 System.out.println("\thAlign: " + hAlign);
134 System.out.println("\tvAlign: " + vAlign);
135 System.out.println("\tdraw: " + visible);
138 AffineTransform ot = g.getTransform();
139 g.transform(transform);
142 Object renderingHint = g.getRenderingHint(RenderingHints.KEY_RENDERING);
144 //g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
153 // Speed rendering optimization: don't draw text that is too small to read
154 if (renderingHint != RenderingHints.VALUE_RENDER_QUALITY) {
155 double viewScale = GeometryUtils.getScale(ot);
156 viewScale *= GeometryUtils.getScale(transform);
161 if (flagText == null || flagText.length == 0)
165 g.setColor(Color.RED);
171 g.setColor(textColor);
173 AffineTransform orig = g.getTransform();
175 double det = orig.getDeterminant();
177 System.out.println("DETERMINANT: " + det);
180 // Invert the Y-axis if the symbol is "flipped" either vertically xor horizontally
181 origin.setLocation(textArea.getMinX(), textArea.getMaxY());
182 xa.setLocation(textArea.getMaxX(), textArea.getMaxY());
183 ya.setLocation(textArea.getMinX(), textArea.getMinY());
185 origin.setLocation(textArea.getMinX(), textArea.getMinY());
186 xa.setLocation(textArea.getMaxX(), textArea.getMinY());
187 ya.setLocation(textArea.getMinX(), textArea.getMaxY());
190 orig.transform(origin, origin);
191 orig.transform(xa, xa);
192 orig.transform(ya, ya);
194 double xAxisX = xa.getX() - origin.getX();
195 double xAxisY = xa.getY() - origin.getY();
196 double yAxisX = ya.getX() - origin.getX();
197 double yAxisY = ya.getY() - origin.getY();
199 boolean needToFlip = xAxisX < 0 || yAxisY < 0;
201 System.out.println("TEXT NEEDS FLIPPING: " + needToFlip);
203 byte horizAlign = hAlign;
206 // Okay, the text would be upside-down if rendered directly with these axes.
207 // Let's flip the origin to the diagonal point and
208 // invert both x & y axis of the text area to get
209 // the text the right way around. Also, horizontal alignment
210 // needs to be switched unless it's centered.
211 origin.setLocation(origin.getX() + xAxisX + yAxisX, origin.getY() + xAxisY + yAxisY);
217 // Must flip horizontal alignment to keep text visually at the same
219 if (horizAlign == LEADING)
220 horizAlign = TRAILING;
221 else if (horizAlign == TRAILING)
222 horizAlign = LEADING;
225 final double gScale = GLOBAL_SCALE;
226 final double gScaleRecip = 1.0 / gScale;
227 final double scale = GeometryUtils.getMaxScale(orig) * gScale;
228 final double rotation = Math.atan2(xAxisY, xAxisX);
229 g.setTransform(IDENTITY);
230 g.translate(origin.getX(), origin.getY());
232 g.scale(scale, scale);
235 System.out.println("ORIGIN: " + origin);
236 System.out.println("X-AXIS: (" + xAxisX + "," + xAxisY + ")");
237 System.out.println("Y-AXIS: (" + yAxisX + "," + yAxisY + ")");
238 System.out.println("rotation: " + Math.toDegrees(rotation));
239 System.out.println("scale: " + scale);
240 System.out.println("ORIG transform: " + orig);
241 System.out.println("transform: " + g.getTransform());
244 FontMetrics fm = g.getFontMetrics(font);
245 double fontHeight = fm.getHeight();
247 if (textLayout == null || (float) scale != lastViewScale)
249 lastViewScale = (float) scale;
250 FontRenderContext frc = g.getFontRenderContext();
251 if (textLayout == null)
252 textLayout = new TextLayout[flagText.length];
254 rects = new Rectangle2D[flagText.length];
256 for (int i = 0; i < flagText.length; ++i) {
257 String txt = flagText[i].isEmpty() ? " " : flagText[i];
258 textLayout[i] = new TextLayout(txt, font, frc);
259 rects[i] = textLayout[i].getBounds();
261 // If the bb height is not overridden with the font height
262 // text lines will not be drawn in the correct Y location.
263 rects[i].setRect(rects[i].getX(), rects[i].getY(), rects[i].getWidth(), fontHeight);
265 textHeight += rects[i].getHeight() * gScale;
267 System.out.println(" bounding rectangle for line " + i + " '" + flagText[i] + "': " + rects[i]);
271 double leftoverHeight = textArea.getHeight() - textHeight;
272 if (leftoverHeight < 0)
276 System.out.println("text area height: " + textArea.getHeight());
277 System.out.println("total text height: " + textHeight);
278 System.out.println("leftover height: " + leftoverHeight);
287 System.out.println("VERTICAL LEADING");
288 lineDist = leftoverHeight / flagText.length;
289 startY = fm.getMaxAscent();
293 System.out.println("VERTICAL TRAILING");
294 lineDist = leftoverHeight / flagText.length;
295 startY = fm.getMaxAscent() + lineDist * gScaleRecip;
299 System.out.println("VERTICAL CENTER");
300 lineDist = leftoverHeight / (flagText.length + 1);
301 startY = fm.getMaxAscent() + lineDist * gScaleRecip;
306 System.out.println("lineDist: " + lineDist);
307 System.out.println("startY: " + startY);
310 lineDist *= gScaleRecip;
312 double textAreaWidth = textArea.getWidth() * gScaleRecip;
313 boolean isRenderingPdf = g.getRenderingHint(G2DPDFRenderingHints.KEY_PDF_WRITER) != null;
315 for (int i = 0; i < flagText.length; ++i) {
316 //String line = flagText[i];
317 Rectangle2D rect = rects[i];
321 switch (horizAlign) {
324 System.out.println("HORIZ LEADING: " + rect);
329 System.out.println("HORIZ TRAILING: " + rect);
330 x = textAreaWidth - rect.getWidth() - TEXT_MARGIN;;
334 System.out.println("HORIZ CENTER: " + rect);
335 x = textAreaWidth * 0.5 - rect.getWidth()*0.5;
340 System.out.println(" X, Y: " + x + ", " + y);
343 System.out.println(" DRAW: '" + flagText[i] + "' with " + g.getTransform());
345 // #6459: render as text in PDF and paths on screen
347 g.drawString(flagText[i], (float) x, (float) y);
349 textLayout[i].draw(g, (float) x, (float) y);
352 y += rect.getHeight();
360 public static double getBeakLength(double height, double beakAngle) {
361 beakAngle = Math.min(180, Math.max(10, beakAngle));
362 return height / (2*Math.tan(Math.toRadians(beakAngle) / 2));
366 public Rectangle2D getBoundsInLocal() {
367 if (flagShape == null)
369 return flagShape.getBounds2D();