1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package de.erichseifert.vectorgraphics2d;
23
24 import java.awt.AlphaComposite;
25 import java.awt.BasicStroke;
26 import java.awt.Color;
27 import java.awt.Composite;
28 import java.awt.Font;
29 import java.awt.FontMetrics;
30 import java.awt.Graphics;
31 import java.awt.Graphics2D;
32 import java.awt.GraphicsConfiguration;
33 import java.awt.Image;
34 import java.awt.MultipleGradientPaint;
35 import java.awt.Paint;
36 import java.awt.Rectangle;
37 import java.awt.RenderingHints;
38 import java.awt.Shape;
39 import java.awt.Stroke;
40 import java.awt.font.FontRenderContext;
41 import java.awt.font.GlyphVector;
42 import java.awt.font.TextLayout;
43 import java.awt.geom.AffineTransform;
44 import java.awt.geom.Arc2D;
45 import java.awt.geom.Ellipse2D;
46 import java.awt.geom.Line2D;
47 import java.awt.geom.Path2D;
48 import java.awt.geom.Rectangle2D;
49 import java.awt.geom.RoundRectangle2D;
50 import java.awt.image.AffineTransformOp;
51 import java.awt.image.BufferedImage;
52 import java.awt.image.BufferedImageOp;
53 import java.awt.image.ImageObserver;
54 import java.awt.image.RenderedImage;
55 import java.awt.image.renderable.RenderableImage;
56 import java.io.UnsupportedEncodingException;
57 import java.text.AttributedCharacterIterator;
58 import java.util.HashMap;
59 import java.util.Locale;
60 import java.util.Map;
61
62 public abstract class VectorGraphics2D extends Graphics2D {
63
64 public static enum FontRendering {
65
66 TEXT,
67
68 VECTORS
69 }
70
71 private final RenderingHints hints;
72 private final StringBuffer document;
73 private final Rectangle2D bounds;
74
75 private Color background;
76 private Color color;
77 private Shape clip;
78 private Rectangle clipBounds;
79 private Composite composite;
80 private GraphicsConfiguration deviceConfig;
81 private Font font;
82 private FontMetrics fontMetrics;
83 private final FontRenderContext fontRenderContext;
84 private Paint paint;
85 private Stroke stroke;
86 private final AffineTransform transform;
87 private boolean transformed;
88 private Color xorMode;
89
90 private FontRendering fontRendering;
91
92
93
94
95
96
97
98
99
100 public VectorGraphics2D(double x, double y, double width, double height) {
101 hints = new RenderingHints(new HashMap<RenderingHints.Key, Object>());
102 document = new StringBuffer();
103 bounds = new Rectangle2D.Double(x, y, width, height);
104 fontRendering = FontRendering.TEXT;
105
106 background = Color.WHITE;
107 color = Color.BLACK;
108 composite = AlphaComposite.getInstance(AlphaComposite.CLEAR);
109 font = Font.decode(null);
110 fontRenderContext = new FontRenderContext(null, false, true);
111 paint = color;
112 stroke = new BasicStroke(1f);
113 transform = new AffineTransform();
114 transformed = false;
115 xorMode = Color.BLACK;
116 }
117
118 @Override
119 public void addRenderingHints(Map<?,?> hints) {
120 this.hints.putAll(hints);
121 }
122
123 @Override
124 public void clip(Shape s) {
125
126 }
127
128 @Override
129 public void draw(Shape s) {
130 writeShape(s);
131 writeClosingDraw();
132 }
133
134 @Override
135 public void drawGlyphVector(GlyphVector g, float x, float y) {
136 draw(g.getOutline(x, y));
137 }
138
139 @Override
140 public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs) {
141 BufferedImage bimg = getTransformedImage(img, xform);
142 drawImage(bimg, null, bimg.getMinX(), bimg.getMinY());
143 return true;
144 }
145
146 @Override
147 public void drawImage(BufferedImage img, BufferedImageOp op, int x, int y) {
148 if (op != null) {
149 img = op.filter(img, null);
150 }
151 drawImage(img, x, y, img.getWidth(), img.getHeight(), null);
152 }
153
154 @Override
155 public void drawRenderableImage(RenderableImage img, AffineTransform xform) {
156 drawRenderedImage(img.createDefaultRendering(), xform);
157 }
158
159 @Override
160 public void drawRenderedImage(RenderedImage img, AffineTransform xform) {
161
162 }
163
164 @Override
165 public void drawString(String str, int x, int y) {
166 drawString(str, (float)x, (float)y);
167 }
168
169 @Override
170 public void drawString(String str, float x, float y) {
171 switch (getFontRendering()) {
172 case VECTORS:
173 TextLayout layout = new TextLayout(str, getFont(), getFontRenderContext());
174 Shape s = layout.getOutline(AffineTransform.getTranslateInstance(x, y));
175 fill(s);
176 break;
177 case TEXT:
178 writeString(str, x, y);
179 break;
180 }
181 }
182
183 @Override
184 public void drawString(AttributedCharacterIterator iterator, int x, int y) {
185 drawString(iterator, (float)x, (float)y);
186 }
187
188 @Override
189 public void drawString(AttributedCharacterIterator iterator, float x, float y) {
190
191 StringBuffer buf = new StringBuffer();
192 for (char c = iterator.first(); c != AttributedCharacterIterator.DONE; c = iterator.next()) {
193 buf.append(c);
194 }
195 drawString(buf.toString(), x, y);
196 }
197
198 @Override
199 public void fill(Shape s) {
200 writeShape(s);
201 writeClosingFill();
202 }
203
204 @Override
205 public Color getBackground() {
206 return background;
207 }
208
209 @Override
210 public Composite getComposite() {
211 return composite;
212 }
213
214 @Override
215 public GraphicsConfiguration getDeviceConfiguration() {
216 return deviceConfig;
217 }
218
219 @Override
220 public FontRenderContext getFontRenderContext() {
221 return fontRenderContext;
222 }
223
224 @Override
225 public Paint getPaint() {
226 return paint;
227 }
228
229 @Override
230 public Object getRenderingHint(RenderingHints.Key hintKey) {
231 if (RenderingHints.KEY_ANTIALIASING.equals(hintKey)) {
232 return RenderingHints.VALUE_ANTIALIAS_OFF;
233 } else if (RenderingHints.KEY_TEXT_ANTIALIASING.equals(hintKey)) {
234 return RenderingHints.VALUE_TEXT_ANTIALIAS_OFF;
235 } else if (RenderingHints.KEY_FRACTIONALMETRICS.equals(hintKey)) {
236 return RenderingHints.VALUE_FRACTIONALMETRICS_ON;
237 }
238 return hints.get(hintKey);
239 }
240
241 @Override
242 public RenderingHints getRenderingHints() {
243 return hints;
244 }
245
246 @Override
247 public Stroke getStroke() {
248 return stroke;
249 }
250
251 @Override
252 public boolean hit(Rectangle rect, Shape s, boolean onStroke) {
253 if (onStroke) {
254 Shape sStroke = getStroke().createStrokedShape(s);
255 return sStroke.intersects(rect);
256 } else {
257 return s.intersects(rect);
258 }
259 }
260
261 @Override
262 public void setBackground(Color color) {
263 background = color;
264 }
265
266 @Override
267 public void setComposite(Composite comp) {
268 composite = comp;
269 }
270
271 @Override
272 public void setPaint(Paint paint) {
273 if (paint != null) {
274 this.paint = paint;
275 if (paint instanceof Color) {
276 setColor((Color) paint);
277 } else if (paint instanceof MultipleGradientPaint) {
278
279 Color[] colors = ((MultipleGradientPaint)paint).getColors();
280 if (colors.length == 1) {
281 setColor(colors[0]);
282 } else if (colors.length > 1) {
283 Color colLight = colors[0];
284 float brightness = getBrightness(colLight);
285 int alpha = colLight.getAlpha();
286
287 for (int i = 1; i < colors.length; i++) {
288 Color c = colors[i];
289 float b = getBrightness(c);
290 int a = c.getAlpha();
291 if (b < brightness || a < alpha) {
292 colLight = c;
293 brightness = b;
294 }
295 }
296 setColor(colLight);
297 }
298 }
299 }
300 }
301
302
303
304
305
306
307 private static float getBrightness(Color c) {
308 return Color.RGBtoHSB(c.getRed(), c.getGreen(), c.getBlue(), null)[2];
309 }
310
311 @Override
312 public void setRenderingHint(RenderingHints.Key hintKey, Object hintValue) {
313 hints.put(hintKey, hintValue);
314 }
315
316 @Override
317 public void setRenderingHints(Map<?, ?> hints) {
318 this.hints.putAll(hints);
319 }
320
321 @Override
322 public void setStroke(Stroke s) {
323 stroke = s;
324 }
325
326 @Override
327 public AffineTransform getTransform() {
328 return new AffineTransform(transform);
329 }
330
331 @Override
332 public void setTransform(AffineTransform tx) {
333 setAffineTransform(tx);
334 }
335
336 protected void setAffineTransform(AffineTransform tx) {
337 if (!transform.equals(tx)) {
338 transform.setTransform(tx);
339 transformed = true;
340 }
341 }
342
343 @Override
344 public void shear(double shx, double shy) {
345 AffineTransform transform = getTransform();
346 transform.shear(shx, shy);
347 setAffineTransform(transform);
348 }
349
350 @Override
351 public void transform(AffineTransform tx) {
352 AffineTransform transform = getTransform();
353 transform.concatenate(tx);
354 setAffineTransform(transform);
355 }
356
357 @Override
358 public void translate(int x, int y) {
359 translate((double)x, (double)y);
360 }
361
362 @Override
363 public void translate(double tx, double ty) {
364 AffineTransform transform = getTransform();
365 transform.translate(tx, ty);
366 setAffineTransform(transform);
367 }
368
369 @Override
370 public void rotate(double theta) {
371 AffineTransform transform = getTransform();
372 transform.rotate(theta);
373 setAffineTransform(transform);
374 }
375
376 @Override
377 public void rotate(double theta, double x, double y) {
378 AffineTransform transform = getTransform();
379 transform.rotate(theta, x, y);
380 setAffineTransform(transform);
381 }
382
383 @Override
384 public void scale(double sx, double sy) {
385 AffineTransform transform = getTransform();
386 transform.scale(sx, sy);
387 setAffineTransform(transform);
388 }
389
390 @Override
391 public void clearRect(int x, int y, int width, int height) {
392
393 }
394
395 @Override
396 public void clipRect(int x, int y, int width, int height) {
397
398 }
399
400 @Override
401 public void copyArea(int x, int y, int width, int height, int dx, int dy) {
402
403 }
404
405 @Override
406 public Graphics create() {
407 return this;
408 }
409
410 @Override
411 public void dispose() {
412 }
413
414 @Override
415 public void drawArc(int x, int y, int width, int height, int startAngle,
416 int arcAngle) {
417 draw(new Arc2D.Double(x, y, width, height, startAngle, arcAngle, Arc2D.OPEN));
418 }
419
420 @Override
421 public boolean drawImage(Image img, int x, int y, ImageObserver observer) {
422 return drawImage(img, x, y, img.getWidth(observer), img.getHeight(observer), observer);
423 }
424
425 @Override
426 public boolean drawImage(Image img, int x, int y, Color bgcolor,
427 ImageObserver observer) {
428 return drawImage(img, x, y, img.getWidth(observer), img.getHeight(observer), observer);
429 }
430
431 @Override
432 public boolean drawImage(Image img, int x, int y, int width, int height,
433 ImageObserver observer) {
434 int imgWidth = img.getWidth(observer);
435 int imgHeight = img.getHeight(observer);
436 writeImage(img, imgWidth, imgHeight, x, y, width, height);
437 return true;
438 }
439
440 @Override
441 public boolean drawImage(Image img, int x, int y, int width, int height,
442 Color bgcolor, ImageObserver observer) {
443 return drawImage(img, x, y, width, height, observer);
444 }
445
446 @Override
447 public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2,
448 int sx1, int sy1, int sx2, int sy2, ImageObserver observer) {
449
450 return false;
451 }
452
453 @Override
454 public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2,
455 int sx1, int sy1, int sx2, int sy2, Color bgcolor,
456 ImageObserver observer) {
457
458 return false;
459 }
460
461 @Override
462 public void drawLine(int x1, int y1, int x2, int y2) {
463 draw(new Line2D.Double(x1, y1, x2, y2));
464 }
465
466 @Override
467 public void drawOval(int x, int y, int width, int height) {
468 draw(new Ellipse2D.Double(x, y, width, height));
469 }
470
471 @Override
472 public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) {
473 Path2D p = new Path2D.Float();
474 for (int i = 0; i < nPoints; i++) {
475 if (i > 0) {
476 p.lineTo(xPoints[i], yPoints[i]);
477 } else {
478 p.moveTo(xPoints[i], yPoints[i]);
479 }
480 }
481 p.closePath();
482 draw(p);
483 }
484
485 @Override
486 public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) {
487 Path2D p = new Path2D.Float();
488 for (int i = 0; i < nPoints; i++) {
489 if (i > 0) {
490 p.lineTo(xPoints[i], yPoints[i]);
491 } else {
492 p.moveTo(xPoints[i], yPoints[i]);
493 }
494 }
495 draw(p);
496 }
497
498 @Override
499 public void drawRect(int x, int y, int width, int height) {
500 draw(new Rectangle2D.Double(x, y, width, height));
501 }
502
503 @Override
504 public void drawRoundRect(int x, int y, int width, int height,
505 int arcWidth, int arcHeight) {
506 draw(new RoundRectangle2D.Double(x, y, width, height, arcWidth, arcHeight));
507 }
508
509 @Override
510 public void fillArc(int x, int y, int width, int height, int startAngle,
511 int arcAngle) {
512 fill(new Arc2D.Double(x, y, width, height, startAngle, arcAngle, Arc2D.PIE));
513 }
514
515 @Override
516 public void fillOval(int x, int y, int width, int height) {
517 fill(new Ellipse2D.Double(x, y, width, height));
518 }
519
520 @Override
521 public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints) {
522 Path2D p = new Path2D.Float();
523 for (int i = 0; i < nPoints; i++) {
524 if (i > 0) {
525 p.lineTo(xPoints[i], yPoints[i]);
526 } else {
527 p.moveTo(xPoints[i], yPoints[i]);
528 }
529 }
530 p.closePath();
531
532 fill(p);
533 }
534
535 @Override
536 public void fillRect(int x, int y, int width, int height) {
537 fill(new Rectangle2D.Double(x, y, width, height));
538 }
539
540 @Override
541 public void fillRoundRect(int x, int y, int width, int height,
542 int arcWidth, int arcHeight) {
543 fill(new RoundRectangle2D.Double(x, y, width, height, arcWidth, arcHeight));
544 }
545
546 @Override
547 public Shape getClip() {
548 return clip;
549 }
550
551 @Override
552 public Rectangle getClipBounds() {
553 return clipBounds;
554 }
555
556 @Override
557 public Color getColor() {
558 return color;
559 }
560
561 @Override
562 public Font getFont() {
563 return font;
564 }
565
566 @Override
567 public FontMetrics getFontMetrics(Font f) {
568 return fontMetrics;
569 }
570
571 @Override
572 public void setClip(Shape clip) {
573 this.clip = clip;
574 }
575
576 @Override
577 public void setClip(int x, int y, int width, int height) {
578 clip = new Rectangle(x, y, width, height);
579 }
580
581 @Override
582 public void setColor(Color c) {
583 color = c;
584 }
585
586 @Override
587 public void setFont(Font font) {
588 if (!this.font.equals(font)) {
589 this.font = font;
590 }
591 }
592
593 @Override
594 public void setPaintMode() {
595
596
597 }
598
599 @Override
600 public void setXORMode(Color c1) {
601 xorMode = c1;
602 }
603
604
605
606
607
608 protected void write(Object... strs) {
609 for (Object o : strs) {
610 String str = o.toString();
611 if ((o instanceof Double) || (o instanceof Float)) {
612 str = String.format(Locale.ENGLISH, "%.7f", o).replaceAll("\\.?0+$", "");
613 }
614 document.append(str);
615 }
616 }
617
618
619
620
621
622 protected void writeln(Object... strs) {
623 write(strs);
624 write("\n");
625 }
626
627
628
629
630
631
632 protected abstract void writeShape(Shape s);
633
634
635
636
637
638
639
640
641
642
643
644
645 protected abstract void writeImage(Image img, int imgWidth, int imgHeight, double x, double y, double width, double height);
646
647
648
649
650
651
652
653 protected abstract void writeString(String str, double x, double y);
654
655
656
657
658 protected abstract void writeClosingDraw();
659
660
661
662
663 protected abstract void writeClosingFill();
664
665
666
667
668 protected abstract void writeHeader();
669
670
671
672
673 protected abstract String getFooter();
674
675 private BufferedImage getTransformedImage(Image image, AffineTransform xform) {
676 Integer interpolationType = (Integer)hints.get(RenderingHints.KEY_INTERPOLATION);
677 if (RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR.equals(interpolationType)) {
678 interpolationType = AffineTransformOp.TYPE_NEAREST_NEIGHBOR;
679 } else if (RenderingHints.VALUE_INTERPOLATION_BILINEAR.equals(interpolationType)) {
680 interpolationType = AffineTransformOp.TYPE_BILINEAR;
681 } else {
682 interpolationType = AffineTransformOp.TYPE_BICUBIC;
683 }
684 AffineTransformOp op = new AffineTransformOp(xform, interpolationType);
685 BufferedImage bufferedImage = GraphicsUtils.toBufferedImage(image);
686 return op.filter(bufferedImage, null);
687 }
688
689
690
691
692
693
694 protected boolean isDistorted() {
695 if (!isTransformed()) {
696 return false;
697 }
698 int type = transform.getType();
699 int otherButTranslatedOrScaled = ~(AffineTransform.TYPE_TRANSLATION | AffineTransform.TYPE_MASK_SCALE);
700 return (type & otherButTranslatedOrScaled) != 0;
701 }
702
703 @Override
704 public String toString() {
705 return document.toString() + getFooter();
706 }
707
708
709
710
711
712 public byte[] getBytes() {
713 try {
714 return toString().getBytes("UTF-8");
715 } catch (UnsupportedEncodingException e) {
716 return toString().getBytes();
717 }
718 }
719
720
721
722
723
724 public Rectangle2D getBounds() {
725 Rectangle2D b = new Rectangle2D.Double();
726 b.setFrame(bounds);
727 return b;
728 }
729
730
731
732
733
734 protected int size() {
735 return document.length();
736 }
737
738
739
740
741
742 public FontRendering getFontRendering() {
743 return fontRendering;
744 }
745
746
747
748
749
750
751 public void setFontRendering(FontRendering mode) {
752 fontRendering = mode;
753 }
754
755
756
757
758
759
760
761 protected boolean isTransformed() {
762 return transformed;
763 }
764
765 }