1
2 package org.freehep.graphicsio.svg;
3
4 import java.awt.BasicStroke;
5 import java.awt.Color;
6 import java.awt.Component;
7 import java.awt.Dimension;
8 import java.awt.Font;
9 import java.awt.GradientPaint;
10 import java.awt.Graphics;
11 import java.awt.GraphicsConfiguration;
12 import java.awt.Paint;
13 import java.awt.Shape;
14 import java.awt.Stroke;
15 import java.awt.TexturePaint;
16 import java.awt.font.TextAttribute;
17 import java.awt.geom.AffineTransform;
18 import java.awt.geom.GeneralPath;
19 import java.awt.geom.PathIterator;
20 import java.awt.geom.Point2D;
21 import java.awt.geom.Rectangle2D;
22 import java.awt.image.RenderedImage;
23 import java.io.BufferedOutputStream;
24 import java.io.File;
25 import java.io.FileOutputStream;
26 import java.io.IOException;
27 import java.io.OutputStream;
28 import java.io.PrintWriter;
29 import java.io.StringWriter;
30 import java.text.DateFormat;
31 import java.text.DecimalFormat;
32 import java.text.DecimalFormatSymbols;
33 import java.util.Arrays;
34 import java.util.Date;
35 import java.util.Enumeration;
36 import java.util.Hashtable;
37 import java.util.Locale;
38 import java.util.Map;
39 import java.util.Properties;
40 import java.util.Stack;
41 import java.util.zip.GZIPOutputStream;
42
43 import org.freehep.graphics2d.font.FontUtilities;
44 import org.freehep.graphicsio.AbstractVectorGraphicsIO;
45 import org.freehep.graphicsio.FontConstants;
46 import org.freehep.graphicsio.ImageConstants;
47 import org.freehep.graphicsio.ImageGraphics2D;
48 import org.freehep.graphicsio.InfoConstants;
49 import org.freehep.graphicsio.PageConstants;
50 import org.freehep.util.UserProperties;
51 import org.freehep.util.Value;
52 import org.freehep.util.io.Base64OutputStream;
53 import org.freehep.util.io.WriterOutputStream;
54 import org.freehep.xml.util.XMLWriter;
55
56
57
58
59
60
61
62
63
64
65 public class SVGGraphics2D extends AbstractVectorGraphicsIO {
66
67 public static final String VERSION_1_1 = "Version 1.1 (REC-SVG11-20030114)";
68
69 private static final String rootKey = SVGGraphics2D.class.getName();
70
71 public static final String TRANSPARENT = rootKey + "."
72 + PageConstants.TRANSPARENT;
73
74 public static final String BACKGROUND = rootKey + "."
75 + PageConstants.BACKGROUND;
76
77 public static final String BACKGROUND_COLOR = rootKey + "."
78 + PageConstants.BACKGROUND_COLOR;
79
80 public static final String VERSION = rootKey + ".Version";
81
82 public static final String COMPRESS = rootKey + ".Binary";
83
84
85
86
87
88 public static final String STYLABLE = rootKey + ".Stylable";
89
90 public static final String IMAGE_SIZE = rootKey + "."
91 + ImageConstants.IMAGE_SIZE;
92
93 public static final String EXPORT_IMAGES = rootKey + ".ExportImages";
94
95 public static final String EXPORT_SUFFIX = rootKey + ".ExportSuffix";
96
97 public static final String WRITE_IMAGES_AS = rootKey + "."
98 + ImageConstants.WRITE_IMAGES_AS;
99
100 public static final String FOR = rootKey + "." + InfoConstants.FOR;
101
102 public static final String TITLE = rootKey + "." + InfoConstants.TITLE;
103
104 private BasicStroke defaultStroke = new BasicStroke();
105
106 public static final String EMBED_FONTS = rootKey + "."
107 + FontConstants.EMBED_FONTS;
108
109 private SVGFontTable fontTable;
110
111 private static final UserProperties defaultProperties = new UserProperties();
112 static {
113 defaultProperties.setProperty(TRANSPARENT, true);
114 defaultProperties.setProperty(BACKGROUND, false);
115 defaultProperties.setProperty(BACKGROUND_COLOR, Color.GRAY);
116
117 defaultProperties.setProperty(VERSION, VERSION_1_1);
118 defaultProperties.setProperty(COMPRESS, false);
119
120 defaultProperties.setProperty(STYLABLE, false);
121
122 defaultProperties.setProperty(IMAGE_SIZE, new Dimension(0, 0));
123
124 defaultProperties.setProperty(EXPORT_IMAGES, false);
125 defaultProperties.setProperty(EXPORT_SUFFIX, "image");
126
127 defaultProperties.setProperty(WRITE_IMAGES_AS, ImageConstants.SMALLEST);
128
129 defaultProperties.setProperty(FOR, "");
130 defaultProperties.setProperty(TITLE, "");
131
132 defaultProperties.setProperty(CLIP, true);
133
134 defaultProperties.setProperty(EMBED_FONTS, false);
135 defaultProperties.setProperty(TEXT_AS_SHAPES, true);
136 }
137
138 public static Properties getDefaultProperties() {
139 return defaultProperties;
140 }
141
142 public static void setDefaultProperties(Properties newProperties) {
143 defaultProperties.setProperties(newProperties);
144 }
145
146 public static final String version = "$Revision: 12753 $";
147
148
149 private String filename;
150
151
152 private int bbx, bby, bbw, bbh;
153
154
155 private OutputStream ros;
156
157 private PrintWriter os;
158
159
160 Hashtable gradients = new Hashtable();
161
162
163 Hashtable textures = new Hashtable();
164
165 private Stack closeTags = new Stack();
166
167 private int imageNumber = 0;
168
169 private Value clipNumber;
170
171 private int width, height;
172
173
174
175
176
177
178 public SVGGraphics2D(File file, Dimension size) throws IOException {
179 this(new FileOutputStream(file), size);
180 this.filename = file.getPath();
181 }
182
183 public SVGGraphics2D(File file, Component component) throws IOException {
184 this(new FileOutputStream(file), component);
185 this.filename = file.getPath();
186 }
187
188 public SVGGraphics2D(OutputStream os, Dimension size) {
189 super(size, false);
190 init(os);
191 width = size.width;
192 height = size.height;
193 }
194
195 public SVGGraphics2D(OutputStream os, Component component) {
196 super(component, false);
197 init(os);
198 width = getSize().width;
199 height = getSize().height;
200 }
201
202 private void init(OutputStream os) {
203 this.ros = os;
204 initProperties(getDefaultProperties());
205
206 this.filename = null;
207
208 this.clipNumber = new Value().set(0);
209 }
210
211 protected SVGGraphics2D(SVGGraphics2D graphics, boolean doRestoreOnDispose) {
212 super(graphics, doRestoreOnDispose);
213
214 filename = graphics.filename;
215 os = graphics.os;
216 bbx = graphics.bbx;
217 bby = graphics.bby;
218 bbw = graphics.bbw;
219 bbh = graphics.bbh;
220 gradients = graphics.gradients;
221 textures = graphics.textures;
222 clipNumber = graphics.clipNumber;
223 fontTable = graphics.fontTable;
224 }
225
226
227
228
229
230
231
232
233
234
235 public void setBoundingBox() {
236 bbx = 0;
237 bby = 0;
238
239 Dimension size = getSize();
240 bbw = size.width;
241 bbh = size.height;
242 }
243
244
245
246
247
248
249
250
251
252
253
254
255
256 public void writeHeader() throws IOException {
257 ros = new BufferedOutputStream(ros);
258 if (isProperty(COMPRESS)) {
259 ros = new GZIPOutputStream(ros);
260 }
261
262 os = new PrintWriter(ros, true);
263 fontTable = new SVGFontTable();
264
265
266 setBoundingBox();
267 imageNumber = 0;
268
269 os.println("<?xml version=\"1.0\" standalone=\"no\"?>");
270 if (getProperty(VERSION).equals(VERSION_1_1)) {
271
272 } else {
273
274 }
275 os.println();
276
277 int x = 0;
278 int y = 0;
279 Dimension size = getPropertyDimension(IMAGE_SIZE);
280 int w = size.width;
281 if (w <= 0)
282 w = width;
283 int h = size.height;
284 if (h <= 0)
285 h = height;
286
287 os.println("<svg ");
288 if (getProperty(VERSION).equals(VERSION_1_1)) {
289 os.println(" version=\"1.1\"");
290 os.println(" baseProfile=\"full\"");
291 os.println(" xmlns=\"http://www.w3.org/2000/svg\"");
292 os.println(" xmlns:xlink=\"http://www.w3.org/1999/xlink\"");
293 os.println(" xmlns:ev=\"http://www.w3.org/2001/xml-events\"");
294 }
295 os.println(" x=\"" + x + "px\"");
296 os.println(" y=\"" + y + "px\"");
297 os.println(" width=\"" + w + "px\"");
298 os.println(" height=\"" + h + "px\"");
299 os.println(" viewBox=\"" + bbx + " " + bby + " " + bbw + " " + bbh
300 + "\"");
301 os.println(" >");
302 closeTags.push("</svg> <!-- bounding box -->");
303
304 os.print("<title>");
305 os.print(XMLWriter.normalizeText(getProperty(TITLE)));
306 os.println("</title>");
307
308 String producer = getClass().getName();
309 if (!isDeviceIndependent()) {
310 producer += " " + version.substring(1, version.length() - 1);
311 }
312
313 os.print("<desc>");
314 os.print("Creator: " + XMLWriter.normalizeText(getCreator()));
315 os.print(" Producer: " + XMLWriter.normalizeText(producer));
316 os.print(" Source: " + XMLWriter.normalizeText(getProperty(FOR)));
317 if (!isDeviceIndependent()) {
318 os.print(" Date: "
319 + DateFormat.getDateTimeInstance(DateFormat.FULL,
320 DateFormat.FULL).format(new Date()));
321 }
322 os.println("</desc>");
323
324
325 os.print("<g ");
326 Properties style = getStrokeProperties(defaultStroke, true);
327 os.print(style(style));
328 os.println(">");
329
330
331 closeTags.push("</g> <!-- default stroke -->");
332 }
333
334 public void writeBackground() throws IOException {
335 if (isProperty(TRANSPARENT)) {
336 setBackground(null);
337 } else if (isProperty(BACKGROUND)) {
338 setBackground(getPropertyColor(BACKGROUND_COLOR));
339 clearRect(0.0, 0.0, getSize().width, getSize().height);
340 } else {
341 setBackground(getComponent() != null ? getComponent()
342 .getBackground() : Color.WHITE);
343 clearRect(0.0, 0.0, getSize().width, getSize().height);
344 }
345 }
346
347
348
349
350
351
352
353 public void writeTrailer() throws IOException {
354
355 if (isProperty(EMBED_FONTS)) {
356 os.println("<defs>");
357 os.println(fontTable.toString());
358 os.println("</defs> <!-- font definitions -->");
359 }
360
361
362 writeGraphicsRestore();
363 }
364
365 public void closeStream() throws IOException {
366 os.close();
367 }
368
369
370
371
372
373
374
375 public Graphics create() {
376 try {
377 writeGraphicsSave();
378 } catch (IOException e) {
379 handleException(e);
380 }
381 return new SVGGraphics2D(this, true);
382 }
383
384 public Graphics create(double x, double y, double width, double height) {
385 try {
386 writeGraphicsSave();
387 } catch (IOException e) {
388 handleException(e);
389 }
390 SVGGraphics2D graphics = new SVGGraphics2D(this, true);
391
392 os.println("<svg x=\"" + fixedPrecision(x) + "\" " + "y=\""
393 + fixedPrecision(y) + "\" " + "width=\""
394 + fixedPrecision(width) + "\" " + "height=\""
395 + fixedPrecision(height) + "\" " + ">");
396 graphics.closeTags.push("</svg> <!-- graphics context -->");
397
398
399 os.print("<g ");
400 Properties style = getStrokeProperties(defaultStroke, true);
401 os.print(style(style));
402 os.println(">");
403
404 graphics.closeTags.push("</g> <!-- default stroke -->");
405
406 return graphics;
407 }
408
409 protected void writeGraphicsSave() throws IOException {
410
411 }
412
413 protected void writeGraphicsRestore() throws IOException {
414 while (!closeTags.empty()) {
415 os.println(closeTags.pop());
416 }
417 }
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432 public void draw(Shape shape) {
433
434
435 if (getStroke() instanceof BasicStroke) {
436 PathIterator path = shape.getPathIterator(null);
437
438 Properties style = new Properties();
439 if (getPaint() != null) {
440 style.put("stroke", hexColor(getPaint()));
441 style.put("stroke-opacity", fixedPrecision(alphaColor(getPaint())));
442 }
443
444
445 style.put("fill", "none");
446 style.putAll(getStrokeProperties(getStroke(), false));
447
448 writePathIterator(path, style);
449 } else if (getStroke() != null) {
450
451 fill(getStroke().createStrokedShape(shape));
452 } else {
453
454 fill(defaultStroke.createStrokedShape(shape));
455 }
456 }
457
458
459
460
461
462
463 public void fill(Shape shape) {
464
465 if (!(getPaint() instanceof Color || getPaint() instanceof GradientPaint)) {
466
467 fill(shape, getPaint());
468 } else {
469 PathIterator path = shape.getPathIterator(null);
470
471 Properties style = new Properties();
472
473 if (path.getWindingRule() == PathIterator.WIND_EVEN_ODD) {
474 style.put("fill-rule", "evenodd");
475 } else {
476 style.put("fill-rule", "nonzero");
477 }
478
479
480 if (getPaint() != null) {
481 style.put("fill", hexColor(getPaint()));
482 style.put("fill-opacity", fixedPrecision(alphaColor(getPaint())));
483 }
484
485
486 style.put("stroke", "none");
487
488 writePathIterator(path, style);
489 }
490 }
491
492
493
494
495
496
497
498
499 private void writePathIterator(PathIterator pi, Properties style) {
500 StringBuffer result = new StringBuffer();
501
502
503 result.append("<g ");
504 result.append(style(style));
505 result.append(">\n ");
506
507
508 result.append(getPath(pi));
509
510
511 result.append("\n</g> <!-- drawing style -->");
512
513 boolean drawClipped = false;
514
515
516 if (getClip() != null) {
517 GeneralPath gp = new GeneralPath();
518 gp.append(pi, true);
519
520 Stroke stroke = getStroke() == null? defaultStroke : getStroke();
521 Rectangle2D bounds = stroke.createStrokedShape(gp).getBounds();
522
523
524 drawClipped = getClip().intersects(bounds) && !getClip().contains(bounds);
525 }
526
527 if (drawClipped) {
528
529 os.println(
530 getTransformedString(
531 getTransform(),
532 getClippedString(result.toString())));
533 } else {
534
535 os.println(
536 getTransformedString(
537 getTransform(),
538 result.toString()));
539 }
540 }
541
542
543 public void copyArea(int x, int y, int width, int height, int dx, int dy) {
544 writeWarning(getClass()
545 + ": copyArea(int, int, int, int, int, int) not implemented.");
546 }
547
548 protected void writeImage(RenderedImage image, AffineTransform xform,
549 Color bkg) throws IOException {
550
551 StringBuffer result = new StringBuffer();
552
553 result.append("<image x=\"0\" y=\"0\" " + "width=\"");
554 result.append(image.getWidth());
555 result.append("\" " + "height=\"");
556 result.append(image.getHeight());
557 result.append("\" " + "xlink:href=\"");
558
559 String writeAs = getProperty(WRITE_IMAGES_AS);
560 boolean isTransparent = image.getColorModel().hasAlpha()
561 && (bkg == null);
562
563 String encode;
564 byte[] imageBytes;
565
566
567 if (ImageConstants.PNG.equalsIgnoreCase(writeAs) || isTransparent) {
568 encode = ImageConstants.PNG;
569 imageBytes = ImageGraphics2D.toByteArray(
570 image, ImageConstants.PNG, null, null);
571 }
572
573
574 else if (ImageConstants.JPG.equalsIgnoreCase(writeAs)) {
575 encode = ImageConstants.JPG;
576 imageBytes = ImageGraphics2D.toByteArray(
577 image, ImageConstants.JPG, null, null);
578 }
579
580
581 else {
582 byte[] pngBytes = ImageGraphics2D.toByteArray(image, ImageConstants.PNG, null, null);
583 byte[] jpgBytes = ImageGraphics2D.toByteArray(image, ImageConstants.JPG, null, null);
584
585
586 if (jpgBytes.length < 0.5 * pngBytes.length) {
587 encode = ImageConstants.JPG;
588 imageBytes = jpgBytes;
589 } else {
590 encode = ImageConstants.PNG;
591 imageBytes = pngBytes;
592 }
593 }
594
595 if (isProperty(EXPORT_IMAGES)) {
596 imageNumber++;
597
598
599 if (filename == null) {
600 writeWarning("SVG: cannot write embedded images, since SVGGraphics2D");
601 writeWarning(" was created from an OutputStream rather than a File.");
602 return;
603 }
604 int pos = filename.lastIndexOf(File.separatorChar);
605 String dirName = (pos < 0) ? "" : filename.substring(0, pos + 1);
606 String imageName = (pos < 0) ? filename : filename
607 .substring(pos + 1);
608 imageName += "." + getProperty(EXPORT_SUFFIX) + "-" + imageNumber
609 + "." + encode;
610
611 result.append(imageName);
612
613
614 FileOutputStream imageStream = new FileOutputStream(dirName
615 + imageName);
616
617 imageStream.write(imageBytes);
618 imageStream.close();
619 } else {
620 result.append("data:image/");
621 result.append(encode);
622 result.append(";base64,");
623
624 StringWriter writer = new StringWriter();
625 Base64OutputStream b64 = new Base64OutputStream(
626 new WriterOutputStream(writer));
627 b64.write(imageBytes);
628 b64.finish();
629
630 result.append(writer.toString());
631 }
632
633 result.append("\"/>");
634
635 os.println(getTransformedString(getTransform(),
636 getClippedString(getTransformedString(xform, result
637 .toString()))));
638 }
639
640
641 protected void writeString(String str, double x, double y)
642 throws IOException {
643
644
645 if (isProperty(EMBED_FONTS)) {
646 fontTable.addGlyphs(str, getFont());
647 }
648
649
650
651
652
653
654
655 Properties style = getFontProperties(getFont());
656
657
658 if (getPaint() != null) {
659 style.put("fill", hexColor(getPaint()));
660 style.put("fill-opacity", fixedPrecision(alphaColor(getPaint())));
661 } else {
662 style.put("fill", "none");
663 }
664 style.put("stroke", "none");
665
666
667 str = XMLWriter.normalizeText(str);
668
669
670 if (str.startsWith(" ")) {
671 str = " " + str.substring(1);
672 }
673
674 os.println(getTransformedString(
675
676 getTransform(),
677
678 getClippedString(
679 getTransformedString(
680
681 new AffineTransform(1, 0, 0, 1, x, y),
682 getTransformedString(
683
684 getFont().getTransform(),
685 "<text "
686
687 + style(style)
688
689 + " x=\"0\" y=\"0\">"
690
691 + str
692 + "</text>")))));
693 }
694
695
696
697
698
699
700
701
702
703
704 private Properties getFontProperties(Font font) {
705 Properties result = new Properties();
706
707
708 Map
709
710
711 SVGFontTable.normalize(attributes);
712
713
714 result.put("font-family", attributes.get(TextAttribute.FAMILY));
715
716
717 if (TextAttribute.WEIGHT_BOLD.equals(attributes.get(TextAttribute.WEIGHT))) {
718 result.put("font-weight", "bold");
719 } else {
720 result.put("font-weight", "normal");
721 }
722
723
724 if (TextAttribute.POSTURE_OBLIQUE.equals(attributes.get(TextAttribute.POSTURE))) {
725 result.put("font-style", "italic");
726 } else {
727 result.put("font-style", "normal");
728 }
729
730 Object ul = attributes.get(TextAttribute.UNDERLINE);
731 if (ul != null) {
732
733 if (TextAttribute.UNDERLINE_LOW_DOTTED.equals(ul)) {
734 result.put("text-underline-style", "dotted");
735 } else if (TextAttribute.UNDERLINE_LOW_DASHED.equals(ul)) {
736 result.put("text-underline-style", "dashed");
737 } else if (TextAttribute.UNDERLINE_ON.equals(ul)) {
738 result.put("text-underline-style", "solid");
739 }
740
741
742 result.put("text-decoration", "underline");
743 }
744
745 if (attributes.get(TextAttribute.STRIKETHROUGH) != null) {
746
747 if (ul == null) {
748 result.put("text-decoration", "underline, line-through");
749 } else {
750 result.put("text-decoration", "line-through");
751 }
752 }
753
754 Float size = (Float) attributes.get(TextAttribute.SIZE);
755 result.put("font-size", fixedPrecision(size.floatValue()));
756
757 return result;
758 }
759
760
761
762
763
764
765 protected void writeTransform(AffineTransform transform) throws IOException {
766
767 }
768
769 protected void writeSetTransform(AffineTransform transform)
770 throws IOException {
771
772 }
773
774
775
776
777
778
779 protected void writeClip(Shape s) throws IOException {
780
781 }
782
783 protected void writeSetClip(Shape s) throws IOException {
784
785 }
786
787
788
789
790
791
792
793 protected void writeWidth(float width) throws IOException {
794
795 }
796
797 protected void writeCap(int cap) throws IOException {
798
799 }
800
801 protected void writeJoin(int join) throws IOException {
802
803 }
804
805 protected void writeMiterLimit(float limit) throws IOException {
806
807 }
808
809 protected void writeDash(float[] dash, float phase) throws IOException {
810
811 }
812
813
814
815
816
817
818
819
820
821
822
823 private Properties getStrokeProperties(Stroke s, boolean all) {
824 Properties result = new Properties();
825
826
827 if (!(s instanceof BasicStroke)) {
828 return result;
829 }
830
831 BasicStroke stroke = (BasicStroke) s;
832
833
834 if (all || (stroke.getEndCap() != defaultStroke.getEndCap())) {
835
836 switch (stroke.getEndCap()) {
837 default:
838 case BasicStroke.CAP_BUTT:
839 result.put("stroke-linecap", "butt");
840 break;
841 case BasicStroke.CAP_ROUND:
842 result.put("stroke-linecap", "round");
843 break;
844 case BasicStroke.CAP_SQUARE:
845 result.put("stroke-linecap", "square");
846 break;
847 }
848 }
849
850
851 if (all
852 || !Arrays.equals(stroke.getDashArray(), defaultStroke
853 .getDashArray())) {
854 if (stroke.getDashArray() != null
855 && stroke.getDashArray().length > 0) {
856 StringBuffer array = new StringBuffer();
857 for (int i = 0; i < stroke.getDashArray().length; i++) {
858 if (i > 0) {
859 array.append(",");
860 }
861
862 float dash = stroke.getDashArray()[i];
863 array.append(fixedPrecision(dash > 0 ? dash : 0.1));
864 }
865 result.put("stroke-dasharray", array.toString());
866 } else {
867 result.put("stroke-dasharray", "none");
868 }
869 }
870
871 if (all || (stroke.getDashPhase() != defaultStroke.getDashPhase())) {
872 result.put("stroke-dashoffset", fixedPrecision(stroke.getDashPhase()));
873 }
874
875
876 if (all || (stroke.getMiterLimit() != defaultStroke.getMiterLimit())) {
877 result.put("stroke-miterlimit", fixedPrecision(stroke.getMiterLimit()));
878 }
879
880
881 if (all || (stroke.getLineJoin() != defaultStroke.getLineJoin())) {
882 switch (stroke.getLineJoin()) {
883 default:
884 case BasicStroke.JOIN_MITER:
885 result.put("stroke-linejoin", "miter");
886 break;
887 case BasicStroke.JOIN_ROUND:
888 result.put("stroke-linejoin", "round");
889 break;
890 case BasicStroke.JOIN_BEVEL:
891 result.put("stroke-linejoin", "bevel");
892 break;
893 }
894 }
895
896
897 if (all || (stroke.getLineWidth() != defaultStroke.getLineWidth())) {
898
899 if (stroke.getLineWidth() == 0) {
900 result.put("stroke-width", fixedPrecision(0.000001f));
901 } else {
902 result.put("stroke-width", fixedPrecision(stroke.getLineWidth()));
903 }
904 }
905
906 return result;
907 }
908
909
910 public void setPaintMode() {
911 writeWarning(getClass() + ": setPaintMode() not implemented.");
912 }
913
914 public void setXORMode(Color c1) {
915 writeWarning(getClass() + ": setXORMode(Color) not implemented.");
916 }
917
918 protected void writePaint(Color c) throws IOException {
919
920 }
921
922 protected void writePaint(GradientPaint paint) throws IOException {
923 if (gradients.get(paint) == null) {
924 String name = "gradient-" + gradients.size();
925 gradients.put(paint, name);
926 Point2D p1 = paint.getPoint1();
927 Point2D p2 = paint.getPoint2();
928 os.println("<defs>");
929 os.print(" <linearGradient id=\"" + name + "\" ");
930 os.print("x1=\"" + fixedPrecision(p1.getX()) + "\" ");
931 os.print("y1=\"" + fixedPrecision(p1.getY()) + "\" ");
932 os.print("x2=\"" + fixedPrecision(p2.getX()) + "\" ");
933 os.print("y2=\"" + fixedPrecision(p2.getY()) + "\" ");
934 os.print("gradientUnits=\"userSpaceOnUse\" ");
935 os.print("spreadMethod=\""
936 + ((paint.isCyclic()) ? "reflect" : "pad") + "\" ");
937 os.println(">");
938 os.println(" <stop offset=\"0\" stop-color=\""
939 + hexColor(paint.getColor1()) + "\" " + "opacity-stop=\""
940 + alphaColor(paint.getColor1()) + "\" />");
941 os.println(" <stop offset=\"1\" stop-color=\""
942 + hexColor(paint.getColor2()) + "\" " + "opacity-stop=\""
943 + alphaColor(paint.getColor2()) + "\" />");
944 os.println(" </linearGradient>");
945 os.println("</defs>");
946 }
947
948
949 Properties style = new Properties();
950 style.put("stroke", hexColor(getPaint()));
951
952
953 os.print("<g ");
954 os.print(style(style));
955 os.println(">");
956
957
958 closeTags.push("</g> <!-- color -->");
959 }
960
961 protected void writePaint(TexturePaint paint) throws IOException {
962
963 }
964
965 protected void writePaint(Paint p) throws IOException {
966
967 }
968
969
970 protected void writeFont(Font font) throws IOException {
971
972 }
973
974
975
976
977
978
979 public GraphicsConfiguration getDeviceConfiguration() {
980 writeWarning(getClass() + ": getDeviceConfiguration() not implemented.");
981 return null;
982 }
983
984 public void writeComment(String s) throws IOException {
985 os.println("<!-- " + s + " -->");
986 }
987
988 public String toString() {
989 return "SVGGraphics2D";
990 }
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006 private String getTransformedString(AffineTransform t, String s) {
1007 StringBuffer result = new StringBuffer();
1008
1009 if (t != null && !t.isIdentity()) {
1010 result.append("<g transform=\"matrix(");
1011 result.append(fixedPrecision(t.getScaleX()));
1012 result.append(", ");
1013 result.append(fixedPrecision(t.getShearY()));
1014 result.append(", ");
1015 result.append(fixedPrecision(t.getShearX()));
1016 result.append(", ");
1017 result.append(fixedPrecision(t.getScaleY()));
1018 result.append(", ");
1019 result.append(fixedPrecision(t.getTranslateX()));
1020 result.append(", ");
1021 result.append(fixedPrecision(t.getTranslateY()));
1022 result.append(")\">\n");
1023 }
1024
1025 result.append(s);
1026
1027 if (t != null && !t.isIdentity()) {
1028 result.append("\n</g> <!-- transform -->");
1029 }
1030
1031 return result.toString();
1032 }
1033
1034
1035
1036
1037
1038
1039
1040 private String getClippedString(String s) {
1041 StringBuffer result = new StringBuffer();
1042
1043
1044 if (isProperty(CLIP) && getClip() != null) {
1045
1046 clipNumber.set(clipNumber.getInt() + 1);
1047
1048
1049 result.append("<clipPath id=\"clip");
1050 result.append(clipNumber.getInt());
1051 result.append("\">\n ");
1052 result.append(getPath(getClip().getPathIterator(null)));
1053 result.append("\n</clipPath>\n");
1054
1055
1056 result.append("<g clip-path=\"url(#clip");
1057 result.append(clipNumber.getInt());
1058 result.append(")\">\n");
1059 }
1060
1061
1062 result.append(s);
1063
1064
1065 if (isProperty(CLIP) && getClip() != null) {
1066 result.append("\n</g> <!-- clip");
1067 result.append(clipNumber.getInt());
1068 result.append(" -->");
1069 }
1070
1071 return result.toString();
1072 }
1073
1074 private float alphaColor(Paint p) {
1075 if (p instanceof Color) {
1076 return (float) (getPrintColor((Color) p).getAlpha() / 255.0);
1077 } else if (p instanceof GradientPaint) {
1078 return 1.0f;
1079 } else if (p instanceof TexturePaint) {
1080 return 1.0f;
1081 }
1082 writeWarning(getClass() + ": alphaColor() not implemented for "
1083 + p.getClass() + ".");
1084 return 1.0f;
1085 }
1086
1087 private String hexColor(Paint p) {
1088 if (p instanceof Color) {
1089 return hexColor(getPrintColor((Color) p));
1090 } else if (p instanceof GradientPaint) {
1091 return hexColor((GradientPaint) p);
1092 } else if (p instanceof TexturePaint) {
1093 return hexColor((TexturePaint) p);
1094 }
1095 writeWarning(getClass() + ": hexColor() not implemented for "
1096 + p.getClass() + ".");
1097 return "#000000";
1098 }
1099
1100 private String hexColor(Color c) {
1101 String s1 = Integer.toHexString(c.getRed());
1102 s1 = (s1.length() != 2) ? "0" + s1 : s1;
1103
1104 String s2 = Integer.toHexString(c.getGreen());
1105 s2 = (s2.length() != 2) ? "0" + s2 : s2;
1106
1107 String s3 = Integer.toHexString(c.getBlue());
1108 s3 = (s3.length() != 2) ? "0" + s3 : s3;
1109
1110 return "#" + s1 + s2 + s3;
1111 }
1112
1113 private String hexColor(GradientPaint p) {
1114 return "url(#" + gradients.get(p) + ")";
1115 }
1116
1117 private String hexColor(TexturePaint p) {
1118 return "url(#" + textures.get(p) + ")";
1119 }
1120
1121 protected static String getPathContent(PathIterator path) {
1122 StringBuffer result = new StringBuffer();
1123
1124 double[] coords = new double[6];
1125 result.append("d=\"");
1126 while (!path.isDone()) {
1127 int segType = path.currentSegment(coords);
1128
1129 switch (segType) {
1130 case PathIterator.SEG_MOVETO:
1131 result.append("M ");
1132 result.append(fixedPrecision(coords[0]));
1133 result.append(" ");
1134 result.append(fixedPrecision(coords[1]));
1135 break;
1136 case PathIterator.SEG_LINETO:
1137 result.append("L ");
1138 result.append(fixedPrecision(coords[0]));
1139 result.append(" ");
1140 result.append(fixedPrecision(coords[1]));
1141 break;
1142 case PathIterator.SEG_CUBICTO:
1143 result.append("C ");
1144 result.append(fixedPrecision(coords[0]));
1145 result.append(" ");
1146 result.append(fixedPrecision(coords[1]));
1147 result.append(" ");
1148 result.append(fixedPrecision(coords[2]));
1149 result.append(" ");
1150 result.append(fixedPrecision(coords[3]));
1151 result.append(" ");
1152 result.append(fixedPrecision(coords[4]));
1153 result.append(" ");
1154 result.append(fixedPrecision(coords[5]));
1155 break;
1156 case PathIterator.SEG_QUADTO:
1157 result.append("Q ");
1158 result.append(fixedPrecision(coords[0]));
1159 result.append(" ");
1160 result.append(fixedPrecision(coords[1]));
1161 result.append(" ");
1162 result.append(fixedPrecision(coords[2]));
1163 result.append(" ");
1164 result.append(fixedPrecision(coords[3]));
1165 break;
1166 case PathIterator.SEG_CLOSE:
1167 result.append("z");
1168 break;
1169 }
1170
1171
1172 path.next();
1173
1174
1175 if (!path.isDone()) {
1176 result.append(" ");
1177 }
1178 }
1179 result.append("\"");
1180
1181 return result.toString();
1182 }
1183
1184 protected String getPath(PathIterator path) {
1185 StringBuffer result = new StringBuffer();
1186
1187 result.append("<path ");
1188 result.append(getPathContent(path));
1189 result.append("/>");
1190
1191 return result.toString();
1192 }
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204 private String style(Properties style) {
1205 if (style == null || style.isEmpty()) {
1206 return "";
1207 }
1208
1209 StringBuffer result = new StringBuffer();
1210 boolean styleable = isProperty(STYLABLE);
1211
1212
1213 if (styleable) {
1214 result.append("style=\"");
1215 }
1216
1217 Enumeration keys = style.keys();
1218 while (keys.hasMoreElements()) {
1219 String key = (String) keys.nextElement();
1220 String value = style.getProperty(key);
1221
1222 result.append(key);
1223
1224 if (styleable) {
1225 result.append(":");
1226 result.append(value);
1227 result.append(";");
1228 } else {
1229 result.append("=\"");
1230 result.append(value);
1231 result.append("\"");
1232 if (keys.hasMoreElements()) {
1233 result.append(" ");
1234 }
1235 }
1236 }
1237
1238
1239 if (styleable) {
1240 result.append("\"");
1241 }
1242
1243 return result.toString();
1244 }
1245
1246
1247
1248
1249
1250 private static DecimalFormat scientific = new DecimalFormat(
1251 "#.####################",
1252 new DecimalFormatSymbols(Locale.US));
1253
1254
1255
1256
1257
1258
1259
1260 public static String fixedPrecision(double d) {
1261 return scientific.format(d);
1262 }
1263
1264 protected PrintWriter getOutputStream() {
1265 return os;
1266 }
1267 }