// Copyright 2000-2007 FreeHEP package org.freehep.graphicsio.emf; import com.google.code.appengine.awt.BasicStroke; import com.google.code.appengine.awt.Color; import com.google.code.appengine.awt.Dimension; import com.google.code.appengine.awt.Font; import com.google.code.appengine.awt.GradientPaint; import com.google.code.appengine.awt.Graphics; import com.google.code.appengine.awt.GraphicsConfiguration; import com.google.code.appengine.awt.Paint; import com.google.code.appengine.awt.Point; import com.google.code.appengine.awt.Rectangle; import com.google.code.appengine.awt.Shape; import com.google.code.appengine.awt.Stroke; import com.google.code.appengine.awt.TexturePaint; import com.google.code.appengine.awt.Toolkit; import com.google.code.appengine.awt.geom.AffineTransform; import com.google.code.appengine.awt.geom.GeneralPath; import com.google.code.appengine.awt.image.BufferedImage; import com.google.code.appengine.awt.image.RenderedImage; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.HashMap; import java.util.Map; import java.util.Properties; import org.freehep.graphics2d.PrintColor; import org.freehep.graphics2d.VectorGraphics; import org.freehep.graphics2d.font.FontEncoder; import org.freehep.graphics2d.font.FontUtilities; import org.freehep.graphicsio.AbstractVectorGraphicsIO; import org.freehep.graphicsio.PageConstants; import org.freehep.graphicsio.emf.gdi.AlphaBlend; import org.freehep.graphicsio.emf.gdi.BeginPath; import org.freehep.graphicsio.emf.gdi.CreateBrushIndirect; import org.freehep.graphicsio.emf.gdi.DeleteObject; import org.freehep.graphicsio.emf.gdi.EOF; import org.freehep.graphicsio.emf.gdi.EndPath; import org.freehep.graphicsio.emf.gdi.ExtCreateFontIndirectW; import org.freehep.graphicsio.emf.gdi.ExtCreatePen; import org.freehep.graphicsio.emf.gdi.ExtLogFontW; import org.freehep.graphicsio.emf.gdi.ExtLogPen; import org.freehep.graphicsio.emf.gdi.ExtTextOutW; import org.freehep.graphicsio.emf.gdi.FillPath; import org.freehep.graphicsio.emf.gdi.LogBrush32; import org.freehep.graphicsio.emf.gdi.ModifyWorldTransform; import org.freehep.graphicsio.emf.gdi.RestoreDC; import org.freehep.graphicsio.emf.gdi.SaveDC; import org.freehep.graphicsio.emf.gdi.SelectClipPath; import org.freehep.graphicsio.emf.gdi.SelectObject; import org.freehep.graphicsio.emf.gdi.SetBkMode; import org.freehep.graphicsio.emf.gdi.SetMapMode; import org.freehep.graphicsio.emf.gdi.SetMiterLimit; import org.freehep.graphicsio.emf.gdi.SetPolyFillMode; import org.freehep.graphicsio.emf.gdi.SetTextAlign; import org.freehep.graphicsio.emf.gdi.SetTextColor; import org.freehep.graphicsio.emf.gdi.SetViewportExtEx; import org.freehep.graphicsio.emf.gdi.SetViewportOrgEx; import org.freehep.graphicsio.emf.gdi.SetWindowExtEx; import org.freehep.graphicsio.emf.gdi.SetWindowOrgEx; import org.freehep.graphicsio.emf.gdi.SetWorldTransform; import org.freehep.graphicsio.emf.gdi.StrokeAndFillPath; import org.freehep.graphicsio.emf.gdi.StrokePath; import org.freehep.graphicsio.emf.gdi.TextW; import org.freehep.graphicsio.font.FontTable; import org.freehep.util.UserProperties; import org.freehep.util.images.ImageUtilities; /** * Enhanced Metafile Format Graphics 2D driver. * * @author Mark Donszelmann * @version $Id: EMFGraphics2D.java 10516 2007-02-06 21:11:19Z duns $ */ public class EMFGraphics2D extends AbstractVectorGraphicsIO implements EMFConstants { public static final String version = "$Revision: 10516 $"; private EMFHandleManager handleManager; private int penHandle; private int brushHandle; private Rectangle imageBounds; private OutputStream ros; private EMFOutputStream os; private Color textColor = null; private Color penColor = null; private Color brushColor = null; private Map fontTable; // java fonts private Map unitFontTable; // windows fonts private EMFPathConstructor pathConstructor; private boolean evenOdd; private static final Rectangle dummy = new Rectangle(0, 0, 0, 0); /* * ================================================================================ * Table of Contents: ------------------ 1. Constructors & Factory Methods * 2. Document Settings 3. Header, Trailer, Multipage & Comments 3.1 Header & * Trailer 3.2 MultipageDocument methods 4. Create & Dispose 5. Drawing * Methods 5.1. shapes (draw/fill) 5.1.1. lines, rectangles, round * rectangles 5.1.2. polylines, polygons 5.1.3. ovals, arcs 5.1.4. shapes * 5.2. Images 5.3. Strings 6. Transformations 7. Clipping 8. Graphics State / * Settings 8.1. stroke/linewidth 8.2. paint/color 8.3. font 8.4. rendering * hints 9. Auxiliary 10. Private/Utility Methos * ================================================================================ */ private static final String rootKey = EMFGraphics2D.class.getName(); public static final String TRANSPARENT = rootKey + "." + PageConstants.TRANSPARENT; public static final String BACKGROUND = rootKey + "." + PageConstants.BACKGROUND; public static final String BACKGROUND_COLOR = rootKey + "." + PageConstants.BACKGROUND_COLOR; private static final UserProperties defaultProperties = new UserProperties(); static { defaultProperties.setProperty(TRANSPARENT, true); defaultProperties.setProperty(BACKGROUND, false); defaultProperties.setProperty(BACKGROUND_COLOR, Color.GRAY); defaultProperties.setProperty(CLIP, true); // NOTE: using TEXT_AS_SHAPES makes the text shapes quite unreadable. defaultProperties.setProperty(TEXT_AS_SHAPES, false); } public static Properties getDefaultProperties() { return defaultProperties; } public static void setDefaultProperties(Properties newProperties) { defaultProperties.setProperties(newProperties); } /* * ================================================================================ * 1. Constructors & Factory Methods * ================================================================================ */ public EMFGraphics2D(File file, Dimension size) throws FileNotFoundException { this(new FileOutputStream(file), size); } public EMFGraphics2D(OutputStream os, Dimension size) { super(size, false); this.imageBounds = new Rectangle(0, 0, size.width, size.height); init(os); } private void init(OutputStream os) { fontTable = new HashMap(); unitFontTable = new HashMap(); evenOdd = false; handleManager = new EMFHandleManager(); ros = os; initProperties(defaultProperties); } protected EMFGraphics2D(EMFGraphics2D graphics, boolean doRestoreOnDispose) { super(graphics, doRestoreOnDispose); // Create a graphics context from a given graphics context. // This constructor is used by the system to clone a given graphics // context. // doRestoreOnDispose is used to call writeGraphicsRestore(), // when the graphics context is being disposed off. os = graphics.os; imageBounds = graphics.imageBounds; handleManager = graphics.handleManager; fontTable = graphics.fontTable; unitFontTable = graphics.unitFontTable; pathConstructor = graphics.pathConstructor; evenOdd = graphics.evenOdd; textColor = graphics.textColor; penColor = graphics.penColor; brushColor = graphics.brushColor; } /* * ================================================================================ | * 2. Document Settings * ================================================================================ */ /* * ================================================================================ | * 3. Header, Trailer, Multipage & Comments * ================================================================================ */ /* 3.1 Header & Trailer */ public void writeHeader() throws IOException { ros = new BufferedOutputStream(ros); Dimension device = new Dimension(1024, 768); String producer = getClass().getName(); if (!isDeviceIndependent()) { producer += " " + version.substring(1, version.length() - 1); } os = new EMFOutputStream(ros, imageBounds, handleManager, getCreator(), producer, device); pathConstructor = new EMFPathConstructor(os, imageBounds); Point orig = new Point(imageBounds.x, imageBounds.y); Dimension size = new Dimension(imageBounds.width, imageBounds.height); os.writeTag(new SetMapMode(MM_ANISOTROPIC)); os.writeTag(new SetWindowOrgEx(orig)); os.writeTag(new SetWindowExtEx(size)); os.writeTag(new SetViewportOrgEx(orig)); os.writeTag(new SetViewportExtEx(size)); os.writeTag(new SetTextAlign(TA_BASELINE)); os.writeTag(new SetTextColor(getColor())); os.writeTag(new SetPolyFillMode(EMFConstants.WINDING)); } public void writeGraphicsState() throws IOException { super.writeGraphicsState(); // write a special matrix here to scale all written coordinates by a // factor of TWIPS AffineTransform n = AffineTransform.getScaleInstance(1.0 / TWIPS, 1.0 / TWIPS); os.writeTag(new SetWorldTransform(n)); } public void writeBackground() throws IOException { if (isProperty(TRANSPARENT)) { setBackground(null); os.writeTag(new SetBkMode(BKG_TRANSPARENT)); } else if (isProperty(BACKGROUND)) { os.writeTag(new SetBkMode(BKG_OPAQUE)); setBackground(getPropertyColor(BACKGROUND_COLOR)); clearRect(0.0, 0.0, getSize().width, getSize().height); } else { os.writeTag(new SetBkMode(BKG_OPAQUE)); setBackground(Color.WHITE); clearRect(0.0, 0.0, getSize().width, getSize().height); } } public void writeTrailer() throws IOException { // delete any remaining objects for (;;) { int handle = handleManager.highestHandleInUse(); if (handle < 0) break; os.writeTag(new DeleteObject(handle)); handleManager.freeHandle(handle); } os.writeTag(new EOF()); } public void closeStream() throws IOException { os.close(); } /* 3.2 MultipageDocument methods */ /* * ================================================================================ * 4. Create & Dispose * ================================================================================ */ public Graphics create() { // Create a new graphics context from the current one. try { // Save the current context for restore later. writeGraphicsSave(); } catch (IOException e) { handleException(e); } // The correct graphics context should be created. return new EMFGraphics2D(this, true); } public Graphics create(double x, double y, double width, double height) { // Create a new graphics context from the current one. try { // Save the current context for restore later. writeGraphicsSave(); } catch (IOException e) { handleException(e); } // The correct graphics context should be created. VectorGraphics graphics = new EMFGraphics2D(this, true); graphics.translate(x, y); graphics.clipRect(0, 0, width, height); return graphics; } protected void writeGraphicsSave() throws IOException { os.writeTag(new SaveDC()); } protected void writeGraphicsRestore() throws IOException { if (penHandle != 0) os.writeTag(new DeleteObject(handleManager.freeHandle(penHandle))); if (brushHandle != 0) os .writeTag(new DeleteObject(handleManager .freeHandle(brushHandle))); os.writeTag(new RestoreDC()); } /* * ================================================================================ | * 5. Drawing Methods * ================================================================================ */ /* 5.1.4. shapes */ public void draw(Shape shape) { try { if (getStroke() instanceof BasicStroke) { writePen((BasicStroke) getStroke(), getColor()); writePath(shape); os.writeTag(new StrokePath(imageBounds)); } else { writeBrush(getColor()); writePath(getStroke().createStrokedShape(shape)); os.writeTag(new FillPath(imageBounds)); } } catch (IOException e) { handleException(e); } } public void fill(Shape shape) { try { if (getPaint() instanceof Color) { writeBrush(getColor()); writePath(shape); os.writeTag(new FillPath(imageBounds)); } else { // draw paint as image fill(shape, getPaint()); } } catch (IOException e) { handleException(e); } } public void fillAndDraw(Shape shape, Color fillColor) { try { if (getPaint() instanceof Color) { writePen((BasicStroke) getStroke(), getColor()); writeBrush(fillColor); writePath(shape); os.writeTag(new StrokeAndFillPath(imageBounds)); } else { // draw paint as image fill(shape, getPaint()); // draw shape draw(shape); } } catch (IOException e) { handleException(e); } } /* 5.2. Images */ public void copyArea(int x, int y, int width, int height, int dx, int dy) { writeWarning(getClass() + ": copyArea(int, int, int, int, int, int) not implemented."); // Mostly unimplemented. } // NOTE: does not use writeGraphicsSave and writeGraphicsRestore since these // delete pen and brush protected void writeImage(RenderedImage image, AffineTransform xform, Color bkg) throws IOException { os.writeTag(new SaveDC()); AffineTransform imageTransform = new AffineTransform( 1.0, 0.0, 0.0, -1.0, 0.0, image.getHeight()); imageTransform.preConcatenate(xform); writeTransform(imageTransform); BufferedImage bufferedImage = ImageUtilities.createBufferedImage( image, null, null); AlphaBlend alphaBlend = new AlphaBlend( imageBounds, toUnit(0), toUnit(0), toUnit(image.getWidth()), toUnit(image.getHeight()), new AffineTransform(), bufferedImage, bkg); os.writeTag(alphaBlend); os.writeTag(new RestoreDC()); } /* 5.3. Strings */ public void writeString(String string, double x, double y) throws IOException { Color color; Paint paint = getPaint(); if (paint instanceof Color) { color = (Color) paint; } else if (paint instanceof GradientPaint) { GradientPaint gp = (GradientPaint) paint; color = PrintColor.mixColor(gp.getColor1(), gp.getColor2()); } else { Color bkg = getBackground(); if (bkg == null) { color = Color.BLACK; } else { color = PrintColor.invert(bkg); } } if (!color.equals(textColor)) { textColor = color; os.writeTag(new SetTextColor(textColor)); } // dialog.bold -> Dialog with TextAttribute.WEIGHT_BOLD Map attributes = FontUtilities.getAttributes(getFont()); FontTable.normalize(attributes); Font font = new Font(attributes); Font unitFont = (Font) unitFontTable.get(font); Integer fontIndex = (Integer) fontTable.get(font); if (fontIndex == null) { // for special fonts (Symbol, ZapfDingbats) we choose a standard // font and // encode using unicode. String fontName = font.getName(); ///string = FontEncoder.getEncodedString(string, fontName); String windowsFontName = FontUtilities .getWindowsFontName(fontName); unitFont = new Font(windowsFontName, font.getStyle(), font .getSize()); unitFont = unitFont.deriveFont(font.getSize2D() * UNITS_PER_PIXEL * TWIPS); unitFontTable.put(font, unitFont); ExtLogFontW logFontW = new ExtLogFontW(unitFont); int handle = handleManager.getHandle(); os.writeTag(new ExtCreateFontIndirectW(handle, logFontW)); fontIndex = new Integer(handle); fontTable.put(font, fontIndex); } os.writeTag(new SelectObject(fontIndex.intValue())); int[] widths = new int[string.length()]; for (int i = 0; i < widths.length; i++) { double w = unitFont.getStringBounds(string, i, i + 1, getFontRenderContext()).getWidth(); widths[i] = (int) w; } // font transformation sould _not_ transform string position translate(x, y); // apply font transformation AffineTransform t = font.getTransform(); if (!t.isIdentity()) { writeGraphicsSave(); writeTransform(t); } TextW text = new TextW(new Point(0, 0), string, 0, dummy, widths); os.writeTag(new ExtTextOutW(imageBounds, EMFConstants.GM_ADVANCED, 1, 1, text)); // revert font transformation if (!t.isIdentity()) { writeGraphicsRestore(); } // translation for string position. translate(-x, -y); } /* * ================================================================================ | * 6. Transformations * ================================================================================ */ protected void writeTransform(AffineTransform t) throws IOException { AffineTransform n = new AffineTransform(t.getScaleX(), t.getShearY(), t .getShearX(), t.getScaleY(), t.getTranslateX() * UNITS_PER_PIXEL * TWIPS, t.getTranslateY() * UNITS_PER_PIXEL * TWIPS); os.writeTag(new ModifyWorldTransform(n, EMFConstants.MWT_LEFTMULTIPLY)); } protected void writeSetTransform(AffineTransform t) throws IOException { // write a special matrix here to scale all written coordinates by a factor of TWIPS AffineTransform n = AffineTransform.getScaleInstance(1.0/TWIPS, 1.0/TWIPS); os.writeTag(new SetWorldTransform(n)); // apply transform writeTransform(t); } /* * ================================================================================ | * 7. Clipping * ================================================================================ */ protected void writeSetClip(Shape s) throws IOException { if (!isProperty(CLIP)) { return; } // if s == null the clip is reset to the imageBounds if (s == null && imageBounds != null) { s = new Rectangle(imageBounds); AffineTransform at = getTransform(); if (at != null) { s = at.createTransformedShape(s); } } writePath(s); os.writeTag(new SelectClipPath(EMFConstants.RGN_COPY)); } protected void writeClip(Shape s) throws IOException { if (s == null || !isProperty(CLIP)) { return; } writePath(s); os.writeTag(new SelectClipPath(EMFConstants.RGN_AND)); } /* * ================================================================================ | * 8. Graphics State * ================================================================================ */ public void writeStroke(Stroke stroke) throws IOException { if (stroke instanceof BasicStroke) { writePen((BasicStroke) stroke, getColor()); } } /* 8.2. paint/color */ public void setPaintMode() { writeWarning(getClass() + ": setPaintMode() not implemented."); // Mostly unimplemented. } public void setXORMode(Color c1) { writeWarning(getClass() + ": setXORMode(Color) not implemented."); // Mostly unimplemented. } protected void writePaint(Color p) throws IOException { // all color setting delayed } protected void writePaint(GradientPaint p) throws IOException { // all paint setting delayed } protected void writePaint(TexturePaint p) throws IOException { // all paint setting delayed } protected void writePaint(Paint p) throws IOException { // all paint setting delayed } /* 8.3. font */ protected void writeFont(Font font) throws IOException { // written when needed } /* 8.4. rendering hints */ /* * ================================================================================ | * 9. Auxiliary * ================================================================================ */ public GraphicsConfiguration getDeviceConfiguration() { writeWarning(getClass() + ": getDeviceConfiguration() not implemented."); // Mostly unimplemented return null; } public void writeComment(String comment) throws IOException { writeWarning(getClass() + ": writeComment(String) not implemented."); // Write out the comment. } public String toString() { return "EMFGraphics2D"; } /** * Implementation of createShape makes sure that the points are different by * at least one Unit. */ protected Shape createShape(double[] xPoints, double[] yPoints, int nPoints, boolean close) { GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD); if (nPoints > 0) { path.moveTo((float) xPoints[0], (float) yPoints[0]); double lastX = xPoints[0]; double lastY = yPoints[0]; if (close && (Math.abs(xPoints[nPoints - 1] - lastX) < 1) && (Math.abs(yPoints[nPoints - 1] - lastY) < 1)) { nPoints--; } for (int i = 1; i < nPoints; i++) { if ((Math.abs(xPoints[i] - lastX) > 1) || (Math.abs(yPoints[i] - lastY) > 1)) { path.lineTo((float) xPoints[i], (float) yPoints[i]); lastX = xPoints[i]; lastY = yPoints[i]; } } if (close) path.closePath(); } return path; } /* * Private methods */ private boolean writePath(Shape shape) throws IOException { boolean eo = EMFPathConstructor.isEvenOdd(shape); if (eo != evenOdd) { evenOdd = eo; os.writeTag(new SetPolyFillMode((evenOdd) ? EMFConstants.ALTERNATE : EMFConstants.WINDING)); } os.writeTag(new BeginPath()); pathConstructor.addPath(shape); os.writeTag(new EndPath()); return evenOdd; } private void writePen(BasicStroke stroke, Color color) throws IOException { if (color.equals(penColor) && stroke.equals(getStroke())) return; penColor = color; int style = EMFConstants.PS_GEOMETRIC; switch (stroke.getEndCap()) { case BasicStroke.CAP_BUTT: style |= EMFConstants.PS_ENDCAP_FLAT; break; case BasicStroke.CAP_ROUND: style |= EMFConstants.PS_ENDCAP_ROUND; break; case BasicStroke.CAP_SQUARE: style |= EMFConstants.PS_ENDCAP_SQUARE; break; } switch (stroke.getLineJoin()) { case BasicStroke.JOIN_MITER: style |= EMFConstants.PS_JOIN_MITER; break; case BasicStroke.JOIN_ROUND: style |= EMFConstants.PS_JOIN_ROUND; break; case BasicStroke.JOIN_BEVEL: style |= EMFConstants.PS_JOIN_BEVEL; break; } // FIXME int conversion // FIXME phase ignored float[] dashArray = stroke.getDashArray(); int[] dash = new int[(dashArray != null) ? dashArray.length : 0]; style |= (dash.length == 0) ? EMFConstants.PS_SOLID : EMFConstants.PS_USERSTYLE; for (int i = 0; i < dash.length; i++) { dash[i] = toUnit(dashArray[i]); } int brushStyle = (color.getAlpha() == 0) ? EMFConstants.BS_NULL : EMFConstants.BS_SOLID; ExtLogPen pen = new ExtLogPen(style, toUnit(stroke.getLineWidth()), brushStyle, getPrintColor(color), 0, dash); if (penHandle != 0) { os.writeTag(new DeleteObject(penHandle)); } else { penHandle = handleManager.getHandle(); } os.writeTag(new ExtCreatePen(penHandle, pen)); os.writeTag(new SelectObject(penHandle)); if (!(getStroke() instanceof BasicStroke) || (((BasicStroke) getStroke()).getMiterLimit() != stroke .getMiterLimit())) { os.writeTag(new SetMiterLimit(toUnit(stroke.getMiterLimit()))); } } private void writeBrush(Color color) throws IOException { if (color.equals(brushColor)) return; brushColor = color; int brushStyle = (color.getAlpha() == 0) ? EMFConstants.BS_NULL : EMFConstants.BS_SOLID; LogBrush32 brush = new LogBrush32(brushStyle, getPrintColor(color), 0); if (brushHandle != 0) { os.writeTag(new DeleteObject(brushHandle)); } else { brushHandle = handleManager.getHandle(); } os.writeTag(new CreateBrushIndirect(brushHandle, brush)); os.writeTag(new SelectObject(brushHandle)); } private int toUnit(double d) { return (int) Math.floor(d * UNITS_PER_PIXEL * TWIPS); } }