1
2 package org.freehep.graphicsio.swf;
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.Rectangle;
14 import java.awt.Shape;
15 import java.awt.Stroke;
16 import java.awt.TexturePaint;
17 import java.awt.font.GlyphVector;
18 import java.awt.geom.AffineTransform;
19 import java.awt.geom.Area;
20 import java.awt.geom.Rectangle2D;
21 import java.awt.image.BufferedImage;
22 import java.awt.image.RenderedImage;
23 import java.io.BufferedOutputStream;
24 import java.io.File;
25 import java.io.FileNotFoundException;
26 import java.io.FileOutputStream;
27 import java.io.IOException;
28 import java.io.OutputStream;
29 import java.util.Properties;
30 import java.util.Vector;
31
32 import org.freehep.graphics2d.PrintColor;
33 import org.freehep.graphics2d.VectorGraphics;
34 import org.freehep.graphics2d.font.FontEncoder;
35 import org.freehep.graphicsio.AbstractVectorGraphicsIO;
36 import org.freehep.graphicsio.ImageConstants;
37 import org.freehep.graphicsio.PageConstants;
38 import org.freehep.util.UserProperties;
39 import org.freehep.util.Value;
40
41
42
43
44
45
46
47 public class SWFGraphics2D extends AbstractVectorGraphicsIO implements
48 SWFConstants {
49
50 private static final int SWF_VERSION = 8;
51 private static final String rootKey = SWFGraphics2D.class.getName();
52
53 public static final String TRANSPARENT = rootKey + "."
54 + PageConstants.TRANSPARENT;
55
56 public static final String BACKGROUND = rootKey + "."
57 + PageConstants.BACKGROUND;
58
59 public static final String BACKGROUND_COLOR = rootKey + "."
60 + PageConstants.BACKGROUND_COLOR;
61
62 public static final String WRITE_IMAGES_AS = rootKey + "."
63 + ImageConstants.WRITE_IMAGES_AS;
64
65 private static final UserProperties defaultProperties = new UserProperties();
66 static {
67 defaultProperties.setProperty(TRANSPARENT, true);
68 defaultProperties.setProperty(BACKGROUND, false);
69 defaultProperties.setProperty(BACKGROUND_COLOR, Color.GRAY);
70 defaultProperties.setProperty(WRITE_IMAGES_AS, ImageConstants.SMALLEST);
71
72 defaultProperties.setProperty(CLIP, false);
73 defaultProperties.setProperty(TEXT_AS_SHAPES, true);
74 }
75
76 public static Properties getDefaultProperties() {
77 return defaultProperties;
78 }
79
80 public static void setDefaultProperties(Properties newProperties) {
81 defaultProperties.setProperties(newProperties);
82 }
83
84 public final static String version = "$Revision: 9981 $";
85
86 private OutputStream ros;
87
88 private SWFOutputStream os;
89
90 private Value id;
91
92 private Value depth;
93
94 private static final float frameRate = 20.0f;
95
96 private boolean compress = true;
97
98 private LineStyleArray lineStyles;
99
100 private FillStyleArray fillStyles;
101
102
103 private Color textColor;
104
105
106 private Shape unwrittenClip = null;
107
108 private AffineTransform clipTransform = null;
109
110 private int clipID, clipDepthID, showClipID, showClipDepthID;
111
112
113 boolean fillStroke;
114
115
116 private static final boolean showBounds = false;
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136 public SWFGraphics2D(File file, Dimension size)
137 throws FileNotFoundException {
138 this(new FileOutputStream(file), size);
139 }
140
141 public SWFGraphics2D(File file, Component component)
142 throws FileNotFoundException {
143 this(new FileOutputStream(file), component);
144 }
145
146 public SWFGraphics2D(OutputStream os, Dimension size) {
147 super(size, true);
148 init(os);
149 }
150
151 public SWFGraphics2D(OutputStream os, Component component) {
152 super(component, true);
153 init(os);
154 }
155
156 private void init(OutputStream os) {
157 initProperties(getDefaultProperties());
158 ros = os;
159 id = new Value().set(1);
160 depth = new Value().set(1);
161 textColor = getColor();
162 fillStroke = true;
163 }
164
165 protected SWFGraphics2D(SWFGraphics2D graphics, boolean doRestoreOnDispose) {
166 super(graphics, doRestoreOnDispose);
167
168
169
170
171
172 os = graphics.os;
173 id = graphics.id;
174 depth = graphics.depth;
175 lineStyles = new LineStyleArray();
176 lineStyles.add(graphics.lineStyles.get(0));
177 if (showBounds || isProperty(CLIP)) {
178 lineStyles.add(graphics.lineStyles.get(1));
179 lineStyles.add(graphics.lineStyles.get(2));
180 }
181 fillStyles = new FillStyleArray();
182 fillStyles.add(graphics.fillStyles.get(0));
183 textColor = graphics.textColor;
184 fillStroke = graphics.fillStroke;
185 }
186
187
188
189
190
191
192
193
194
195
196
197
198
199 public void writeHeader() throws IOException {
200 os = new SWFOutputStream(new BufferedOutputStream(ros), SWF_VERSION, getSize(),
201 frameRate, compress);
202
203 String description = getCreator() + ":" + getClass().getName();
204 if (!isDeviceIndependent()) {
205 description += " " + version.substring(1, version.length() - 1);
206 }
207
208 }
209
210 public void writeBackground() throws IOException {
211 if (isProperty(TRANSPARENT)) {
212 setBackground(null);
213 } else if (isProperty(BACKGROUND)) {
214 setBackground(getPropertyColor(BACKGROUND_COLOR));
215 clearRect(0.0, 0.0, getSize().width, getSize().height);
216 } else {
217 setBackground(getComponent() != null ? getComponent()
218 .getBackground() : Color.WHITE);
219 clearRect(0.0, 0.0, getSize().width, getSize().height);
220 }
221 }
222
223 public void writeTrailer() throws IOException {
224 os.writeTag(new ShowFrame());
225 os.writeTag(new End());
226 }
227
228 public void closeStream() throws IOException {
229 os.close();
230 }
231
232
233
234
235
236
237
238
239
240 public Graphics create() {
241
242 try {
243
244 writeGraphicsSave();
245 } catch (IOException e) {
246 handleException(e);
247 }
248
249 return new SWFGraphics2D(this, true);
250 }
251
252 public Graphics create(double x, double y, double width, double height) {
253
254 try {
255
256 writeGraphicsSave();
257 } catch (IOException e) {
258 handleException(e);
259 }
260
261 VectorGraphics graphics = new SWFGraphics2D(this, true);
262 graphics.translate(x, y);
263 graphics.clipRect(0, 0, width, height);
264 return graphics;
265 }
266
267 protected void writeGraphicsSave() throws IOException {
268 }
269
270 protected void writeGraphicsRestore() throws IOException {
271 popStreamAndWriteClip();
272 }
273
274
275
276
277
278
279
280
281 public void draw(Shape shape) {
282 Shape strokedShape = getStroke().createStrokedShape(shape);
283 if (fillStroke) {
284
285 fill(new Area(strokedShape));
286 return;
287 }
288
289 try {
290 Rectangle2D bounds = strokedShape.getBounds2D();
291 SWFShape swfShape = createShape(shape, 1, 0, -1);
292 os.writeTag(new DefineShape4(id.getInt(), bounds, fillStyles,
293 lineStyles, swfShape));
294 os.writeTag(new PlaceObject2(id.getInt(), depth.getInt(),
295 getTransform()));
296 id.set(id.getInt() + 1);
297 depth.set(depth.getInt() + 1);
298
299 if (showBounds) {
300 SWFShape swfBounds = createShape(bounds, 2, 0, -1);
301 os.writeTag(new DefineShape4(id.getInt(), bounds, fillStyles,
302 lineStyles, swfBounds));
303 os.writeTag(new PlaceObject2(id.getInt(), depth.getInt(),
304 getTransform()));
305 id.set(id.getInt() + 1);
306 depth.set(depth.getInt() + 1);
307 }
308
309 } catch (IOException e) {
310 handleException(e);
311 }
312 }
313
314 public void fill(Shape shape) {
315 try {
316 Rectangle2D bounds = new BasicStroke().createStrokedShape(shape)
317 .getBounds2D();
318 SWFShape swfShape = createShape(shape, 0, 1, -1);
319 os.writeTag(new DefineShape4(id.getInt(), bounds, fillStyles,
320 lineStyles, swfShape));
321 os.writeTag(new PlaceObject2(id.getInt(), depth.getInt(),
322 getTransform()));
323 id.set(id.getInt() + 1);
324 depth.set(depth.getInt() + 1);
325
326 if (showBounds) {
327 SWFShape swfBounds = createShape(bounds, 2, 0, -1);
328 os.writeTag(new DefineShape4(id.getInt(), bounds, fillStyles,
329 lineStyles, swfBounds));
330 os.writeTag(new PlaceObject2(id.getInt(), depth.getInt(),
331 getTransform()));
332 id.set(id.getInt() + 1);
333 depth.set(depth.getInt() + 1);
334 }
335
336 boolean eo = SWFPathConstructor.isEvenOdd(shape);
337 if (!eo) {
338 writeWarning(getClass()
339 + ": cannot fill using non-zero winding rule, used even-odd instead.");
340 }
341 } catch (IOException e) {
342 handleException(e);
343 }
344 }
345
346 public void fillAndDraw(Shape shape, Color fillColor) {
347 try {
348 setFillColor(fillColor);
349 fill(shape);
350 setFillColor(getColor());
351 draw(shape);
352 } catch (IOException e) {
353 handleException(e);
354 }
355 }
356
357
358 public void copyArea(int x, int y, int width, int height, int dx, int dy) {
359 writeWarning(getClass()
360 + ": copyArea(int, int, int, int, int, int) not implemented.");
361
362 }
363
364 protected void writeImage(RenderedImage image, AffineTransform xform,
365 Color bkg) throws IOException {
366
367
368 int imageID = id.getInt();
369 os.writeTag(getImageTag(imageID, image, bkg));
370 id.set(id.getInt() + 1);
371
372
373 Shape shape = xform.createTransformedShape(new Rectangle(0, 0, image
374 .getWidth(), image.getHeight()));
375
376 AffineTransform imageTransform = new AffineTransform(TWIPS, 0, 0,
377 TWIPS, 0, 0);
378 xform.concatenate(imageTransform);
379
380
381 SWFShape imageShape = createShape(shape, 0, 0, 1);
382 FillStyleArray imageFill = new FillStyleArray();
383 imageFill.add(new FillStyle(imageID, false, xform));
384
385 LineStyleArray imageLine = new LineStyleArray();
386 Rectangle bounds = shape.getBounds();
387 os.writeTag(new DefineShape4(id.getInt(), bounds, imageFill, imageLine,
388 imageShape));
389
390
391 os.writeTag(new PlaceObject2(id.getInt(), depth.getInt(),
392 getTransform()));
393 id.set(id.getInt() + 1);
394 depth.set(depth.getInt() + 1);
395
396 if (showBounds) {
397 SWFShape swfBounds = createShape(bounds, 2, 0, -1);
398 os.writeTag(new DefineShape4(id.getInt(), bounds, fillStyles,
399 lineStyles, swfBounds));
400 os.writeTag(new PlaceObject2(id.getInt(), depth.getInt(),
401 getTransform()));
402 id.set(id.getInt() + 1);
403 depth.set(depth.getInt() + 1);
404 }
405 }
406
407 private final static Properties replaceFonts = new Properties();
408 static {
409 replaceFonts.setProperty("Symbol", "Serif");
410 replaceFonts.setProperty("ZapfDingbats", "Serif");
411 }
412
413
414 protected void writeString(String string, double x, double y)
415 throws IOException {
416
417
418
419 String fontName = getFont().getName();
420 string = FontEncoder.getEncodedString(string, fontName);
421 fontName = replaceFonts.getProperty(fontName, null);
422
423 Font font = (fontName == null) ? getFont() : new Font(fontName,
424 getFont().getStyle(), getFont().getSize());
425 Font font1024 = font.deriveFont((float) (1024.0 / TWIPS));
426
427 GlyphVector glyphs = font1024.createGlyphVector(getFontRenderContext(),
428 string);
429 Rectangle2D bounds = font.createGlyphVector(getFontRenderContext(),
430 string).getVisualBounds();
431 bounds.setRect(bounds.getX() + x, bounds.getY() + y, bounds.getWidth(),
432 bounds.getHeight());
433
434
435 int fontID = id.getInt();
436 DefineFont swfFont = new DefineFont(fontID);
437 id.set(id.getInt() + 1);
438
439
440 Vector text = new Vector();
441 DefineText2.RecordType1 record1 = new DefineText2.RecordType1(fontID,
442 textColor, (int) (x * TWIPS), (int) (y * TWIPS), (int) (font
443 .getSize2D() * TWIPS));
444 DefineText2.RecordType0 record0 = new DefineText2.RecordType0();
445 text.add(record1);
446 text.add(record0);
447 int textID = id.getInt();
448 DefineText2 swfText = new DefineText2(textID, bounds,
449 new AffineTransform(), text);
450 id.set(id.getInt() + 1);
451
452
453 for (int i = 0; i < glyphs.getNumGlyphs(); i++) {
454
455 swfFont.add(createShape(glyphs.getGlyphOutline(i), -1, 1, -1));
456
457
458
459 record0.add(new DefineText2.GlyphEntry(i, 0));
460 }
461
462
463 os.writeTag(swfFont);
464 os.writeTag(swfText);
465
466
467 os.writeTag(new PlaceObject2(textID, depth.getInt(), getTransform()));
468 depth.set(depth.getInt() + 1);
469
470 if (showBounds) {
471 SWFShape swfBounds = createShape(bounds, 2, 0, -1);
472 os.writeTag(new DefineShape4(id.getInt(), bounds, fillStyles,
473 lineStyles, swfBounds));
474 os.writeTag(new PlaceObject2(id.getInt(), depth.getInt(),
475 getTransform()));
476 id.set(id.getInt() + 1);
477 depth.set(depth.getInt() + 1);
478 }
479 }
480
481
482
483
484
485
486
487 protected void writeTransform(AffineTransform t) throws IOException {
488
489 }
490
491 protected void writeSetTransform(AffineTransform t) throws IOException {
492
493 }
494
495
496
497
498
499
500 protected void writeSetClip(Shape s) throws IOException {
501 writeClip(s);
502 }
503
504 protected void writeClip(Shape s) throws IOException {
505
506 popStreamAndWriteClip();
507
508 unwrittenClip = s;
509 clipTransform = (unwrittenClip != null) ? new AffineTransform(
510 getTransform()) : null;
511
512 if (unwrittenClip != null) {
513
514 clipID = id.getInt();
515 id.set(id.getInt() + 1);
516 clipDepthID = depth.getInt();
517 depth.set(depth.getInt() + 1);
518
519 if (isProperty(CLIP)) {
520 showClipID = id.getInt();
521 id.set(id.getInt() + 1);
522 showClipDepthID = depth.getInt();
523 depth.set(depth.getInt() + 1);
524 }
525
526 os.pushBuffer();
527 } else {
528 clipID = 0;
529 clipDepthID = 0;
530
531 if (isProperty(CLIP)) {
532 showClipID = 0;
533 showClipDepthID = 0;
534 }
535 }
536 }
537
538
539
540
541
542
543 protected void writeStroke(Stroke stroke) throws IOException {
544 fillStroke = true;
545 if (stroke instanceof BasicStroke) {
546 BasicStroke bs = (BasicStroke) stroke;
547 setPen(bs, getColor());
548 if (bs.getLineWidth() == 0) {
549 fillStroke = false;
550 }
551 }
552 }
553
554
555 public void setPaintMode() {
556 writeWarning(getClass() + ": setPaintMode() not implemented.");
557
558 }
559
560 public void setXORMode(Color c1) {
561 writeWarning(getClass() + ": setXORMode(Color) not implemented.");
562
563 }
564
565 protected void writePaint(Color p) throws IOException {
566 setPen((BasicStroke) getStroke(), p);
567 setFillColor(p);
568 textColor = PrintColor.createPrintColor(p);
569 }
570
571 protected void writePaint(GradientPaint p) throws IOException {
572 Gradient[] gradient = new Gradient[2];
573 gradient[0] = new Gradient(0, p.getColor1());
574 gradient[1] = new Gradient(255, p.getColor2());
575 double x0 = p.getPoint1().getX();
576 double y0 = p.getPoint1().getY();
577 double dx = p.getPoint2().getX() - x0;
578 double dy = p.getPoint2().getY() - y0;
579 double scale = p.getPoint1().distance(p.getPoint2()) * TWIPS / 32768;
580 double angle = Math.atan2(dy, dx);
581 AffineTransform transform = new AffineTransform(scale, 0, 0, scale, dx
582 / 2 + x0, dy / 2 + y0);
583 transform.rotate(angle);
584
585 int spreadMode = p.isCyclic() ? FillStyle.SPREAD_MODE_REFLECT : FillStyle.SPREAD_MODE_PAD;
586 fillStyles = new FillStyleArray();
587 fillStyles.add(new FillStyle(gradient, FillStyle.LINEAR_GRADIENT, spreadMode, FillStyle.INTERPOLATION_MODE_NORMAL_RGB, 0.0f, transform));
588
589 textColor = PrintColor.mixColor(p.getColor1(), p.getColor2());
590 }
591
592 protected void writePaint(TexturePaint p) throws IOException {
593
594 BufferedImage image = p.getImage();
595 int imageID = id.getInt();
596 os.writeTag(getImageTag(imageID, image, null));
597 id.set(id.getInt() + 1);
598
599
600 Rectangle2D a = p.getAnchorRect();
601 double sx = a.getWidth() / image.getWidth();
602 double sy = a.getHeight() / image.getHeight();
603 fillStyles = new FillStyleArray();
604 fillStyles.add(new FillStyle(imageID, true, new AffineTransform(sx
605 * TWIPS, 0, 0, sy * TWIPS, a.getX(), a.getY())));
606 textColor = PrintColor.black;
607 }
608
609 protected void writePaint(Paint p) throws IOException {
610 writeWarning(getClass() + ": writePaint(Paint) not implemented for "
611 + p.getClass());
612
613 writePaint(Color.BLACK);
614 }
615
616
617 protected void writeFont(Font font) throws IOException {
618
619 }
620
621
622
623
624
625
626
627
628 public GraphicsConfiguration getDeviceConfiguration() {
629 writeWarning(getClass() + ": getDeviceConfiguration() not implemented.");
630
631 return null;
632 }
633
634 public boolean hit(Rectangle rect, Shape s, boolean onStroke) {
635 writeWarning(getClass()
636 + ": hit(Rectangle, Shape, boolean) not implemented.");
637
638 return false;
639 }
640
641 public void writeComment(String comment) throws IOException {
642 writeWarning(getClass() + ": writeComment(String) not implemented.");
643
644 }
645
646 public String toString() {
647 return "SWFGraphics2D";
648 }
649
650
651
652
653 SWFShape createShape(Shape shape, int stroke, int fill0, int fill1)
654 throws IOException {
655
656 AffineTransform t = getTransform();
657 double resolution = 0.5 / (TWIPS * Math.min(t.getScaleX(), t
658 .getScaleY()));
659
660 Vector path = new Vector();
661 SWFPathConstructor pc = new SWFPathConstructor(path, stroke, fill0,
662 fill1, resolution);
663 pc.addPath(shape);
664 return new SWFShape(path);
665 }
666
667 private void setPen(BasicStroke stroke, Color color) throws IOException {
668 lineStyles = new LineStyleArray();
669 lineStyles.add(new LineStyle((int) (stroke.getLineWidth() * TWIPS), getPrintColor(color)));
670
671 if (showBounds || isProperty(CLIP)) {
672 lineStyles.add(new LineStyle(TWIPS, Color.cyan));
673 lineStyles.add(new LineStyle(TWIPS, Color.orange));
674 }
675 }
676
677 private void setFillColor(Color color) throws IOException {
678 fillStyles = new FillStyleArray();
679 fillStyles.add(new FillStyle(getPrintColor(color)));
680 }
681
682 private void popStreamAndWriteClip() throws IOException {
683 if (unwrittenClip == null)
684 return;
685
686 if ((clipID == 0) || (clipDepthID == 0)) {
687 System.err
688 .println("SWFGraphics2D: internal error, invalid clipID or clipDepthID");
689 return;
690 }
691
692
693 os.popBuffer();
694
695 Rectangle2D bounds = unwrittenClip.getBounds2D();
696 SWFShape clipShape = createShape(unwrittenClip, 0, 1, -1);
697 os.writeTag(new DefineShape4(clipID, bounds, fillStyles, lineStyles,
698 clipShape));
699
700 int clipDepth = depth.getInt() - 1;
701 os.writeTag(new PlaceObject2(clipID, clipDepthID, clipTransform,
702 clipDepth));
703
704
705
706
707 if (isProperty(CLIP)) {
708 if ((showClipID == 0) || (showClipDepthID == 0)) {
709 System.err
710 .println("SWFGraphics2D: internal error, invalid showClipID or showClipDepthID");
711 return;
712 }
713
714 SWFShape swfBounds = createShape(bounds, 3, 0, -1);
715 os.writeTag(new DefineShape4(showClipID, bounds, fillStyles,
716 lineStyles, swfBounds));
717 os.writeTag(new PlaceObject(showClipID, showClipDepthID,
718 clipTransform));
719 }
720
721
722 os.append();
723 }
724
725 private SWFTag getImageTag(int imageID, RenderedImage image, Color bkg)
726 throws IOException {
727 String writeAs = getProperty(WRITE_IMAGES_AS);
728
729 DefineBitsLossless2 flateTag = null;
730 if (writeAs.equals(ImageConstants.ZLIB)
731 || writeAs.equals(ImageConstants.SMALLEST)) {
732 flateTag = new DefineBitsLossless2(imageID, image, bkg);
733 }
734
735 DefineBitsJPEG3 jpgTag = null;
736 if (writeAs.equals(ImageConstants.JPG)
737 || writeAs.equals(ImageConstants.SMALLEST)) {
738 jpgTag = new DefineBitsJPEG3(imageID, image, bkg, new Properties());
739 }
740
741 SWFTag imageTag;
742 if (writeAs.equals(ImageConstants.ZLIB)) {
743 imageTag = flateTag;
744 } else if (writeAs.equals(ImageConstants.JPG)) {
745 imageTag = jpgTag;
746 } else {
747 imageTag = (jpgTag.getLength() < 0.5 * flateTag.getLength()) ? (SWFTag) jpgTag
748 : (SWFTag) flateTag;
749 }
750
751 return imageTag;
752 }
753 }