From 22a9bade84793a48954645c2529a42709ef06edc Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Sun, 10 Jan 2016 01:12:16 +0000 Subject: [PATCH] WMF fixes git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1723898 13f79535-47bb-0310-9956-ffa450edef68 --- .../poi/hwmf/draw/HwmfDrawProperties.java | 96 ++++++++ .../apache/poi/hwmf/draw/HwmfGraphics.java | 73 ++++-- .../org/apache/poi/hwmf/record/HwmfDraw.java | 138 ++++++----- .../org/apache/poi/hwmf/record/HwmfFill.java | 52 +++- .../org/apache/poi/hwmf/record/HwmfFont.java | 60 +++++ .../org/apache/poi/hwmf/record/HwmfMisc.java | 10 +- .../apache/poi/hwmf/record/HwmfPalette.java | 223 +++++++++++------- .../apache/poi/hwmf/record/HwmfPenStyle.java | 8 +- .../poi/hwmf/record/HwmfPlaceableHeader.java | 7 +- .../org/apache/poi/hwmf/record/HwmfText.java | 204 +++++++++++----- .../poi/hwmf/usermodel/HwmfPicture.java | 34 ++- .../org/apache/poi/hwmf/TestHwmfParsing.java | 44 +++- 12 files changed, 698 insertions(+), 251 deletions(-) diff --git a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java index aa8a2f3cb..8c112169f 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java @@ -25,14 +25,19 @@ import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.WritableRaster; +import java.util.List; import org.apache.poi.hwmf.record.HwmfBrushStyle; import org.apache.poi.hwmf.record.HwmfColorRef; +import org.apache.poi.hwmf.record.HwmfFont; import org.apache.poi.hwmf.record.HwmfFill.WmfSetPolyfillMode.HwmfPolyfillMode; import org.apache.poi.hwmf.record.HwmfHatchStyle; import org.apache.poi.hwmf.record.HwmfMapMode; import org.apache.poi.hwmf.record.HwmfMisc.WmfSetBkMode.HwmfBkMode; +import org.apache.poi.hwmf.record.HwmfPalette.PaletteEntry; import org.apache.poi.hwmf.record.HwmfPenStyle; +import org.apache.poi.hwmf.record.HwmfText.HwmfTextAlignment; +import org.apache.poi.hwmf.record.HwmfText.HwmfTextVerticalAlignment; public class HwmfDrawProperties { private final Rectangle2D window; @@ -51,6 +56,14 @@ public class HwmfDrawProperties { private HwmfBkMode bkMode = HwmfBkMode.OPAQUE; private HwmfPolyfillMode polyfillMode = HwmfPolyfillMode.WINDING; private Shape region = null; + private List palette = null; + private int paletteOffset = 0; + private HwmfFont font = null; + private HwmfColorRef textColor = new HwmfColorRef(Color.BLACK); + private HwmfTextAlignment textAlignLatin = HwmfTextAlignment.LEFT; + private HwmfTextVerticalAlignment textVAlignLatin = HwmfTextVerticalAlignment.TOP; + private HwmfTextAlignment textAlignAsian = HwmfTextAlignment.RIGHT; + private HwmfTextVerticalAlignment textVAlignAsian = HwmfTextVerticalAlignment.TOP; public HwmfDrawProperties() { window = new Rectangle2D.Double(0, 0, 1, 1); @@ -84,9 +97,16 @@ public class HwmfDrawProperties { } else if (other.region instanceof Area) { this.region = new Area(other.region); } + this.palette = other.palette; + this.paletteOffset = other.paletteOffset; + this.font = other.font; + this.textColor = (other.textColor == null) ? null : other.textColor.clone(); } public void setViewportExt(double width, double height) { + if (viewport == null) { + viewport = (Rectangle2D)window.clone(); + } double x = viewport.getX(); double y = viewport.getY(); double w = (width != 0) ? width : viewport.getWidth(); @@ -244,4 +264,80 @@ public class HwmfDrawProperties { public void setRegion(Shape region) { this.region = region; } + + /** + * Returns the current palette. + * Callers may modify the palette. + * + * @return the current palette or null, if it hasn't been set + */ + public List getPalette() { + return palette; + } + + /** + * Sets the current palette. + * It's the callers duty to set a modifiable copy of the palette. + * + * @param palette + */ + public void setPalette(List palette) { + this.palette = palette; + } + + public int getPaletteOffset() { + return paletteOffset; + } + + public void setPaletteOffset(int paletteOffset) { + this.paletteOffset = paletteOffset; + } + + public HwmfColorRef getTextColor() { + return textColor; + } + + public void setTextColor(HwmfColorRef textColor) { + this.textColor = textColor; + } + + public HwmfFont getFont() { + return font; + } + + public void setFont(HwmfFont font) { + this.font = font; + } + + public HwmfTextAlignment getTextAlignLatin() { + return textAlignLatin; + } + + public void setTextAlignLatin(HwmfTextAlignment textAlignLatin) { + this.textAlignLatin = textAlignLatin; + } + + public HwmfTextVerticalAlignment getTextVAlignLatin() { + return textVAlignLatin; + } + + public void setTextVAlignLatin(HwmfTextVerticalAlignment textVAlignLatin) { + this.textVAlignLatin = textVAlignLatin; + } + + public HwmfTextAlignment getTextAlignAsian() { + return textAlignAsian; + } + + public void setTextAlignAsian(HwmfTextAlignment textAlignAsian) { + this.textAlignAsian = textAlignAsian; + } + + public HwmfTextVerticalAlignment getTextVAlignAsian() { + return textVAlignAsian; + } + + public void setTextVAlignAsian(HwmfTextVerticalAlignment textVAlignAsian) { + this.textVAlignAsian = textVAlignAsian; + } } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java index a61390704..75e815f70 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java @@ -25,10 +25,12 @@ import java.awt.Paint; import java.awt.Rectangle; import java.awt.Shape; import java.awt.TexturePaint; +import java.awt.font.TextAttribute; import java.awt.geom.AffineTransform; import java.awt.geom.GeneralPath; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; +import java.text.AttributedString; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; @@ -36,13 +38,13 @@ import java.util.ListIterator; import java.util.NoSuchElementException; import org.apache.poi.hwmf.record.HwmfBrushStyle; +import org.apache.poi.hwmf.record.HwmfFont; import org.apache.poi.hwmf.record.HwmfHatchStyle; import org.apache.poi.hwmf.record.HwmfMapMode; import org.apache.poi.hwmf.record.HwmfMisc.WmfSetBkMode.HwmfBkMode; import org.apache.poi.hwmf.record.HwmfObjectTableEntry; import org.apache.poi.hwmf.record.HwmfPenStyle; import org.apache.poi.hwmf.record.HwmfPenStyle.HwmfLineDash; -import org.apache.poi.util.Units; public class HwmfGraphics { private final Graphics2D graphicsCtx; @@ -118,7 +120,7 @@ public class HwmfGraphics { boolean dashAlt = ps.isAlternateDash(); // This value is not an integer index into the dash pattern array. // Instead, it is a floating-point value that specifies a linear distance. - float dashStart = (dashAlt && dashes.length > 1) ? dashes[0] : 0; + float dashStart = (dashAlt && dashes != null && dashes.length > 1) ? dashes[0] : 0; return new BasicStroke(width, cap, join, miterLimit, dashes, dashStart); } @@ -243,8 +245,8 @@ public class HwmfGraphics { /** * Restores the properties from the stack * - * @param index if the index is positive, the n-th element from the start is removed and activated. - * If the index is negative, the n-th previous element relative to the current properties element is removed and activated. + * @param index if the index is positive, the n-th element from the start is activated. + * If the index is negative, the n-th previous element relative to the current properties element is activated. */ public void restoreProperties(int index) { if (index == 0) { @@ -253,12 +255,20 @@ public class HwmfGraphics { int stackIndex = index; if (stackIndex < 0) { int curIdx = propStack.indexOf(prop); - assert (curIdx != -1); + if (curIdx == -1) { + // the current element is not pushed to the stacked, i.e. it's the last + curIdx = propStack.size(); + } stackIndex = curIdx + index; } - prop = propStack.remove(stackIndex); + prop = propStack.get(stackIndex); } + /** + * After setting various window and viewport related properties, + * the underlying graphics context needs to be adapted. + * This methods gathers and sets the corresponding graphics transformations. + */ public void updateWindowMapMode() { GraphicsConfiguration gc = graphicsCtx.getDeviceConfiguration(); Rectangle2D win = prop.getWindow(); @@ -268,23 +278,15 @@ public class HwmfGraphics { switch (mapMode) { default: case MM_ANISOTROPIC: - // scale output bounds to image bounds - graphicsCtx.scale(gc.getBounds().getWidth()/bbox.getWidth(), gc.getBounds().getHeight()/bbox.getHeight()); - graphicsCtx.translate(-bbox.getX(), -bbox.getY()); - // scale window bounds to output bounds - graphicsCtx.translate(win.getCenterX(), win.getCenterY()); graphicsCtx.scale(bbox.getWidth()/win.getWidth(), bbox.getHeight()/win.getHeight()); - graphicsCtx.translate(-win.getCenterX(), -win.getCenterY()); + graphicsCtx.translate(-win.getX(), -win.getY()); break; case MM_ISOTROPIC: // TODO: to be validated ... // like anisotropic, but use x-axis as reference - graphicsCtx.scale(gc.getBounds().getWidth()/bbox.getWidth(), gc.getBounds().getWidth()/bbox.getWidth()); - graphicsCtx.translate(-bbox.getX(), -bbox.getY()); - graphicsCtx.translate(win.getCenterX(), win.getCenterY()); graphicsCtx.scale(bbox.getWidth()/win.getWidth(), bbox.getWidth()/win.getWidth()); - graphicsCtx.translate(-win.getCenterX(), -win.getCenterY()); + graphicsCtx.translate(-win.getX(), -win.getY()); break; case MM_LOMETRIC: case MM_HIMETRIC: @@ -294,11 +296,48 @@ public class HwmfGraphics { // TODO: to be validated ... graphicsCtx.transform(gc.getNormalizingTransform()); graphicsCtx.scale(1./mapMode.scale, -1./mapMode.scale); - graphicsCtx.translate(-bbox.getX(), -bbox.getY()); + graphicsCtx.translate(-win.getX(), -win.getY()); break; case MM_TEXT: // TODO: to be validated ... break; } } + + public void drawString(String text, Rectangle2D bounds) { + HwmfFont font = prop.getFont(); + if (font == null) { + return; + } + AttributedString as = new AttributedString(text); + as.addAttribute(TextAttribute.FAMILY, font.getFacename()); + // TODO: fix font height calculation + as.addAttribute(TextAttribute.SIZE, Math.abs(font.getHeight())); + as.addAttribute(TextAttribute.STRIKETHROUGH, font.isStrikeOut()); + if (font.isUnderline()) { + as.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON); + } + if (font.isItalic()) { + as.addAttribute(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE); + } + as.addAttribute(TextAttribute.WEIGHT, font.getWeight()); + + double angle = Math.toRadians(-font.getEscapement()/10.); + + + final AffineTransform at = graphicsCtx.getTransform(); + try { + graphicsCtx.translate(bounds.getX(), bounds.getY()); + graphicsCtx.rotate(angle); + if (prop.getBkMode() == HwmfBkMode.OPAQUE) { + // TODO: validate bounds + graphicsCtx.setBackground(prop.getBackgroundColor().getColor()); + graphicsCtx.fill(bounds); + } + graphicsCtx.setColor(prop.getTextColor().getColor()); + graphicsCtx.drawString(as.getIterator(), 0, 0); // (float)bounds.getX(), (float)bounds.getY()); + } finally { + graphicsCtx.setTransform(at); + } + } } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java index 4ed79ceb4..b167d0236 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java @@ -17,17 +17,18 @@ package org.apache.poi.hwmf.record; -import java.awt.Polygon; -import java.awt.Rectangle; import java.awt.Shape; import java.awt.geom.Arc2D; +import java.awt.geom.Area; import java.awt.geom.Ellipse2D; -import java.awt.geom.GeneralPath; import java.awt.geom.Line2D; +import java.awt.geom.Path2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.geom.RoundRectangle2D; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import org.apache.poi.hwmf.draw.HwmfGraphics; import org.apache.poi.util.LittleEndianConsts; @@ -114,13 +115,8 @@ public class HwmfDraw { */ public static class WmfPolygon implements HwmfRecord { - /** - * A 16-bit signed integer that defines the number of points in the array. - */ - private int numberofPoints; - - short xPoints[], yPoints[]; - + private Path2D poly = new Path2D.Double(); + @Override public HwmfRecordType getRecordType() { return HwmfRecordType.polygon; @@ -128,15 +124,21 @@ public class HwmfDraw { @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { - numberofPoints = leis.readShort(); - xPoints = new short[numberofPoints]; - yPoints = new short[numberofPoints]; + /** + * A 16-bit signed integer that defines the number of points in the array. + */ + int numberofPoints = leis.readShort(); for (int i=0; i polyList = new ArrayList(); + @Override public HwmfRecordType getRecordType() { return HwmfRecordType.polyPolygon; @@ -308,10 +301,15 @@ public class HwmfDraw { @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { // see http://secunia.com/gfx/pdf/SA31675_BA.pdf ;) - numberOfPolygons = leis.readUShort(); - pointsPerPolygon = new int[numberOfPolygons]; - xPoints = new int[numberOfPolygons][]; - yPoints = new int[numberOfPolygons][]; + /** + * A 16-bit unsigned integer that defines the number of polygons in the object. + */ + int numberOfPolygons = leis.readUShort(); + /** + * A NumberOfPolygons array of 16-bit unsigned integers that define the number of + * points for each polygon in the object. + */ + int[] pointsPerPolygon = new int[numberOfPolygons]; int size = LittleEndianConsts.SHORT_SIZE; @@ -320,16 +318,23 @@ public class HwmfDraw { size += LittleEndianConsts.SHORT_SIZE; } - for (int i=0; i palette = new ArrayList(); + @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { start = leis.readUShort(); - numberOfEntries = leis.readUShort(); + /** + * NumberOfEntries (2 bytes): A 16-bit unsigned integer that defines the number of objects in + * aPaletteEntries. + */ + int numberOfEntries = leis.readUShort(); int size = 2*LittleEndianConsts.SHORT_SIZE; - entries = new PaletteEntry[numberOfEntries]; for (int i=0; i getPaletteCopy() { + List newPalette = new ArrayList(); + for (PaletteEntry et : palette) { + newPalette.add(new PaletteEntry(et)); + } + return newPalette; + } + protected int getPaletteStart() { + return start; } } - + /** * The META_CREATEPALETTE record creates a Palette Object */ @@ -132,10 +147,10 @@ public class HwmfPalette { public void draw(HwmfGraphics ctx) { ctx.addObjectTableEntry(this); } - + @Override public void applyObject(HwmfGraphics ctx) { - + ctx.getProperties().setPalette(getPaletteCopy()); } } @@ -151,35 +166,62 @@ public class HwmfPalette { @Override public void draw(HwmfGraphics ctx) { - + HwmfDrawProperties props = ctx.getProperties(); + List palette = props.getPalette(); + if (palette == null) { + palette = new ArrayList(); + } + int start = getPaletteStart(); + for (int i=palette.size(); i palette = props.getPalette(); + if (palette == null) { + palette = new ArrayList(); + } + for (int i=palette.size(); i dest = props.getPalette(); + List src = getPaletteCopy(); + int start = getPaletteStart(); + if (dest == null) { + dest = new ArrayList(); + } + for (int i=dest.size(); i records = new ArrayList(); @@ -85,9 +89,23 @@ public class HwmfPicture { } public void draw(Graphics2D ctx) { + Dimension dim = getSize(); + int width = Units.pointsToPixel(dim.getWidth()); + // keep aspect ratio for height + int height = Units.pointsToPixel(dim.getHeight()); + Rectangle2D bounds = new Rectangle2D.Double(0,0,width,height); + draw(ctx, bounds); + } + + public void draw(Graphics2D ctx, Rectangle2D graphicsBounds) { AffineTransform at = ctx.getTransform(); try { - HwmfGraphics g = new HwmfGraphics(ctx, getBounds()); + Rectangle2D wmfBounds = getBounds(); + // scale output bounds to image bounds + ctx.scale(graphicsBounds.getWidth()/wmfBounds.getWidth(), graphicsBounds.getHeight()/wmfBounds.getHeight()); + ctx.translate(-wmfBounds.getX(), -wmfBounds.getY()); + + HwmfGraphics g = new HwmfGraphics(ctx, wmfBounds); for (HwmfRecord r : records) { r.draw(g); } @@ -131,4 +149,18 @@ public class HwmfPicture { public HwmfHeader getHeader() { return header; } + + /** + * Return the image size in points + * + * @return the image size in points + */ + public Dimension getSize() { + double inch = (placeableHeader == null) ? 1440 : placeableHeader.getUnitsPerInch(); + Rectangle2D bounds = getBounds(); + + //coefficient to translate from WMF dpi to 72dpi + double coeff = Units.POINT_DPI/inch; + return new Dimension((int)Math.round(bounds.getWidth()*coeff), (int)Math.round(bounds.getHeight()*coeff)); + } } diff --git a/src/scratchpad/testcases/org/apache/poi/hwmf/TestHwmfParsing.java b/src/scratchpad/testcases/org/apache/poi/hwmf/TestHwmfParsing.java index 8d14f0c7d..7ddacf475 100644 --- a/src/scratchpad/testcases/org/apache/poi/hwmf/TestHwmfParsing.java +++ b/src/scratchpad/testcases/org/apache/poi/hwmf/TestHwmfParsing.java @@ -19,9 +19,9 @@ package org.apache.poi.hwmf; import static org.junit.Assert.assertEquals; +import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.RenderingHints; -import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.io.File; import java.io.FileFilter; @@ -45,6 +45,7 @@ import org.apache.poi.sl.usermodel.PictureData; import org.apache.poi.sl.usermodel.PictureData.PictureType; import org.apache.poi.sl.usermodel.SlideShow; import org.apache.poi.sl.usermodel.SlideShowFactory; +import org.apache.poi.util.Units; import org.junit.Ignore; import org.junit.Test; @@ -60,17 +61,18 @@ public class TestHwmfParsing { } @Test - @Ignore + @Ignore("This is work-in-progress and not a real unit test ...") public void paint() throws IOException { File f = POIDataSamples.getSlideShowInstance().getFile("santa.wmf"); +// File f = new File("E:\\project\\poi\\misc\\govdocs-ppt", "000133-0001.wmf"); FileInputStream fis = new FileInputStream(f); HwmfPicture wmf = new HwmfPicture(fis); fis.close(); - Rectangle2D bounds = wmf.getBounds(); - int width = 300; + Dimension dim = wmf.getSize(); + int width = Units.pointsToPixel(dim.getWidth()); // keep aspect ratio for height - int height = (int)(width*bounds.getHeight()/bounds.getWidth()); + int height = Units.pointsToPixel(dim.getHeight()); BufferedImage bufImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); Graphics2D g = bufImg.createGraphics(); @@ -87,7 +89,7 @@ public class TestHwmfParsing { } @Test - @Ignore + @Ignore("This is work-in-progress and not a real unit test ...") public void fetchWmfFromGovdocs() throws IOException { URL url = new URL("http://digitalcorpora.org/corpora/files/govdocs1/by_type/ppt.zip"); File outdir = new File("build/ppt"); @@ -117,12 +119,16 @@ public class TestHwmfParsing { } } } - + @Test - @Ignore + @Ignore("This is work-in-progress and not a real unit test ...") public void parseWmfs() throws IOException { + // parse and render the extracted wmfs from the fetchWmfFromGovdocs step boolean outputFiles = false; - File indir = new File("build/ppt"), outdir = indir; + boolean renderWmf = true; + File indir = new File("E:\\project\\poi\\misc\\govdocs-ppt"); + File outdir = new File("build/wmf"); + outdir.mkdirs(); final String startFile = ""; File files[] = indir.listFiles(new FileFilter() { boolean foundStartFile = false; @@ -149,6 +155,26 @@ public class TestHwmfParsing { bmpIndex++; } } + + if (renderWmf) { + Dimension dim = wmf.getSize(); + int width = Units.pointsToPixel(dim.getWidth()); + // keep aspect ratio for height + int height = Units.pointsToPixel(dim.getHeight()); + + BufferedImage bufImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = bufImg.createGraphics(); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); + g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); + + wmf.draw(g); + + g.dispose(); + + ImageIO.write(bufImg, "PNG", new File(outdir, basename+".png")); + } } catch (Exception e) { System.out.println(f.getName()+" ignored."); }