1 package org.opentrafficsim.draw;
2
3 import java.awt.Color;
4 import java.awt.Dimension;
5 import java.awt.Font;
6 import java.awt.FontMetrics;
7 import java.awt.Graphics2D;
8 import java.awt.RenderingHints;
9 import java.awt.Shape;
10 import java.awt.geom.Point2D;
11 import java.awt.geom.Rectangle2D;
12 import java.awt.geom.RoundRectangle2D;
13 import java.awt.image.ImageObserver;
14 import java.util.function.Supplier;
15
16 import org.djutils.draw.Directed;
17 import org.djutils.draw.bounds.Bounds2d;
18 import org.djutils.draw.line.Polygon2d;
19 import org.djutils.draw.point.DirectedPoint2d;
20 import org.djutils.draw.point.Point2d;
21 import org.opentrafficsim.base.geometry.OtsShape;
22
23 import nl.tudelft.simulation.dsol.animation.d2.Renderable2d;
24 import nl.tudelft.simulation.dsol.animation.d2.RenderableScale;
25 import nl.tudelft.simulation.language.d2.Angle;
26 import nl.tudelft.simulation.naming.context.Contextualized;
27
28
29
30
31
32
33
34
35
36
37
38
39
40 public abstract class RenderableTextSource<L extends OtsShape, T extends RenderableTextSource<L, T>> implements OtsShape
41 {
42
43 private final L source;
44
45
46 private Supplier<String> text;
47
48
49 private float dx;
50
51
52 private float dy;
53
54
55 private final TextAlignment textAlignment;
56
57
58 private Color color;
59
60
61 private final float fontSize;
62
63
64 private final float minFontSize;
65
66
67 private final float maxFontSize;
68
69
70 private final RenderableText animationImpl;
71
72
73 private Font font;
74
75
76 private final ContrastToBackground background;
77
78
79 private final ScaleDependentRendering scaleDependentRendering;
80
81
82 private boolean dynamic = false;
83
84
85 private DirectedPoint2d location;
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102 @SuppressWarnings("checkstyle:parameternumber")
103 public RenderableTextSource(final L source, final Supplier<String> text, final float dx, final float dy,
104 final TextAlignment textAlignment, final Color color, final float fontSize, final float minFontSize,
105 final float maxFontSize, final Contextualized contextualized, final ContrastToBackground background,
106 final ScaleDependentRendering scaleDependentRendering)
107 {
108 this.source = source;
109 this.text = text;
110 this.dx = dx;
111 this.dy = dy;
112 this.textAlignment = textAlignment;
113 this.color = color;
114 this.fontSize = fontSize;
115 this.minFontSize = minFontSize;
116 this.maxFontSize = maxFontSize;
117 this.background = background;
118 this.scaleDependentRendering = scaleDependentRendering;
119
120 this.font = new Font("SansSerif", Font.PLAIN, 2);
121 if (this.fontSize != 2.0f)
122 {
123 this.font = this.font.deriveFont(this.fontSize);
124 }
125
126 this.animationImpl = new RenderableText(this, contextualized);
127 setScaleY(false);
128 }
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144 @SuppressWarnings("checkstyle:parameternumber")
145 public RenderableTextSource(final L source, final Supplier<String> text, final float dx, final float dy,
146 final TextAlignment textAlignment, final Color color, final float fontSize, final float minFontSize,
147 final float maxFontSize, final Contextualized contextualized, final ScaleDependentRendering scaleDependentRendering)
148 {
149 this(source, text, dx, dy, textAlignment, color, fontSize, minFontSize, maxFontSize, contextualized, null,
150 scaleDependentRendering);
151 }
152
153
154
155
156
157
158
159
160
161
162
163
164 public RenderableTextSource(final L source, final Supplier<String> text, final float dx, final float dy,
165 final TextAlignment textAlignment, final Color color, final Contextualized contextualized,
166 final ScaleDependentRendering scaleDependentRendering)
167 {
168 this(source, text, dx, dy, textAlignment, color, 2.0f, 12.0f, 50f, contextualized, scaleDependentRendering);
169 }
170
171
172
173
174
175
176 @SuppressWarnings("all")
177 public T setDynamic(final boolean dynamic)
178 {
179 this.dynamic = dynamic;
180 return (T) this;
181 }
182
183 @Override
184 public DirectedPoint2d getLocation()
185 {
186 if (this.location == null || this.dynamic)
187 {
188 Point2d p = getSource().getLocation();
189 if (isRotate() && p instanceof Directed dir)
190 {
191
192 double a = Angle.normalizePi(dir.getDirZ());
193 if (a > Math.PI / 2.0 || a < -0.99 * Math.PI / 2.0)
194 {
195 a += Math.PI;
196 }
197 this.location = new DirectedPoint2d(p, a);
198 }
199 else
200 {
201 this.location = new DirectedPoint2d(p, 0.0);
202 }
203 }
204 return this.location;
205 }
206
207
208
209
210
211 public L getSource()
212 {
213 return this.source;
214 }
215
216 @Override
217 public final Bounds2d getRelativeBounds()
218 {
219 return new Bounds2d(2.0, 2.0);
220 }
221
222 @Override
223 public Polygon2d getAbsoluteContour()
224 {
225 return getSource().getAbsoluteContour();
226 }
227
228 @Override
229 public Polygon2d getRelativeContour()
230 {
231 return getSource().getRelativeContour();
232 }
233
234
235
236
237
238
239 @SuppressWarnings("checkstyle:designforextension")
240 public void paint(final Graphics2D graphics, final ImageObserver observer)
241 {
242 double scale = Math.sqrt(graphics.getTransform().getDeterminant());
243 Rectangle2D scaledFontRectangle;
244 String str = this.text.get();
245 synchronized (this.font)
246 {
247 if (!this.scaleDependentRendering.isRendered(scale))
248 {
249 return;
250 }
251 if (scale < this.minFontSize / this.fontSize)
252 {
253 graphics.setFont(this.font.deriveFont((float) (this.minFontSize / scale)));
254 FontMetrics fm = graphics.getFontMetrics();
255 scaledFontRectangle = fm.getStringBounds(str, graphics);
256 }
257 else if (scale > this.maxFontSize / this.fontSize)
258 {
259 graphics.setFont(this.font.deriveFont((float) (this.maxFontSize / scale)));
260 FontMetrics fm = graphics.getFontMetrics();
261 scaledFontRectangle = fm.getStringBounds(str, graphics);
262 }
263 else
264 {
265 graphics.setFont(this.font);
266 FontMetrics fm = graphics.getFontMetrics();
267 scaledFontRectangle = fm.getStringBounds(str, graphics);
268 }
269 Color useColor = this.color;
270 if (null != this.background && isSimilar(useColor, this.background.getBackgroundColor()))
271 {
272
273 if (Color.BLACK.equals(useColor))
274 {
275 useColor = Color.WHITE;
276 }
277 else
278 {
279 useColor = Color.BLACK;
280 }
281 }
282
283 float dxText =
284 this.textAlignment.equals(TextAlignment.LEFT) ? 0.0f : this.textAlignment.equals(TextAlignment.CENTER)
285 ? (float) -scaledFontRectangle.getWidth() / 2.0f : (float) -scaledFontRectangle.getWidth();
286 Object antialias = graphics.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
287 graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
288 if (null != this.background)
289 {
290
291
292
293 double r = scaledFontRectangle.getHeight() / 2.0;
294 double dh = scaledFontRectangle.getHeight() / 5.0;
295 Shape s = new RoundRectangle2D.Double(this.dx - scaledFontRectangle.getWidth() - dxText,
296 this.dy + dh - scaledFontRectangle.getHeight(), scaledFontRectangle.getWidth(),
297 scaledFontRectangle.getHeight(), r, r);
298 Color bg = this.background.getBackgroundColor();
299 graphics.setColor(new Color(bg.getRed(), bg.getGreen(), bg.getBlue(), 92));
300 graphics.fill(s);
301 }
302 graphics.setColor(useColor);
303 graphics.drawString(str, dxText + this.dx, -this.dy);
304
305 graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, antialias);
306 }
307 }
308
309
310
311
312
313
314
315 private boolean isSimilar(final Color color1, final Color color2)
316 {
317 int r = color1.getRed() - color2.getRed();
318 int g = color1.getGreen() - color2.getGreen();
319 int b = color1.getBlue() - color2.getBlue();
320 return r * r + g * g + b * b < 2000;
321
322 }
323
324
325
326
327
328 public final void destroy(final Contextualized contextProvider)
329 {
330 this.animationImpl.destroy(contextProvider);
331 }
332
333
334
335
336
337 protected final float getDx()
338 {
339 return this.dx;
340 }
341
342
343
344
345
346 protected final float getDy()
347 {
348 return this.dy;
349 }
350
351
352
353
354
355
356 protected final void setXY(final float x, final float y)
357 {
358 this.dx = x;
359 this.dy = y;
360 }
361
362 @Override
363 public double getZ()
364 {
365 return DrawLevel.LABEL.getZ();
366 }
367
368
369
370
371
372 protected TextAlignment getTextAlignment()
373 {
374 return this.textAlignment;
375 }
376
377
378
379
380
381 protected float getFontSize()
382 {
383 return this.fontSize;
384 }
385
386
387
388
389
390 protected Font getFont()
391 {
392 return this.font;
393 }
394
395
396
397
398
399 protected String getText()
400 {
401 return this.text.get();
402 }
403
404
405
406
407
408 public void setText(final Supplier<String> text)
409 {
410 this.text = text;
411 }
412
413
414
415
416
417 protected Color getColor()
418 {
419 return this.color;
420 }
421
422
423
424
425
426 protected void setColor(final Color color)
427 {
428 this.color = color;
429 }
430
431
432
433
434
435 public boolean isFlip()
436 {
437 return this.animationImpl.isFlip();
438 }
439
440
441
442
443
444 public void setFlip(final boolean flip)
445 {
446 this.animationImpl.setFlip(flip);
447 }
448
449
450
451
452
453 public boolean isRotate()
454 {
455 return this.animationImpl.isRotate();
456 }
457
458
459
460
461
462 public void setRotate(final boolean rotate)
463 {
464 this.animationImpl.setRotate(rotate);
465
466 }
467
468
469
470
471
472 public boolean isScale()
473 {
474 return this.animationImpl.isScale();
475 }
476
477
478
479
480
481 public void setScale(final boolean scale)
482 {
483 this.animationImpl.setScale(scale);
484 }
485
486
487
488
489
490 public boolean isTranslate()
491 {
492 return this.animationImpl.isTranslate();
493 }
494
495
496
497
498
499 public void setTranslate(final boolean translate)
500 {
501 this.animationImpl.setTranslate(translate);
502 }
503
504
505
506
507
508 public boolean isScaleY()
509 {
510 return this.animationImpl.isScaleY();
511 }
512
513
514
515
516
517 public void setScaleY(final boolean scaleY)
518 {
519 this.animationImpl.setScaleY(scaleY);
520 }
521
522
523
524
525
526 public boolean isScaleObject()
527 {
528 return this.animationImpl.isScaleObject();
529 }
530
531
532
533
534
535
536 public void setScaleObject(final boolean scaleObject)
537 {
538 this.animationImpl.setScaleObject(scaleObject);
539 }
540
541
542
543
544
545
546
547
548
549
550
551
552 private static class RenderableText extends Renderable2d<RenderableTextSource<?, ?>>
553 {
554
555
556
557
558
559 RenderableText(final RenderableTextSource<?, ?> source, final Contextualized contextualized)
560 {
561 super(source, contextualized);
562 }
563
564 @Override
565 public final void paint(final Graphics2D graphics, final ImageObserver observer)
566 {
567 getSource().paint(graphics, observer);
568 }
569
570 @Override
571 public boolean contains(final Point2D pointScreenCoordinates, final Bounds2d extent, final Dimension screenSize,
572 final RenderableScale scale, final double worldMargin, final double pixelMargin)
573 {
574 return false;
575 }
576
577 @Override
578 public final String toString()
579 {
580 return "RenderableText []";
581 }
582 }
583
584
585
586
587
588 protected ScaleDependentRendering getScaleDependentRendering()
589 {
590 return this.scaleDependentRendering;
591 }
592
593
594
595
596 public interface ContrastToBackground
597 {
598
599
600
601
602 Color getBackgroundColor();
603 }
604
605
606
607
608 public interface ScaleDependentRendering
609 {
610
611
612
613
614
615 boolean isRendered(double scale);
616 }
617
618
619 public static final ScaleDependentRendering RENDERALWAYS = new ScaleDependentRendering()
620 {
621 @Override
622 public boolean isRendered(final double scale)
623 {
624 return true;
625 }
626 };
627
628
629 public static final ScaleDependentRendering RENDERWHEN1 = new ScaleDependentRendering()
630 {
631 @Override
632 public boolean isRendered(final double scale)
633 {
634 return scale >= 1.0;
635 }
636 };
637
638
639 public static final ScaleDependentRendering RENDERWHEN10 = new ScaleDependentRendering()
640 {
641 @Override
642 public boolean isRendered(final double scale)
643 {
644 return scale >= 0.1;
645 }
646 };
647
648
649 public static final ScaleDependentRendering RENDERWHEN100 = new ScaleDependentRendering()
650 {
651 @Override
652 public boolean isRendered(final double scale)
653 {
654 return scale >= 0.01;
655 }
656 };
657
658 }