1 package org.opentrafficsim.draw.core;
2
3 import java.awt.Color;
4 import java.awt.Font;
5 import java.awt.FontMetrics;
6 import java.awt.Graphics2D;
7 import java.awt.geom.Rectangle2D;
8 import java.awt.image.ImageObserver;
9 import java.io.Serializable;
10 import java.rmi.RemoteException;
11
12 import javax.naming.NamingException;
13
14 import org.djutils.draw.Oriented;
15 import org.djutils.draw.bounds.Bounds2d;
16 import org.djutils.draw.point.Point;
17 import org.djutils.draw.point.Point2d;
18 import org.djutils.logger.CategoryLogger;
19 import org.opentrafficsim.core.geometry.Bounds;
20 import org.opentrafficsim.core.geometry.DirectedPoint;
21
22 import nl.tudelft.simulation.dsol.animation.Locatable;
23 import nl.tudelft.simulation.dsol.animation.D2.Renderable2D;
24 import nl.tudelft.simulation.dsol.simulators.SimulatorInterface;
25
26 /**
27 * Display a text for another Locatable object.
28 * <p>
29 * Copyright (c) 2013-2022 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
30 * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
31 * </p>
32 * $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
33 * initial version Dec 11, 2016 <br>
34 * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
35 * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
36 * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
37 */
38 public abstract class TextAnimation implements Locatable, Serializable
39 {
40 /** */
41 private static final long serialVersionUID = 20161211L;
42
43 /** The object for which the text is displayed. */
44 private final Locatable source;
45
46 /** The text to display. */
47 private String text;
48
49 /** The horizontal movement of the text, in meters. */
50 private float dx;
51
52 /** The vertical movement of the text, in meters. */
53 private float dy;
54
55 /** Whether to center or not. */
56 private final TextAlignment textAlignment;
57
58 /** The color of the text. */
59 private Color color;
60
61 /** FontSize the size of the font; default = 2.0 (meters). */
62 private final float fontSize;
63
64 /** Minimum font size to trigger scaling. */
65 private final float minFontSize;
66
67 /** Maximum font size to trigger scaling. */
68 private final float maxFontSize;
69
70 /** The animation implementation. */
71 private final AnimationImpl animationImpl;
72
73 /** The font. */
74 private Font font;
75
76 /** Access to the current background color. */
77 private final ContrastToBackground background;
78
79 /** The font rectangle. */
80 private Rectangle2D fontRectangle = null;
81
82 /** Render dependent on font scale. */
83 private final ScaleDependentRendering scaleDependentRendering;
84
85 /**
86 * Construct a new TextAnimation.
87 * @param source Locatable; the object for which the text is displayed
88 * @param text String; the text to display
89 * @param dx float; the horizontal movement of the text, in meters
90 * @param dy float; the vertical movement of the text, in meters
91 * @param textAlignment TextAlignment; where to place the text
92 * @param color Color; the color of the text
93 * @param fontSize float; the size of the font; default = 2.0 (meters)
94 * @param minFontSize float; minimum font size resulting from scaling
95 * @param maxFontSize float; maximum font size resulting from scaling
96 * @param simulator SimulatorInterface.TimeDoubleUnit; the simulator
97 * @param background ContrastToBackground; allows querying the background color and adaptation of the actual color of the
98 * text to ensure contrast
99 * @param scaleDependentRendering ScaleDependentRendering; suppress rendering when font scale is too small
100 * @throws NamingException when animation context cannot be created or retrieved
101 * @throws RemoteException when remote context cannot be found
102 */
103 @SuppressWarnings("checkstyle:parameternumber")
104 public TextAnimation(final Locatable source, final String text, final float dx, final float dy,
105 final TextAlignment textAlignment, final Color color, final float fontSize, final float minFontSize,
106 final float maxFontSize, final SimulatorInterface.TimeDoubleUnit simulator, final ContrastToBackground background,
107 final ScaleDependentRendering scaleDependentRendering) throws RemoteException, NamingException
108 {
109 this.source = source;
110 this.text = text;
111 this.dx = dx;
112 this.dy = dy;
113 this.textAlignment = textAlignment;
114 this.color = color;
115 this.fontSize = fontSize;
116 this.minFontSize = minFontSize;
117 this.maxFontSize = maxFontSize;
118 this.background = background;
119 this.scaleDependentRendering = scaleDependentRendering;
120
121 this.font = new Font("SansSerif", Font.PLAIN, 2);
122 if (this.fontSize != 2.0f)
123 {
124 this.font = this.font.deriveFont(this.fontSize);
125 }
126
127 this.animationImpl = new AnimationImpl(this, simulator);
128 }
129
130 /**
131 * Construct a new TextAnimation without contrast to background protection and no minimum font scale.
132 * @param source Locatable; the object for which the text is displayed
133 * @param text String; the text to display
134 * @param dx float; the horizontal movement of the text, in meters
135 * @param dy float; the vertical movement of the text, in meters
136 * @param textAlignment TextAlignment; where to place the text
137 * @param color Color; the color of the text
138 * @param fontSize float; the size of the font; default = 2.0 (meters)
139 * @param minFontSize float; minimum font size resulting from scaling
140 * @param maxFontSize float; maximum font size resulting from scaling
141 * @param simulator SimulatorInterface.TimeDoubleUnit; the simulator
142 * @param scaleDependentRendering ScaleDependentRendering; render text only when bigger than minimum scale
143 * @throws NamingException when animation context cannot be created or retrieved
144 * @throws RemoteException when remote context cannot be found
145 */
146 @SuppressWarnings("checkstyle:parameternumber")
147 public TextAnimation(final Locatable source, final String text, final float dx, final float dy,
148 final TextAlignment textAlignment, final Color color, final float fontSize, final float minFontSize,
149 final float maxFontSize, final SimulatorInterface.TimeDoubleUnit simulator,
150 final ScaleDependentRendering scaleDependentRendering) throws RemoteException, NamingException
151 {
152 this(source, text, dx, dy, textAlignment, color, fontSize, minFontSize, maxFontSize, simulator, null,
153 scaleDependentRendering);
154 }
155
156 /**
157 * @param source Locatable; the object for which the text is displayed
158 * @param text String; the text to display
159 * @param dx float; the horizontal movement of the text, in meters
160 * @param dy float; the vertical movement of the text, in meters
161 * @param textAlignment TextAlignment; where to place the text
162 * @param color Color; the color of the text
163 * @param simulator SimulatorInterface.TimeDoubleUnit; the simulator
164 * @param scaleDependentRendering ScaleDependentRendering; render text only when bigger than minimum scale
165 * @throws NamingException when animation context cannot be created or retrieved
166 * @throws RemoteException when remote context cannot be found
167 */
168 public TextAnimation(final Locatable source, final String text, final float dx, final float dy,
169 final TextAlignment textAlignment, final Color color, final SimulatorInterface.TimeDoubleUnit simulator,
170 final ScaleDependentRendering scaleDependentRendering) throws RemoteException, NamingException
171 {
172 this(source, text, dx, dy, textAlignment, color, 2.0f, 12.0f, 50f, simulator, scaleDependentRendering);
173 }
174
175 /** {@inheritDoc} */
176 @Override
177 public DirectedPoint getLocation()
178 {
179 // draw always on top.
180 try
181 {
182 Point<?> p = this.source.getLocation();
183 return new DirectedPoint(p.getX(), p.getY(), Double.MAX_VALUE, 0.0, 0.0,
184 p instanceof Oriented ? ((Oriented<?>) p).getDirZ() : 0.0);
185 }
186 catch (RemoteException exception)
187 {
188 CategoryLogger.always().warn(exception);
189 return new DirectedPoint(0, 0, 0);
190 }
191 }
192
193 /** {@inheritDoc} */
194 @Override
195 public final Bounds getBounds() throws RemoteException
196 {
197 return new Bounds(0.0, 0.0, 0.0);
198 }
199
200 /**
201 * paint() method so it can be overridden or extended.
202 * @param graphics Graphics2D; the graphics object
203 * @param observer ImageObserver; the observer
204 */
205 @SuppressWarnings("checkstyle:designforextension")
206 public void paint(final Graphics2D graphics, final ImageObserver observer)
207 {
208 double scale = Math.sqrt(graphics.getTransform().getDeterminant());
209 Rectangle2D scaledFontRectangle;
210 synchronized (this.font)
211 {
212 if (!this.scaleDependentRendering.isRendered(scale))
213 {
214 return;
215 }
216 if (scale < this.minFontSize / this.fontSize)
217 {
218 graphics.setFont(this.font.deriveFont((float) (this.minFontSize / scale)));
219 FontMetrics fm = graphics.getFontMetrics();
220 scaledFontRectangle = fm.getStringBounds(this.text, graphics);
221 }
222 else if (scale > this.maxFontSize / this.fontSize)
223 {
224 graphics.setFont(this.font.deriveFont((float) (this.maxFontSize / scale)));
225 FontMetrics fm = graphics.getFontMetrics();
226 scaledFontRectangle = fm.getStringBounds(this.text, graphics);
227 }
228 else
229 {
230 graphics.setFont(this.font);
231 if (this.fontRectangle == null)
232 {
233 FontMetrics fm = graphics.getFontMetrics();
234 this.fontRectangle = fm.getStringBounds(this.text, graphics);
235 }
236 scaledFontRectangle = this.fontRectangle;
237 }
238 Color useColor = this.color;
239 if (null != this.background && useColor.equals(this.background.getBackgroundColor()))
240 {
241 // Construct an alternative color
242 if (Color.BLACK.equals(useColor))
243 {
244 useColor = Color.WHITE;
245 }
246 else
247 {
248 useColor = Color.BLACK;
249 }
250 }
251 graphics.setColor(useColor);
252 float dxText =
253 this.textAlignment.equals(TextAlignment.LEFT) ? 0.0f : this.textAlignment.equals(TextAlignment.CENTER)
254 ? (float) -scaledFontRectangle.getWidth() / 2.0f : (float) -scaledFontRectangle.getWidth();
255 graphics.drawString(this.text, dxText + this.dx, this.fontSize / 2.0f - this.dy);
256 }
257 }
258
259 /**
260 * Destroy the text animation.
261 * @param simulator SimulatorInterface<?, ?, ?>; the simulator
262 */
263 public final void destroy(final SimulatorInterface<?, ?, ?> simulator)
264 {
265 this.animationImpl.destroy(simulator);
266 }
267
268 /**
269 * Clone the TextAnimation and return a copy for the new source on the new simulator.
270 * @param newSource Locatable; the new source to link to the text animation
271 * @param newSimulator SimulatorInterface.TimeDoubleUnit; the new simulator to register the animation on
272 * @return TextAnimation; a copy of this TextAnimation
273 * @throws RemoteException when remote animation cannot be reached
274 * @throws NamingException when animation name cannot be found or bound in the Context
275 */
276 public abstract TextAnimation clone(Locatable newSource, SimulatorInterface.TimeDoubleUnit newSimulator)
277 throws RemoteException, NamingException;
278
279 /**
280 * Retrieve the source.
281 * @return Locatable; the source
282 */
283 protected final Locatable getSource()
284 {
285 return this.source;
286 }
287
288 /**
289 * Retrieve dx.
290 * @return float; the value of dx
291 */
292 protected final float getDx()
293 {
294 return this.dx;
295 }
296
297 /**
298 * Retrieve dy.
299 * @return float; the value of dy
300 */
301 protected final float getDy()
302 {
303 return this.dy;
304 }
305
306 /**
307 * Sets a new offset.
308 * @param x float; dx
309 * @param y float; dy
310 */
311 protected final void setXY(final float x, final float y)
312 {
313 this.dx = x;
314 this.dy = y;
315 }
316
317 /**
318 * Retrieve the text alignment.
319 * @return TextAlignment; the text alignment
320 */
321 protected final TextAlignment getTextAlignment()
322 {
323 return this.textAlignment;
324 }
325
326 /**
327 * Retrieve the font size.
328 * @return float; the font size
329 */
330 protected final float getFontSize()
331 {
332 return this.fontSize;
333 }
334
335 /**
336 * Retrieve the font.
337 * @return Font; the font
338 */
339 protected final Font getFont()
340 {
341 return this.font;
342 }
343
344 /**
345 * Retrieve the current text.
346 * @return String; the current text
347 */
348 protected final String getText()
349 {
350 return this.text;
351 }
352
353 /**
354 * Update the text.
355 * @param text String; the new text
356 */
357 protected final void setText(final String text)
358 {
359 this.text = text;
360 synchronized (this.font)
361 {
362 this.fontRectangle = null;
363 }
364 }
365
366 /**
367 * Retrieve the current color.
368 * @return Color; the current color
369 */
370 protected final Color getColor()
371 {
372 return this.color;
373 }
374
375 /**
376 * Update the color.
377 * @param color Color; the new color
378 */
379 protected final void setColor(final Color color)
380 {
381 this.color = color;
382 }
383
384 /**
385 * Retrieve the current flip status.
386 * @return boolean; the current flip status
387 */
388 public final boolean isFlip()
389 {
390 return this.animationImpl.isFlip();
391 }
392
393 /**
394 * Update the flip status.
395 * @param flip boolean; the new flip status
396 */
397 public final void setFlip(final boolean flip)
398 {
399 this.animationImpl.setFlip(flip);
400 }
401
402 /**
403 * Retrieve the current rotation status.
404 * @return boolean; the current rotation status
405 */
406 public final boolean isRotate()
407 {
408 return this.animationImpl.isRotate();
409 }
410
411 /**
412 * Update the rotation status.
413 * @param rotate boolean; the new rotation status
414 */
415 public final void setRotate(final boolean rotate)
416 {
417 this.animationImpl.setRotate(rotate);
418
419 }
420
421 /**
422 * Retrieve the current scale status.
423 * @return boolean; the current scale status
424 */
425 public final boolean isScale()
426 {
427 return this.animationImpl.isScale();
428 }
429
430 /**
431 * Update the scale status.
432 * @param scale boolean; the new scale status
433 */
434 public final void setScale(final boolean scale)
435 {
436 this.animationImpl.setScale(scale);
437 }
438
439 /**
440 * Retrieve the current translate status.
441 * @return boolean; the current translate status
442 */
443 public final boolean isTranslate()
444 {
445 return this.animationImpl.isTranslate();
446 }
447
448 /**
449 * Update the translate status.
450 * @param translate boolean; the new translate status
451 */
452 public final void setTranslate(final boolean translate)
453 {
454 this.animationImpl.setTranslate(translate);
455 }
456
457 /**
458 * The implementation of the text animation. Cloning will be taken care of by the overarching TextAnimation-derived class.
459 * <p>
460 * Copyright (c) 2013-2022 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
461 * <br>
462 * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
463 * </p>
464 * $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
465 * initial version Dec 11, 2016 <br>
466 * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
467 * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
468 * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
469 */
470 private static class AnimationImpl extends Renderable2D<Locatable> implements Serializable
471 {
472 /** */
473 private static final long serialVersionUID = 20170400L;
474
475 /**
476 * Construct a new AnimationImpl.
477 * @param source Locatable; the source
478 * @param simulator SimulatorInterface.TimeDoubleUnit; the simulator
479 * @throws NamingException when animation context cannot be created or retrieved
480 * @throws RemoteException when remote context cannot be found
481 */
482 AnimationImpl(final Locatable source, final SimulatorInterface.TimeDoubleUnit simulator)
483 throws NamingException, RemoteException
484 {
485 super(source, simulator);
486 }
487
488 /** {@inheritDoc} */
489 @Override
490 public final void paint(final Graphics2D graphics, final ImageObserver observer)
491 {
492 TextAnimation./../../org/opentrafficsim/draw/core/TextAnimation.html#TextAnimation">TextAnimation ta = ((TextAnimation) getSource());
493 ta.paint(graphics, observer);
494 }
495
496 /** {@inheritDoc} */
497 @Override
498 public boolean contains(final Point2d pointWorldCoordinates, final Bounds2d extent)
499 {
500 return false;
501 }
502
503 /** {@inheritDoc} */
504 @Override
505 public final String toString()
506 {
507 return "TextAnimation.AnimationImpl []";
508 }
509
510 }
511
512 /**
513 * Retrieve the scale dependent rendering qualifier (used in cloning).
514 * @return ScaleDependentRendering; the rendering qualifier of this TextAnimation
515 */
516 protected ScaleDependentRendering getScaleDependentRendering()
517 {
518 return this.scaleDependentRendering;
519 }
520
521 /**
522 * Interface to obtain the color of the background.
523 */
524 public interface ContrastToBackground
525 {
526 /**
527 * Retrieve the color of the background.
528 * @return Color; the (current) color of the background
529 */
530 Color getBackgroundColor();
531 }
532
533 /**
534 * Determine if a Feature object should be rendered.
535 */
536 public interface ScaleDependentRendering
537 {
538 /**
539 * Determine if a Text should be rendered, depending on the scale.
540 * @param scale double; the current font scale
541 * @return boolean; true if the text should be rendered at the scale; false if the text should not be rendered at the
542 * scale
543 */
544 boolean isRendered(double scale);
545 }
546
547 /** Always render the Text. */
548 public static final ScaleDependentRendering RENDERALWAYS = new ScaleDependentRendering()
549 {
550
551 @Override
552 public boolean isRendered(final double scale)
553 {
554 return true;
555 }
556 };
557
558 /** Don't render texts when smaller than 1. */
559 public static final ScaleDependentRendering RENDERWHEN1 = new ScaleDependentRendering()
560 {
561
562 @Override
563 public boolean isRendered(final double scale)
564 {
565 return scale >= 1.0;
566 }
567 };
568
569 /** Don't render texts when smaller than 2. */
570 public static final ScaleDependentRendering RENDERWHEN10 = new ScaleDependentRendering()
571 {
572
573 @Override
574 public boolean isRendered(final double scale)
575 {
576 return scale >= 0.1;
577 }
578 };
579
580 /** Don't render texts when smaller than 2. */
581 public static final ScaleDependentRendering RENDERWHEN100 = new ScaleDependentRendering()
582 {
583
584 @Override
585 public boolean isRendered(final double scale)
586 {
587 return scale >= 0.01;
588 }
589 };
590
591 }