View Javadoc

1   // Copyright 2000-2007 FreeHEP
2   package org.freehep.graphicsio.emf;
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.Point;
14  import java.awt.Rectangle;
15  import java.awt.Shape;
16  import java.awt.Stroke;
17  import java.awt.TexturePaint;
18  import java.awt.Toolkit;
19  import java.awt.geom.AffineTransform;
20  import java.awt.geom.GeneralPath;
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.HashMap;
30  import java.util.Map;
31  import java.util.Properties;
32  
33  import org.freehep.graphics2d.PrintColor;
34  import org.freehep.graphics2d.VectorGraphics;
35  import org.freehep.graphics2d.font.FontEncoder;
36  import org.freehep.graphics2d.font.FontUtilities;
37  import org.freehep.graphicsio.AbstractVectorGraphicsIO;
38  import org.freehep.graphicsio.PageConstants;
39  import org.freehep.graphicsio.emf.gdi.AlphaBlend;
40  import org.freehep.graphicsio.emf.gdi.BeginPath;
41  import org.freehep.graphicsio.emf.gdi.CreateBrushIndirect;
42  import org.freehep.graphicsio.emf.gdi.DeleteObject;
43  import org.freehep.graphicsio.emf.gdi.EOF;
44  import org.freehep.graphicsio.emf.gdi.EndPath;
45  import org.freehep.graphicsio.emf.gdi.ExtCreateFontIndirectW;
46  import org.freehep.graphicsio.emf.gdi.ExtCreatePen;
47  import org.freehep.graphicsio.emf.gdi.ExtLogFontW;
48  import org.freehep.graphicsio.emf.gdi.ExtLogPen;
49  import org.freehep.graphicsio.emf.gdi.ExtTextOutW;
50  import org.freehep.graphicsio.emf.gdi.FillPath;
51  import org.freehep.graphicsio.emf.gdi.LogBrush32;
52  import org.freehep.graphicsio.emf.gdi.ModifyWorldTransform;
53  import org.freehep.graphicsio.emf.gdi.RestoreDC;
54  import org.freehep.graphicsio.emf.gdi.SaveDC;
55  import org.freehep.graphicsio.emf.gdi.SelectClipPath;
56  import org.freehep.graphicsio.emf.gdi.SelectObject;
57  import org.freehep.graphicsio.emf.gdi.SetBkMode;
58  import org.freehep.graphicsio.emf.gdi.SetMapMode;
59  import org.freehep.graphicsio.emf.gdi.SetMiterLimit;
60  import org.freehep.graphicsio.emf.gdi.SetPolyFillMode;
61  import org.freehep.graphicsio.emf.gdi.SetTextAlign;
62  import org.freehep.graphicsio.emf.gdi.SetTextColor;
63  import org.freehep.graphicsio.emf.gdi.SetViewportExtEx;
64  import org.freehep.graphicsio.emf.gdi.SetViewportOrgEx;
65  import org.freehep.graphicsio.emf.gdi.SetWindowExtEx;
66  import org.freehep.graphicsio.emf.gdi.SetWindowOrgEx;
67  import org.freehep.graphicsio.emf.gdi.SetWorldTransform;
68  import org.freehep.graphicsio.emf.gdi.StrokeAndFillPath;
69  import org.freehep.graphicsio.emf.gdi.StrokePath;
70  import org.freehep.graphicsio.emf.gdi.TextW;
71  import org.freehep.graphicsio.font.FontTable;
72  import org.freehep.util.UserProperties;
73  import org.freehep.util.images.ImageUtilities;
74  
75  /**
76   * Enhanced Metafile Format Graphics 2D driver.
77   *
78   * @author Mark Donszelmann
79   * @version $Id: EMFGraphics2D.java 10516 2007-02-06 21:11:19Z duns $
80   */
81  public class EMFGraphics2D extends AbstractVectorGraphicsIO implements
82          EMFConstants {
83      public static final String version = "$Revision: 10516 $";
84  
85      private EMFHandleManager handleManager;
86  
87      private int penHandle;
88  
89      private int brushHandle;
90  
91      private Rectangle imageBounds;
92  
93      private OutputStream ros;
94  
95      private EMFOutputStream os;
96  
97      private Color textColor = null;
98  
99      private Color penColor = null;
100 
101     private Color brushColor = null;
102 
103     private Map fontTable; // java fonts
104 
105     private Map unitFontTable; // windows fonts
106 
107     private EMFPathConstructor pathConstructor;
108 
109     private boolean evenOdd;
110 
111     private static final Rectangle dummy = new Rectangle(0, 0, 0, 0);
112 
113     /*
114      * ================================================================================
115      * Table of Contents: ------------------ 1. Constructors & Factory Methods
116      * 2. Document Settings 3. Header, Trailer, Multipage & Comments 3.1 Header &
117      * Trailer 3.2 MultipageDocument methods 4. Create & Dispose 5. Drawing
118      * Methods 5.1. shapes (draw/fill) 5.1.1. lines, rectangles, round
119      * rectangles 5.1.2. polylines, polygons 5.1.3. ovals, arcs 5.1.4. shapes
120      * 5.2. Images 5.3. Strings 6. Transformations 7. Clipping 8. Graphics State /
121      * Settings 8.1. stroke/linewidth 8.2. paint/color 8.3. font 8.4. rendering
122      * hints 9. Auxiliary 10. Private/Utility Methos
123      * ================================================================================
124      */
125     private static final String rootKey = EMFGraphics2D.class.getName();
126 
127     public static final String TRANSPARENT = rootKey + "."
128             + PageConstants.TRANSPARENT;
129 
130     public static final String BACKGROUND = rootKey + "."
131             + PageConstants.BACKGROUND;
132 
133     public static final String BACKGROUND_COLOR = rootKey + "."
134             + PageConstants.BACKGROUND_COLOR;
135 
136     private static final UserProperties defaultProperties = new UserProperties();
137     static {
138         defaultProperties.setProperty(TRANSPARENT, true);
139         defaultProperties.setProperty(BACKGROUND, false);
140         defaultProperties.setProperty(BACKGROUND_COLOR, Color.GRAY);
141         defaultProperties.setProperty(CLIP, true);
142         // NOTE: using TEXT_AS_SHAPES makes the text shapes quite unreadable.
143         defaultProperties.setProperty(TEXT_AS_SHAPES, false);
144     }
145 
146     public static Properties getDefaultProperties() {
147         return defaultProperties;
148     }
149 
150     public static void setDefaultProperties(Properties newProperties) {
151         defaultProperties.setProperties(newProperties);
152     }
153 
154     /*
155      * ================================================================================
156      * 1. Constructors & Factory Methods
157      * ================================================================================
158      */
159     public EMFGraphics2D(File file, Dimension size)
160             throws FileNotFoundException {
161         this(new FileOutputStream(file), size);
162     }
163 
164     public EMFGraphics2D(File file, Component component)
165             throws FileNotFoundException {
166         this(new FileOutputStream(file), component);
167     }
168 
169     public EMFGraphics2D(OutputStream os, Dimension size) {
170         super(size, false);
171         this.imageBounds = new Rectangle(0, 0, size.width, size.height);
172         init(os);
173     }
174 
175     public EMFGraphics2D(OutputStream os, Component component) {
176         super(component, false);
177         this.imageBounds = new Rectangle(0, 0, getSize().width,
178                 getSize().height);
179         init(os);
180     }
181 
182     private void init(OutputStream os) {
183         fontTable = new HashMap();
184         unitFontTable = new HashMap();
185         evenOdd = false;
186 
187         handleManager = new EMFHandleManager();
188         ros = os;
189         initProperties(defaultProperties);
190     }
191 
192     protected EMFGraphics2D(EMFGraphics2D graphics, boolean doRestoreOnDispose) {
193         super(graphics, doRestoreOnDispose);
194         // Create a graphics context from a given graphics context.
195         // This constructor is used by the system to clone a given graphics
196         // context.
197         // doRestoreOnDispose is used to call writeGraphicsRestore(),
198         // when the graphics context is being disposed off.
199         os = graphics.os;
200         imageBounds = graphics.imageBounds;
201         handleManager = graphics.handleManager;
202         fontTable = graphics.fontTable;
203         unitFontTable = graphics.unitFontTable;
204         pathConstructor = graphics.pathConstructor;
205         evenOdd = graphics.evenOdd;
206         textColor = graphics.textColor;
207         penColor = graphics.penColor;
208         brushColor = graphics.brushColor;
209     }
210 
211     /*
212      * ================================================================================ |
213      * 2. Document Settings
214      * ================================================================================
215      */
216 
217     /*
218      * ================================================================================ |
219      * 3. Header, Trailer, Multipage & Comments
220      * ================================================================================
221      */
222     /* 3.1 Header & Trailer */
223     public void writeHeader() throws IOException {
224         ros = new BufferedOutputStream(ros);
225         Dimension device = isDeviceIndependent() ? new Dimension(1024, 768)
226                 : Toolkit.getDefaultToolkit().getScreenSize();
227         String producer = getClass().getName();
228         if (!isDeviceIndependent()) {
229             producer += " " + version.substring(1, version.length() - 1);
230         }
231         os = new EMFOutputStream(ros, imageBounds, handleManager, getCreator(),
232                 producer, device);
233         pathConstructor = new EMFPathConstructor(os, imageBounds);
234 
235         Point orig = new Point(imageBounds.x, imageBounds.y);
236         Dimension size = new Dimension(imageBounds.width, imageBounds.height);
237 
238         os.writeTag(new SetMapMode(MM_ANISOTROPIC));
239         os.writeTag(new SetWindowOrgEx(orig));
240         os.writeTag(new SetWindowExtEx(size));
241         os.writeTag(new SetViewportOrgEx(orig));
242         os.writeTag(new SetViewportExtEx(size));
243         os.writeTag(new SetTextAlign(TA_BASELINE));
244         os.writeTag(new SetTextColor(getColor()));
245         os.writeTag(new SetPolyFillMode(EMFConstants.WINDING));
246 
247     }
248 
249     public void writeGraphicsState() throws IOException {
250         super.writeGraphicsState();
251         // write a special matrix here to scale all written coordinates by a
252         // factor of TWIPS
253         AffineTransform n = AffineTransform.getScaleInstance(1.0 / TWIPS,
254                 1.0 / TWIPS);
255         os.writeTag(new SetWorldTransform(n));
256     }
257 
258     public void writeBackground() throws IOException {
259         if (isProperty(TRANSPARENT)) {
260             setBackground(null);
261             os.writeTag(new SetBkMode(BKG_TRANSPARENT));
262         } else if (isProperty(BACKGROUND)) {
263             os.writeTag(new SetBkMode(BKG_OPAQUE));
264             setBackground(getPropertyColor(BACKGROUND_COLOR));
265             clearRect(0.0, 0.0, getSize().width, getSize().height);
266         } else {
267             os.writeTag(new SetBkMode(BKG_OPAQUE));
268             setBackground(getComponent() != null ? getComponent()
269                     .getBackground() : Color.WHITE);
270             clearRect(0.0, 0.0, getSize().width, getSize().height);
271         }
272     }
273 
274     public void writeTrailer() throws IOException {
275         // delete any remaining objects
276         for (;;) {
277             int handle = handleManager.highestHandleInUse();
278             if (handle < 0)
279                 break;
280             os.writeTag(new DeleteObject(handle));
281             handleManager.freeHandle(handle);
282         }
283         os.writeTag(new EOF());
284     }
285 
286     public void closeStream() throws IOException {
287         os.close();
288     }
289 
290     /* 3.2 MultipageDocument methods */
291 
292     /*
293      * ================================================================================
294      * 4. Create & Dispose
295      * ================================================================================
296      */
297 
298     public Graphics create() {
299         // Create a new graphics context from the current one.
300         try {
301             // Save the current context for restore later.
302             writeGraphicsSave();
303         } catch (IOException e) {
304             handleException(e);
305         }
306         // The correct graphics context should be created.
307         return new EMFGraphics2D(this, true);
308     }
309 
310     public Graphics create(double x, double y, double width, double height) {
311         // Create a new graphics context from the current one.
312         try {
313             // Save the current context for restore later.
314             writeGraphicsSave();
315         } catch (IOException e) {
316             handleException(e);
317         }
318         // The correct graphics context should be created.
319         VectorGraphics graphics = new EMFGraphics2D(this, true);
320         graphics.translate(x, y);
321         graphics.clipRect(0, 0, width, height);
322         return graphics;
323     }
324 
325     protected void writeGraphicsSave() throws IOException {
326         os.writeTag(new SaveDC());
327     }
328 
329     protected void writeGraphicsRestore() throws IOException {
330         if (penHandle != 0)
331             os.writeTag(new DeleteObject(handleManager.freeHandle(penHandle)));
332         if (brushHandle != 0)
333             os
334                     .writeTag(new DeleteObject(handleManager
335                             .freeHandle(brushHandle)));
336         os.writeTag(new RestoreDC());
337     }
338 
339     /*
340      * ================================================================================ |
341      * 5. Drawing Methods
342      * ================================================================================
343      */
344     /* 5.1.4. shapes */
345     public void draw(Shape shape) {
346         try {
347             if (getStroke() instanceof BasicStroke) {
348                 writePen((BasicStroke) getStroke(), getColor());
349                 writePath(shape);
350                 os.writeTag(new StrokePath(imageBounds));
351             } else {
352                 writeBrush(getColor());
353                 writePath(getStroke().createStrokedShape(shape));
354                 os.writeTag(new FillPath(imageBounds));
355             }
356         } catch (IOException e) {
357             handleException(e);
358         }
359     }
360 
361     public void fill(Shape shape) {
362         try {
363             if (getPaint() instanceof Color) {
364                 writeBrush(getColor());
365                 writePath(shape);
366                 os.writeTag(new FillPath(imageBounds));
367             } else {
368                 // draw paint as image
369                 fill(shape, getPaint());
370             }
371         } catch (IOException e) {
372             handleException(e);
373         }
374     }
375 
376     public void fillAndDraw(Shape shape, Color fillColor) {
377         try {
378             if (getPaint() instanceof Color) {
379                 writePen((BasicStroke) getStroke(), getColor());
380                 writeBrush(fillColor);
381                 writePath(shape);
382                 os.writeTag(new StrokeAndFillPath(imageBounds));
383             } else {
384                 // draw paint as image
385                 fill(shape, getPaint());
386                 // draw shape
387                 draw(shape);
388             }
389         } catch (IOException e) {
390             handleException(e);
391         }
392     }
393 
394     /* 5.2. Images */
395     public void copyArea(int x, int y, int width, int height, int dx, int dy) {
396         writeWarning(getClass()
397                 + ": copyArea(int, int, int, int, int, int) not implemented.");
398         // Mostly unimplemented.
399     }
400 
401     // NOTE: does not use writeGraphicsSave and writeGraphicsRestore since these
402     // delete pen and brush
403     protected void writeImage(RenderedImage image, AffineTransform xform,
404             Color bkg) throws IOException {
405         os.writeTag(new SaveDC());
406 
407         AffineTransform imageTransform = new AffineTransform(
408             1.0, 0.0, 0.0, -1.0, 0.0, image.getHeight());
409         imageTransform.preConcatenate(xform);
410         writeTransform(imageTransform);
411 
412         BufferedImage bufferedImage = ImageUtilities.createBufferedImage(
413             image, null, null);
414         AlphaBlend alphaBlend = new AlphaBlend(
415             imageBounds,
416             toUnit(0),
417             toUnit(0),
418             toUnit(image.getWidth()),
419             toUnit(image.getHeight()),
420             new AffineTransform(),
421             bufferedImage,
422             bkg);
423 
424         os.writeTag(alphaBlend);
425         os.writeTag(new RestoreDC());
426     }
427 
428     /* 5.3. Strings */
429     public void writeString(String string, double x, double y)
430             throws IOException {
431 
432         Color color;
433         Paint paint = getPaint();
434         if (paint instanceof Color) {
435             color = (Color) paint;
436         } else if (paint instanceof GradientPaint) {
437             GradientPaint gp = (GradientPaint) paint;
438             color = PrintColor.mixColor(gp.getColor1(), gp.getColor2());
439         } else {
440             Color bkg = getBackground();
441             if (bkg == null) {
442                 color = Color.BLACK;
443             } else {
444                 color = PrintColor.invert(bkg);
445             }
446         }
447         if (!color.equals(textColor)) {
448             textColor = color;
449             os.writeTag(new SetTextColor(textColor));
450         }
451 
452         // dialog.bold -> Dialog with TextAttribute.WEIGHT_BOLD
453         Map attributes = FontUtilities.getAttributes(getFont());
454         FontTable.normalize(attributes);
455         Font font = new Font(attributes);
456 
457         Font unitFont = (Font) unitFontTable.get(font);
458 
459         Integer fontIndex = (Integer) fontTable.get(font);
460         if (fontIndex == null) {
461             // for special fonts (Symbol, ZapfDingbats) we choose a standard
462             // font and
463             // encode using unicode.
464             String fontName = font.getName();
465             string = FontEncoder.getEncodedString(string, fontName);
466 
467             String windowsFontName = FontUtilities
468                 .getWindowsFontName(fontName);
469 
470             unitFont = new Font(windowsFontName, font.getStyle(), font
471                 .getSize());
472             unitFont = unitFont.deriveFont(font.getSize2D()
473                 * UNITS_PER_PIXEL * TWIPS);
474             unitFontTable.put(font, unitFont);
475 
476             ExtLogFontW logFontW = new ExtLogFontW(unitFont);
477             int handle = handleManager.getHandle();
478             os.writeTag(new ExtCreateFontIndirectW(handle, logFontW));
479 
480             fontIndex = new Integer(handle);
481             fontTable.put(font, fontIndex);
482         }
483         os.writeTag(new SelectObject(fontIndex.intValue()));
484 
485         int[] widths = new int[string.length()];
486         for (int i = 0; i < widths.length; i++) {
487             double w = unitFont.getStringBounds(string, i, i + 1,
488                 getFontRenderContext()).getWidth();
489             widths[i] = (int) w;
490         }
491 
492         // font transformation sould _not_ transform string position
493         translate(x, y);
494 
495         // apply font transformation
496         AffineTransform t = font.getTransform();
497         if (!t.isIdentity()) {
498             writeGraphicsSave();
499             writeTransform(t);
500         }
501 
502         TextW text = new TextW(new Point(0, 0), string, 0, dummy, widths);
503         os.writeTag(new ExtTextOutW(imageBounds, EMFConstants.GM_ADVANCED, 1, 1, text));
504 
505         // revert font transformation
506         if (!t.isIdentity()) {
507             writeGraphicsRestore();
508         }
509 
510         // translation for string position.
511         translate(-x, -y);
512     }
513 
514     /*
515      * ================================================================================ |
516      * 6. Transformations
517      * ================================================================================
518      */
519     protected void writeTransform(AffineTransform t) throws IOException {
520         AffineTransform n = new AffineTransform(t.getScaleX(), t.getShearY(), t
521                 .getShearX(), t.getScaleY(), t.getTranslateX()
522                 * UNITS_PER_PIXEL * TWIPS, t.getTranslateY() * UNITS_PER_PIXEL
523                 * TWIPS);
524         os.writeTag(new ModifyWorldTransform(n, EMFConstants.MWT_LEFTMULTIPLY));
525     }
526 
527     protected void writeSetTransform(AffineTransform t) throws IOException {
528         // write a special matrix here to scale all written coordinates by a factor of TWIPS
529         AffineTransform n = AffineTransform.getScaleInstance(1.0/TWIPS, 1.0/TWIPS);
530         os.writeTag(new SetWorldTransform(n));
531         // apply transform
532         writeTransform(t);
533     }
534 
535     /*
536      * ================================================================================ |
537      * 7. Clipping
538      * ================================================================================
539      */
540     protected void writeSetClip(Shape s) throws IOException {
541         if (!isProperty(CLIP)) {
542             return;
543         }
544 
545         // if s == null the clip is reset to the imageBounds
546         if (s == null && imageBounds != null) {
547             s = new Rectangle(imageBounds);
548             AffineTransform at = getTransform();
549             if (at != null) {
550                 s = at.createTransformedShape(s);
551             }
552         }
553 
554         writePath(s);
555         os.writeTag(new SelectClipPath(EMFConstants.RGN_COPY));
556     }
557 
558    protected void writeClip(Shape s) throws IOException {
559 
560        if (s == null || !isProperty(CLIP)) {
561            return;
562        }
563 
564        writePath(s);
565        os.writeTag(new SelectClipPath(EMFConstants.RGN_AND));
566    }
567 
568     /*
569      * ================================================================================ |
570      * 8. Graphics State
571      * ================================================================================
572      */
573     public void writeStroke(Stroke stroke) throws IOException {
574         if (stroke instanceof BasicStroke) {
575             writePen((BasicStroke) stroke, getColor());
576         }
577     }
578 
579     /* 8.2. paint/color */
580     public void setPaintMode() {
581         writeWarning(getClass() + ": setPaintMode() not implemented.");
582         // Mostly unimplemented.
583     }
584 
585     public void setXORMode(Color c1) {
586         writeWarning(getClass() + ": setXORMode(Color) not implemented.");
587         // Mostly unimplemented.
588     }
589 
590     protected void writePaint(Color p) throws IOException {
591         // all color setting delayed
592     }
593 
594     protected void writePaint(GradientPaint p) throws IOException {
595         // all paint setting delayed
596     }
597 
598     protected void writePaint(TexturePaint p) throws IOException {
599         // all paint setting delayed
600     }
601 
602     protected void writePaint(Paint p) throws IOException {
603         // all paint setting delayed
604     }
605 
606     /* 8.3. font */
607     protected void writeFont(Font font) throws IOException {
608     	// written when needed
609     }
610 
611     /* 8.4. rendering hints */
612 
613     /*
614      * ================================================================================ |
615      * 9. Auxiliary
616      * ================================================================================
617      */
618     public GraphicsConfiguration getDeviceConfiguration() {
619         writeWarning(getClass() + ": getDeviceConfiguration() not implemented.");
620         // Mostly unimplemented
621         return null;
622     }
623 
624     public void writeComment(String comment) throws IOException {
625         writeWarning(getClass() + ": writeComment(String) not implemented.");
626         // Write out the comment.
627     }
628 
629     public String toString() {
630         return "EMFGraphics2D";
631     }
632 
633     /**
634      * Implementation of createShape makes sure that the points are different by
635      * at least one Unit.
636      */
637     protected Shape createShape(double[] xPoints, double[] yPoints,
638             int nPoints, boolean close) {
639         GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
640         if (nPoints > 0) {
641             path.moveTo((float) xPoints[0], (float) yPoints[0]);
642             double lastX = xPoints[0];
643             double lastY = yPoints[0];
644             if (close && (Math.abs(xPoints[nPoints - 1] - lastX) < 1)
645                     && (Math.abs(yPoints[nPoints - 1] - lastY) < 1)) {
646                 nPoints--;
647             }
648             for (int i = 1; i < nPoints; i++) {
649                 if ((Math.abs(xPoints[i] - lastX) > 1)
650                         || (Math.abs(yPoints[i] - lastY) > 1)) {
651                     path.lineTo((float) xPoints[i], (float) yPoints[i]);
652                     lastX = xPoints[i];
653                     lastY = yPoints[i];
654                 }
655             }
656             if (close)
657                 path.closePath();
658         }
659         return path;
660     }
661 
662     /*
663      * Private methods
664      */
665     private boolean writePath(Shape shape) throws IOException {
666         boolean eo = EMFPathConstructor.isEvenOdd(shape);
667         if (eo != evenOdd) {
668             evenOdd = eo;
669             os.writeTag(new SetPolyFillMode((evenOdd) ? EMFConstants.ALTERNATE
670                     : EMFConstants.WINDING));
671         }
672         os.writeTag(new BeginPath());
673         pathConstructor.addPath(shape);
674         os.writeTag(new EndPath());
675         return evenOdd;
676     }
677 
678     private void writePen(BasicStroke stroke, Color color) throws IOException {
679         if (color.equals(penColor) && stroke.equals(getStroke()))
680             return;
681         penColor = color;
682 
683         int style = EMFConstants.PS_GEOMETRIC;
684 
685         switch (stroke.getEndCap()) {
686         case BasicStroke.CAP_BUTT:
687             style |= EMFConstants.PS_ENDCAP_FLAT;
688             break;
689         case BasicStroke.CAP_ROUND:
690             style |= EMFConstants.PS_ENDCAP_ROUND;
691             break;
692         case BasicStroke.CAP_SQUARE:
693             style |= EMFConstants.PS_ENDCAP_SQUARE;
694             break;
695         }
696 
697         switch (stroke.getLineJoin()) {
698         case BasicStroke.JOIN_MITER:
699             style |= EMFConstants.PS_JOIN_MITER;
700             break;
701         case BasicStroke.JOIN_ROUND:
702             style |= EMFConstants.PS_JOIN_ROUND;
703             break;
704         case BasicStroke.JOIN_BEVEL:
705             style |= EMFConstants.PS_JOIN_BEVEL;
706             break;
707         }
708 
709         // FIXME int conversion
710         // FIXME phase ignored
711         float[] dashArray = stroke.getDashArray();
712         int[] dash = new int[(dashArray != null) ? dashArray.length : 0];
713         style |= (dash.length == 0) ? EMFConstants.PS_SOLID
714                 : EMFConstants.PS_USERSTYLE;
715         for (int i = 0; i < dash.length; i++) {
716             dash[i] = toUnit(dashArray[i]);
717         }
718 
719         int brushStyle = (color.getAlpha() == 0) ? EMFConstants.BS_NULL
720                 : EMFConstants.BS_SOLID;
721 
722         ExtLogPen pen = new ExtLogPen(style, toUnit(stroke.getLineWidth()),
723                 brushStyle, getPrintColor(color), 0, dash);
724         if (penHandle != 0) {
725             os.writeTag(new DeleteObject(penHandle));
726         } else {
727             penHandle = handleManager.getHandle();
728         }
729         os.writeTag(new ExtCreatePen(penHandle, pen));
730         os.writeTag(new SelectObject(penHandle));
731 
732         if (!(getStroke() instanceof BasicStroke)
733                 || (((BasicStroke) getStroke()).getMiterLimit() != stroke
734                         .getMiterLimit())) {
735             os.writeTag(new SetMiterLimit(toUnit(stroke.getMiterLimit())));
736         }
737     }
738 
739     private void writeBrush(Color color) throws IOException {
740         if (color.equals(brushColor))
741             return;
742         brushColor = color;
743 
744         int brushStyle = (color.getAlpha() == 0) ? EMFConstants.BS_NULL
745                 : EMFConstants.BS_SOLID;
746 
747         LogBrush32 brush = new LogBrush32(brushStyle, getPrintColor(color), 0);
748         if (brushHandle != 0) {
749             os.writeTag(new DeleteObject(brushHandle));
750         } else {
751             brushHandle = handleManager.getHandle();
752         }
753         os.writeTag(new CreateBrushIndirect(brushHandle, brush));
754         os.writeTag(new SelectObject(brushHandle));
755     }
756 
757     private int toUnit(double d) {
758         return (int) Math.floor(d * UNITS_PER_PIXEL * TWIPS);
759     }
760 }