From c6fa344c54b9d820596609d038754671aa4fa427 Mon Sep 17 00:00:00 2001 From: Andreas Beeker <kiwiwings@apache.org> Date: Wed, 24 Feb 2016 22:43:51 +0000 Subject: [PATCH] Regression fixes for H/XSLF and HWMF see http://apache-poi.1045710.n5.nabble.com/3-14-beta-2-3-14-final-tt5721829.html git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1732236 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/poi/stress/SlideShowHandler.java | 28 +- .../apache/poi/ddf/EscherArrayProperty.java | 11 +- .../apache/poi/sl/draw/DrawBackground.java | 3 + .../org/apache/poi/sl/draw/DrawFactory.java | 36 ++- .../org/apache/poi/sl/draw/DrawPaint.java | 29 +- .../apache/poi/sl/draw/DrawPictureShape.java | 3 +- .../org/apache/poi/sl/draw/DrawShape.java | 20 +- .../apache/poi/sl/draw/DrawSimpleShape.java | 26 +- .../apache/poi/sl/draw/DrawTextParagraph.java | 4 +- .../org/apache/poi/sl/draw/DrawTextShape.java | 34 +-- .../apache/poi/sl/draw/PathGradientPaint.java | 8 +- .../org/apache/poi/sl/draw/SLGraphics.java | 25 +- .../apache/poi/sl/draw/geom/ArcToCommand.java | 8 +- .../poi/sl/draw/geom/ClosePathCommand.java | 4 +- .../poi/sl/draw/geom/CurveToCommand.java | 8 +- .../poi/sl/draw/geom/LineToCommand.java | 8 +- .../poi/sl/draw/geom/MoveToCommand.java | 8 +- .../org/apache/poi/sl/draw/geom/Path.java | 18 +- .../apache/poi/sl/draw/geom/PathCommand.java | 4 +- .../poi/sl/draw/geom/QuadToCommand.java | 8 +- .../poi/sl/usermodel/FreeformShape.java | 6 +- .../poi/sl/usermodel/PlaceableShape.java | 5 + .../org/apache/poi/sl/usermodel/Shape.java | 3 +- .../poi/xslf/usermodel/XSLFFreeformShape.java | 10 +- .../poi/xslf/usermodel/XSLFHyperlink.java | 4 +- .../poi/xslf/usermodel/XSLFTextRun.java | 11 +- .../org/apache/poi/xslf/util/PPTX2PNG.java | 117 +++++--- .../poi/xslf/usermodel/TestPPTX2PNG.java | 17 +- .../xslf/usermodel/TestXSLFFreeformShape.java | 11 +- .../apache/poi/hslf/model/PPGraphics2D.java | 5 +- .../poi/hslf/record/InteractiveInfo.java | 12 +- .../org/apache/poi/hslf/record/Record.java | 2 +- .../apache/poi/hslf/usermodel/HSLFFill.java | 129 +++++++-- .../poi/hslf/usermodel/HSLFFreeformShape.java | 267 ++++++++++++++---- .../poi/hslf/usermodel/HSLFHyperlink.java | 3 + .../poi/hslf/usermodel/HSLFPictureShape.java | 7 + .../apache/poi/hslf/usermodel/HSLFShape.java | 15 +- .../poi/hslf/usermodel/HSLFShapeFactory.java | 11 +- .../poi/hslf/usermodel/HSLFSimpleShape.java | 9 +- .../poi/hslf/usermodel/HSLFTextParagraph.java | 20 +- .../poi/hwmf/draw/HwmfDrawProperties.java | 3 + .../apache/poi/hwmf/draw/HwmfGraphics.java | 118 ++++++-- .../poi/hwmf/record/HwmfBinaryRasterOp.java | 112 ++++++++ .../apache/poi/hwmf/record/HwmfBitmapDib.java | 29 +- .../org/apache/poi/hwmf/record/HwmfDraw.java | 41 ++- .../apache/poi/hwmf/record/HwmfEscape.java | 146 +++++++++- .../org/apache/poi/hwmf/record/HwmfFill.java | 45 ++- .../org/apache/poi/hwmf/record/HwmfFont.java | 19 +- .../apache/poi/hwmf/record/HwmfMapMode.java | 2 +- .../org/apache/poi/hwmf/record/HwmfMisc.java | 22 +- .../apache/poi/hwmf/record/HwmfPalette.java | 23 +- .../poi/hwmf/record/HwmfTernaryRasterOp.java | 72 ++++- .../org/apache/poi/hwmf/record/HwmfText.java | 118 ++++---- .../apache/poi/hwmf/record/HwmfWindowing.java | 24 +- .../poi/hwmf/usermodel/HwmfPicture.java | 10 +- .../apache/poi/hslf/model/TestFreeform.java | 15 +- .../poi/hslf/usermodel/TestPicture.java | 36 ++- .../org/apache/poi/hwmf/TestHwmfParsing.java | 10 +- .../sl/draw/geom/TestPresetGeometries.java | 4 +- 59 files changed, 1342 insertions(+), 464 deletions(-) create mode 100644 src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBinaryRasterOp.java diff --git a/src/integrationtest/org/apache/poi/stress/SlideShowHandler.java b/src/integrationtest/org/apache/poi/stress/SlideShowHandler.java index 9d6d1b3dcd..e798b35339 100644 --- a/src/integrationtest/org/apache/poi/stress/SlideShowHandler.java +++ b/src/integrationtest/org/apache/poi/stress/SlideShowHandler.java @@ -26,10 +26,8 @@ import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import org.apache.poi.sl.draw.Drawable; +import org.apache.poi.sl.draw.DrawFactory; import org.apache.poi.sl.usermodel.PictureData; import org.apache.poi.sl.usermodel.Shape; import org.apache.poi.sl.usermodel.ShapeContainer; @@ -39,7 +37,6 @@ import org.apache.poi.sl.usermodel.SlideShowFactory; import org.apache.poi.sl.usermodel.TextParagraph; import org.apache.poi.sl.usermodel.TextRun; import org.apache.poi.sl.usermodel.TextShape; -import org.apache.poi.util.JvmBugs; public abstract class SlideShowHandler extends POIFSFileHandler { public void handleSlideShow(SlideShow<?,?> ss) throws IOException { @@ -55,10 +52,12 @@ public abstract class SlideShowHandler extends POIFSFileHandler { // read in the writen file SlideShow<?,?> read = SlideShowFactory.create(new ByteArrayInputStream(out.toByteArray())); - assertNotNull(read); - - readContent(read); - + try { + assertNotNull(read); + readContent(read); + } finally { + read.close(); + } } private ByteArrayOutputStream writeToArray(SlideShow<?,?> ss) throws IOException { @@ -109,7 +108,7 @@ public abstract class SlideShowHandler extends POIFSFileHandler { for (Slide<?,?> s : ss.getSlides()) { BufferedImage img = new BufferedImage(pgsize.width, pgsize.height, BufferedImage.TYPE_INT_ARGB); Graphics2D graphics = img.createGraphics(); - fixFonts(graphics); + DrawFactory.getInstance(graphics).fixFonts(graphics); // default rendering options graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); @@ -124,15 +123,4 @@ public abstract class SlideShowHandler extends POIFSFileHandler { img.flush(); } } - - @SuppressWarnings("unchecked") - private static void fixFonts(Graphics2D graphics) { - if (!JvmBugs.hasLineBreakMeasurerBug()) return; - Map<String,String> fontMap = (Map<String,String>)graphics.getRenderingHint(Drawable.FONT_MAP); - if (fontMap == null) fontMap = new HashMap<String,String>(); - fontMap.put("Calibri", "Lucida Sans"); - fontMap.put("Cambria", "Lucida Bright"); - graphics.setRenderingHint(Drawable.FONT_MAP, fontMap); - } - } \ No newline at end of file diff --git a/src/java/org/apache/poi/ddf/EscherArrayProperty.java b/src/java/org/apache/poi/ddf/EscherArrayProperty.java index 880057e0ff..bec5f24d00 100644 --- a/src/java/org/apache/poi/ddf/EscherArrayProperty.java +++ b/src/java/org/apache/poi/ddf/EscherArrayProperty.java @@ -43,7 +43,7 @@ public final class EscherArrayProperty extends EscherComplexProperty implements private boolean sizeIncludesHeaderSize = true; /** - * When reading a property from data stream remeber if the complex part is empty and set this flag. + * When reading a property from data stream remember if the complex part is empty and set this flag. */ private boolean emptyComplexPart = false; @@ -65,10 +65,7 @@ public final class EscherArrayProperty extends EscherComplexProperty implements } public int getNumberOfElementsInArray() { - if (emptyComplexPart){ - return 0; - } - return LittleEndian.getUShort(_complexData, 0); + return (emptyComplexPart) ? 0 : LittleEndian.getUShort(_complexData, 0); } public void setNumberOfElementsInArray(int numberOfElements) { @@ -82,7 +79,7 @@ public final class EscherArrayProperty extends EscherComplexProperty implements } public int getNumberOfElementsInMemory() { - return LittleEndian.getUShort(_complexData, 2); + return (emptyComplexPart) ? 0 : LittleEndian.getUShort(_complexData, 2); } public void setNumberOfElementsInMemory(int numberOfElements) { @@ -96,7 +93,7 @@ public final class EscherArrayProperty extends EscherComplexProperty implements } public short getSizeOfElements() { - return LittleEndian.getShort( _complexData, 4 ); + return (emptyComplexPart) ? 0 : LittleEndian.getShort( _complexData, 4 ); } public void setSizeOfElements(int sizeOfElements) { diff --git a/src/java/org/apache/poi/sl/draw/DrawBackground.java b/src/java/org/apache/poi/sl/draw/DrawBackground.java index b09bc98589..aa12b470cb 100644 --- a/src/java/org/apache/poi/sl/draw/DrawBackground.java +++ b/src/java/org/apache/poi/sl/draw/DrawBackground.java @@ -25,6 +25,7 @@ import java.awt.geom.Rectangle2D; import org.apache.poi.sl.usermodel.Background; import org.apache.poi.sl.usermodel.PlaceableShape; import org.apache.poi.sl.usermodel.ShapeContainer; +import org.apache.poi.sl.usermodel.Sheet; public class DrawBackground extends DrawShape { @@ -47,6 +48,7 @@ public class DrawBackground extends DrawShape { public void setFlipVertical(boolean flip) {} public boolean getFlipHorizontal() { return false; } public boolean getFlipVertical() { return false; } + public Sheet<?,?> getSheet() { return shape.getSheet(); } }; DrawFactory drawFact = DrawFactory.getInstance(graphics); @@ -55,6 +57,7 @@ public class DrawBackground extends DrawShape { Rectangle2D anchor2 = getAnchor(graphics, anchor); if(fill != null) { + graphics.setRenderingHint(Drawable.GRADIENT_SHAPE, anchor); graphics.setPaint(fill); graphics.fill(anchor2); } diff --git a/src/java/org/apache/poi/sl/draw/DrawFactory.java b/src/java/org/apache/poi/sl/draw/DrawFactory.java index 971feae185..a7edeafbb0 100644 --- a/src/java/org/apache/poi/sl/draw/DrawFactory.java +++ b/src/java/org/apache/poi/sl/draw/DrawFactory.java @@ -17,11 +17,11 @@ package org.apache.poi.sl.draw; -import static org.apache.poi.sl.draw.Drawable.DRAW_FACTORY; - import java.awt.Graphics2D; import java.awt.font.TextLayout; import java.text.AttributedString; +import java.util.HashMap; +import java.util.Map; import org.apache.poi.sl.usermodel.Background; import org.apache.poi.sl.usermodel.ConnectorShape; @@ -38,6 +38,7 @@ import org.apache.poi.sl.usermodel.TableShape; import org.apache.poi.sl.usermodel.TextBox; import org.apache.poi.sl.usermodel.TextParagraph; import org.apache.poi.sl.usermodel.TextShape; +import org.apache.poi.util.JvmBugs; public class DrawFactory { protected static final ThreadLocal<DrawFactory> defaultFactory = new ThreadLocal<DrawFactory>(); @@ -58,7 +59,7 @@ public class DrawFactory { DrawFactory factory = null; boolean isHint = false; if (graphics != null) { - factory = (DrawFactory)graphics.getRenderingHint(DRAW_FACTORY); + factory = (DrawFactory)graphics.getRenderingHint(Drawable.DRAW_FACTORY); isHint = (factory != null); } // secondly try the thread local default @@ -70,7 +71,7 @@ public class DrawFactory { factory = new DrawFactory(); } if (graphics != null && !isHint) { - graphics.setRenderingHint(DRAW_FACTORY, factory); + graphics.setRenderingHint(Drawable.DRAW_FACTORY, factory); } return factory; } @@ -166,4 +167,29 @@ public class DrawFactory { public DrawPaint getPaint(PlaceableShape<?,?> shape) { return new DrawPaint(shape); } -} + + + /** + * Replace font families for Windows JVM 6, which contains a font rendering error. + * This is likely to be removed, when POI upgrades to JDK 7 + * + * @param graphics the graphics context which will contain the font mapping + */ + public void fixFonts(Graphics2D graphics) { + if (!JvmBugs.hasLineBreakMeasurerBug()) return; + @SuppressWarnings("unchecked") + Map<String,String> fontMap = (Map<String,String>)graphics.getRenderingHint(Drawable.FONT_MAP); + if (fontMap == null) { + fontMap = new HashMap<String,String>(); + graphics.setRenderingHint(Drawable.FONT_MAP, fontMap); + } + + String fonts[][] = { { "Calibri", "Lucida Sans" }, { "Cambria", "Lucida Bright" } }; + + for (String f[] : fonts) { + if (!fontMap.containsKey(f[0])) { + fontMap.put(f[0], f[1]); + } + } + } +} \ No newline at end of file diff --git a/src/java/org/apache/poi/sl/draw/DrawPaint.java b/src/java/org/apache/poi/sl/draw/DrawPaint.java index 8d5648f852..d4da431366 100644 --- a/src/java/org/apache/poi/sl/draw/DrawPaint.java +++ b/src/java/org/apache/poi/sl/draw/DrawPaint.java @@ -134,8 +134,11 @@ public class DrawPaint { ImageRenderer renderer = DrawPictureShape.getImageRenderer(graphics, fill.getContentType()); try { - renderer.loadImage(is, fill.getContentType()); - is.close(); + try { + renderer.loadImage(is, fill.getContentType()); + } finally { + is.close(); + } } catch (IOException e) { LOG.log(POILogger.ERROR, "Can't load image data - using transparent color", e); return null; @@ -274,15 +277,35 @@ public class DrawPaint { Point2D p2 = new Point2D.Double(anchor.getX() + anchor.getWidth(), anchor.getY() + anchor.getHeight() / 2); p2 = at.transform(p2, null); - + snapToAnchor(p1, anchor); snapToAnchor(p2, anchor); + if (p1.equals(p2)) { + // gradient paint on the same point throws an exception ... and doesn't make sense + return null; + } + float[] fractions = fill.getGradientFractions(); Color[] colors = new Color[fractions.length]; int i = 0; for (ColorStyle fc : fill.getGradientColors()) { + if (fc == null) { + // get color of background + fc = new ColorStyle() { + public int getTint() { return -1; } + public int getShade() { return -1; } + public int getSatOff() { return -1; } + public int getSatMod() { return -1; } + public int getLumOff() { return -1; } + public int getLumMod() { return -1; } + public int getHueOff() { return -1; } + public int getHueMod() { return -1; } + public Color getColor() { return Color.white; } + public int getAlpha() { return 0; } + }; + } colors[i++] = applyColorTransform(fc); } diff --git a/src/java/org/apache/poi/sl/draw/DrawPictureShape.java b/src/java/org/apache/poi/sl/draw/DrawPictureShape.java index beadc1f988..bdc5ab68c4 100644 --- a/src/java/org/apache/poi/sl/draw/DrawPictureShape.java +++ b/src/java/org/apache/poi/sl/draw/DrawPictureShape.java @@ -52,8 +52,7 @@ public class DrawPictureShape extends DrawSimpleShape { renderer.loadImage(data.getData(), data.getContentType()); renderer.drawImage(graphics, anchor, insets); } catch (IOException e) { - // TODO: draw specific runtime exception? - throw new RuntimeException(e); + LOG.log(POILogger.ERROR, "image can't be loaded/rendered.", e); } } diff --git a/src/java/org/apache/poi/sl/draw/DrawShape.java b/src/java/org/apache/poi/sl/draw/DrawShape.java index d036e52733..0c465bc52a 100644 --- a/src/java/org/apache/poi/sl/draw/DrawShape.java +++ b/src/java/org/apache/poi/sl/draw/DrawShape.java @@ -90,17 +90,23 @@ public class DrawShape implements Drawable { Rectangle2D anchor2 = txs.createTransformedShape(ps.getAnchor()).getBounds2D(); - scaleX = anchor.getWidth() == 0. ? 1.0 : anchor.getWidth() / anchor2.getWidth(); - scaleY = anchor.getHeight() == 0. ? 1.0 : anchor.getHeight() / anchor2.getHeight(); + scaleX = safeScale(anchor.getWidth(), anchor2.getWidth()); + scaleY = safeScale(anchor.getHeight(), anchor2.getHeight()); } else { quadrant = 0; } // transformation is applied reversed ... graphics.translate(centerX, centerY); - graphics.rotate(Math.toRadians(rotation-quadrant*90.)); + double rot = Math.toRadians(rotation-quadrant*90.); + if (rot != 0) { + graphics.rotate(rot); + } graphics.scale(scaleX, scaleY); - graphics.rotate(Math.toRadians(quadrant*90)); + rot = Math.toRadians(quadrant*90); + if (rot != 0) { + graphics.rotate(rot); + } graphics.translate(-centerX, -centerY); } @@ -119,6 +125,12 @@ public class DrawShape implements Drawable { } } + private static double safeScale(double dim1, double dim2) { + if (dim1 == 0.) { + return 1; + } + return (dim2 == 0.) ? 1 : dim1/dim2; + } public void draw(Graphics2D graphics) { } diff --git a/src/java/org/apache/poi/sl/draw/DrawSimpleShape.java b/src/java/org/apache/poi/sl/draw/DrawSimpleShape.java index aa03f8a53b..7206996660 100644 --- a/src/java/org/apache/poi/sl/draw/DrawSimpleShape.java +++ b/src/java/org/apache/poi/sl/draw/DrawSimpleShape.java @@ -23,7 +23,7 @@ import java.awt.Graphics2D; import java.awt.Paint; import java.awt.geom.AffineTransform; import java.awt.geom.Ellipse2D; -import java.awt.geom.GeneralPath; +import java.awt.geom.Path2D; import java.awt.geom.Rectangle2D; import java.io.IOException; import java.io.InputStream; @@ -179,20 +179,20 @@ public class DrawSimpleShape extends DrawShape { case STEALTH: case ARROW: p = new Path(false, true); - GeneralPath arrow = new GeneralPath(); - arrow.moveTo((float) (-lineWidth * scaleX), (float) (-lineWidth * scaleY / 2)); + Path2D.Double arrow = new Path2D.Double(); + arrow.moveTo((-lineWidth * scaleX), (-lineWidth * scaleY / 2)); arrow.lineTo(0, 0); - arrow.lineTo((float) (-lineWidth * scaleX), (float) (lineWidth * scaleY / 2)); + arrow.lineTo((-lineWidth * scaleX), (lineWidth * scaleY / 2)); tailShape = arrow; at.translate(x2, y2); at.rotate(alpha); break; case TRIANGLE: p = new Path(); - GeneralPath triangle = new GeneralPath(); - triangle.moveTo((float) (-lineWidth * scaleX), (float) (-lineWidth * scaleY / 2)); + Path2D.Double triangle = new Path2D.Double(); + triangle.moveTo((-lineWidth * scaleX), (-lineWidth * scaleY / 2)); triangle.lineTo(0, 0); - triangle.lineTo((float) (-lineWidth * scaleX), (float) (lineWidth * scaleY / 2)); + triangle.lineTo((-lineWidth * scaleX), (lineWidth * scaleY / 2)); triangle.closePath(); tailShape = triangle; at.translate(x2, y2); @@ -252,20 +252,20 @@ public class DrawSimpleShape extends DrawShape { case STEALTH: case ARROW: p = new Path(false, true); - GeneralPath arrow = new GeneralPath(); - arrow.moveTo((float) (lineWidth * scaleX), (float) (-lineWidth * scaleY / 2)); + Path2D.Double arrow = new Path2D.Double(); + arrow.moveTo((lineWidth * scaleX), (-lineWidth * scaleY / 2)); arrow.lineTo(0, 0); - arrow.lineTo((float) (lineWidth * scaleX), (float) (lineWidth * scaleY / 2)); + arrow.lineTo((lineWidth * scaleX), (lineWidth * scaleY / 2)); headShape = arrow; at.translate(x1, y1); at.rotate(alpha); break; case TRIANGLE: p = new Path(); - GeneralPath triangle = new GeneralPath(); - triangle.moveTo((float) (lineWidth * scaleX), (float) (-lineWidth * scaleY / 2)); + Path2D.Double triangle = new Path2D.Double(); + triangle.moveTo((lineWidth * scaleX), (-lineWidth * scaleY / 2)); triangle.lineTo(0, 0); - triangle.lineTo((float) (lineWidth * scaleX), (float) (lineWidth * scaleY / 2)); + triangle.lineTo((lineWidth * scaleX), (lineWidth * scaleY / 2)); triangle.closePath(); headShape = triangle; at.translate(x1, y1); diff --git a/src/java/org/apache/poi/sl/draw/DrawTextParagraph.java b/src/java/org/apache/poi/sl/draw/DrawTextParagraph.java index e8a7b0b216..7b28bde582 100644 --- a/src/java/org/apache/poi/sl/draw/DrawTextParagraph.java +++ b/src/java/org/apache/poi/sl/draw/DrawTextParagraph.java @@ -38,6 +38,7 @@ import org.apache.poi.sl.usermodel.Insets2D; import org.apache.poi.sl.usermodel.PaintStyle; import org.apache.poi.sl.usermodel.PlaceableShape; import org.apache.poi.sl.usermodel.ShapeContainer; +import org.apache.poi.sl.usermodel.Sheet; import org.apache.poi.sl.usermodel.TextParagraph; import org.apache.poi.sl.usermodel.TextParagraph.BulletStyle; import org.apache.poi.sl.usermodel.TextParagraph.TextAlign; @@ -465,6 +466,7 @@ public class DrawTextParagraph implements Drawable { public void setFlipVertical(boolean flip) {} public boolean getFlipHorizontal() { return false; } public boolean getFlipVertical() { return false; } + public Sheet<?,?> getSheet() { return paragraph.getParentShape().getSheet(); } }; return ps; } @@ -530,7 +532,7 @@ public class DrawTextParagraph implements Drawable { attList.add(new AttributedStringData(TextAttribute.SUPERSCRIPT, TextAttribute.SUPERSCRIPT_SUPER, beginIndex, endIndex)); } - Hyperlink hl = run.getHyperlink(); + Hyperlink<?,?> hl = run.getHyperlink(); if (hl != null) { attList.add(new AttributedStringData(HYPERLINK_HREF, hl.getAddress(), beginIndex, endIndex)); attList.add(new AttributedStringData(HYPERLINK_LABEL, hl.getLabel(), beginIndex, endIndex)); diff --git a/src/java/org/apache/poi/sl/draw/DrawTextShape.java b/src/java/org/apache/poi/sl/draw/DrawTextShape.java index 06b57dab26..648dd48cec 100644 --- a/src/java/org/apache/poi/sl/draw/DrawTextShape.java +++ b/src/java/org/apache/poi/sl/draw/DrawTextShape.java @@ -21,11 +21,15 @@ import java.awt.Graphics2D; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; -import java.util.*; +import java.util.Iterator; -import org.apache.poi.sl.usermodel.*; +import org.apache.poi.sl.usermodel.Insets2D; +import org.apache.poi.sl.usermodel.PlaceableShape; +import org.apache.poi.sl.usermodel.ShapeContainer; +import org.apache.poi.sl.usermodel.TextParagraph; import org.apache.poi.sl.usermodel.TextParagraph.BulletStyle; -import org.apache.poi.util.JvmBugs; +import org.apache.poi.sl.usermodel.TextRun; +import org.apache.poi.sl.usermodel.TextShape; public class DrawTextShape extends DrawSimpleShape { @@ -35,7 +39,7 @@ public class DrawTextShape extends DrawSimpleShape { @Override public void drawContent(Graphics2D graphics) { - fixFonts(graphics); + DrawFactory.getInstance(graphics).fixFonts(graphics); TextShape<?,?> s = getShape(); @@ -71,7 +75,7 @@ public class DrawTextShape extends DrawSimpleShape { } Double textRot = s.getTextRotation(); - if (textRot != null) { + if (textRot != null && textRot != 0) { graphics.translate(anchor.getCenterX(), anchor.getCenterY()); graphics.rotate(Math.toRadians(textRot)); graphics.translate(-anchor.getCenterX(), -anchor.getCenterY()); @@ -110,8 +114,9 @@ public class DrawTextShape extends DrawSimpleShape { double y0 = y; //noinspection RedundantCast - Iterator<? extends TextParagraph<?,?,? extends TextRun>> paragraphs = (Iterator<? extends TextParagraph<?, ?, ? extends TextRun>>) getShape().iterator(); - + Iterator<? extends TextParagraph<?,?,? extends TextRun>> paragraphs = + (Iterator<? extends TextParagraph<?,?,? extends TextRun>>) getShape().iterator(); + boolean isFirstLine = true; for (int autoNbrIdx=0; paragraphs.hasNext(); autoNbrIdx++){ TextParagraph<?,?,? extends TextRun> p = paragraphs.next(); @@ -170,23 +175,10 @@ public class DrawTextShape extends DrawSimpleShape { // dry-run in a 1x1 image and return the vertical advance BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB); Graphics2D graphics = img.createGraphics(); - fixFonts(graphics); + DrawFactory.getInstance(graphics).fixFonts(graphics); return drawParagraphs(graphics, 0, 0); } - @SuppressWarnings("unchecked") - private static void fixFonts(Graphics2D graphics) { - if (!JvmBugs.hasLineBreakMeasurerBug()) return; - Map<String,String> fontMap = (Map<String,String>)graphics.getRenderingHint(Drawable.FONT_MAP); - if (fontMap == null) { - fontMap = new HashMap<String,String>(); - graphics.setRenderingHint(Drawable.FONT_MAP, fontMap); - } - - if (!fontMap.containsKey("Calibri")) fontMap.put("Calibri", "Lucida Sans"); - if (!fontMap.containsKey("Cambria")) fontMap.put("Cambria", "Lucida Bright"); - } - @Override protected TextShape<?,?> getShape() { return (TextShape<?,?>)shape; diff --git a/src/java/org/apache/poi/sl/draw/PathGradientPaint.java b/src/java/org/apache/poi/sl/draw/PathGradientPaint.java index c5ad799f4c..292ee3a7e7 100644 --- a/src/java/org/apache/poi/sl/draw/PathGradientPaint.java +++ b/src/java/org/apache/poi/sl/draw/PathGradientPaint.java @@ -87,7 +87,7 @@ class PathGradientPaint implements Paint { ) { shape = (Shape)hints.get(Drawable.GRADIENT_SHAPE); if (shape == null) { - throw new IllegalPathStateException("PathGradientPaint needs a shape to be set via the rendering hint PathGradientPaint.GRADIANT_SHAPE."); + throw new IllegalPathStateException("PathGradientPaint needs a shape to be set via the rendering hint Drawable.GRADIANT_SHAPE."); } this.deviceBounds = deviceBounds; @@ -137,14 +137,14 @@ class PathGradientPaint implements Paint { return childRaster; } - protected int getGradientSteps(Shape shape) { - Rectangle rect = shape.getBounds(); + protected int getGradientSteps(Shape gradientShape) { + Rectangle rect = gradientShape.getBounds(); int lower = 1; int upper = (int)(Math.max(rect.getWidth(),rect.getHeight())/2.0); while (lower < upper-1) { int mid = lower + (upper - lower) / 2; BasicStroke bs = new BasicStroke(mid, capStyle, joinStyle); - Area area = new Area(bs.createStrokedShape(shape)); + Area area = new Area(bs.createStrokedShape(gradientShape)); if (area.isSingular()) { upper = mid; } else { diff --git a/src/java/org/apache/poi/sl/draw/SLGraphics.java b/src/java/org/apache/poi/sl/draw/SLGraphics.java index 2a6d884ca1..b4bc14ae96 100644 --- a/src/java/org/apache/poi/sl/draw/SLGraphics.java +++ b/src/java/org/apache/poi/sl/draw/SLGraphics.java @@ -42,6 +42,7 @@ import java.awt.geom.Arc2D; import java.awt.geom.Ellipse2D; import java.awt.geom.GeneralPath; import java.awt.geom.Line2D; +import java.awt.geom.Path2D; import java.awt.geom.RoundRectangle2D; import java.awt.image.BufferedImage; import java.awt.image.BufferedImageOp; @@ -243,7 +244,7 @@ public final class SLGraphics extends Graphics2D implements Cloneable { * @see #setComposite */ public void draw(Shape shape){ - GeneralPath path = new GeneralPath(_transform.createTransformedShape(shape)); + Path2D.Double path = new Path2D.Double(_transform.createTransformedShape(shape)); FreeformShape<?,?> p = _group.createFreeform(); p.setPath(path); p.setFillColor(null); @@ -339,7 +340,7 @@ public final class SLGraphics extends Graphics2D implements Cloneable { * @see #setClip */ public void fill(Shape shape){ - GeneralPath path = new GeneralPath(_transform.createTransformedShape(shape)); + Path2D.Double path = new Path2D.Double(_transform.createTransformedShape(shape)); FreeformShape<?,?> p = _group.createFreeform(); p.setPath(path); applyPaint(p); @@ -450,7 +451,7 @@ public final class SLGraphics extends Graphics2D implements Cloneable { */ public void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight){ - RoundRectangle2D rect = new RoundRectangle2D.Float(x, y, width, height, arcWidth, arcHeight); + RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, arcWidth, arcHeight); draw(rect); } @@ -481,7 +482,7 @@ public final class SLGraphics extends Graphics2D implements Cloneable { * @see java.awt.Graphics#drawOval */ public void fillOval(int x, int y, int width, int height){ - Ellipse2D oval = new Ellipse2D.Float(x, y, width, height); + Ellipse2D oval = new Ellipse2D.Double(x, y, width, height); fill(oval); } @@ -504,7 +505,7 @@ public final class SLGraphics extends Graphics2D implements Cloneable { public void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight){ - RoundRectangle2D rect = new RoundRectangle2D.Float(x, y, width, height, arcWidth, arcHeight); + RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, arcWidth, arcHeight); fill(rect); } @@ -544,9 +545,8 @@ public final class SLGraphics extends Graphics2D implements Cloneable { * relative to the start angle. * @see java.awt.Graphics#drawArc */ - public void fillArc(int x, int y, int width, int height, - int startAngle, int arcAngle){ - Arc2D arc = new Arc2D.Float(x, y, width, height, startAngle, arcAngle, Arc2D.PIE); + public void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle){ + Arc2D arc = new Arc2D.Double(x, y, width, height, startAngle, arcAngle, Arc2D.PIE); fill(arc); } @@ -587,9 +587,8 @@ public final class SLGraphics extends Graphics2D implements Cloneable { * relative to the start angle. * @see java.awt.Graphics#fillArc */ - public void drawArc(int x, int y, int width, int height, - int startAngle, int arcAngle) { - Arc2D arc = new Arc2D.Float(x, y, width, height, startAngle, arcAngle, Arc2D.OPEN); + public void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle) { + Arc2D arc = new Arc2D.Double(x, y, width, height, startAngle, arcAngle, Arc2D.OPEN); draw(arc); } @@ -636,7 +635,7 @@ public final class SLGraphics extends Graphics2D implements Cloneable { * @see java.awt.Graphics#fillOval */ public void drawOval(int x, int y, int width, int height){ - Ellipse2D oval = new Ellipse2D.Float(x, y, width, height); + Ellipse2D oval = new Ellipse2D.Double(x, y, width, height); draw(oval); } @@ -932,7 +931,7 @@ public final class SLGraphics extends Graphics2D implements Cloneable { * @param y2 the second point's <i>y</i> coordinate. */ public void drawLine(int x1, int y1, int x2, int y2){ - Line2D line = new Line2D.Float(x1, y1, x2, y2); + Line2D line = new Line2D.Double(x1, y1, x2, y2); draw(line); } diff --git a/src/java/org/apache/poi/sl/draw/geom/ArcToCommand.java b/src/java/org/apache/poi/sl/draw/geom/ArcToCommand.java index 0382d7f997..18531d7ed9 100644 --- a/src/java/org/apache/poi/sl/draw/geom/ArcToCommand.java +++ b/src/java/org/apache/poi/sl/draw/geom/ArcToCommand.java @@ -19,12 +19,12 @@ package org.apache.poi.sl.draw.geom; -import org.apache.poi.sl.draw.binding.CTPath2DArcTo; - import java.awt.geom.Arc2D; -import java.awt.geom.GeneralPath; +import java.awt.geom.Path2D; import java.awt.geom.Point2D; +import org.apache.poi.sl.draw.binding.CTPath2DArcTo; + /** * ArcTo command within a shape path in DrawingML: * @@ -48,7 +48,7 @@ public class ArcToCommand implements PathCommand { swAng = arc.getSwAng().toString(); } - public void execute(GeneralPath path, Context ctx){ + public void execute(Path2D.Double path, Context ctx){ double rx = ctx.getValue(wr); double ry = ctx.getValue(hr); double start = ctx.getValue(stAng) / 60000; diff --git a/src/java/org/apache/poi/sl/draw/geom/ClosePathCommand.java b/src/java/org/apache/poi/sl/draw/geom/ClosePathCommand.java index 9d29062806..66b35ed585 100644 --- a/src/java/org/apache/poi/sl/draw/geom/ClosePathCommand.java +++ b/src/java/org/apache/poi/sl/draw/geom/ClosePathCommand.java @@ -19,7 +19,7 @@ package org.apache.poi.sl.draw.geom; -import java.awt.geom.GeneralPath; +import java.awt.geom.Path2D; /** * Date: 10/25/11 @@ -31,7 +31,7 @@ public class ClosePathCommand implements PathCommand { ClosePathCommand(){ } - public void execute(GeneralPath path, Context ctx){ + public void execute(Path2D.Double path, Context ctx){ path.closePath(); } } diff --git a/src/java/org/apache/poi/sl/draw/geom/CurveToCommand.java b/src/java/org/apache/poi/sl/draw/geom/CurveToCommand.java index 02eeb2953e..6c6361aed9 100644 --- a/src/java/org/apache/poi/sl/draw/geom/CurveToCommand.java +++ b/src/java/org/apache/poi/sl/draw/geom/CurveToCommand.java @@ -19,9 +19,9 @@ package org.apache.poi.sl.draw.geom; -import org.apache.poi.sl.draw.binding.CTAdjPoint2D; +import java.awt.geom.Path2D; -import java.awt.geom.GeneralPath; +import org.apache.poi.sl.draw.binding.CTAdjPoint2D; /** * Date: 10/25/11 @@ -40,13 +40,13 @@ public class CurveToCommand implements PathCommand { arg6 = pt3.getY().toString(); } - public void execute(GeneralPath path, Context ctx){ + public void execute(Path2D.Double path, Context ctx){ double x1 = ctx.getValue(arg1); double y1 = ctx.getValue(arg2); double x2 = ctx.getValue(arg3); double y2 = ctx.getValue(arg4); double x3 = ctx.getValue(arg5); double y3 = ctx.getValue(arg6); - path.curveTo((float)x1, (float)y1, (float)x2, (float)y2, (float)x3, (float)y3); + path.curveTo(x1, y1, x2, y2, x3, y3); } } diff --git a/src/java/org/apache/poi/sl/draw/geom/LineToCommand.java b/src/java/org/apache/poi/sl/draw/geom/LineToCommand.java index 99c5a6b20c..7f6e13c542 100644 --- a/src/java/org/apache/poi/sl/draw/geom/LineToCommand.java +++ b/src/java/org/apache/poi/sl/draw/geom/LineToCommand.java @@ -19,9 +19,9 @@ package org.apache.poi.sl.draw.geom; -import org.apache.poi.sl.draw.binding.CTAdjPoint2D; +import java.awt.geom.Path2D; -import java.awt.geom.GeneralPath; +import org.apache.poi.sl.draw.binding.CTAdjPoint2D; /** * Date: 10/25/11 @@ -41,9 +41,9 @@ public class LineToCommand implements PathCommand { arg2 = s2; } - public void execute(GeneralPath path, Context ctx){ + public void execute(Path2D.Double path, Context ctx){ double x = ctx.getValue(arg1); double y = ctx.getValue(arg2); - path.lineTo((float)x, (float)y); + path.lineTo(x, y); } } diff --git a/src/java/org/apache/poi/sl/draw/geom/MoveToCommand.java b/src/java/org/apache/poi/sl/draw/geom/MoveToCommand.java index 22ccd54092..59c7a8adf2 100644 --- a/src/java/org/apache/poi/sl/draw/geom/MoveToCommand.java +++ b/src/java/org/apache/poi/sl/draw/geom/MoveToCommand.java @@ -19,9 +19,9 @@ package org.apache.poi.sl.draw.geom; -import org.apache.poi.sl.draw.binding.CTAdjPoint2D; +import java.awt.geom.Path2D; -import java.awt.geom.GeneralPath; +import org.apache.poi.sl.draw.binding.CTAdjPoint2D; /** * Date: 10/25/11 @@ -41,9 +41,9 @@ public class MoveToCommand implements PathCommand { arg2 = s2; } - public void execute(GeneralPath path, Context ctx){ + public void execute(Path2D.Double path, Context ctx){ double x = ctx.getValue(arg1); double y = ctx.getValue(arg2); - path.moveTo((float)x, (float)y); + path.moveTo(x, y); } } diff --git a/src/java/org/apache/poi/sl/draw/geom/Path.java b/src/java/org/apache/poi/sl/draw/geom/Path.java index b496e9fc96..78590faf06 100644 --- a/src/java/org/apache/poi/sl/draw/geom/Path.java +++ b/src/java/org/apache/poi/sl/draw/geom/Path.java @@ -19,11 +19,19 @@ package org.apache.poi.sl.draw.geom; -import java.awt.geom.GeneralPath; +import java.awt.geom.Path2D; import java.util.ArrayList; import java.util.List; -import org.apache.poi.sl.draw.binding.*; +import org.apache.poi.sl.draw.binding.CTAdjPoint2D; +import org.apache.poi.sl.draw.binding.CTPath2D; +import org.apache.poi.sl.draw.binding.CTPath2DArcTo; +import org.apache.poi.sl.draw.binding.CTPath2DClose; +import org.apache.poi.sl.draw.binding.CTPath2DCubicBezierTo; +import org.apache.poi.sl.draw.binding.CTPath2DLineTo; +import org.apache.poi.sl.draw.binding.CTPath2DMoveTo; +import org.apache.poi.sl.draw.binding.CTPath2DQuadBezierTo; +import org.apache.poi.sl.draw.binding.STPathFillMode; /** * Specifies a creation path consisting of a series of moves, lines and curves @@ -90,10 +98,10 @@ public class Path { } /** - * Convert the internal represenation to java.awt.GeneralPath + * Convert the internal represenation to java.awt.geom.Path2D */ - public GeneralPath getPath(Context ctx) { - GeneralPath path = new GeneralPath(); + public Path2D.Double getPath(Context ctx) { + Path2D.Double path = new Path2D.Double(); for(PathCommand cmd : commands) cmd.execute(path, ctx); return path; diff --git a/src/java/org/apache/poi/sl/draw/geom/PathCommand.java b/src/java/org/apache/poi/sl/draw/geom/PathCommand.java index 3063ab81bc..41fa21a546 100644 --- a/src/java/org/apache/poi/sl/draw/geom/PathCommand.java +++ b/src/java/org/apache/poi/sl/draw/geom/PathCommand.java @@ -19,7 +19,7 @@ package org.apache.poi.sl.draw.geom; -import java.awt.geom.GeneralPath; +import java.awt.geom.Path2D; /** * A path command in DrawingML. One of: @@ -41,5 +41,5 @@ public interface PathCommand { * @param path the path to append the result to * @param ctx the context to lookup variables */ - void execute(GeneralPath path, Context ctx); + void execute(Path2D.Double path, Context ctx); } diff --git a/src/java/org/apache/poi/sl/draw/geom/QuadToCommand.java b/src/java/org/apache/poi/sl/draw/geom/QuadToCommand.java index e9a9364b2d..d5e848b5a9 100644 --- a/src/java/org/apache/poi/sl/draw/geom/QuadToCommand.java +++ b/src/java/org/apache/poi/sl/draw/geom/QuadToCommand.java @@ -19,9 +19,9 @@ package org.apache.poi.sl.draw.geom; -import org.apache.poi.sl.draw.binding.CTAdjPoint2D; +import java.awt.geom.Path2D; -import java.awt.geom.GeneralPath; +import org.apache.poi.sl.draw.binding.CTAdjPoint2D; /** * Date: 10/25/11 @@ -38,11 +38,11 @@ public class QuadToCommand implements PathCommand { arg4 = pt2.getY().toString(); } - public void execute(GeneralPath path, Context ctx){ + public void execute(Path2D.Double path, Context ctx){ double x1 = ctx.getValue(arg1); double y1 = ctx.getValue(arg2); double x2 = ctx.getValue(arg3); double y2 = ctx.getValue(arg4); - path.quadTo((float)x1, (float)y1, (float)x2, (float)y2); + path.quadTo(x1, y1, x2, y2); } } diff --git a/src/java/org/apache/poi/sl/usermodel/FreeformShape.java b/src/java/org/apache/poi/sl/usermodel/FreeformShape.java index ca9a8d30f1..9536065ef0 100644 --- a/src/java/org/apache/poi/sl/usermodel/FreeformShape.java +++ b/src/java/org/apache/poi/sl/usermodel/FreeformShape.java @@ -17,7 +17,7 @@ package org.apache.poi.sl.usermodel; -import java.awt.geom.GeneralPath; +import java.awt.geom.Path2D; public interface FreeformShape< S extends Shape<S,P>, @@ -33,7 +33,7 @@ public interface FreeformShape< * * @return the path */ - GeneralPath getPath(); + Path2D.Double getPath(); /** * Set the shape path @@ -41,5 +41,5 @@ public interface FreeformShape< * @param path shape outline * @return the number of points written */ - int setPath(GeneralPath path); + int setPath(Path2D.Double path); } diff --git a/src/java/org/apache/poi/sl/usermodel/PlaceableShape.java b/src/java/org/apache/poi/sl/usermodel/PlaceableShape.java index 191bad65f6..9fdcf91f6b 100644 --- a/src/java/org/apache/poi/sl/usermodel/PlaceableShape.java +++ b/src/java/org/apache/poi/sl/usermodel/PlaceableShape.java @@ -25,6 +25,11 @@ public interface PlaceableShape< > { ShapeContainer<S,P> getParent(); + /** + * @return the sheet this shape belongs to + */ + Sheet<S,P> getSheet(); + /** * @return the position of this shape within the drawing canvas. * The coordinates are expressed in points diff --git a/src/java/org/apache/poi/sl/usermodel/Shape.java b/src/java/org/apache/poi/sl/usermodel/Shape.java index 36f8379c58..4d053df6fe 100644 --- a/src/java/org/apache/poi/sl/usermodel/Shape.java +++ b/src/java/org/apache/poi/sl/usermodel/Shape.java @@ -25,8 +25,7 @@ public interface Shape< > { ShapeContainer<S,P> getParent(); - /** - * + /** * @return the sheet this shape belongs to */ Sheet<S,P> getSheet(); diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFFreeformShape.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFFreeformShape.java index f75ffa8ed4..77e99d2d39 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFFreeformShape.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFFreeformShape.java @@ -20,7 +20,7 @@ package org.apache.poi.xslf.usermodel; import java.awt.geom.AffineTransform; -import java.awt.geom.GeneralPath; +import java.awt.geom.Path2D; import java.awt.geom.PathIterator; import java.awt.geom.Rectangle2D; @@ -57,7 +57,7 @@ public class XSLFFreeformShape extends XSLFAutoShape } @Override - public int setPath(GeneralPath path) { + public int setPath(Path2D.Double path) { CTPath2D ctPath = CTPath2D.Factory.newInstance(); Rectangle2D bounds = path.getBounds2D(); @@ -119,8 +119,8 @@ public class XSLFFreeformShape extends XSLFAutoShape } @Override - public GeneralPath getPath() { - GeneralPath path = new GeneralPath(); + public Path2D.Double getPath() { + Path2D.Double path = new Path2D.Double(); Rectangle2D bounds = getAnchor(); CTCustomGeometry2D geom = getSpPr().getCustGeom(); @@ -168,7 +168,7 @@ public class XSLFFreeformShape extends XSLFAutoShape // The returned path should fit in the bounding rectangle AffineTransform at = new AffineTransform(); at.translate(bounds.getX(), bounds.getY()); - return new GeneralPath(at.createTransformedShape(path)); + return new Path2D.Double(at.createTransformedShape(path)); } /** * @param shapeId 1-based shapeId diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFHyperlink.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFHyperlink.java index 6f4a5228d0..fd66d1f7e5 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFHyperlink.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFHyperlink.java @@ -48,11 +48,11 @@ public class XSLFHyperlink implements Hyperlink<XSLFShape,XSLFTextParagraph> { @Override public String getAddress() { - if (!_link.isSetId()) { + String id = _link.getId(); + if (id == null || "".equals(id)) { return _link.getAction(); } - String id = _link.getId(); URI targetURI = _sheet.getPackagePart().getRelationship(id).getTargetURI(); return targetURI.toASCIIString(); diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextRun.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextRun.java index 049de50261..08804b0a8e 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextRun.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextRun.java @@ -455,8 +455,15 @@ public class XSLFTextRun implements TextRun { @Override public XSLFHyperlink getHyperlink(){ - if(!_r.getRPr().isSetHlinkClick()) return null; - return new XSLFHyperlink(_r.getRPr().getHlinkClick(), _p.getParentShape().getSheet()); + CTTextCharacterProperties rPr = _r.getRPr(); + if (rPr == null) { + return null; + } + CTHyperlink hl = rPr.getHlinkClick(); + if (hl == null) { + return null; + } + return new XSLFHyperlink(hl, _p.getParentShape().getSheet()); } private boolean fetchCharacterProperty(CharacterPropertyFetcher<?> fetcher){ diff --git a/src/ooxml/java/org/apache/poi/xslf/util/PPTX2PNG.java b/src/ooxml/java/org/apache/poi/xslf/util/PPTX2PNG.java index 2ec85f5ac7..7f2f039688 100644 --- a/src/ooxml/java/org/apache/poi/xslf/util/PPTX2PNG.java +++ b/src/ooxml/java/org/apache/poi/xslf/util/PPTX2PNG.java @@ -24,18 +24,17 @@ import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.image.BufferedImage; import java.io.File; -import java.util.HashMap; import java.util.List; import java.util.Locale; -import java.util.Map; +import java.util.Set; +import java.util.TreeSet; import javax.imageio.ImageIO; -import org.apache.poi.sl.draw.Drawable; +import org.apache.poi.sl.draw.DrawFactory; import org.apache.poi.sl.usermodel.Slide; import org.apache.poi.sl.usermodel.SlideShow; import org.apache.poi.sl.usermodel.SlideShowFactory; -import org.apache.poi.util.JvmBugs; /** * An utility to convert slides of a .pptx slide show to a PNG image @@ -65,7 +64,7 @@ public class PPTX2PNG { return; } - int slidenum = -1; + String slidenumStr = "-1"; float scale = 1; File file = null; String format = "png"; @@ -77,7 +76,7 @@ public class PPTX2PNG { if ("-scale".equals(args[i])) { scale = Float.parseFloat(args[++i]); } else if ("-slide".equals(args[i])) { - slidenum = Integer.parseInt(args[++i]); + slidenumStr = args[++i]; } else if ("-format".equals(args[i])) { format = args[++i]; } else if ("-outdir".equals(args[i])) { @@ -120,9 +119,11 @@ public class PPTX2PNG { SlideShow<?,?> ss = SlideShowFactory.create(file, null, true); List<? extends Slide<?,?>> slides = ss.getSlides(); + Set<Integer> slidenum = slideIndexes(slides.size(), slidenumStr); - if (slidenum < -1 || slidenum == 0 || slidenum > slides.size()) { + if (slidenum.isEmpty()) { usage("slidenum must be either -1 (for all) or within range: [1.."+slides.size()+"] for "+file); + ss.close(); return; } @@ -130,39 +131,36 @@ public class PPTX2PNG { int width = (int) (pgsize.width * scale); int height = (int) (pgsize.height * scale); - int slideNo=1; - for(Slide<?,?> slide : slides) { - if (slidenum == -1 || slideNo == slidenum) { - String title = slide.getTitle(); - if (!quiet) { - System.out.println("Rendering slide " + slideNo + (title == null ? "" : ": " + title)); - } + for(Integer slideNo : slidenum) { + Slide<?,?> slide = slides.get(slideNo); + String title = slide.getTitle(); + if (!quiet) { + System.out.println("Rendering slide " + slideNo + (title == null ? "" : ": " + title)); + } - BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); - Graphics2D graphics = img.createGraphics(); - fixFonts(graphics); - - // default rendering options - graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); - graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); - graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); + BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + Graphics2D graphics = img.createGraphics(); + DrawFactory.getInstance(graphics).fixFonts(graphics); + + // default rendering options + graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); + graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); - graphics.scale(scale, scale); + graphics.scale(scale, scale); - // draw stuff - slide.draw(graphics); + // draw stuff + slide.draw(graphics); - // save the result - if (!"null".equals(format)) { - String outname = file.getName().replaceFirst(".pptx?", ""); - outname = String.format(Locale.ROOT, "%1$s-%2$04d.%3$s", outname, slideNo, format); - File outfile = new File(outdir, outname); - ImageIO.write(img, format, outfile); - } - } - slideNo++; - } + // save the result + if (!"null".equals(format)) { + String outname = file.getName().replaceFirst(".pptx?", ""); + outname = String.format(Locale.ROOT, "%1$s-%2$04d.%3$s", outname, slideNo, format); + File outfile = new File(outdir, outname); + ImageIO.write(img, format, outfile); + } + } if (!quiet) { System.out.println("Done"); @@ -170,14 +168,43 @@ public class PPTX2PNG { ss.close(); } - - @SuppressWarnings("unchecked") - private static void fixFonts(Graphics2D graphics) { - if (!JvmBugs.hasLineBreakMeasurerBug()) return; - Map<String,String> fontMap = (Map<String,String>)graphics.getRenderingHint(Drawable.FONT_MAP); - if (fontMap == null) fontMap = new HashMap<String,String>(); - fontMap.put("Calibri", "Lucida Sans"); - fontMap.put("Cambria", "Lucida Bright"); - graphics.setRenderingHint(Drawable.FONT_MAP, fontMap); + + private static Set<Integer> slideIndexes(final int slideCount, String range) { + Set<Integer> slideIdx = new TreeSet<Integer>(); + if ("-1".equals(range)) { + for (int i=0; i<slideCount; i++) { + slideIdx.add(i); + } + } else { + for (String subrange : range.split(",")) { + String idx[] = subrange.split("-"); + switch (idx.length) { + default: + case 0: break; + case 1: { + int subidx = Integer.parseInt(idx[0]); + if (subrange.contains("-")) { + int startIdx = subrange.startsWith("-") ? 0 : subidx; + int endIdx = subrange.endsWith("-") ? slideCount : Math.min(subidx,slideCount); + for (int i=Math.max(startIdx,1); i<endIdx; i++) { + slideIdx.add(i-1); + } + } else { + slideIdx.add(Math.max(subidx,1)-1); + } + break; + } + case 2: { + int startIdx = Math.min(Integer.parseInt(idx[0]), slideCount); + int endIdx = Math.min(Integer.parseInt(idx[1]), slideCount); + for (int i=Math.max(startIdx,1); i<endIdx; i++) { + slideIdx.add(i-1); + } + break; + } + } + } + } + return slideIdx; } } diff --git a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestPPTX2PNG.java b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestPPTX2PNG.java index b80ef77fb8..9ad888da41 100644 --- a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestPPTX2PNG.java +++ b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestPPTX2PNG.java @@ -36,17 +36,28 @@ public class TestPPTX2PNG { public void render() throws Exception { POIDataSamples samples = POIDataSamples.getSlideShowInstance(); - String[] testFiles = {"alterman_security.ppt","alterman_security.pptx","KEY02.pptx","themes.pptx","backgrounds.pptx","layouts.pptx", "sample.pptx", "shapes.pptx",}; -// String[] testFiles = {"41246-2.ppt","45543.ppt","53446.ppt","ParagraphStylesShorterThanCharStyles.ppt"}; +// File testFilesX[] = new File("tmp_ppt").listFiles(new FileFilter() { +// public boolean accept(File pathname) { +// return pathname.getName().toLowerCase().contains("ppt"); +// } +// }); +// String testFiles[] = new String[testFilesX.length]; +// for (int i=0; i<testFilesX.length; i++) { +// testFiles[i] = testFilesX[i].getPath(); +// } + + + String[] testFiles = {"53446.ppt", "alterman_security.ppt","alterman_security.pptx","KEY02.pptx","themes.pptx","backgrounds.pptx","layouts.pptx", "sample.pptx", "shapes.pptx",}; String[] args = { "-format", "null", // png,gif,jpg or null for test "-slide", "-1", // -1 for all "-outdir", new File("build/tmp/").getCanonicalPath(), - "-quite", + "-quiet", "dummyfile" }; for(String sampleFile : testFiles){ args[args.length-1] = samples.getFile(sampleFile).getCanonicalPath(); +// args[args.length-1] = new File(sampleFile).getCanonicalPath(); try { PPTX2PNG.main(args); } catch (IllegalStateException e) { diff --git a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFFreeformShape.java b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFFreeformShape.java index 720c5f6e00..b7b902f542 100644 --- a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFFreeformShape.java +++ b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFFreeformShape.java @@ -19,8 +19,9 @@ package org.apache.poi.xslf.usermodel; import static org.junit.Assert.assertEquals; import java.awt.geom.Ellipse2D; -import java.awt.geom.GeneralPath; +import java.awt.geom.Path2D; import java.awt.geom.Rectangle2D; +import java.io.IOException; import org.junit.Test; @@ -30,16 +31,16 @@ import org.junit.Test; public class TestXSLFFreeformShape { @Test - public void testSetPath() { + public void testSetPath() throws IOException { XMLSlideShow ppt = new XMLSlideShow(); XSLFSlide slide = ppt.createSlide(); XSLFFreeformShape shape1 = slide.createFreeform(); // comples path consisting of a rectangle and an ellipse inside it - GeneralPath path1 = new GeneralPath(new Rectangle2D.Double(150, 150, 300, 300)); + Path2D.Double path1 = new Path2D.Double(new Rectangle2D.Double(150, 150, 300, 300)); path1.append(new Ellipse2D.Double(200, 200, 100, 50), false); shape1.setPath(path1); - GeneralPath path2 = shape1.getPath(); + Path2D.Double path2 = shape1.getPath(); // YK: how to compare the original path1 and the value returned by XSLFFreeformShape.getPath() ? // one way is to create another XSLFFreeformShape from path2 and compare the resulting xml @@ -49,5 +50,7 @@ public class TestXSLFFreeformShape { shape2.setPath(path2); assertEquals(shape1.getSpPr().getCustGeom().toString(), shape2.getSpPr().getCustGeom().toString()); + + ppt.close(); } } \ No newline at end of file diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/PPGraphics2D.java b/src/scratchpad/src/org/apache/poi/hslf/model/PPGraphics2D.java index 4a3af15f8d..7d6b0c2626 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/PPGraphics2D.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/PPGraphics2D.java @@ -42,6 +42,7 @@ import java.awt.geom.Arc2D; import java.awt.geom.Ellipse2D; import java.awt.geom.GeneralPath; import java.awt.geom.Line2D; +import java.awt.geom.Path2D; import java.awt.geom.RoundRectangle2D; import java.awt.image.BufferedImage; import java.awt.image.BufferedImageOp; @@ -244,7 +245,7 @@ public final class PPGraphics2D extends Graphics2D implements Cloneable { * @see #setComposite */ public void draw(Shape shape){ - GeneralPath path = new GeneralPath(_transform.createTransformedShape(shape)); + Path2D.Double path = new Path2D.Double(_transform.createTransformedShape(shape)); HSLFFreeformShape p = new HSLFFreeformShape(_group); p.setPath(path); p.getFill().setForegroundColor(null); @@ -346,7 +347,7 @@ public final class PPGraphics2D extends Graphics2D implements Cloneable { * @see #setClip */ public void fill(Shape shape){ - GeneralPath path = new GeneralPath(_transform.createTransformedShape(shape)); + Path2D.Double path = new Path2D.Double(_transform.createTransformedShape(shape)); HSLFFreeformShape p = new HSLFFreeformShape(_group); p.setPath(path); applyPaint(p); diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/InteractiveInfo.java b/src/scratchpad/src/org/apache/poi/hslf/record/InteractiveInfo.java index f51d20ae5d..8ca722d856 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/record/InteractiveInfo.java +++ b/src/scratchpad/src/org/apache/poi/hslf/record/InteractiveInfo.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.io.OutputStream; import org.apache.poi.util.LittleEndian; +import org.apache.poi.util.POILogger; /** * This class represents the metadata of a link in a slide/notes/etc. @@ -59,11 +60,12 @@ public class InteractiveInfo extends RecordContainer { */ private void findInterestingChildren() { // First child should be the InteractiveInfoAtom - if(_children[0] instanceof InteractiveInfoAtom) { - infoAtom = (InteractiveInfoAtom)_children[0]; - } else { - throw new IllegalStateException("First child record wasn't a InteractiveInfoAtom, was of type " + _children[0].getRecordType()); - } + if (_children == null || _children.length == 0 || !(_children[0] instanceof InteractiveInfoAtom)) { + logger.log(POILogger.WARN, "First child record wasn't a InteractiveInfoAtom - leaving this atom in an invalid state..."); + return; + } + + infoAtom = (InteractiveInfoAtom)_children[0]; } /** diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/Record.java b/src/scratchpad/src/org/apache/poi/hslf/record/Record.java index 61034f1f94..e2987b3d62 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/record/Record.java +++ b/src/scratchpad/src/org/apache/poi/hslf/record/Record.java @@ -168,7 +168,7 @@ public abstract class Record try { c = RecordTypes.forTypeID((short)type).handlingClass; if(c == null) { - // How odd. RecordTypes normally subsitutes in + // How odd. RecordTypes normally substitutes in // a default handler class if it has heard of the record // type but there's no support for it. Explicitly request // that now diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFFill.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFFill.java index 5c4f1461af..06e8d00f8c 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFFill.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFFill.java @@ -23,23 +23,28 @@ import java.io.InputStream; import java.util.List; import org.apache.poi.ddf.AbstractEscherOptRecord; +import org.apache.poi.ddf.EscherArrayProperty; import org.apache.poi.ddf.EscherBSERecord; +import org.apache.poi.ddf.EscherColorRef; import org.apache.poi.ddf.EscherContainerRecord; import org.apache.poi.ddf.EscherProperties; import org.apache.poi.ddf.EscherRecord; import org.apache.poi.ddf.EscherSimpleProperty; import org.apache.poi.hslf.record.Document; import org.apache.poi.sl.draw.DrawPaint; +import org.apache.poi.sl.usermodel.ColorStyle; import org.apache.poi.sl.usermodel.FillStyle; import org.apache.poi.sl.usermodel.PaintStyle; +import org.apache.poi.sl.usermodel.PaintStyle.GradientPaint; +import org.apache.poi.sl.usermodel.PaintStyle.GradientPaint.GradientType; import org.apache.poi.sl.usermodel.PaintStyle.TexturePaint; +import org.apache.poi.util.LittleEndian; import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; +import org.apache.poi.util.Units; /** * Represents functionality provided by the 'Fill Effects' dialog in PowerPoint. - * - * @author Yegor Kozlov */ public final class HSLFFill { // For logging @@ -118,36 +123,110 @@ public final class HSLFFill { public FillStyle getFillStyle() { return new FillStyle() { public PaintStyle getPaint() { - switch (getFillType()) { + final int fillType = getFillType(); + // TODO: fix gradient types, this mismatches with the MS-ODRAW definition ... + // need to handle (not only) the type (radial,rectangular,linear), + // the direction, e.g. top right, and bounds (e.g. for rectangular boxes) + switch (fillType) { case FILL_SOLID: return DrawPaint.createSolidPaint(getForegroundColor()); - case FILL_PICTURE: { - final HSLFPictureData pd = getPictureData(); - if (pd == null) break; - - return new TexturePaint() { - public InputStream getImageData() { - return new ByteArrayInputStream(pd.getData()); - } - - public String getContentType() { - return pd.getContentType(); - } - - public int getAlpha() { - return (int)(shape.getAlpha(EscherProperties.FILL__FILLOPACITY)*100000.0); - } - }; - } + case FILL_SHADE_SHAPE: + return getGradientPaint(GradientType.shape); + case FILL_SHADE_CENTER: + case FILL_SHADE_TITLE: + return getGradientPaint(GradientType.circular); + case FILL_SHADE: + case FILL_SHADE_SCALE: + return getGradientPaint(GradientType.linear); + case FILL_PICTURE: + return getTexturePaint(); default: - logger.log(POILogger.WARN, "unsuported fill type: " + getFillType()); - break; + logger.log(POILogger.WARN, "unsuported fill type: " + fillType); + return null; } - return null; } }; } + + + private GradientPaint getGradientPaint(final GradientType gradientType) { + final AbstractEscherOptRecord opt = shape.getEscherOptRecord(); + final EscherArrayProperty ep = HSLFShape.getEscherProperty(opt, EscherProperties.FILL__SHADECOLORS); + final int colorCnt = (ep == null) ? 0 : ep.getNumberOfElementsInArray(); + + return new GradientPaint() { + public double getGradientAngle() { + // A value of type FixedPoint, as specified in [MS-OSHARED] section 2.2.1.6, + // that specifies the angle of the gradient fill. Zero degrees represents a vertical vector from + // bottom to top. The default value for this property is 0x00000000. + int rot = shape.getEscherProperty(EscherProperties.FILL__ANGLE); + return 90-Units.fixedPointToDouble(rot); + } + public ColorStyle[] getGradientColors() { + ColorStyle cs[]; + if (colorCnt == 0) { + cs = new ColorStyle[2]; + cs[0] = wrapColor(getBackgroundColor()); + cs[1] = wrapColor(getForegroundColor()); + } else { + cs = new ColorStyle[colorCnt]; + int idx = 0; + // TODO: handle palette colors and alpha(?) value + for (byte data[] : ep) { + EscherColorRef ecr = new EscherColorRef(data, 0, 4); + cs[idx++] = wrapColor(shape.getColor(ecr)); + } + } + return cs; + } + private ColorStyle wrapColor(Color col) { + return (col == null) ? null : DrawPaint.createSolidPaint(col).getSolidColor(); + } + public float[] getGradientFractions() { + float frc[]; + if (colorCnt == 0) { + frc = new float[]{0, 1}; + } else { + frc = new float[colorCnt]; + int idx = 0; + for (byte data[] : ep) { + double pos = Units.fixedPointToDouble(LittleEndian.getInt(data, 4)); + frc[idx++] = (float)pos; + } + } + return frc; + } + public boolean isRotatedWithShape() { + return false; + } + public GradientType getGradientType() { + return gradientType; + } + }; + } + + private TexturePaint getTexturePaint() { + final HSLFPictureData pd = getPictureData(); + if (pd == null) { + return null; + } + + return new TexturePaint() { + public InputStream getImageData() { + return new ByteArrayInputStream(pd.getData()); + } + + public String getContentType() { + return pd.getContentType(); + } + + public int getAlpha() { + return (int)(shape.getAlpha(EscherProperties.FILL__FILLOPACITY)*100000.0); + } + }; + } + /** * Returns fill type. * Must be one of the <code>FILL_*</code> constants defined in this class. @@ -172,6 +251,7 @@ public final class HSLFFill { } } + @SuppressWarnings("resource") protected EscherBSERecord getEscherBSERecord(int idx){ HSLFSheet sheet = shape.getSheet(); if(sheet == null) { @@ -258,6 +338,7 @@ public final class HSLFFill { /** * <code>PictureData</code> object used in a texture, pattern of picture fill. */ + @SuppressWarnings("resource") public HSLFPictureData getPictureData(){ AbstractEscherOptRecord opt = shape.getEscherOptRecord(); EscherSimpleProperty p = HSLFShape.getEscherProperty(opt, EscherProperties.FILL__PATTERNTEXTURE); diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFFreeformShape.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFFreeformShape.java index dde778ffcc..8be86f0995 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFFreeformShape.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFFreeformShape.java @@ -18,22 +18,25 @@ package org.apache.poi.hslf.usermodel; import java.awt.geom.AffineTransform; -import java.awt.geom.GeneralPath; +import java.awt.geom.Path2D; import java.awt.geom.PathIterator; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.ArrayList; -import java.util.Arrays; +import java.util.Iterator; import java.util.List; import org.apache.poi.ddf.AbstractEscherOptRecord; import org.apache.poi.ddf.EscherArrayProperty; import org.apache.poi.ddf.EscherContainerRecord; import org.apache.poi.ddf.EscherProperties; +import org.apache.poi.ddf.EscherProperty; import org.apache.poi.ddf.EscherSimpleProperty; import org.apache.poi.sl.usermodel.FreeformShape; import org.apache.poi.sl.usermodel.ShapeContainer; import org.apache.poi.sl.usermodel.ShapeType; +import org.apache.poi.util.BitField; +import org.apache.poi.util.BitFieldFactory; import org.apache.poi.util.LittleEndian; import org.apache.poi.util.POILogger; import org.apache.poi.util.Units; @@ -57,6 +60,85 @@ public final class HSLFFreeformShape extends HSLFAutoShape implements FreeformSh public static final byte[] SEGMENTINFO_CLOSE = new byte[]{0x01, (byte)0x60}; public static final byte[] SEGMENTINFO_END = new byte[]{0x00, (byte)0x80}; + private static BitField PATH_INFO = BitFieldFactory.getInstance(0xE000); + private static BitField ESCAPE_INFO = BitFieldFactory.getInstance(0x1F00); + + enum PathInfo { + lineTo(0),curveTo(1),moveTo(2),close(3),end(4),escape(5),clientEscape(6); + int flag; + PathInfo(int flag) { + this.flag = flag; + } + static PathInfo valueOf(int flag) { + for (PathInfo v : values()) { + if (v.flag == flag) { + return v; + } + } + return null; + } + } + + enum EscapeInfo { + EXTENSION(0x0000), + ANGLE_ELLIPSE_TO(0x0001), + ANGLE_ELLIPSE(0x0002), + ARC_TO(0x0003), + ARC(0x0004), + CLOCKWISE_ARC_TO(0x0005), + CLOCKWISE_ARC(0x0006), + ELLIPTICAL_QUADRANT_X(0x0007), + ELLIPTICAL_QUADRANT_Y(0x0008), + QUADRATIC_BEZIER(0x0009), + NO_FILL(0X000A), + NO_LINE(0X000B), + AUTO_LINE(0X000C), + AUTO_CURVE(0X000D), + CORNER_LINE(0X000E), + CORNER_CURVE(0X000F), + SMOOTH_LINE(0X0010), + SMOOTH_CURVE(0X0011), + SYMMETRIC_LINE(0X0012), + SYMMETRIC_CURVE(0X0013), + FREEFORM(0X0014), + FILL_COLOR(0X0015), + LINE_COLOR(0X0016); + + int flag; + EscapeInfo(int flag) { + this.flag = flag; + } + static EscapeInfo valueOf(int flag) { + for (EscapeInfo v : values()) { + if (v.flag == flag) { + return v; + } + } + return null; + } + } + + enum ShapePath { + LINES(0), + LINES_CLOSED(1), + CURVES(2), + CURVES_CLOSED(3), + COMPLEX(4); + + int flag; + ShapePath(int flag) { + this.flag = flag; + } + static ShapePath valueOf(int flag) { + for (ShapePath v : values()) { + if (v.flag == flag) { + return v; + } + } + return null; + } + } + /** * Create a Freeform object and initialize it from the supplied Record container. * @@ -88,7 +170,7 @@ public final class HSLFFreeformShape extends HSLFAutoShape implements FreeformSh } @Override - public int setPath(GeneralPath path) { + public int setPath(Path2D.Double path) { Rectangle2D bounds = path.getBounds2D(); PathIterator it = path.getPathIterator(new AffineTransform()); @@ -174,23 +256,19 @@ public final class HSLFFreeformShape extends HSLFAutoShape implements FreeformSh opt.sortProperties(); setAnchor(bounds); - + return numPoints; } @Override - public GeneralPath getPath(){ + public Path2D.Double getPath(){ AbstractEscherOptRecord opt = getEscherOptRecord(); - opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.GEOMETRY__SHAPEPATH, 0x4)); - EscherArrayProperty verticesProp = getEscherProperty(opt, (short)(EscherProperties.GEOMETRY__VERTICES + 0x4000)); - if(verticesProp == null) verticesProp = getEscherProperty(opt, EscherProperties.GEOMETRY__VERTICES); - - EscherArrayProperty segmentsProp = getEscherProperty(opt, (short)(EscherProperties.GEOMETRY__SEGMENTINFO + 0x4000)); - if(segmentsProp == null) segmentsProp = getEscherProperty(opt, EscherProperties.GEOMETRY__SEGMENTINFO); + EscherArrayProperty verticesProp = getShapeProp(opt, EscherProperties.GEOMETRY__VERTICES); + EscherArrayProperty segmentsProp = getShapeProp(opt, EscherProperties.GEOMETRY__SEGMENTINFO); // return empty path if either GEOMETRY__VERTICES or GEOMETRY__SEGMENTINFO is missing, see Bugzilla 54188 - GeneralPath path = new GeneralPath(); + Path2D.Double path = new Path2D.Double(); //sanity check if(verticesProp == null) { @@ -202,46 +280,60 @@ public final class HSLFFreeformShape extends HSLFAutoShape implements FreeformSh return path; } - int numPoints = verticesProp.getNumberOfElementsInArray(); - int numSegments = segmentsProp.getNumberOfElementsInArray(); - for (int i = 0, j = 0; i < numSegments && j < numPoints; i++) { - byte[] elem = segmentsProp.getElement(i); - if(Arrays.equals(elem, SEGMENTINFO_MOVETO)){ - byte[] p = verticesProp.getElement(j++); - short x = LittleEndian.getShort(p, 0); - short y = LittleEndian.getShort(p, 2); - path.moveTo(Units.masterToPoints(x), Units.masterToPoints(y)); - } else if (Arrays.equals(elem, SEGMENTINFO_CUBICTO) || Arrays.equals(elem, SEGMENTINFO_CUBICTO2)){ - i++; - byte[] p1 = verticesProp.getElement(j++); - short x1 = LittleEndian.getShort(p1, 0); - short y1 = LittleEndian.getShort(p1, 2); - byte[] p2 = verticesProp.getElement(j++); - short x2 = LittleEndian.getShort(p2, 0); - short y2 = LittleEndian.getShort(p2, 2); - byte[] p3 = verticesProp.getElement(j++); - short x3 = LittleEndian.getShort(p3, 0); - short y3 = LittleEndian.getShort(p3, 2); - path.curveTo( - Units.masterToPoints(x1), Units.masterToPoints(y1), - Units.masterToPoints(x2), Units.masterToPoints(y2), - Units.masterToPoints(x3), Units.masterToPoints(y3)); - - } else if (Arrays.equals(elem, SEGMENTINFO_LINETO)){ - i++; - byte[] pnext = segmentsProp.getElement(i); - if(Arrays.equals(pnext, SEGMENTINFO_ESCAPE)){ - if(j + 1 < numPoints){ - byte[] p = verticesProp.getElement(j++); - short x = LittleEndian.getShort(p, 0); - short y = LittleEndian.getShort(p, 2); - path.lineTo(Units.masterToPoints(x), Units.masterToPoints(y)); - } - } else if (Arrays.equals(pnext, SEGMENTINFO_CLOSE)){ - path.closePath(); + Iterator<byte[]> vertIter = verticesProp.iterator(); + Iterator<byte[]> segIter = segmentsProp.iterator(); + + byte segPushBack[] = null; + while (vertIter.hasNext() && segIter.hasNext()) { + byte[] segElem = (segPushBack != null) ? segPushBack : segIter.next(); + segPushBack = null; + PathInfo pi = getPathInfo(segElem); + switch (pi) { + case escape: { + handleEscapeInfo(path, segElem, vertIter); + break; } + case moveTo: { + byte[] p = vertIter.next(); + double x = Units.masterToPoints(LittleEndian.getShort(p, 0)); + double y = Units.masterToPoints(LittleEndian.getShort(p, 2)); + path.moveTo(x,y); + break; + } + case curveTo: { + byte[] p1 = vertIter.next(); + double x1 = Units.masterToPoints(LittleEndian.getShort(p1, 0)); + double y1 = Units.masterToPoints(LittleEndian.getShort(p1, 2)); + byte[] p2 = vertIter.next(); + double x2 = Units.masterToPoints(LittleEndian.getShort(p2, 0)); + double y2 = Units.masterToPoints(LittleEndian.getShort(p2, 2)); + byte[] p3 = vertIter.next(); + double x3 = Units.masterToPoints(LittleEndian.getShort(p3, 0)); + double y3 = Units.masterToPoints(LittleEndian.getShort(p3, 2)); + path.curveTo(x1,y1,x2,y2,x3,y3); + break; + } + case lineTo: + if (vertIter.hasNext()) { + byte[] p = vertIter.next(); + double x = Units.masterToPoints(LittleEndian.getShort(p, 0)); + double y = Units.masterToPoints(LittleEndian.getShort(p, 2)); + path.lineTo(x,y); + } + break; + case close: + path.closePath(); + break; + default: + break; } } + + EscherSimpleProperty shapePath = getShapeProp(opt, EscherProperties.GEOMETRY__SHAPEPATH); + ShapePath sp = ShapePath.valueOf(shapePath == null ? 1 : shapePath.getPropertyValue()); + if (sp == ShapePath.LINES_CLOSED || sp == ShapePath.CURVES_CLOSED) { + path.closePath(); + } Rectangle2D anchor = getAnchor(); Rectangle2D bounds = path.getBounds2D(); @@ -251,6 +343,81 @@ public final class HSLFFreeformShape extends HSLFAutoShape implements FreeformSh anchor.getWidth()/bounds.getWidth(), anchor.getHeight()/bounds.getHeight() ); - return new GeneralPath(at.createTransformedShape(path)); + return new Path2D.Double(at.createTransformedShape(path)); + } + + private static <T extends EscherProperty> T getShapeProp(AbstractEscherOptRecord opt, int propId) { + T prop = getEscherProperty(opt, (short)(propId + 0x4000)); + if (prop == null) { + prop = getEscherProperty(opt, propId); + } + return prop; + } + + private void handleEscapeInfo(Path2D path, byte segElem[], Iterator<byte[]> vertIter) { + EscapeInfo ei = getEscapeInfo(segElem); + switch (ei) { + case EXTENSION: + break; + case ANGLE_ELLIPSE_TO: + break; + case ANGLE_ELLIPSE: + break; + case ARC_TO: + break; + case ARC: + break; + case CLOCKWISE_ARC_TO: + break; + case CLOCKWISE_ARC: + break; + case ELLIPTICAL_QUADRANT_X: + break; + case ELLIPTICAL_QUADRANT_Y: + break; + case QUADRATIC_BEZIER: + break; + case NO_FILL: + break; + case NO_LINE: + break; + case AUTO_LINE: + break; + case AUTO_CURVE: + break; + case CORNER_LINE: + break; + case CORNER_CURVE: + break; + case SMOOTH_LINE: + break; + case SMOOTH_CURVE: + break; + case SYMMETRIC_LINE: + break; + case SYMMETRIC_CURVE: + break; + case FREEFORM: + break; + case FILL_COLOR: + break; + case LINE_COLOR: + break; + default: + break; + } + } + + + private static PathInfo getPathInfo(byte elem[]) { + int elemUS = LittleEndian.getUShort(elem, 0); + int pathInfo = PATH_INFO.getValue(elemUS); + return PathInfo.valueOf(pathInfo); + } + + private static EscapeInfo getEscapeInfo(byte elem[]) { + int elemUS = LittleEndian.getUShort(elem, 0); + int escInfo = ESCAPE_INFO.getValue(elemUS); + return EscapeInfo.valueOf(escInfo); } } diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFHyperlink.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFHyperlink.java index 74fe3cb183..949a1230dd 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFHyperlink.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFHyperlink.java @@ -363,6 +363,9 @@ public final class HSLFHyperlink implements Hyperlink<HSLFShape,HSLFTextParagrap InteractiveInfo hldr = (InteractiveInfo)r; InteractiveInfoAtom info = hldr.getInteractiveInfoAtom(); + if (info == null) { + continue; + } int id = info.getHyperlinkID(); ExHyperlink exHyper = exobj.get(id); if (exHyper == null) { diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFPictureShape.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFPictureShape.java index 81acffa25e..6f8802d313 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFPictureShape.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFPictureShape.java @@ -209,6 +209,13 @@ public class HSLFPictureShape extends HSLFSimpleShape implements PictureShape<HS ? null : new Insets((int)(top*100000), (int)(left*100000), (int)(bottom*100000), (int)(right*100000)); } + + @Override + public ShapeType getShapeType() { + // this is kind of a hack, as picture/ole shapes can have a shape type of "frame" + // but rendering is handled like a rectangle + return ShapeType.RECT; + } /** * @return the fractional property or 0 if not defined diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFShape.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFShape.java index 0210a1d8a6..12f781ee43 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFShape.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFShape.java @@ -346,7 +346,13 @@ public abstract class HSLFShape implements Shape<HSLFShape,HSLFTextParagraph> { int val = (p == null) ? defaultColor : p.getPropertyValue(); EscherColorRef ecr = new EscherColorRef(val); - + Color col = getColor(ecr); + + double alpha = getAlpha(opacityProperty); + return new Color(col.getRed(), col.getGreen(), col.getBlue(), (int)(alpha*255.0)); + } + + Color getColor(EscherColorRef ecr) { boolean fPaletteIndex = ecr.hasPaletteIndexFlag(); boolean fPaletteRGB = ecr.hasPaletteRGBFlag(); boolean fSystemRGB = ecr.hasSystemRGBFlag(); @@ -373,11 +379,10 @@ public abstract class HSLFShape implements Shape<HSLFShape,HSLFTextParagraph> { } else if (fSysIndex){ //TODO } - - double alpha = getAlpha(opacityProperty); - return new Color(rgb[0], rgb[1], rgb[2], (int)(alpha*255.0)); + + return new Color(rgb[0], rgb[1], rgb[2]); } - + double getAlpha(short opacityProperty) { AbstractEscherOptRecord opt = getEscherOptRecord(); EscherSimpleProperty op = getEscherProperty(opt, opacityProperty); diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFShapeFactory.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFShapeFactory.java index 1971713536..4797ff5f57 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFShapeFactory.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFShapeFactory.java @@ -111,8 +111,15 @@ public final class HSLFShapeFactory { shape = createNonPrimitive(spContainer, parent); break; default: - EscherTextboxRecord etr = spContainer.getChildById(EscherTextboxRecord.RECORD_ID); - if (parent instanceof HSLFTable && etr != null) { + if (parent instanceof HSLFTable) { + EscherTextboxRecord etr = spContainer.getChildById(EscherTextboxRecord.RECORD_ID); + if (etr == null) { + logger.log(POILogger.WARN, "invalid ppt - add EscherTextboxRecord to cell"); + etr = new EscherTextboxRecord(); + etr.setRecordId(EscherTextboxRecord.RECORD_ID); + etr.setOptions((short)15); + spContainer.addChildRecord(etr); + } shape = new HSLFTableCell(spContainer, (HSLFTable)parent); } else { shape = new HSLFAutoShape(spContainer, parent); diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSimpleShape.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSimpleShape.java index 1326de19a0..0a13a62d57 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSimpleShape.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSimpleShape.java @@ -275,7 +275,8 @@ public abstract class HSLFSimpleShape extends HSLFShape implements SimpleShape<H public Guide getAdjustValue(String name) { if (name == null || !name.matches("adj([1-9]|10)?")) { - throw new IllegalArgumentException("Adjust value '"+name+"' not supported."); + logger.log(POILogger.INFO, "Adjust value '"+name+"' not supported. Using default value."); + return null; } name = name.replace("adj", ""); @@ -296,7 +297,13 @@ public abstract class HSLFSimpleShape extends HSLFShape implements SimpleShape<H default: throw new RuntimeException(); } + // TODO: the adjust values need to be corrected somehow depending on the shape width/height + // see https://social.msdn.microsoft.com/Forums/en-US/3f69ebb3-62a0-4fdd-b367-64790dfb2491/presetshapedefinitionsxml-does-not-specify-width-and-height-form-some-autoshapes?forum=os_binaryfile + + // the adjust value can be format dependent, e.g. hexagon has different values, + // other shape types have the same adjust values in OOXML and native int adjval = getEscherProperty(escherProp, -1); + return (adjval == -1) ? null : new Guide(name, "val "+adjval); } diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFTextParagraph.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFTextParagraph.java index ca76f84ee3..7db12d97de 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFTextParagraph.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFTextParagraph.java @@ -717,9 +717,25 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText String propNames[] = propName.split(","); for (String pn : propNames) { TextProp prop = props.findByName(pn); - if (prop != null) return prop; - } + if (prop == null) { + continue; + } + // Font properties (maybe other too???) can have an index of -1 + // so we check the master for this font index then + if (pn.contains("font") && prop.getValue() == -1) { + return getMasterPropVal(props, pn, paragraph); + } + + return prop; + } + + return getMasterPropVal(props, propName, paragraph); + } + + private static TextProp getMasterPropVal(TextPropCollection props, String propName, HSLFTextParagraph paragraph) { + String propNames[] = propName.split(","); + BitMaskTextProp maskProp = (BitMaskTextProp) props.findByName(ParagraphFlagsTextProp.NAME); boolean hardAttribute = (maskProp != null && maskProp.getValue() == 0); if (hardAttribute) return null; 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 8c112169f1..88a9766e9b 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java @@ -115,6 +115,9 @@ public class HwmfDrawProperties { } public void setViewportOrg(double x, double y) { + if (viewport == null) { + viewport = (Rectangle2D)window.clone(); + } double w = viewport.getWidth(); double h = viewport.getHeight(); viewport.setRect(x, y, w, h); 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 0c13d13074..ace62b49ba 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java @@ -27,7 +27,6 @@ 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; @@ -35,6 +34,7 @@ import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; +import java.util.Map; import java.util.NoSuchElementException; import org.apache.poi.hwmf.record.HwmfBrushStyle; @@ -45,6 +45,9 @@ 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.sl.draw.DrawFactory; +import org.apache.poi.sl.draw.DrawFontManager; +import org.apache.poi.sl.draw.Drawable; public class HwmfGraphics { private final Graphics2D graphicsCtx; @@ -65,6 +68,7 @@ public class HwmfGraphics { this.graphicsCtx = graphicsCtx; this.bbox = (Rectangle2D)bbox.clone(); this.initialAT = graphicsCtx.getTransform(); + DrawFactory.getInstance(graphicsCtx).fixFonts(graphicsCtx); } public HwmfDrawProperties getProperties() { @@ -96,8 +100,8 @@ public class HwmfGraphics { public void fill(Shape shape) { if (prop.getBrushStyle() != HwmfBrushStyle.BS_NULL) { - GeneralPath gp = new GeneralPath(shape); - gp.setWindingRule(prop.getPolyfillMode().awtFlag); +// GeneralPath gp = new GeneralPath(shape); +// gp.setWindingRule(prop.getPolyfillMode().awtFlag); graphicsCtx.setPaint(getFill()); graphicsCtx.fill(shape); } @@ -111,7 +115,12 @@ public class HwmfGraphics { if (view == null) { view = win; } - float width = (float)(prop.getPenWidth() * view.getWidth() / win.getWidth()); + + // TODO: fix line width calculation + float width = (float)prop.getPenWidth(); + if (width == 0) { + width = 1; + } HwmfPenStyle ps = prop.getPenStyle(); int cap = ps.getLineCap().awtFlag; int join = ps.getLineJoin().awtFlag; @@ -261,6 +270,10 @@ public class HwmfGraphics { } stackIndex = curIdx + index; } + if (stackIndex == -1) { + // roll to last when curIdx == 0 + stackIndex = propStack.size()-1; + } prop = propStack.get(stackIndex); } @@ -306,14 +319,72 @@ public class HwmfGraphics { } public void drawString(String text, Rectangle2D bounds) { + drawString(text, bounds, null); + } + + public void drawString(String text, Rectangle2D bounds, int dx[]) { HwmfFont font = prop.getFont(); - if (font == null) { + if (font == null || text == null || text.isEmpty()) { return; } + + double fontH = getFontHeight(font); + // TODO: another approx. ... + double fontW = fontH/1.8; + + int len = text.length(); AttributedString as = new AttributedString(text); - as.addAttribute(TextAttribute.FAMILY, font.getFacename()); - // TODO: fix font height calculation - as.addAttribute(TextAttribute.SIZE, Math.abs(font.getHeight())); + if (dx == null || dx.length == 0) { + addAttributes(as, font, 0, len); + } else { + for (int i=0; i<len; i++) { + addAttributes(as, font, i, i+1); + // Tracking works as a prefix/advance space on characters whereas + // dx[...] is the complete width of the current char + // therefore we need to add the additional/suffix width to the next char + if (i<len-1) { + as.addAttribute(TextAttribute.TRACKING, (dx[i]-fontW)/fontH, i+1, i+2); + } + } + } + + + double angle = Math.toRadians(-font.getEscapement()/10.); + + + final AffineTransform at = graphicsCtx.getTransform(); + try { + graphicsCtx.translate(bounds.getX(), bounds.getY()+fontH); + graphicsCtx.rotate(angle); + if (prop.getBkMode() == HwmfBkMode.OPAQUE) { + // TODO: validate bounds + graphicsCtx.setBackground(prop.getBackgroundColor().getColor()); + graphicsCtx.fill(new Rectangle2D.Double(0, 0, bounds.getWidth(), bounds.getHeight())); + } + graphicsCtx.setColor(prop.getTextColor().getColor()); + graphicsCtx.drawString(as.getIterator(), 0, 0); // (float)bounds.getX(), (float)bounds.getY()); + } finally { + graphicsCtx.setTransform(at); + } + } + + private void addAttributes(AttributedString as, HwmfFont font, int start, int end) { + DrawFontManager fontHandler = (DrawFontManager)graphicsCtx.getRenderingHint(Drawable.FONT_HANDLER); + String fontFamily = null; + @SuppressWarnings("unchecked") + Map<String,String> fontMap = (Map<String,String>)graphicsCtx.getRenderingHint(Drawable.FONT_MAP); + if (fontMap != null && fontMap.containsKey(font.getFacename())) { + fontFamily = fontMap.get(font.getFacename()); + } + if (fontHandler != null) { + fontFamily = fontHandler.getRendererableFont(font.getFacename(), font.getPitchAndFamily()); + } + if (fontFamily == null) { + fontFamily = font.getFacename(); + } + + as.addAttribute(TextAttribute.FAMILY, fontFamily); + as.addAttribute(TextAttribute.SIZE, getFontHeight(font)); as.addAttribute(TextAttribute.STRIKETHROUGH, font.isStrikeOut()); if (font.isUnderline()) { as.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON); @@ -322,23 +393,20 @@ public class HwmfGraphics { 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); + } + + private double getFontHeight(HwmfFont font) { + // see HwmfFont#height for details + double fontHeight = font.getHeight(); + if (fontHeight == 0) { + return 12; + } else if (fontHeight < 0) { + return -fontHeight; + } else { + // TODO: fix font height calculation + // the height is given as font size + ascent + descent + // as an approximation we reduce the height by a static factor + return fontHeight*3/4; } } } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBinaryRasterOp.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBinaryRasterOp.java new file mode 100644 index 0000000000..31ddcd76e8 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBinaryRasterOp.java @@ -0,0 +1,112 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hwmf.record; + +/** + * The BinaryRasterOperation Enumeration section lists the binary raster-operation codes. + * Rasteroperation codes define how metafile processing combines the bits from the selected + * pen with the bits in the destination bitmap. + * + * Each raster-operation code represents a Boolean operation in which the values of the pixels in the + * selected pen and the destination bitmap are combined. Following are the two operands used in these + * operations. + * + * <table> + * <tr><th>Operand</th><th>Meaning</th></tr> + * <tr><td>P</td><td>Selected pen</td></tr> + * <tr><td>D</td><td>Destination bitmap</td></tr> + * </table> + * + * Following are the Boolean operators used in these operations. + * <table> + * <tr><th>Operand</th><th>Meaning</th></tr> + * <tr><td>a</td><td>Bitwise AND</td></tr> + * <tr><td>n</td><td>Bitwise NOT (inverse)</td></tr> + * <tr><td>o</td><td>Bitwise OR</td></tr> + * <tr><td>x</td><td>Bitwise exclusive OR (XOR)</td></tr> + * </table> + * + * All Boolean operations are presented in reverse Polish notation. For example, the following + * operation replaces the values of the pixels in the destination bitmap with a combination of the pixel + * values of the pen and the selected brush: DPo. + * + * Each raster-operation code is a 32-bit integer whose high-order word is a Boolean operation index and + * whose low-order word is the operation code. The 16-bit operation index is a zero-extended, 8-bit + * value that represents all possible outcomes resulting from the Boolean operation on two parameters + * (in this case, the pen and destination values). For example, the operation indexes for the DPo and + * DPan operations are shown in the following list. + * + * <table> + * <tr><th>P</th><th>D</th><th>DPo</th><th>DPan</th></tr> + * <tr><td>0</td><td>0</td><td>0</td><td>1</td></tr> + * <tr><td>0</td><td>1</td><td>1</td><td>1</td></tr> + * <tr><td>1</td><td>0</td><td>1</td><td>1</td></tr> + * <tr><td>1</td><td>1</td><td>1</td><td>0</td></tr> + * </table> + * + */ +public enum HwmfBinaryRasterOp { + /** 0, Pixel is always 0 */ + R2_BLACK(0x0001), + /** DPon, Pixel is the inverse of the R2_MERGEPEN color. */ + R2_NOTMERGEPEN(0x0002), + /** DPna, Pixel is a combination of the screen color and the inverse of the pen color. */ + R2_MASKNOTPEN(0x0003), + /** Pn, Pixel is the inverse of the pen color. */ + R2_NOTCOPYPEN(0x0004), + /** PDna, Pixel is a combination of the colors common to both the pen and the inverse of the screen. */ + R2_MASKPENNOT(0x0005), + /** Dn, Pixel is the inverse of the screen color. */ + R2_NOT(0x0006), + /** DPx, Pixel is a combination of the colors in the pen or in the screen, but not in both. */ + R2_XORPEN(0x0007), + /** DPan, Pixel is the inverse of the R2_MASKPEN color. */ + R2_NOTMASKPEN(0x0008), + /** DPa, Pixel is a combination of the colors common to both the pen and the screen. */ + R2_MASKPEN(0x0009), + /** DPxn, Pixel is the inverse of the R2_XORPEN color. */ + R2_NOTXORPEN(0x000A), + /** D, Pixel remains unchanged. */ + R2_NOP(0x000B), + /** DPno, Pixel is a combination of the colors common to both the screen and the inverse of the pen. */ + R2_MERGENOTPEN(0x000C), + /** P, Pixel is the pen color. */ + R2_COPYPEN(0x000D), + /** PDno, Pixel is a combination of the pen color and the inverse of the screen color.*/ + R2_MERGEPENNOT(0x000E), + /** DPo, Pixel is a combination of the pen color and the screen color. */ + R2_MERGEPEN(0x000F), + /** 1, Pixel is always 1 */ + R2_WHITE(0x0010); + + int opIndex; + + HwmfBinaryRasterOp(int opIndex) { + this.opIndex=opIndex; + } + + public static HwmfBinaryRasterOp valueOf(int opIndex) { + for (HwmfBinaryRasterOp bb : HwmfBinaryRasterOp.values()) { + if (bb.opIndex == opIndex) { + return bb; + } + } + return null; + } + +} diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBitmapDib.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBitmapDib.java index 0f720239f3..ae7f5cb307 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBitmapDib.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBitmapDib.java @@ -18,13 +18,11 @@ package org.apache.poi.hwmf.record; import java.awt.Color; +import java.awt.Graphics2D; import java.awt.image.BufferedImage; -import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; import javax.imageio.ImageIO; @@ -32,6 +30,8 @@ import org.apache.poi.hssf.record.RecordFormatException; import org.apache.poi.util.LittleEndian; import org.apache.poi.util.LittleEndianConsts; import org.apache.poi.util.LittleEndianInputStream; +import org.apache.poi.util.POILogFactory; +import org.apache.poi.util.POILogger; /** * The DeviceIndependentBitmap Object defines an image in device-independent bitmap (DIB) format. @@ -188,6 +188,7 @@ public class HwmfBitmapDib { } } + private static POILogger logger = POILogFactory.getLogger(HwmfBitmapDib.class); private static final int BMP_HEADER_SIZE = 14; private int headerSize; @@ -204,8 +205,6 @@ public class HwmfBitmapDib { private long headerColorUsed = -1; @SuppressWarnings("unused") private long headerColorImportant = -1; - - @SuppressWarnings("unused") private Color colorTable[]; @SuppressWarnings("unused") private int colorMaskR=0,colorMaskG=0,colorMaskB=0; @@ -360,22 +359,24 @@ public class HwmfBitmapDib { protected int readRGBQuad(LittleEndianInputStream leis, int count) throws IOException { int size = 0; - List<Color> colorList = new ArrayList<Color>(); + colorTable = new Color[count]; for (int i=0; i<count; i++) { int blue = leis.readUByte(); int green = leis.readUByte(); int red = leis.readUByte(); @SuppressWarnings("unused") int reserved = leis.readUByte(); - Color c = new Color(red, green, blue); - colorList.add(c); + colorTable[i] = new Color(red, green, blue); size += 4 * LittleEndianConsts.BYTE_SIZE; } - colorTable = colorList.toArray(new Color[colorList.size()]); return size; } public InputStream getBMPStream() { + return new ByteArrayInputStream(getBMPData()); + } + + private byte[] getBMPData() { if (imageData == null) { throw new RecordFormatException("bitmap not initialized ... need to call init() before"); } @@ -398,14 +399,20 @@ public class HwmfBitmapDib { // fill the "known" image data System.arraycopy(imageData, 0, buf, BMP_HEADER_SIZE, imageData.length); - return new ByteArrayInputStream(buf); + return buf; } public BufferedImage getImage() { try { return ImageIO.read(getBMPStream()); } catch (IOException e) { - throw new RecordFormatException("invalid bitmap data", e); + logger.log(POILogger.ERROR, "invalid bitmap data - returning black opaque image"); + BufferedImage bi = new BufferedImage(headerWidth, headerHeight, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = bi.createGraphics(); + g.setPaint(Color.black); + g.fillRect(0, 0, headerWidth, headerHeight); + g.dispose(); + return bi; } } } 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 b167d0236a..acfbd77b0c 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java @@ -17,6 +17,7 @@ package org.apache.poi.hwmf.record; +import java.awt.Color; import java.awt.Shape; import java.awt.geom.Arc2D; import java.awt.geom.Area; @@ -146,9 +147,10 @@ public class HwmfDraw { @Override public void draw(HwmfGraphics ctx) { - Path2D p = getShape(); - p.closePath(); - p.setWindingRule(ctx.getProperties().getPolyfillMode().awtFlag); + Path2D shape = getShape(); +// shape.closePath(); + Path2D p = (Path2D)shape.clone(); + p.setWindingRule(getWindingRule(ctx)); ctx.fill(p); } @@ -170,8 +172,9 @@ public class HwmfDraw { @Override public void draw(HwmfGraphics ctx) { - Path2D p = getShape(); - p.setWindingRule(ctx.getProperties().getPolyfillMode().awtFlag); + Path2D shape = getShape(); + Path2D p = (Path2D)shape.clone(); + p.setWindingRule(getWindingRule(ctx)); ctx.draw(p); } } @@ -320,12 +323,13 @@ public class HwmfDraw { for (int nPoints : pointsPerPolygon) { /** - * An array of 16-bit unsigned integers that define the coordinates of the polygons. + * An array of 16-bit signed integers that define the coordinates of the polygons. + * (Note: MS-WMF wrongly says unsigned integers ...) */ Path2D poly = new Path2D.Double(); for (int i=0; i<nPoints; i++) { - int x = leis.readUShort(); - int y = leis.readUShort(); + int x = leis.readShort(); + int y = leis.readShort(); size += 2*LittleEndianConsts.SHORT_SIZE; if (i == 0) { poly.moveTo(x, y); @@ -342,14 +346,23 @@ public class HwmfDraw { @Override public void draw(HwmfGraphics ctx) { - int windingRule = ctx.getProperties().getPolyfillMode().awtFlag; - Area area = new Area(); + if (polyList.isEmpty()) { + return; + } + + int windingRule = getWindingRule(ctx); + Area area = null; for (Path2D poly : polyList) { Path2D p = (Path2D)poly.clone(); p.setWindingRule(windingRule); - area.add(new Area(p)); + Area newArea = new Area(p); + if (area == null) { + area = newArea; + } else { + area.exclusiveOr(newArea); + } } - ctx.draw(area); + ctx.fill(area); } } @@ -679,4 +692,8 @@ public class HwmfDraw { ctx.applyObjectTableEntry(objectIndex); } } + + private static int getWindingRule(HwmfGraphics ctx) { + return ctx.getProperties().getPolyfillMode().awtFlag; + } } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfEscape.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfEscape.java index 7d616271df..b005abc8c0 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfEscape.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfEscape.java @@ -24,13 +24,155 @@ import org.apache.poi.util.HexDump; import org.apache.poi.util.LittleEndianConsts; import org.apache.poi.util.LittleEndianInputStream; +/** + * The MetafileEscapes specifies printer driver functionality that + * might not be directly accessible through WMF records + */ public class HwmfEscape implements HwmfRecord { + public enum EscapeFunction { + /** Notifies the printer driver that the application has finished writing to a page. */ + NEWFRAME(0x0001), + /** Stops processing the current document. */ + ABORTDOC(0x0002), + /** Notifies the printer driver that the application has finished writing to a band. */ + NEXTBAND(0x0003), + /** Sets color table values. */ + SETCOLORTABLE(0x0004), + /** Gets color table values. */ + GETCOLORTABLE(0x0005), + /** Causes all pending output to be flushed to the output device. */ + FLUSHOUT(0x0006), + /** Indicates that the printer driver SHOULD print text only, and no graphics. */ + DRAFTMODE(0x0007), + /** Queries a printer driver to determine whether a specific escape function is supported on the output device it drives. */ + QUERYESCSUPPORT(0x0008), + /** Sets the application-defined function that allows a print job to be canceled during printing. */ + SETABORTPROC(0x0009), + /** Notifies the printer driver that a new print job is starting. */ + STARTDOC(0x000A), + /** Notifies the printer driver that the current print job is ending. */ + ENDDOC(0x000B), + /** Retrieves the physical page size currently selected on an output device. */ + GETPHYSPAGESIZE(0x000C), + /** Retrieves the offset from the upper-left corner of the physical page where the actual printing or drawing begins. */ + GETPRINTINGOFFSET(0x000D), + /** Retrieves the scaling factors for the x-axis and the y-axis of a printer. */ + GETSCALINGFACTOR(0x000E), + /** Used to embed an enhanced metafile format (EMF) metafile within a WMF metafile. */ + META_ESCAPE_ENHANCED_METAFILE(0x000F), + /** Sets the width of a pen in pixels. */ + SETPENWIDTH(0x0010), + /** Sets the number of copies. */ + SETCOPYCOUNT(0x0011), + /** Sets the source, such as a particular paper tray or bin on a printer, for output forms. */ + SETPAPERSOURCE(0x0012), + /** This record passes through arbitrary data. */ + PASSTHROUGH(0x0013), + /** Gets information concerning graphics technology that is supported on a device. */ + GETTECHNOLOGY(0x0014), + /** Specifies the line-drawing mode to use in output to a device. */ + SETLINECAP(0x0015), + /** Specifies the line-joining mode to use in output to a device. */ + SETLINEJOIN(0x0016), + /** Sets the limit for the length of miter joins to use in output to a device. */ + SETMITERLIMIT(0x0017), + /** Retrieves or specifies settings concerning banding on a device, such as the number of bands. */ + BANDINFO(0x0018), + /** Draws a rectangle with a defined pattern. */ + DRAWPATTERNRECT(0x0019), + /** Retrieves the physical pen size currently defined on a device. */ + GETVECTORPENSIZE(0x001A), + /** Retrieves the physical brush size currently defined on a device. */ + GETVECTORBRUSHSIZE(0x001B), + /** Enables or disables double-sided (duplex) printing on a device. */ + ENABLEDUPLEX(0x001C), + /** Retrieves or specifies the source of output forms on a device. */ + GETSETPAPERBINS(0x001D), + /** Retrieves or specifies the paper orientation on a device. */ + GETSETPRINTORIENT(0x001E), + /** Retrieves information concerning the sources of different forms on an output device. */ + ENUMPAPERBINS(0x001F), + /** Specifies the scaling of device-independent bitmaps (DIBs). */ + SETDIBSCALING(0x0020), + /** Indicates the start and end of an encapsulated PostScript (EPS) section. */ + EPSPRINTING(0x0021), + /** Queries a printer driver for paper dimensions and other forms data. */ + ENUMPAPERMETRICS(0x0022), + /** Retrieves or specifies paper dimensions and other forms data on an output device. */ + GETSETPAPERMETRICS(0x0023), + /** Sends arbitrary PostScript data to an output device. */ + POSTSCRIPT_DATA(0x0025), + /** Notifies an output device to ignore PostScript data. */ + POSTSCRIPT_IGNORE(0x0026), + /** Gets the device units currently configured on an output device. */ + GETDEVICEUNITS(0x002A), + /** Gets extended text metrics currently configured on an output device. */ + GETEXTENDEDTEXTMETRICS(0x0100), + /** Gets the font kern table currently defined on an output device. */ + GETPAIRKERNTABLE(0x0102), + /** Draws text using the currently selected font, background color, and text color. */ + EXTTEXTOUT(0x0200), + /** Gets the font face name currently configured on a device. */ + GETFACENAME(0x0201), + /** Sets the font face name on a device. */ + DOWNLOADFACE(0x0202), + /** Queries a printer driver about the support for metafiles on an output device. */ + METAFILE_DRIVER(0x0801), + /** Queries the printer driver about its support for DIBs on an output device. */ + QUERYDIBSUPPORT(0x0C01), + /** Opens a path. */ + BEGIN_PATH(0x1000), + /** Defines a clip region that is bounded by a path. The input MUST be a 16-bit quantity that defines the action to take. */ + CLIP_TO_PATH(0x1001), + /** Ends a path. */ + END_PATH(0x1002), + /** The same as STARTDOC specified with a NULL document and output filename, data in raw mode, and a type of zero. */ + OPEN_CHANNEL(0x100E), + /** Instructs the printer driver to download sets of PostScript procedures. */ + DOWNLOADHEADER(0x100F), + /** The same as ENDDOC. See OPEN_CHANNEL. */ + CLOSE_CHANNEL(0x1010), + /** Sends arbitrary data directly to a printer driver, which is expected to process this data only when in PostScript mode. */ + POSTSCRIPT_PASSTHROUGH(0x1013), + /** Sends arbitrary data directly to the printer driver. */ + ENCAPSULATED_POSTSCRIPT(0x1014), + /** Sets the printer driver to either PostScript or GDI mode. */ + POSTSCRIPT_IDENTIFY(0x1015), + /** Inserts a block of raw data into a PostScript stream. The input MUST be + a 32-bit quantity specifying the number of bytes to inject, a 16-bit quantity specifying the + injection point, and a 16-bit quantity specifying the page number, followed by the bytes to + inject. */ + POSTSCRIPT_INJECTION(0x1016), + /** Checks whether the printer supports a JPEG image. */ + CHECKJPEGFORMAT(0x1017), + /** Checks whether the printer supports a PNG image */ + CHECKPNGFORMAT(0x1018), + /** Gets information on a specified feature setting for a PostScript printer driver. */ + GET_PS_FEATURESETTING(0x1019), + /** Enables applications to write documents to a file or to a printer in XML Paper Specification (XPS) format. */ + MXDC_ESCAPE(0x101A), + /** Enables applications to include private procedures and other arbitrary data in documents. */ + SPCLPASSTHROUGH2(0x11D8); + + int flag; + EscapeFunction(int flag) { + this.flag = flag; + } + + static EscapeFunction valueOf(int flag) { + for (EscapeFunction hs : values()) { + if (hs.flag == flag) return hs; + } + return null; + } + } + /** * A 16-bit unsigned integer that defines the escape function. The * value MUST be from the MetafileEscapes enumeration. */ - private int escapeFunction; + private EscapeFunction escapeFunction; /** * A 16-bit unsigned integer that specifies the size, in bytes, of the * EscapeData field. @@ -48,7 +190,7 @@ public class HwmfEscape implements HwmfRecord { @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { - escapeFunction = leis.readUShort(); + escapeFunction = EscapeFunction.valueOf(leis.readUShort()); byteCount = leis.readUShort(); escapeData = new byte[byteCount]; leis.read(escapeData); diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java index d77aa49323..2dabb069fd 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java @@ -20,13 +20,9 @@ package org.apache.poi.hwmf.record; import java.awt.Shape; import java.awt.geom.Path2D; import java.awt.image.BufferedImage; -import java.io.File; import java.io.IOException; -import javax.imageio.ImageIO; - import org.apache.poi.hwmf.draw.HwmfGraphics; -import org.apache.poi.hwmf.record.HwmfWindowing.WmfCreateRegion; import org.apache.poi.util.LittleEndianConsts; import org.apache.poi.util.LittleEndianInputStream; @@ -240,7 +236,7 @@ public class HwmfFill { @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { - polyfillMode = HwmfPolyfillMode.valueOf(leis.readUShort()); + polyfillMode = HwmfPolyfillMode.valueOf(leis.readUShort() & 3); return LittleEndianConsts.SHORT_SIZE; } @@ -402,6 +398,15 @@ public class HwmfFill { } /** + * The META_STRETCHBLT record specifies the transfer of a block of pixels according to a raster + * operation, with possible expansion or contraction. + * The destination of the transfer is the current output region in the playback device context. + * There are two forms of META_STRETCHBLT, one which specifies a bitmap as the source, and the other + * which uses the playback device context as the source. Definitions follow for the fields that are the + * same in the two forms of META_STRETCHBLT are defined below. The subsections that follow specify + * the packet structures of the two forms of META_STRETCHBLT. + * The expansion or contraction is performed according to the stretching mode currently set in the + * playback device context, which MUST be a value from the StretchMode. */ public static class WmfStretchBlt implements HwmfRecord { /** @@ -502,12 +507,12 @@ public class HwmfFill { /** * The META_STRETCHDIB record specifies the transfer of color data from a - * block of pixels in deviceindependent format according to a raster operation, + * block of pixels in device independent format according to a raster operation, * with possible expansion or contraction. * The source of the color data is a DIB, and the destination of the transfer is * the current output region in the playback device context. */ - public static class WmfStretchDib implements HwmfRecord, HwmfImageRecord { + public static class WmfStretchDib implements HwmfRecord, HwmfImageRecord, HwmfObjectTableEntry { /** * A 32-bit unsigned integer that defines how the source pixels, the current brush in * the playback device context, and the destination pixels are to be combined to @@ -599,6 +604,11 @@ public class HwmfFill { @Override public void draw(HwmfGraphics ctx) { + ctx.addObjectTableEntry(this); + } + + @Override + public void applyObject(HwmfGraphics ctx) { } @@ -706,7 +716,7 @@ public class HwmfFill { * using deviceindependent color data. * The source of the color data is a DIB */ - public static class WmfSetDibToDev implements HwmfRecord, HwmfImageRecord { + public static class WmfSetDibToDev implements HwmfRecord, HwmfImageRecord, HwmfObjectTableEntry { /** * A 16-bit unsigned integer that defines whether the Colors field of the @@ -783,6 +793,11 @@ public class HwmfFill { @Override public void draw(HwmfGraphics ctx) { + ctx.addObjectTableEntry(this); + } + + @Override + public void applyObject(HwmfGraphics ctx) { } @@ -793,7 +808,7 @@ public class HwmfFill { } - public static class WmfDibBitBlt implements HwmfRecord, HwmfImageRecord { + public static class WmfDibBitBlt implements HwmfRecord, HwmfImageRecord, HwmfObjectTableEntry { /** * A 32-bit unsigned integer that defines how the source pixels, the current brush @@ -877,6 +892,11 @@ public class HwmfFill { @Override public void draw(HwmfGraphics ctx) { + ctx.addObjectTableEntry(this); + } + + @Override + public void applyObject(HwmfGraphics ctx) { } @@ -886,7 +906,7 @@ public class HwmfFill { } } - public static class WmfDibStretchBlt implements HwmfRecord, HwmfImageRecord { + public static class WmfDibStretchBlt implements HwmfRecord, HwmfImageRecord, HwmfObjectTableEntry { /** * A 32-bit unsigned integer that defines how the source pixels, the current brush * in the playback device context, and the destination pixels are to be combined to form the @@ -978,6 +998,11 @@ public class HwmfFill { @Override public void draw(HwmfGraphics ctx) { + ctx.addObjectTableEntry(this); + } + + @Override + public void applyObject(HwmfGraphics ctx) { } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFont.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFont.java index d23b2381b6..fce75a3656 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFont.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFont.java @@ -448,6 +448,13 @@ public class HwmfFont { */ WmfFontQuality quality; + /** + * A PitchAndFamily object that defines the pitch and the family of the font. + * Font families specify the look of fonts in a general way and are intended for + * specifying fonts when the exact typeface wanted is not available. + */ + int pitchAndFamily; + /** * Font families specify the look of fonts in a general way and are * intended for specifying fonts when the exact typeface wanted is not available. @@ -480,9 +487,7 @@ public class HwmfFont { outPrecision = WmfOutPrecision.valueOf(leis.readUByte()); clipPrecision = WmfClipPrecision.valueOf(leis.readUByte()); quality = WmfFontQuality.valueOf(leis.readUByte()); - int pitchAndFamily = leis.readUByte(); - family = WmfFontFamilyClass.valueOf(pitchAndFamily & 0xF); - pitch = WmfFontPitch.valueOf((pitchAndFamily >>> 6) & 3); + pitchAndFamily = leis.readUByte(); byte buf[] = new byte[32], b, readBytes = 0; do { @@ -546,12 +551,16 @@ public class HwmfFont { return quality; } + public int getPitchAndFamily() { + return pitchAndFamily; + } + public WmfFontFamilyClass getFamily() { - return family; + return WmfFontFamilyClass.valueOf(pitchAndFamily & 0xF); } public WmfFontPitch getPitch() { - return pitch; + return WmfFontPitch.valueOf((pitchAndFamily >>> 6) & 3); } public String getFacename() { diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMapMode.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMapMode.java index 28a26a88f6..cae700e6dc 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMapMode.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMapMode.java @@ -109,6 +109,6 @@ public enum HwmfMapMode { for (HwmfMapMode mm : values()) { if (mm.flag == flag) return mm; } - return null; + return MM_ISOTROPIC; } } \ No newline at end of file diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java index 2d6ab4e77b..c597b1d964 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java @@ -265,25 +265,9 @@ public class HwmfMisc { /** * A 16-bit unsigned integer that defines the foreground binary raster - * operation mixing mode. This MUST be one of the values: - * R2_BLACK = 0x0001, - * R2_NOTMERGEPEN = 0x0002, - * R2_MASKNOTPEN = 0x0003, - * R2_NOTCOPYPEN = 0x0004, - * R2_MASKPENNOT = 0x0005, - * R2_NOT = 0x0006, - * R2_XORPEN = 0x0007, - * R2_NOTMASKPEN = 0x0008, - * R2_MASKPEN = 0x0009, - * R2_NOTXORPEN = 0x000A, - * R2_NOP = 0x000B, - * R2_MERGENOTPEN = 0x000C, - * R2_COPYPEN = 0x000D, - * R2_MERGEPENNOT = 0x000E, - * R2_MERGEPEN = 0x000F, - * R2_WHITE = 0x0010 + * operation mixing mode */ - private int drawMode; + private HwmfBinaryRasterOp drawMode; @Override public HwmfRecordType getRecordType() { @@ -292,7 +276,7 @@ public class HwmfMisc { @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { - drawMode = leis.readUShort(); + drawMode = HwmfBinaryRasterOp.valueOf(leis.readUShort()); return LittleEndianConsts.SHORT_SIZE; } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPalette.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPalette.java index 42e661b17e..eb6139c217 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPalette.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPalette.java @@ -93,7 +93,7 @@ public class HwmfPalette { } } - public static abstract class WmfPaletteParent implements HwmfRecord { + public static abstract class WmfPaletteParent implements HwmfRecord, HwmfObjectTableEntry { /** * Start (2 bytes): A 16-bit unsigned integer that defines the offset into the Palette Object when @@ -121,6 +121,11 @@ public class HwmfPalette { return size; } + @Override + public final void draw(HwmfGraphics ctx) { + ctx.addObjectTableEntry(this); + } + protected List<PaletteEntry> getPaletteCopy() { List<PaletteEntry> newPalette = new ArrayList<PaletteEntry>(); for (PaletteEntry et : palette) { @@ -143,11 +148,6 @@ public class HwmfPalette { return HwmfRecordType.createPalette; } - @Override - public void draw(HwmfGraphics ctx) { - ctx.addObjectTableEntry(this); - } - @Override public void applyObject(HwmfGraphics ctx) { ctx.getProperties().setPalette(getPaletteCopy()); @@ -165,7 +165,7 @@ public class HwmfPalette { } @Override - public void draw(HwmfGraphics ctx) { + public void applyObject(HwmfGraphics ctx) { HwmfDrawProperties props = ctx.getProperties(); List<PaletteEntry> palette = props.getPalette(); if (palette == null) { @@ -192,7 +192,7 @@ public class HwmfPalette { * The META_RESIZEPALETTE record redefines the size of the logical palette that is defined in the * playback device context. */ - public static class WmfResizePalette implements HwmfRecord { + public static class WmfResizePalette implements HwmfRecord, HwmfObjectTableEntry { /** * A 16-bit unsigned integer that defines the number of entries in * the logical palette. @@ -212,6 +212,11 @@ public class HwmfPalette { @Override public void draw(HwmfGraphics ctx) { + ctx.addObjectTableEntry(this); + } + + @Override + public void applyObject(HwmfGraphics ctx) { HwmfDrawProperties props = ctx.getProperties(); List<PaletteEntry> palette = props.getPalette(); if (palette == null) { @@ -292,7 +297,7 @@ public class HwmfPalette { } @Override - public void draw(HwmfGraphics ctx) { + public void applyObject(HwmfGraphics ctx) { HwmfDrawProperties props = ctx.getProperties(); List<PaletteEntry> dest = props.getPalette(); List<PaletteEntry> src = getPaletteCopy(); diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfTernaryRasterOp.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfTernaryRasterOp.java index 5ee3100fc0..9cd9b1e81c 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfTernaryRasterOp.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfTernaryRasterOp.java @@ -17,6 +17,68 @@ package org.apache.poi.hwmf.record; +/** + * Each ternary raster operation code represents a Boolean operation in which the values of the pixels in + * the source, the selected brush, and the destination are combined. Following are the three operands + * used in these operations. + * + * <table> + * <tr><th>Operand</th><th>Meaning</th></tr> + * <tr><td>D</td><td>Destination bitmap</td></tr> + * <tr><td>P</td><td>Selected brush (also called pattern)</td></tr> + * <tr><td>S</td><td>Source bitmap</td></tr> + * </table> + * + * Following are the Boolean operators used in these operations. + * <table> + * <tr><th>Operand</th><th>Meaning</th></tr> + * <tr><td>a</td><td>Bitwise AND</td></tr> + * <tr><td>n</td><td>Bitwise NOT (inverse)</td></tr> + * <tr><td>o</td><td>Bitwise OR</td></tr> + * <tr><td>x</td><td>Bitwise exclusive OR (XOR)</td></tr> + * </table> + * + * All Boolean operations are presented in reverse Polish notation. For example, the following operation + * replaces the values of the pixels in the destination bitmap with a combination of the pixel values of the + * source and brush: PSo. + * + * The following operation combines the values of the pixels in the source and brush with the pixel values + * of the destination bitmap: DPSoo (there are alternative spellings of some functions, so although a + * particular spelling MAY NOT be listed in the enumeration, an equivalent form SHOULD be). + * + * Each raster operation code is a 32-bit integer whose high-order word is a Boolean operation index and + * whose low-order word is the operation code. The 16-bit operation index is a zero-extended, 8-bit + * value that represents the result of the Boolean operation on predefined brush, source, and destination + * values. For example, the operation indexes for the PSo and DPSoo operations are shown in the + * following list. + * + * <table> + * <tr><th>P</th><th>S</th><th>D</th><th>DPo</th><th>DPan</th></tr> + * <tr><td>0</td><td>0</td><td>0</td><td>0</td><td>0</td></tr> + * <tr><td>0</td><td>0</td><td>1</td><td>0</td><td>1</td></tr> + * <tr><td>0</td><td>1</td><td>0</td><td>1</td><td>1</td></tr> + * <tr><td>0</td><td>1</td><td>1</td><td>1</td><td>1</td></tr> + * <tr><td>1</td><td>0</td><td>0</td><td>1</td><td>1</td></tr> + * <tr><td>1</td><td>0</td><td>1</td><td>1</td><td>1</td></tr> + * <tr><td>1</td><td>1</td><td>0</td><td>1</td><td>1</td></tr> + * <tr><td>1</td><td>1</td><td>1</td><td>1</td><td>1</td></tr> + * </table> + * + * The operation indexes are determined by reading the binary values in a column of the table from the + * bottom up. For example, in the PSo column, the binary value is 11111100, which is equivalent to 00FC + * (hexadecimal is implicit for these values), which is the operation index for PSo. + * + * Using this method, DPSoo can be seen to have the operation index 00FE. Operation indexes define the + * locations of corresponding raster operation codes in the preceding enumeration. The PSo operation is + * in line 252 (0x00FC) of the enumeration; DPSoo is in line 254 (0x00FE). + * + * The most commonly used raster operations have been given explicit enumeration names, which + * SHOULD be used; examples are PATCOPY and WHITENESS. + * + * When the source and destination bitmaps are monochrome, a bit value of 0 represents a black pixel + * and a bit value of 1 represents a white pixel. When the source and the destination bitmaps are color, + * those colors are represented with red green blue (RGB) values. + */ public enum HwmfTernaryRasterOp { BLACKNESS(0x0000,0x0042,"0"), DPSOON(0x0001,0x0289,"DPSoon"), @@ -274,17 +336,17 @@ public enum HwmfTernaryRasterOp { PSDNOO(0x00FD,0x0A0A,"PSDnoo"), DPSOO(0x00FE,0x02A9,"DPSoo"), WHITENESS(0x00FF,0x0062,"1"); - + int opIndex; int opCode; String opCmd; - + HwmfTernaryRasterOp(int opIndex, int opCode, String opCmd) { this.opIndex=opIndex; this.opCode=opCode; this.opCmd=opCmd; } - + public static HwmfTernaryRasterOp valueOf(int opIndex) { for (HwmfTernaryRasterOp bb : HwmfTernaryRasterOp.values()) { if (bb.opIndex == opIndex) { @@ -293,11 +355,11 @@ public enum HwmfTernaryRasterOp { } return null; } - + public String describeCmd() { String stack[] = new String[10]; int stackPnt = 0; - + for (char c : opCmd.toCharArray()) { switch (c) { case 'S': diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java index 8c6c41549b..e4e4ecb4d8 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java @@ -19,7 +19,6 @@ package org.apache.poi.hwmf.record; import java.awt.geom.Rectangle2D; import java.io.IOException; -import java.text.AttributedString; import org.apache.poi.hwmf.draw.HwmfDrawProperties; import org.apache.poi.hwmf.draw.HwmfGraphics; @@ -29,10 +28,13 @@ import org.apache.poi.util.BitFieldFactory; import org.apache.poi.util.LittleEndianConsts; import org.apache.poi.util.LittleEndianInputStream; import org.apache.poi.util.LocaleUtil; +import org.apache.poi.util.POILogFactory; +import org.apache.poi.util.POILogger; import org.apache.poi.util.RecordFormatException; public class HwmfText { - + private static final POILogger logger = POILogFactory.getLogger(HwmfText.class); + /** * The META_SETTEXTCHAREXTRA record defines inter-character spacing for text justification in the * playback device context. Spacing is added to the white space between each character, including @@ -185,6 +187,48 @@ public class HwmfText { */ public static class WmfExtTextOut implements HwmfRecord { + /** + * Indicates that the background color that is defined in the playback device context + * SHOULD be used to fill the rectangle. + */ + private static final BitField ETO_OPAQUE = BitFieldFactory.getInstance(0x0002); + + /** + * Indicates that the text SHOULD be clipped to the rectangle. + */ + private static final BitField ETO_CLIPPED = BitFieldFactory.getInstance(0x0004); + + /** + * Indicates that the string to be output SHOULD NOT require further processing + * with respect to the placement of the characters, and an array of character + * placement values SHOULD be provided. This character placement process is + * useful for fonts in which diacritical characters affect character spacing. + */ + private static final BitField ETO_GLYPH_INDEX = BitFieldFactory.getInstance(0x0010); + + /** + * Indicates that the text MUST be laid out in right-to-left reading order, instead of + * the default left-to-right order. This SHOULD be applied only when the font that is + * defined in the playback device context is either Hebrew or Arabic. + */ + private static final BitField ETO_RTLREADING = BitFieldFactory.getInstance(0x0080); + + /** + * Indicates that to display numbers, digits appropriate to the locale SHOULD be used. + */ + private static final BitField ETO_NUMERICSLOCAL = BitFieldFactory.getInstance(0x0400); + + /** + * Indicates that to display numbers, European digits SHOULD be used. + */ + private static final BitField ETO_NUMERICSLATIN = BitFieldFactory.getInstance(0x0800); + + /** + * Indicates that both horizontal and vertical character displacement values + * SHOULD be provided. + */ + private static final BitField ETO_PDY = BitFieldFactory.getInstance(0x2000); + /** * A 16-bit signed integer that defines the y-coordinate, in logical units, where the text string is to be located. @@ -199,40 +243,12 @@ public class HwmfText { * A 16-bit signed integer that defines the length of the string. */ private int stringLength; - /** - * A 16-bit unsigned integer that defines the use of the application-defined - * rectangle. This member can be a combination of one or more values in the - * ExtTextOutOptions Flags: - * - * ETO_OPAQUE (0x0002): - * Indicates that the background color that is defined in the playback device context - * SHOULD be used to fill the rectangle. - * - * ETO_CLIPPED (0x0004): - * Indicates that the text SHOULD be clipped to the rectangle. - * - * ETO_GLYPH_INDEX (0x0010): - * Indicates that the string to be output SHOULD NOT require further processing - * with respect to the placement of the characters, and an array of character - * placement values SHOULD be provided. This character placement process is - * useful for fonts in which diacritical characters affect character spacing. - * - * ETO_RTLREADING (0x0080): - * Indicates that the text MUST be laid out in right-to-left reading order, instead of - * the default left-to-right order. This SHOULD be applied only when the font that is - * defined in the playback device context is either Hebrew or Arabic. <37> - * - * ETO_NUMERICSLOCAL (0x0400): - * Indicates that to display numbers, digits appropriate to the locale SHOULD be - * used. - * - * ETO_NUMERICSLATIN (0x0800): - * Indicates that to display numbers, European digits SHOULD be used. <39> - * - * ETO_PDY (0x2000): - * Indicates that both horizontal and vertical character displacement values - * SHOULD be provided. - */ + + /** + * A 16-bit unsigned integer that defines the use of the application-defined + * rectangle. This member can be a combination of one or more values in the + * ExtTextOutOptions Flags (ETO_*) + */ private int fwOpts; /** * An optional 8-byte Rect Object (section 2.2.2.18) that defines the @@ -275,7 +291,8 @@ public class HwmfText { int size = 4*LittleEndianConsts.SHORT_SIZE; - if (fwOpts != 0 && size+8<=remainingRecordSize) { + // Check if we have a rectangle + if ((ETO_OPAQUE.isSet(fwOpts) || ETO_CLIPPED.isSet(fwOpts)) && size+8<=remainingRecordSize) { // the bounding rectangle is optional and only read when fwOpts are given left = leis.readShort(); top = leis.readShort(); @@ -289,16 +306,20 @@ public class HwmfText { text = new String(buf, 0, stringLength, LocaleUtil.CHARSET_1252); size += buf.length; - if (size < remainingRecordSize) { - if (size + stringLength*LittleEndianConsts.SHORT_SIZE < remainingRecordSize) { - throw new RecordFormatException("can't read Dx array - given recordSize doesn't contain enough values for string length "+stringLength); - } - - dx = new int[stringLength]; - for (int i=0; i<dx.length; i++) { - dx[i] = leis.readShort(); - } - size += dx.length*LittleEndianConsts.SHORT_SIZE; + if (size >= remainingRecordSize) { + logger.log(POILogger.INFO, "META_EXTTEXTOUT doesn't contain character tracking info"); + return size; + } + + int dxLen = Math.min(stringLength, (remainingRecordSize-size)/LittleEndianConsts.SHORT_SIZE); + if (dxLen < stringLength) { + logger.log(POILogger.WARN, "META_EXTTEXTOUT tracking info doesn't cover all characters"); + } + + dx = new int[stringLength]; + for (int i=0; i<dxLen; i++) { + dx[i] = leis.readShort(); + size += LittleEndianConsts.SHORT_SIZE; } return size; @@ -306,7 +327,8 @@ public class HwmfText { @Override public void draw(HwmfGraphics ctx) { - + Rectangle2D bounds = new Rectangle2D.Double(x, y, 0, 0); + ctx.drawString(text, bounds, dx); } } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java index 6dbde2bc4f..d6c4d196b5 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java @@ -376,7 +376,7 @@ public class HwmfWindowing { * The META_OFFSETCLIPRGN record moves the clipping region in the playback device context by the * specified offsets. */ - public static class WmfOffsetClipRgn implements HwmfRecord { + public static class WmfOffsetClipRgn implements HwmfRecord, HwmfObjectTableEntry { /** * A 16-bit signed integer that defines the number of logical units to move up or down. @@ -402,7 +402,11 @@ public class HwmfWindowing { @Override public void draw(HwmfGraphics ctx) { - + ctx.addObjectTableEntry(this); + } + + @Override + public void applyObject(HwmfGraphics ctx) { } } @@ -410,7 +414,7 @@ public class HwmfWindowing { * The META_EXCLUDECLIPRECT record sets the clipping region in the playback device context to the * existing clipping region minus the specified rectangle. */ - public static class WmfExcludeClipRect implements HwmfRecord { + public static class WmfExcludeClipRect implements HwmfRecord, HwmfObjectTableEntry { /** * A 16-bit signed integer that defines the y-coordinate, in logical units, of the @@ -452,7 +456,11 @@ public class HwmfWindowing { @Override public void draw(HwmfGraphics ctx) { - + ctx.addObjectTableEntry(this); + } + + @Override + public void applyObject(HwmfGraphics ctx) { } } @@ -461,7 +469,7 @@ public class HwmfWindowing { * The META_INTERSECTCLIPRECT record sets the clipping region in the playback device context to the * intersection of the existing clipping region and the specified rectangle. */ - public static class WmfIntersectClipRect implements HwmfRecord { + public static class WmfIntersectClipRect implements HwmfRecord, HwmfObjectTableEntry { /** * A 16-bit signed integer that defines the y-coordinate, in logical units, of the @@ -503,7 +511,11 @@ public class HwmfWindowing { @Override public void draw(HwmfGraphics ctx) { - + ctx.addObjectTableEntry(this); + } + + @Override + public void applyObject(HwmfGraphics ctx) { } } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfPicture.java b/src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfPicture.java index a5144d4ae3..5b7601f7a3 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfPicture.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfPicture.java @@ -19,10 +19,8 @@ package org.apache.poi.hwmf.usermodel; import java.awt.Dimension; import java.awt.Graphics2D; -import java.awt.GraphicsConfiguration; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; -import java.awt.geom.Rectangle2D.Double; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; @@ -38,9 +36,13 @@ import org.apache.poi.hwmf.record.HwmfRecordType; import org.apache.poi.hwmf.record.HwmfWindowing.WmfSetWindowExt; import org.apache.poi.hwmf.record.HwmfWindowing.WmfSetWindowOrg; import org.apache.poi.util.LittleEndianInputStream; +import org.apache.poi.util.POILogFactory; +import org.apache.poi.util.POILogger; import org.apache.poi.util.Units; public class HwmfPicture { + private static final POILogger logger = POILogFactory.getLogger(HwmfPicture.class); + final List<HwmfRecord> records = new ArrayList<HwmfRecord>(); final HwmfPlaceableHeader placeableHeader; final HwmfHeader header; @@ -52,6 +54,10 @@ public class HwmfPicture { header = new HwmfHeader(leis); for (;;) { + if (leis.available() < 6) { + logger.log(POILogger.ERROR, "unexpected eof - wmf file was truncated"); + break; + } // recordSize in DWORDs long recordSize = leis.readUInt()*2; int recordFunction = leis.readShort(); diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/model/TestFreeform.java b/src/scratchpad/testcases/org/apache/poi/hslf/model/TestFreeform.java index 7b3b785444..91d23664d8 100644 --- a/src/scratchpad/testcases/org/apache/poi/hslf/model/TestFreeform.java +++ b/src/scratchpad/testcases/org/apache/poi/hslf/model/TestFreeform.java @@ -20,7 +20,10 @@ package org.apache.poi.hslf.model; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import java.awt.geom.*; +import java.awt.geom.Area; +import java.awt.geom.Line2D; +import java.awt.geom.Path2D; +import java.awt.geom.Rectangle2D; import org.apache.poi.hslf.usermodel.HSLFFreeformShape; import org.junit.Test; @@ -38,7 +41,7 @@ public final class TestFreeform { @Test public void testClosedPath() { - GeneralPath path1 = new GeneralPath(); + Path2D.Double path1 = new Path2D.Double(); path1.moveTo(100, 100); path1.lineTo(200, 100); path1.lineTo(200, 200); @@ -55,7 +58,7 @@ public final class TestFreeform { @Test public void testLine() { - GeneralPath path1 = new GeneralPath(new Line2D.Double(100, 100, 200, 100)); + Path2D.Double path1 = new Path2D.Double(new Line2D.Double(100, 100, 200, 100)); HSLFFreeformShape p = new HSLFFreeformShape(); p.setPath(path1); @@ -67,7 +70,7 @@ public final class TestFreeform { @Test public void testRectangle() { - GeneralPath path1 = new GeneralPath(new Rectangle2D.Double(100, 100, 200, 50)); + Path2D.Double path1 = new Path2D.Double(new Rectangle2D.Double(100, 100, 200, 50)); HSLFFreeformShape p = new HSLFFreeformShape(); p.setPath(path1); @@ -84,8 +87,8 @@ public final class TestFreeform { public void test54188() { HSLFFreeformShape p = new HSLFFreeformShape(); - GeneralPath path = p.getPath(); - GeneralPath emptyPath = new GeneralPath(); + Path2D.Double path = p.getPath(); + Path2D.Double emptyPath = new Path2D.Double(); assertEquals(emptyPath.getBounds2D(), path.getBounds2D()); } } diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestPicture.java b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestPicture.java index 7dc57f772c..08d6c64211 100644 --- a/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestPicture.java +++ b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestPicture.java @@ -30,23 +30,22 @@ import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.File; +import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.util.BitSet; -import java.util.HashMap; import java.util.List; -import java.util.Map; import javax.imageio.ImageIO; import org.apache.poi.POIDataSamples; import org.apache.poi.ddf.EscherBSERecord; import org.apache.poi.hssf.usermodel.DummyGraphics2d; -import org.apache.poi.sl.draw.Drawable; +import org.apache.poi.sl.draw.DrawFactory; import org.apache.poi.sl.usermodel.PictureData.PictureType; import org.apache.poi.sl.usermodel.Slide; import org.apache.poi.sl.usermodel.SlideShow; -import org.apache.poi.util.JvmBugs; import org.junit.Ignore; import org.junit.Test; @@ -64,7 +63,7 @@ public final class TestPicture { * */ @Test - public void multiplePictures() throws Exception { + public void multiplePictures() throws IOException { HSLFSlideShow ppt = new HSLFSlideShow(); HSLFSlide s = ppt.createSlide(); @@ -92,6 +91,8 @@ public final class TestPicture { EscherBSERecord bse3 = pict.getEscherBSERecord(); assertSame(bse2, bse3); assertEquals(3, bse1.getRef()); + + ppt.close(); } /** @@ -99,7 +100,7 @@ public final class TestPicture { * was not found. The correct behaviour is to return null. */ @Test - public void bug46122() { + public void bug46122() throws IOException { HSLFSlideShow ppt = new HSLFSlideShow(); HSLFSlide slide = ppt.createSlide(); HSLFPictureData pd = HSLFPictureData.create(PictureType.PNG); @@ -112,10 +113,12 @@ public final class TestPicture { BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB); Graphics2D graphics = img.createGraphics(); pict.draw(graphics); + + ppt.close(); } @Test - public void macImages() throws Exception { + public void macImages() throws IOException { HSLFSlideShowImpl hss = new HSLFSlideShowImpl(_slTests.openResourceAsStream("53446.ppt")); List<HSLFPictureData> pictures = hss.getPictureData(); @@ -154,11 +157,14 @@ public final class TestPicture { break; } } + + hss.close(); } @Test @Ignore("Just for visual validation - antialiasing is different on various systems") - public void bug54541() throws Exception { + public void bug54541() + throws IOException, ClassNotFoundException, SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException { String files[] = { // "sample_pptx_grouping_issues.pptx", // "54542_cropped_bitmap.pptx", @@ -196,7 +202,7 @@ public final class TestPicture { } else { BufferedImage img = new BufferedImage(pg.width, pg.height, BufferedImage.TYPE_INT_ARGB); Graphics2D graphics = img.createGraphics(); - fixFonts(graphics); + DrawFactory.getInstance(graphics).fixFonts(graphics); slide.draw(graphics); graphics.setColor(Color.BLACK); graphics.setStroke(new BasicStroke(1)); @@ -204,16 +210,8 @@ public final class TestPicture { ImageIO.write(img, "PNG", new File(file.replaceFirst(".pptx?", "-")+slideNo+".png")); } } + + ss.close(); } } - - @SuppressWarnings("unchecked") - private void fixFonts(Graphics2D graphics) { - if (!JvmBugs.hasLineBreakMeasurerBug()) return; - Map<String,String> fontMap = (Map<String,String>)graphics.getRenderingHint(Drawable.FONT_MAP); - if (fontMap == null) fontMap = new HashMap<String,String>(); - fontMap.put("Calibri", "Lucida Sans"); - fontMap.put("Cambria", "Lucida Bright"); - graphics.setRenderingHint(Drawable.FONT_MAP, fontMap); - } } diff --git a/src/scratchpad/testcases/org/apache/poi/hwmf/TestHwmfParsing.java b/src/scratchpad/testcases/org/apache/poi/hwmf/TestHwmfParsing.java index 7ddacf475a..78e6534569 100644 --- a/src/scratchpad/testcases/org/apache/poi/hwmf/TestHwmfParsing.java +++ b/src/scratchpad/testcases/org/apache/poi/hwmf/TestHwmfParsing.java @@ -22,6 +22,7 @@ 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; @@ -64,7 +65,7 @@ public class TestHwmfParsing { @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"); + // File f = new File("bla.wmf"); FileInputStream fis = new FileInputStream(f); HwmfPicture wmf = new HwmfPicture(fis); fis.close(); @@ -73,6 +74,11 @@ public class TestHwmfParsing { int width = Units.pointsToPixel(dim.getWidth()); // keep aspect ratio for height int height = Units.pointsToPixel(dim.getHeight()); + double max = Math.max(width, height); + if (max > 1500) { + width *= 1500/max; + height *= 1500/max; + } BufferedImage bufImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); Graphics2D g = bufImg.createGraphics(); @@ -81,7 +87,7 @@ public class TestHwmfParsing { g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); - wmf.draw(g); + wmf.draw(g, new Rectangle2D.Double(0,0,width,height)); g.dispose(); diff --git a/src/testcases/org/apache/poi/sl/draw/geom/TestPresetGeometries.java b/src/testcases/org/apache/poi/sl/draw/geom/TestPresetGeometries.java index 49338e0b68..24c6f7f91b 100644 --- a/src/testcases/org/apache/poi/sl/draw/geom/TestPresetGeometries.java +++ b/src/testcases/org/apache/poi/sl/draw/geom/TestPresetGeometries.java @@ -22,7 +22,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; -import java.awt.geom.GeneralPath; +import java.awt.geom.Path2D; import java.awt.geom.Rectangle2D; import java.net.URL; import java.util.Enumeration; @@ -44,7 +44,7 @@ public class TestPresetGeometries { } }); for(Path p : geom){ - GeneralPath path = p.getPath(ctx); + Path2D path = p.getPath(ctx); assertNotNull(path); } }