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.G2DRenderingHints;
32 import org.simantics.scenegraph.g2d.G2DRenderingHints.TextRenderingMode;
33 import org.simantics.scenegraph.utils.GeometryUtils;
35 public class FlagNode extends G2DNode {
37 private static final long serialVersionUID = -1716729504104107151L;
39 private static final AffineTransform IDENTITY = new AffineTransform();
41 private static final byte LEADING = 0;
42 private static final byte TRAILING = 1;
43 private static final byte CENTER = 2;
45 private static final boolean DEBUG = false;
47 private static final double GLOBAL_SCALE = 0.1;
49 private static final double TEXT_MARGIN = 5;
51 static transient final BasicStroke STROKE = new BasicStroke(0.25f, BasicStroke.CAP_BUTT,
52 BasicStroke.JOIN_MITER);
54 public final static Font DEFAULT_FONT = Font.decode("Arial 12");
56 protected boolean visible;
58 protected Shape flagShape;
59 protected String[] flagText;
60 protected Stroke stroke;
61 protected Color border;
63 protected Color textColor;
64 protected float width;
65 protected float height;
66 protected double direction; // in radians
67 protected float beakAngle;
68 protected Rectangle2D textArea;
69 protected byte hAlign;
70 protected byte vAlign;
71 protected Font font = DEFAULT_FONT;
73 private transient final Point2D origin = new Point2D.Double();
74 private transient final Point2D xa = new Point2D.Double();
75 private transient final Point2D ya = new Point2D.Double();
77 protected transient TextLayout[] textLayout = null;
78 protected transient Rectangle2D[] rects = null;
79 protected transient float textHeight = 0;
80 protected transient float lastViewScale = 0;
83 public void setVisible(boolean visible) {
84 this.visible = visible;
87 public boolean isVisible() {
91 @SyncField({"visible", "flagShape", "flagText", "stroke", "border", "fill", "textColor", "width", "height", "direction", "beakAngle", "textSize", "hAlign", "vAlign", "font"})
92 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) {
94 this.flagShape = flagShape;
95 this.flagText = flagText;
99 this.textColor = textColor;
101 this.height = height;
102 this.direction = direction;
103 this.beakAngle = beakAngle;
104 this.textArea = textArea;
105 this.hAlign = (byte) hAlign;
106 this.vAlign = (byte) vAlign;
112 private void resetCaches() {
118 public void render(Graphics2D g) {
123 System.out.println("FlagNode.render:");
124 System.out.println("\tflagShape: " + flagShape);
125 System.out.println("\tflagText: " + Arrays.toString(flagText));
126 System.out.println("\tstroke: " + stroke);
127 System.out.println("\tborder: " + border);
128 System.out.println("\tfill: " + fill);
129 System.out.println("\ttextColor: " + textColor);
130 System.out.println("\twidth: " + width);
131 System.out.println("\theight: " + height);
132 System.out.println("\tdirection: " + direction);
133 System.out.println("\tbeakAngle: " + beakAngle);
134 System.out.println("\ttextArea: " + textArea);
135 System.out.println("\thAlign: " + hAlign);
136 System.out.println("\tvAlign: " + vAlign);
137 System.out.println("\tdraw: " + visible);
140 AffineTransform ot = g.getTransform();
141 g.transform(transform);
144 Object renderingHint = g.getRenderingHint(RenderingHints.KEY_RENDERING);
146 //g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
155 // Speed rendering optimization: don't draw text that is too small to read
156 if (renderingHint != RenderingHints.VALUE_RENDER_QUALITY) {
157 double viewScale = GeometryUtils.getScale(ot);
158 viewScale *= GeometryUtils.getScale(transform);
163 if (flagText == null || flagText.length == 0)
167 g.setColor(Color.RED);
173 g.setColor(textColor);
175 AffineTransform orig = g.getTransform();
177 double det = orig.getDeterminant();
179 System.out.println("DETERMINANT: " + det);
182 // Invert the Y-axis if the symbol is "flipped" either vertically xor horizontally
183 origin.setLocation(textArea.getMinX(), textArea.getMaxY());
184 xa.setLocation(textArea.getMaxX(), textArea.getMaxY());
185 ya.setLocation(textArea.getMinX(), textArea.getMinY());
187 origin.setLocation(textArea.getMinX(), textArea.getMinY());
188 xa.setLocation(textArea.getMaxX(), textArea.getMinY());
189 ya.setLocation(textArea.getMinX(), textArea.getMaxY());
192 orig.transform(origin, origin);
193 orig.transform(xa, xa);
194 orig.transform(ya, ya);
196 double xAxisX = xa.getX() - origin.getX();
197 double xAxisY = xa.getY() - origin.getY();
198 double yAxisX = ya.getX() - origin.getX();
199 double yAxisY = ya.getY() - origin.getY();
201 boolean needToFlip = xAxisX < 0 || yAxisY < 0;
203 System.out.println("TEXT NEEDS FLIPPING: " + needToFlip);
205 byte horizAlign = hAlign;
208 // Okay, the text would be upside-down if rendered directly with these axes.
209 // Let's flip the origin to the diagonal point and
210 // invert both x & y axis of the text area to get
211 // the text the right way around. Also, horizontal alignment
212 // needs to be switched unless it's centered.
213 origin.setLocation(origin.getX() + xAxisX + yAxisX, origin.getY() + xAxisY + yAxisY);
219 // Must flip horizontal alignment to keep text visually at the same
221 if (horizAlign == LEADING)
222 horizAlign = TRAILING;
223 else if (horizAlign == TRAILING)
224 horizAlign = LEADING;
227 final double gScale = GLOBAL_SCALE;
228 final double gScaleRecip = 1.0 / gScale;
229 final double scale = GeometryUtils.getMaxScale(orig) * gScale;
230 final double rotation = Math.atan2(xAxisY, xAxisX);
231 g.setTransform(IDENTITY);
232 g.translate(origin.getX(), origin.getY());
234 g.scale(scale, scale);
237 System.out.println("ORIGIN: " + origin);
238 System.out.println("X-AXIS: (" + xAxisX + "," + xAxisY + ")");
239 System.out.println("Y-AXIS: (" + yAxisX + "," + yAxisY + ")");
240 System.out.println("rotation: " + Math.toDegrees(rotation));
241 System.out.println("scale: " + scale);
242 System.out.println("ORIG transform: " + orig);
243 System.out.println("transform: " + g.getTransform());
246 FontMetrics fm = g.getFontMetrics(font);
247 double fontHeight = fm.getHeight();
249 if (textLayout == null || (float) scale != lastViewScale)
251 lastViewScale = (float) scale;
252 FontRenderContext frc = g.getFontRenderContext();
253 if (textLayout == null)
254 textLayout = new TextLayout[flagText.length];
256 rects = new Rectangle2D[flagText.length];
258 for (int i = 0; i < flagText.length; ++i) {
259 String txt = flagText[i].isEmpty() ? " " : flagText[i];
260 textLayout[i] = new TextLayout(txt, font, frc);
261 rects[i] = textLayout[i].getBounds();
263 // If the bb height is not overridden with the font height
264 // text lines will not be drawn in the correct Y location.
265 rects[i].setRect(rects[i].getX(), rects[i].getY(), rects[i].getWidth(), fontHeight);
267 textHeight += rects[i].getHeight() * gScale;
269 System.out.println(" bounding rectangle for line " + i + " '" + flagText[i] + "': " + rects[i]);
273 double leftoverHeight = textArea.getHeight() - textHeight;
274 if (leftoverHeight < 0)
278 System.out.println("text area height: " + textArea.getHeight());
279 System.out.println("total text height: " + textHeight);
280 System.out.println("leftover height: " + leftoverHeight);
289 System.out.println("VERTICAL LEADING");
290 lineDist = leftoverHeight / flagText.length;
291 startY = fm.getMaxAscent();
295 System.out.println("VERTICAL TRAILING");
296 lineDist = leftoverHeight / flagText.length;
297 startY = fm.getMaxAscent() + lineDist * gScaleRecip;
301 System.out.println("VERTICAL CENTER");
302 lineDist = leftoverHeight / (flagText.length + 1);
303 startY = fm.getMaxAscent() + lineDist * gScaleRecip;
308 System.out.println("lineDist: " + lineDist);
309 System.out.println("startY: " + startY);
312 lineDist *= gScaleRecip;
314 double textAreaWidth = textArea.getWidth() * gScaleRecip;
315 boolean renderAsText = g.getRenderingHint(G2DPDFRenderingHints.KEY_PDF_WRITER) != null
316 || g.getRenderingHint(G2DRenderingHints.KEY_TEXT_RENDERING_MODE) == TextRenderingMode.AS_TEXT;
318 for (int i = 0; i < flagText.length; ++i) {
319 //String line = flagText[i];
320 Rectangle2D rect = rects[i];
324 switch (horizAlign) {
327 System.out.println("HORIZ LEADING: " + rect);
332 System.out.println("HORIZ TRAILING: " + rect);
333 x = textAreaWidth - rect.getWidth() - TEXT_MARGIN;;
337 System.out.println("HORIZ CENTER: " + rect);
338 x = textAreaWidth * 0.5 - rect.getWidth()*0.5;
343 System.out.println(" X, Y: " + x + ", " + y);
346 System.out.println(" DRAW: '" + flagText[i] + "' with " + g.getTransform());
348 // #6459: render as text in PDF and paths on screen
350 g.drawString(flagText[i], (float) x, (float) y);
352 textLayout[i].draw(g, (float) x, (float) y);
355 y += rect.getHeight();
363 public static double getBeakLength(double height, double beakAngle) {
364 beakAngle = Math.min(180, Math.max(10, beakAngle));
365 return height / (2*Math.tan(Math.toRadians(beakAngle) / 2));
369 public Rectangle2D getBoundsInLocal() {
370 if (flagShape == null)
372 return flagShape.getBounds2D();