From: Andreas Beeker Date: Sun, 23 Sep 2018 01:58:40 +0000 (+0000) Subject: #60656 - Support export file that contains emf and render it correctly X-Git-Tag: REL_4_1_0~189^2~22 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=5073f22fce96dcc74529baaa8ee6e7e3c732ee56;p=poi.git #60656 - Support export file that contains emf and render it correctly git-svn-id: https://svn.apache.org/repos/asf/poi/branches/hemf@1841712 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/src/integrationtest/org/apache/poi/stress/SlideShowHandler.java b/src/integrationtest/org/apache/poi/stress/SlideShowHandler.java index 63ffdbd58c..20460cc2db 100644 --- a/src/integrationtest/org/apache/poi/stress/SlideShowHandler.java +++ b/src/integrationtest/org/apache/poi/stress/SlideShowHandler.java @@ -105,7 +105,6 @@ 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(); - DrawFactory.getInstance(graphics).fixFonts(graphics); // default rendering options graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); diff --git a/src/java/org/apache/poi/sl/draw/BitmapImageRenderer.java b/src/java/org/apache/poi/sl/draw/BitmapImageRenderer.java index a92c8dc590..c37ce006a0 100644 --- a/src/java/org/apache/poi/sl/draw/BitmapImageRenderer.java +++ b/src/java/org/apache/poi/sl/draw/BitmapImageRenderer.java @@ -40,6 +40,7 @@ import javax.imageio.ImageTypeSpecifier; import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.MemoryCacheImageInputStream; +import org.apache.poi.sl.usermodel.PictureData.PictureType; import org.apache.poi.util.IOUtils; import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; @@ -48,10 +49,23 @@ import org.apache.poi.util.POILogger; * For now this class renders only images supported by the javax.imageio.ImageIO framework. **/ public class BitmapImageRenderer implements ImageRenderer { - private final static POILogger LOG = POILogFactory.getLogger(ImageRenderer.class); + private final static POILogger LOG = POILogFactory.getLogger(BitmapImageRenderer.class); protected BufferedImage img; + @Override + public boolean canRender(String contentType) { + PictureType[] pts = { + PictureType.JPEG, PictureType.PNG, PictureType.BMP, PictureType.GIF + }; + for (PictureType pt : pts) { + if (pt.contentType.equalsIgnoreCase(contentType)) { + return true; + } + } + return false; + } + @Override public void loadImage(InputStream data, String contentType) throws IOException { img = readImage(data, contentType); diff --git a/src/java/org/apache/poi/sl/draw/DrawFactory.java b/src/java/org/apache/poi/sl/draw/DrawFactory.java index 98c41ed993..99c9942b67 100644 --- a/src/java/org/apache/poi/sl/draw/DrawFactory.java +++ b/src/java/org/apache/poi/sl/draw/DrawFactory.java @@ -22,8 +22,6 @@ import java.awt.font.TextLayout; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; 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; @@ -40,18 +38,18 @@ 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 defaultFactory = new ThreadLocal<>(); + private static final ThreadLocal defaultFactory = new ThreadLocal<>(); /** * Set a custom draw factory for the current thread. * This is a fallback, for operations where usercode can't set a graphics context. * Preferably use the rendering hint {@link Drawable#DRAW_FACTORY} to set the factory. * - * @param factory + * @param factory the custom factory */ + @SuppressWarnings("unused") public static void setDefaultFactory(DrawFactory factory) { defaultFactory.set(factory); } @@ -170,6 +168,7 @@ public class DrawFactory { return new DrawBackground(shape); } + @SuppressWarnings("WeakerAccess") public DrawTextFragment getTextFragment(TextLayout layout, AttributedString str) { return new DrawTextFragment(layout, str); } @@ -213,35 +212,6 @@ public class DrawFactory { } - /** - * 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 fontMap = (Map)graphics.getRenderingHint(Drawable.FONT_MAP); - if (fontMap == null) { - fontMap = new HashMap<>(); - graphics.setRenderingHint(Drawable.FONT_MAP, fontMap); - } - - String fonts[][] = { - { "Calibri", "Lucida Sans" }, - { "Cambria", "Lucida Bright" }, - { "Times New Roman", "Lucida Bright" }, - { "serif", "Lucida Bright" } - }; - - for (String f[] : fonts) { - if (!fontMap.containsKey(f[0])) { - fontMap.put(f[0], f[1]); - } - } - } - /** * Return a FontManager, either registered beforehand or a default implementation * diff --git a/src/java/org/apache/poi/sl/draw/DrawPictureShape.java b/src/java/org/apache/poi/sl/draw/DrawPictureShape.java index fcca6d07a7..5c42d6fd13 100644 --- a/src/java/org/apache/poi/sl/draw/DrawPictureShape.java +++ b/src/java/org/apache/poi/sl/draw/DrawPictureShape.java @@ -22,19 +22,19 @@ import java.awt.Graphics2D; import java.awt.Insets; import java.awt.geom.Rectangle2D; import java.io.IOException; +import java.util.ServiceLoader; import org.apache.poi.sl.usermodel.PictureData; -import org.apache.poi.sl.usermodel.PictureData.PictureType; -import org.apache.poi.util.POILogFactory; -import org.apache.poi.util.POILogger; import org.apache.poi.sl.usermodel.PictureShape; import org.apache.poi.sl.usermodel.RectAlign; +import org.apache.poi.util.POILogFactory; +import org.apache.poi.util.POILogger; public class DrawPictureShape extends DrawSimpleShape { private static final POILogger LOG = POILogFactory.getLogger(DrawPictureShape.class); - private static final String WMF_IMAGE_RENDERER = "org.apache.poi.hwmf.draw.HwmfSLImageRenderer"; - + private static final ServiceLoader rendererLoader = ServiceLoader.load(ImageRenderer.class); + public DrawPictureShape(PictureShape shape) { super(shape); } @@ -59,28 +59,26 @@ public class DrawPictureShape extends DrawSimpleShape { /** * Returns an ImageRenderer for the PictureData * - * @param graphics + * @param graphics the graphics context * @return the image renderer */ + @SuppressWarnings("WeakerAccess") public static ImageRenderer getImageRenderer(Graphics2D graphics, String contentType) { ImageRenderer renderer = (ImageRenderer)graphics.getRenderingHint(Drawable.IMAGE_RENDERER); if (renderer != null) { return renderer; } - - if (PictureType.WMF.contentType.equals(contentType)) { - try { - @SuppressWarnings("unchecked") - Class irc = (Class) - DrawPictureShape.class.getClassLoader().loadClass(WMF_IMAGE_RENDERER); - return irc.newInstance(); - } catch (Exception e) { - // WMF image renderer is not on the classpath, continuing with BitmapRenderer - // although this doesn't make much sense ... - LOG.log(POILogger.ERROR, "WMF image renderer is not on the classpath - include poi-scratchpad jar!", e); + + for (ImageRenderer ir : rendererLoader) { + if (ir.canRender(contentType)) { + return ir; } } - + + LOG.log(POILogger.ERROR, "No suiteable image renderer found for content-type '"+ + contentType+"' - include poi-scratchpad jar!"); + + // falling back to BitmapImageRenderer although this doesn't make much sense ... return new BitmapImageRenderer(); } diff --git a/src/java/org/apache/poi/sl/draw/DrawTextParagraph.java b/src/java/org/apache/poi/sl/draw/DrawTextParagraph.java index 938d46230a..1ab4af3a1e 100644 --- a/src/java/org/apache/poi/sl/draw/DrawTextParagraph.java +++ b/src/java/org/apache/poi/sl/draw/DrawTextParagraph.java @@ -254,7 +254,6 @@ public class DrawTextParagraph implements Drawable { lines.clear(); DrawFactory fact = DrawFactory.getInstance(graphics); - fact.fixFonts(graphics); StringBuilder text = new StringBuilder(); AttributedString at = getAttributedString(graphics, text); boolean emptyParagraph = text.toString().trim().isEmpty(); @@ -635,13 +634,6 @@ public class DrawTextParagraph implements Drawable { *
  • determine the font group - a text run can have different font groups. Depending on the chars, * the correct font group needs to be used * - * @param graphics - * @param dfm - * @param attList - * @param beginIndex - * @param run - * @param runText - * * @see Office Open XML Themes, Schemes, and Fonts */ private void processGlyphs(Graphics2D graphics, DrawFontManager dfm, List attList, final int beginIndex, TextRun run, String runText) { diff --git a/src/java/org/apache/poi/sl/draw/DrawTextShape.java b/src/java/org/apache/poi/sl/draw/DrawTextShape.java index 413ab218c9..c4dd65bb75 100644 --- a/src/java/org/apache/poi/sl/draw/DrawTextShape.java +++ b/src/java/org/apache/poi/sl/draw/DrawTextShape.java @@ -40,8 +40,6 @@ public class DrawTextShape extends DrawSimpleShape { @Override public void drawContent(Graphics2D graphics) { - DrawFactory.getInstance(graphics).fixFonts(graphics); - TextShape s = getShape(); Rectangle2D anchor = DrawShape.getAnchor(graphics, s); @@ -219,10 +217,9 @@ public class DrawTextShape extends DrawSimpleShape { graphics.addRenderingHints(oldGraphics.getRenderingHints()); graphics.setTransform(oldGraphics.getTransform()); } - DrawFactory.getInstance(graphics).fixFonts(graphics); return drawParagraphs(graphics, 0, 0); } - + @Override protected TextShape> getShape() { return (TextShape>)shape; diff --git a/src/java/org/apache/poi/sl/draw/ImageRenderer.java b/src/java/org/apache/poi/sl/draw/ImageRenderer.java index 7ecc96a967..b2355b3012 100644 --- a/src/java/org/apache/poi/sl/draw/ImageRenderer.java +++ b/src/java/org/apache/poi/sl/draw/ImageRenderer.java @@ -75,6 +75,13 @@ import java.io.InputStream; * */ public interface ImageRenderer { + /** + * Determines if this image renderer implementation supports the given contentType + * @param contentType the image content type + * @return if the content type is supported + */ + boolean canRender(String contentType); + /** * Load and buffer the image * 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 79327bce4f..c4fbb4ca3b 100644 --- a/src/ooxml/java/org/apache/poi/xslf/util/PPTX2PNG.java +++ b/src/ooxml/java/org/apache/poi/xslf/util/PPTX2PNG.java @@ -139,7 +139,6 @@ public class PPTX2PNG { 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); diff --git a/src/ooxml/testcases/org/apache/poi/sl/TestFonts.java b/src/ooxml/testcases/org/apache/poi/sl/TestFonts.java index b7a6d16a54..846335ce90 100644 --- a/src/ooxml/testcases/org/apache/poi/sl/TestFonts.java +++ b/src/ooxml/testcases/org/apache/poi/sl/TestFonts.java @@ -131,8 +131,6 @@ public class TestFonts { graphics.setRenderingHint(Drawable.FONT_FALLBACK, fallbackMap); graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); - DrawFactory.getInstance(graphics).fixFonts(graphics); - tb.resizeToFitText(graphics); graphics.dispose(); diff --git a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSimpleShape.java b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSimpleShape.java index e35ed35a29..ddbfe26652 100644 --- a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSimpleShape.java +++ b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSimpleShape.java @@ -361,7 +361,6 @@ public class TestXSLFSimpleShape { BufferedImage img = new BufferedImage(pgsize.width, pgsize.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); diff --git a/src/resources/main/META-INF/services/org.apache.poi.sl.draw.ImageRenderer b/src/resources/main/META-INF/services/org.apache.poi.sl.draw.ImageRenderer new file mode 100644 index 0000000000..1e7699fe57 --- /dev/null +++ b/src/resources/main/META-INF/services/org.apache.poi.sl.draw.ImageRenderer @@ -0,0 +1 @@ +org.apache.poi.sl.draw.BitmapImageRenderer \ No newline at end of file diff --git a/src/resources/scratchpad/META-INF/services/org.apache.poi.sl.draw.ImageRenderer b/src/resources/scratchpad/META-INF/services/org.apache.poi.sl.draw.ImageRenderer new file mode 100644 index 0000000000..b23f654157 --- /dev/null +++ b/src/resources/scratchpad/META-INF/services/org.apache.poi.sl.draw.ImageRenderer @@ -0,0 +1,2 @@ +org.apache.poi.hwmf.draw.HwmfImageRenderer +org.apache.poi.hemf.draw.HemfImageRenderer \ No newline at end of file diff --git a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java index f2e1957a28..8bb8af33bf 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java +++ b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java @@ -17,14 +17,29 @@ package org.apache.poi.hemf.draw; +import java.awt.geom.Path2D; + import org.apache.poi.hwmf.draw.HwmfDrawProperties; public class HemfDrawProperties extends HwmfDrawProperties { + /** Path for path bracket operations */ + protected final Path2D path; + + public HemfDrawProperties() { + path = new Path2D.Double(); } public HemfDrawProperties(HemfDrawProperties other) { super(other); + path = (Path2D)other.path.clone(); + } + + /** + * @return the current path used for bracket operations + */ + public Path2D getPath() { + return path; } } diff --git a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java index ff190652e1..05701ef823 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java +++ b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java @@ -18,14 +18,23 @@ package org.apache.poi.hemf.draw; import java.awt.Graphics2D; +import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; +import java.util.ArrayDeque; +import java.util.Deque; -import org.apache.poi.hwmf.draw.HwmfDrawProperties; +import org.apache.poi.hemf.record.emf.HemfBounded; +import org.apache.poi.hemf.record.emf.HemfRecord; import org.apache.poi.hwmf.draw.HwmfGraphics; public class HemfGraphics extends HwmfGraphics { + + private final Deque transforms = new ArrayDeque<>(); + public HemfGraphics(Graphics2D graphicsCtx, Rectangle2D bbox) { super(graphicsCtx,bbox); + // add dummy entry for object index 0, as emf is 1-based + addObjectTableEntry((ctx)->{}); } @Override @@ -42,4 +51,43 @@ public class HemfGraphics extends HwmfGraphics { propStack.add(prop); prop = new HemfDrawProperties((HemfDrawProperties)prop); } + + @Override + public void updateWindowMapMode() { + // ignore window settings + } + + public void draw(HemfRecord r) { + if (r instanceof HemfBounded) { + saveTransform(); + final HemfBounded bounded = (HemfBounded)r; + final Rectangle2D tgt = bounded.getRecordBounds(); + if (tgt != null && !tgt.isEmpty()) { + final Rectangle2D src = bounded.getShapeBounds(this); + if (src != null && !src.isEmpty()) { + graphicsCtx.translate(tgt.getCenterX() - src.getCenterX(), tgt.getCenterY() - src.getCenterY()); + graphicsCtx.translate(src.getCenterX(), src.getCenterY()); + graphicsCtx.scale(tgt.getWidth() / src.getWidth(), tgt.getHeight() / src.getHeight()); + graphicsCtx.translate(-src.getCenterX(), -src.getCenterY()); + } + } + } + + r.draw(this); + + if (r instanceof HemfBounded) { + restoreTransform(); + } + } + + + /** saves the current affine transform on the stack */ + private void saveTransform() { + transforms.push(graphicsCtx.getTransform()); + } + + /** restore the last saved affine transform */ + private void restoreTransform() { + graphicsCtx.setTransform(transforms.pop()); + } } diff --git a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfImageRenderer.java b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfImageRenderer.java new file mode 100644 index 0000000000..712c2e6c0e --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfImageRenderer.java @@ -0,0 +1,126 @@ +/* ==================================================================== + 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.hemf.draw; + +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.Insets; +import java.awt.RenderingHints; +import java.awt.geom.Dimension2D; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.awt.image.RescaleOp; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.apache.poi.hemf.usermodel.HemfPicture; +import org.apache.poi.sl.draw.ImageRenderer; +import org.apache.poi.sl.usermodel.PictureData; +import org.apache.poi.util.Units; + +public class HemfImageRenderer implements ImageRenderer { + HemfPicture image; + double alpha; + + @Override + public boolean canRender(String contentType) { + return PictureData.PictureType.EMF.contentType.equalsIgnoreCase(contentType); + } + + @Override + public void loadImage(InputStream data, String contentType) throws IOException { + if (!PictureData.PictureType.EMF.contentType.equals(contentType)) { + throw new IOException("Invalid picture type"); + } + image = new HemfPicture(data); + } + + @Override + public void loadImage(byte[] data, String contentType) throws IOException { + if (!PictureData.PictureType.EMF.contentType.equals(contentType)) { + throw new IOException("Invalid picture type"); + } + image = new HemfPicture(new ByteArrayInputStream(data)); + } + + @Override + public Dimension getDimension() { + int width = 0, height = 0; + if (image != null) { + Dimension2D dim = image.getSize(); + width = Units.pointsToPixel(dim.getWidth()); + // keep aspect ratio for height + height = Units.pointsToPixel(dim.getHeight()); + } + return new Dimension(width, height); + } + + @Override + public void setAlpha(double alpha) { + this.alpha = alpha; + } + + @Override + public BufferedImage getImage() { + return getImage(getDimension()); + } + + @Override + public BufferedImage getImage(Dimension dim) { + if (image == null) { + return new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); + } + + BufferedImage bufImg = new BufferedImage((int)dim.getWidth(), (int)dim.getHeight(), BufferedImage.TYPE_INT_ARGB); + Graphics2D g = bufImg.createGraphics(); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); + g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); + image.draw(g, new Rectangle2D.Double(0,0,dim.getWidth(),dim.getHeight())); + g.dispose(); + + if (alpha != 0) { + BufferedImage newImg = new BufferedImage((int)dim.getWidth(), (int)dim.getHeight(), BufferedImage.TYPE_INT_ARGB); + g = newImg.createGraphics(); + RescaleOp op = new RescaleOp(new float[]{1.0f, 1.0f, 1.0f, (float)alpha}, new float[]{0,0,0,0}, null); + g.drawImage(bufImg, op, 0, 0); + g.dispose(); + bufImg = newImg; + } + + return bufImg; + } + + @Override + public boolean drawImage(Graphics2D graphics, Rectangle2D anchor) { + return drawImage(graphics, anchor, null); + } + + @Override + public boolean drawImage(Graphics2D graphics, Rectangle2D anchor, Insets clip) { + if (image == null) { + return false; + } else { + image.draw(graphics, anchor); + return true; + } + } + +} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfBounded.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfBounded.java new file mode 100644 index 0000000000..dee8f94ffa --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfBounded.java @@ -0,0 +1,45 @@ +/* ==================================================================== + 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.hemf.record.emf; + +import java.awt.geom.Rectangle2D; + +import org.apache.poi.hemf.draw.HemfGraphics; + +/** + * In EMF, shape records bring their own bounding. + * The record bounding is in the same space as the global drawing context, + * but the specified shape points can have a different space and therefore + * need to be translated/normalized + */ +public interface HemfBounded { + /** + * Getter for the outer bounds which are given in the record + * + * @return the bounds specified in the record + */ + Rectangle2D getRecordBounds(); + + /** + * Getter for the inner bounds which are calculated by the shape points + * + * @param ctx the graphics context + * @return the bounds of the shape points + */ + Rectangle2D getShapeBounds(HemfGraphics ctx); +} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java index 77ddf7e39e..c2cc9c3de2 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java @@ -21,6 +21,8 @@ import static org.apache.poi.hwmf.record.HwmfBrushStyle.BS_NULL; import static org.apache.poi.hwmf.record.HwmfBrushStyle.BS_SOLID; import java.awt.Color; +import java.awt.geom.Arc2D; +import java.awt.geom.Area; import java.awt.geom.Dimension2D; import java.awt.geom.Path2D; import java.awt.geom.PathIterator; @@ -30,7 +32,6 @@ import java.io.IOException; import org.apache.poi.hemf.draw.HemfDrawProperties; import org.apache.poi.hemf.draw.HemfGraphics; -import org.apache.poi.hwmf.draw.HwmfGraphics; import org.apache.poi.hwmf.record.HwmfColorRef; import org.apache.poi.hwmf.record.HwmfDraw; import org.apache.poi.hwmf.record.HwmfDraw.WmfSelectObject; @@ -187,7 +188,7 @@ public class HemfDraw { /** The EMR_POLYBEZIER record specifies one or more Bezier curves. */ - public static class EmfPolyBezier extends HwmfDraw.WmfPolygon implements HemfRecord { + public static class EmfPolyBezier extends HwmfDraw.WmfPolygon implements HemfRecord, HemfBounded { private final Rectangle2D bounds = new Rectangle2D.Double(); @Override @@ -238,9 +239,13 @@ public class HemfDraw { for (int i=0; i+3= 0); @@ -622,7 +626,7 @@ public class HemfFill { * The EMR_FILLPATH record closes any open figures in the current path and fills the path's interior by * using the current brush and polygon-filling mode. */ - public static class EmfFillPath implements HemfRecord { + public static class EmfFillPath implements HemfRecord, HemfBounded { protected final Rectangle2D bounds = new Rectangle2D.Double(); @Override @@ -635,5 +639,29 @@ public class HemfFill { // A 128-bit WMF RectL object, which specifies bounding rectangle, in device units return readRectL(leis, bounds); } + + @Override + public Rectangle2D getRecordBounds() { + return bounds; + } + + @Override + public Rectangle2D getShapeBounds(HemfGraphics ctx) { + final HemfDrawProperties prop = ctx.getProperties(); + final Path2D path = prop.getPath(); + return path.getBounds2D(); + } + + @Override + public void draw(HemfGraphics ctx) { + final HemfDrawProperties prop = ctx.getProperties(); + final Path2D path = (Path2D)prop.getPath().clone(); + path.setWindingRule(ctx.getProperties().getWindingRule()); + if (prop.getBrushStyle() == HwmfBrushStyle.BS_NULL) { + ctx.draw(path); + } else { + ctx.fill(path); + } + } } } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfHeader.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfHeader.java index 0821d363ad..c1c0712c48 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfHeader.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfHeader.java @@ -111,12 +111,16 @@ public class HemfHeader implements HemfRecord { return hasExtension2; } - public long getMicrometersX() { - return (long)microDimension.getWidth(); + public Dimension2D getDeviceDimension() { + return deviceDimension; } - public long getMicrometersY() { - return (long)microDimension.getHeight(); + public Dimension2D getMilliDimension() { + return milliDimension; + } + + public Dimension2D getMicroDimension() { + return microDimension; } @Override @@ -135,8 +139,9 @@ public class HemfHeader implements HemfRecord { ", offPixelFormat=" + offPixelFormat + ", bOpenGL=" + bOpenGL + ", hasExtension2=" + hasExtension2 + - ", micrometersX=" + getMicrometersX() + - ", micrometersY=" + getMicrometersY() + + ", deviceDimension=" + deviceDimension + + ", microDimension=" + microDimension + + ", milliDimension=" + milliDimension + '}'; } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java index e843dc4f11..f3424d08d0 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java @@ -19,8 +19,10 @@ package org.apache.poi.hemf.record.emf; import static org.apache.poi.hemf.record.emf.HemfDraw.readPointL; import static org.apache.poi.hemf.record.emf.HemfFill.readBitmap; +import static org.apache.poi.hemf.record.emf.HemfFill.readXForm; import static org.apache.poi.hemf.record.emf.HemfRecordIterator.HEADER_SIZE; +import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.io.IOException; import java.util.ArrayList; @@ -54,34 +56,40 @@ public class HemfMisc { @Override public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + final int startIdx = leis.getReadIndex(); // A 32-bit unsigned integer that specifies the number of palette entries. - int nPalEntries = (int) leis.readUInt(); + final int nPalEntries = (int) leis.readUInt(); // A 32-bit unsigned integer that specifies the offset to the palette entries from the start of this record. - int offPalEntries = (int) leis.readUInt(); + final int offPalEntries = (int) leis.readUInt(); int size = 2 * LittleEndianConsts.INT_SIZE; - int undefinedSpace1 = (int) (offPalEntries - size - HEADER_SIZE); - assert (undefinedSpace1 >= 0); - leis.skipFully(undefinedSpace1); - size += undefinedSpace1; - - for (int i = 0; i < nPalEntries; i++) { - PaletteEntry pe = new PaletteEntry(); - size += pe.init(leis); - } - int undefinedSpace2 = (int) (recordSize - size - LittleEndianConsts.INT_SIZE); - assert (undefinedSpace2 >= 0); - leis.skipFully(undefinedSpace2); - size += undefinedSpace2; + if (offPalEntries > 0) { + int undefinedSpace1 = (int) (offPalEntries - size - HEADER_SIZE); + assert (undefinedSpace1 >= 0); + leis.skipFully(undefinedSpace1); + size += undefinedSpace1; + + for (int i = 0; i < nPalEntries; i++) { + PaletteEntry pe = new PaletteEntry(); + size += pe.init(leis); + } + + int undefinedSpace2 = (int) (recordSize - size - LittleEndianConsts.INT_SIZE); + assert (undefinedSpace2 >= 0); + leis.skipFully(undefinedSpace2); + size += undefinedSpace2; + } // A 32-bit unsigned integer that MUST be the same as Size and MUST be the // last field of the record and hence the metafile. // LogPaletteEntry objects, if they exist, MUST precede this field. long sizeLast = leis.readUInt(); size += LittleEndianConsts.INT_SIZE; - assert ((sizeLast - HEADER_SIZE) == recordSize && recordSize == size); + // some files store the whole file size in sizeLast, other just the last record size + // assert (sizeLast == size+HEADER_SIZE); + assert (recordSize == size); return size; } @@ -381,6 +389,7 @@ public class HemfMisc { // PS_COSMETIC, this value MUST be 0x00000001. long width = leis.readUInt(); dimension.setSize(width, 0); + int size = 7 * LittleEndianConsts.INT_SIZE; // A 32-bit unsigned integer that specifies a brush style for the pen from the WMF BrushStyle enumeration // @@ -389,16 +398,17 @@ public class HemfMisc { // The BS_NULL style SHOULD be used to specify a brush that has no effect brushStyle = HwmfBrushStyle.valueOf((int) leis.readUInt()); - int size = 8 * LittleEndianConsts.INT_SIZE; + size += LittleEndianConsts.INT_SIZE; size += colorRef.init(leis); hatchStyle = HwmfHatchStyle.valueOf(leis.readInt()); + size += LittleEndianConsts.INT_SIZE; // The number of elements in the array specified in the StyleEntry // field. This value SHOULD be zero if PenStyle does not specify PS_USERSTYLE. final int numStyleEntries = (int) leis.readUInt(); - size += 2 * LittleEndianConsts.INT_SIZE; + size += LittleEndianConsts.INT_SIZE; assert (numStyleEntries == 0 || penStyle.getLineDash() == HwmfLineDash.USERSTYLE); @@ -463,4 +473,42 @@ public class HemfMisc { return readPointL(leis, origin); } } + + public static class EmfSetWorldTransform implements HemfRecord { + protected final AffineTransform xForm = new AffineTransform(); + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.setWorldTransform; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + return readXForm(leis, xForm); + } + } + + public static class EmfModifyWorldTransform implements HemfRecord { + protected final AffineTransform xForm = new AffineTransform(); + protected int modifyWorldTransformMode; + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.modifyWorldTransform; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + // An XForm object that defines a two-dimensional linear transform in logical units. + // This transform is used according to the ModifyWorldTransformMode to define a new value for + // the world-space to page-space transform in the playback device context. + int size = readXForm(leis, xForm); + + // A 32-bit unsigned integer that specifies how the transform specified in Xform is used. + // This value MUST be in the ModifyWorldTransformMode enumeration + modifyWorldTransformMode = (int)leis.readUInt(); + + return size + LittleEndianConsts.INT_SIZE; + } + } } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecord.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecord.java index 627bd73155..4f11906bbd 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecord.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecord.java @@ -21,6 +21,7 @@ package org.apache.poi.hemf.record.emf; import java.io.IOException; import org.apache.poi.hemf.draw.HemfGraphics; +import org.apache.poi.hwmf.record.HwmfRecord; import org.apache.poi.util.Internal; import org.apache.poi.util.LittleEndianInputStream; @@ -42,5 +43,9 @@ public interface HemfRecord { */ long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException; - default void draw(HemfGraphics ctx) {} + default void draw(HemfGraphics ctx) { + if (this instanceof HwmfRecord) { + ((HwmfRecord) this).draw(ctx); + } + } } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java index c3e97b8e75..6c858ccc53 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java @@ -58,8 +58,8 @@ public enum HemfRecordType { scaleWindowExtEx(0x00000020, HemfWindowing.EmfScaleWindowExtEx::new), saveDc(0x00000021, HemfMisc.EmfSaveDc::new), restoreDc(0x00000022, HemfMisc.EmfRestoreDc::new), - setworldtransform(0x00000023, UnimplementedHemfRecord::new), - modifyworldtransform(0x00000024, UnimplementedHemfRecord::new), + setWorldTransform(0x00000023, HemfMisc.EmfSetWorldTransform::new), + modifyWorldTransform(0x00000024, HemfMisc.EmfModifyWorldTransform::new), selectObject(0x00000025, HemfDraw.EmfSelectObject::new), createPen(0x00000026, HemfMisc.EmfCreatePen::new), createBrushIndirect(0x00000027, HemfMisc.EmfCreateBrushIndirect::new), diff --git a/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java b/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java index 40b04d716a..3a68547823 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java +++ b/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java @@ -18,6 +18,10 @@ package org.apache.poi.hemf.usermodel; +import java.awt.Graphics2D; +import java.awt.geom.AffineTransform; +import java.awt.geom.Dimension2D; +import java.awt.geom.Rectangle2D; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; @@ -26,11 +30,14 @@ import java.util.List; import java.util.Spliterator; import java.util.function.Consumer; +import org.apache.poi.hemf.draw.HemfGraphics; import org.apache.poi.hemf.record.emf.HemfHeader; import org.apache.poi.hemf.record.emf.HemfRecord; import org.apache.poi.hemf.record.emf.HemfRecordIterator; +import org.apache.poi.util.Dimension2DDouble; import org.apache.poi.util.Internal; import org.apache.poi.util.LittleEndianInputStream; +import org.apache.poi.util.Units; /** * Read-only EMF extractor. Lots remain @@ -74,4 +81,42 @@ public class HemfPicture implements Iterable { public void forEach(Consumer action) { getRecords().forEach(action); } + + /** + * Return the image size in points + * + * @return the image size in points + */ + public Dimension2D getSize() { + HemfHeader header = (HemfHeader)getRecords().get(0); + Rectangle2D dim = header.getFrameRectangle(); + + double coeff = (double)Units.EMU_PER_CENTIMETER/Units.EMU_PER_POINT/10.; + return new Dimension2DDouble(dim.getWidth()*coeff, dim.getHeight()*coeff); + } + + public void draw(Graphics2D ctx, Rectangle2D graphicsBounds) { + HemfHeader header = (HemfHeader)getRecords().get(0); + + AffineTransform at = ctx.getTransform(); + try { + Rectangle2D emfBounds = header.getBoundsRectangle(); + ctx.translate(graphicsBounds.getCenterX()-emfBounds.getCenterX(), graphicsBounds.getCenterY()-emfBounds.getCenterY()); + + // scale output bounds to image bounds + ctx.translate(emfBounds.getCenterX(), emfBounds.getCenterY()); + ctx.scale(graphicsBounds.getWidth()/emfBounds.getWidth(), graphicsBounds.getHeight()/emfBounds.getHeight()); + ctx.translate(-emfBounds.getCenterX(), -emfBounds.getCenterY()); + + + + HemfGraphics g = new HemfGraphics(ctx, emfBounds); + for (HemfRecord r : getRecords()) { + g.draw(r); + } + } finally { + ctx.setTransform(at); + } + } + } 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 cdbfefe71e..705f02705d 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java @@ -20,6 +20,7 @@ package org.apache.poi.hwmf.draw; import java.awt.Color; import java.awt.Shape; import java.awt.geom.Area; +import java.awt.geom.Path2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; @@ -88,7 +89,7 @@ public class HwmfDrawProperties { } public HwmfDrawProperties(HwmfDrawProperties other) { - this.window = (Rectangle2D)other.window.clone(); + this.window = (other.window == null) ? null : (Rectangle2D)other.window.clone(); this.viewport = (other.viewport == null) ? null : (Rectangle2D)other.viewport.clone(); this.location = (Point2D)other.location.clone(); this.mapMode = other.mapMode; @@ -367,4 +368,11 @@ public class HwmfDrawProperties { public void setTextVAlignAsian(HwmfTextVerticalAlignment textVAlignAsian) { this.textVAlignAsian = textVAlignAsian; } + + /** + * @return the current active winding rule ({@link Path2D#WIND_EVEN_ODD} or {@link Path2D#WIND_NON_ZERO}) + */ + public int getWindingRule() { + return getPolyfillMode().awtFlag; + } } 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 1789337d2a..de348480ad 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java @@ -54,9 +54,9 @@ public class HwmfGraphics { protected final List propStack = new LinkedList<>(); protected HwmfDrawProperties prop; + protected final Graphics2D graphicsCtx; private static final Charset DEFAULT_CHARSET = LocaleUtil.CHARSET_1252; - private final Graphics2D graphicsCtx; private final List objectTable = new ArrayList<>(); /** Bounding box from the placeable header */ private final Rectangle2D bbox; @@ -72,7 +72,6 @@ public class HwmfGraphics { this.graphicsCtx = graphicsCtx; this.bbox = (Rectangle2D)bbox.clone(); this.initialAT = graphicsCtx.getTransform(); - DrawFactory.getInstance(graphicsCtx).fixFonts(graphicsCtx); } public HwmfDrawProperties getProperties() { @@ -178,6 +177,7 @@ public class HwmfGraphics { if (h == HwmfHatchStyle.HS_BDIAGONAL || h == HwmfHatchStyle.HS_DIAGCROSS) { g.drawLine(0, dim, dim, 0); } + // TODO: handle new HS_* enumeration values g.dispose(); return new TexturePaint(bi, new Rectangle(0,0,dim,dim)); } @@ -248,9 +248,10 @@ public class HwmfGraphics { * Saves the current properties to the stack */ public void saveProperties() { - assert(prop != null); - propStack.add(prop); - prop = new HwmfDrawProperties(prop); + final HwmfDrawProperties p = getProperties(); + assert(p != null); + propStack.add(p); + prop = new HwmfDrawProperties(p); } /** diff --git a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfImageRenderer.java b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfImageRenderer.java new file mode 100644 index 0000000000..a87db042d6 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfImageRenderer.java @@ -0,0 +1,129 @@ +/* ==================================================================== + 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.draw; + +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.Insets; +import java.awt.RenderingHints; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.awt.image.RescaleOp; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.apache.poi.hwmf.usermodel.HwmfPicture; +import org.apache.poi.sl.draw.DrawPictureShape; +import org.apache.poi.sl.draw.ImageRenderer; +import org.apache.poi.sl.usermodel.PictureData.PictureType; +import org.apache.poi.util.Units; + +/** + * Helper class which is instantiated by {@link DrawPictureShape} + * via reflection + */ +public class HwmfImageRenderer implements ImageRenderer { + HwmfPicture image; + double alpha; + + @Override + public boolean canRender(String contentType) { + return PictureType.WMF.contentType.equalsIgnoreCase(contentType); + } + + @Override + public void loadImage(InputStream data, String contentType) throws IOException { + if (!PictureType.WMF.contentType.equals(contentType)) { + throw new IOException("Invalid picture type"); + } + image = new HwmfPicture(data); + } + + @Override + public void loadImage(byte[] data, String contentType) throws IOException { + if (!PictureType.WMF.contentType.equals(contentType)) { + throw new IOException("Invalid picture type"); + } + image = new HwmfPicture(new ByteArrayInputStream(data)); + } + + @Override + public Dimension getDimension() { + int width = 0, height = 0; + if (image != null) { + Dimension dim = image.getSize(); + width = Units.pointsToPixel(dim.getWidth()); + // keep aspect ratio for height + height = Units.pointsToPixel(dim.getHeight()); + } + return new Dimension(width, height); + } + + @Override + public void setAlpha(double alpha) { + this.alpha = alpha; + } + + @Override + public BufferedImage getImage() { + return getImage(getDimension()); + } + + @Override + public BufferedImage getImage(Dimension dim) { + if (image == null) { + return new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); + } + + BufferedImage bufImg = new BufferedImage((int)dim.getWidth(), (int)dim.getHeight(), BufferedImage.TYPE_INT_ARGB); + Graphics2D g = bufImg.createGraphics(); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); + g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); + image.draw(g, new Rectangle2D.Double(0,0,dim.getWidth(),dim.getHeight())); + g.dispose(); + + if (alpha != 0) { + BufferedImage newImg = new BufferedImage((int)dim.getWidth(), (int)dim.getHeight(), BufferedImage.TYPE_INT_ARGB); + g = newImg.createGraphics(); + RescaleOp op = new RescaleOp(new float[]{1.0f, 1.0f, 1.0f, (float)alpha}, new float[]{0,0,0,0}, null); + g.drawImage(bufImg, op, 0, 0); + g.dispose(); + bufImg = newImg; + } + + return bufImg; + } + + @Override + public boolean drawImage(Graphics2D graphics, Rectangle2D anchor) { + return drawImage(graphics, anchor, null); + } + + @Override + public boolean drawImage(Graphics2D graphics, Rectangle2D anchor, Insets clip) { + if (image == null) { + return false; + } else { + image.draw(graphics, anchor); + return true; + } + } +} diff --git a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfSLImageRenderer.java b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfSLImageRenderer.java deleted file mode 100644 index e2601bc65e..0000000000 --- a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfSLImageRenderer.java +++ /dev/null @@ -1,124 +0,0 @@ -/* ==================================================================== - 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.draw; - -import java.awt.Dimension; -import java.awt.Graphics2D; -import java.awt.Insets; -import java.awt.RenderingHints; -import java.awt.geom.Rectangle2D; -import java.awt.image.BufferedImage; -import java.awt.image.RescaleOp; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; - -import org.apache.poi.hwmf.usermodel.HwmfPicture; -import org.apache.poi.sl.draw.DrawPictureShape; -import org.apache.poi.sl.draw.ImageRenderer; -import org.apache.poi.sl.usermodel.PictureData; -import org.apache.poi.util.Units; - -/** - * Helper class which is instantiated by {@link DrawPictureShape} - * via reflection - */ -public class HwmfSLImageRenderer implements ImageRenderer { - HwmfPicture image; - double alpha; - - @Override - public void loadImage(InputStream data, String contentType) throws IOException { - if (!PictureData.PictureType.WMF.contentType.equals(contentType)) { - throw new IOException("Invalid picture type"); - } - image = new HwmfPicture(data); - } - - @Override - public void loadImage(byte[] data, String contentType) throws IOException { - if (!PictureData.PictureType.WMF.contentType.equals(contentType)) { - throw new IOException("Invalid picture type"); - } - image = new HwmfPicture(new ByteArrayInputStream(data)); - } - - @Override - public Dimension getDimension() { - int width = 0, height = 0; - if (image != null) { - Dimension dim = image.getSize(); - width = Units.pointsToPixel(dim.getWidth()); - // keep aspect ratio for height - height = Units.pointsToPixel(dim.getHeight()); - } - return new Dimension(width, height); - } - - @Override - public void setAlpha(double alpha) { - this.alpha = alpha; - } - - @Override - public BufferedImage getImage() { - return getImage(getDimension()); - } - - @Override - public BufferedImage getImage(Dimension dim) { - if (image == null) { - return new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); - } - - BufferedImage bufImg = new BufferedImage((int)dim.getWidth(), (int)dim.getHeight(), BufferedImage.TYPE_INT_ARGB); - Graphics2D g = bufImg.createGraphics(); - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); - g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); - g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); - image.draw(g, new Rectangle2D.Double(0,0,dim.getWidth(),dim.getHeight())); - g.dispose(); - - if (alpha != 0) { - BufferedImage newImg = new BufferedImage((int)dim.getWidth(), (int)dim.getHeight(), BufferedImage.TYPE_INT_ARGB); - g = newImg.createGraphics(); - RescaleOp op = new RescaleOp(new float[]{1.0f, 1.0f, 1.0f, (float)alpha}, new float[]{0,0,0,0}, null); - g.drawImage(bufImg, op, 0, 0); - g.dispose(); - bufImg = newImg; - } - - return bufImg; - } - - @Override - public boolean drawImage(Graphics2D graphics, Rectangle2D anchor) { - return drawImage(graphics, anchor, null); - } - - @Override - public boolean drawImage(Graphics2D graphics, Rectangle2D anchor, Insets clip) { - if (image == null) { - return false; - } else { - image.draw(graphics, anchor); - return true; - } - } -} 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 fb78158a9d..2f052075b5 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java @@ -124,9 +124,9 @@ public class HwmfDraw { @Override public void draw(HwmfGraphics ctx) { - Path2D p = getShape(ctx); + Path2D p = (Path2D)poly.clone(); // don't close the path - p.setWindingRule(getWindingRule(ctx)); + p.setWindingRule(ctx.getProperties().getWindingRule()); if (isFill()) { ctx.fill(p); } else { @@ -134,10 +134,6 @@ public class HwmfDraw { } } - protected Path2D getShape(HwmfGraphics ctx) { - return (Path2D)poly.clone(); - } - /** * @return true, if the shape should be filled */ @@ -303,11 +299,20 @@ public class HwmfDraw { @Override public void draw(HwmfGraphics ctx) { - if (polyList.isEmpty()) { + Area area = getShape(ctx); + if (area == null) { return; } - int windingRule = getWindingRule(ctx); + if (isFill()) { + ctx.fill(area); + } else { + ctx.draw(area); + } + } + + protected Area getShape(HwmfGraphics ctx) { + int windingRule = ctx.getProperties().getWindingRule(); Area area = null; for (Path2D poly : polyList) { Path2D p = (Path2D)poly.clone(); @@ -320,14 +325,9 @@ public class HwmfDraw { } } - if (isFill()) { - ctx.fill(area); - } else { - ctx.draw(area); - } + return area; } - /** * @return true, if the shape should be filled */ @@ -459,6 +459,20 @@ public class HwmfDraw { @Override public void draw(HwmfGraphics ctx) { + Shape s = getShape(); + switch (getWmfRecordType()) { + default: + case arc: + ctx.draw(s); + break; + case chord: + case pie: + ctx.fill(s); + break; + } + } + + protected Arc2D getShape() { double startAngle = Math.toDegrees(Math.atan2(-(startPoint.getY() - bounds.getCenterY()), startPoint.getX() - bounds.getCenterX())); double endAngle = Math.toDegrees(Math.atan2(-(endPoint.getY() - bounds.getCenterY()), endPoint.getX() - bounds.getCenterX())); double arcAngle = (endAngle - startAngle) + (endAngle - startAngle > 0 ? 0 : 360); @@ -472,24 +486,16 @@ public class HwmfDraw { default: case arc: arcClosure = Arc2D.OPEN; - fillShape = false; break; case chord: arcClosure = Arc2D.CHORD; - fillShape = true; break; case pie: arcClosure = Arc2D.PIE; - fillShape = true; break; } - - Shape s = new Arc2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), startAngle, arcAngle, arcClosure); - if (fillShape) { - ctx.fill(s); - } else { - ctx.draw(s); - } + + return new Arc2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), startAngle, arcAngle, arcClosure); } } @@ -552,10 +558,6 @@ public class HwmfDraw { } } - private static int getWindingRule(HwmfGraphics ctx) { - return ctx.getProperties().getPolyfillMode().awtFlag; - } - static int readBounds(LittleEndianInputStream leis, Rectangle2D bounds) { /** * The 16-bit signed integers that defines the corners of the bounding rectangle. diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfHatchStyle.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfHatchStyle.java index e364d83b3b..6574deaed3 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfHatchStyle.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfHatchStyle.java @@ -32,7 +32,20 @@ public enum HwmfHatchStyle { /** +++++ - A horizontal and vertical cross-hatch. */ HS_CROSS(0x0004), /** xxxxx - A 45-degree crosshatch. */ - HS_DIAGCROSS(0x0005); + HS_DIAGCROSS(0x0005), + /** The hatch is not a pattern, but is a solid color. */ + HS_SOLIDCLR(0x0006), + /** The hatch is not a pattern, but is a dithered color. */ + HS_DITHEREDCLR(0x0007), + /** The hatch is not a pattern, but is a solid color, defined by the current text (foreground) color. */ + HS_SOLIDTEXTCLR(0x0008), + /** The hatch is not a pattern, but is a dithered color, defined by the current text (foreground) color. */ + HS_DITHEREDTEXTCLR(0x0009), + /** The hatch is not a pattern, but is a solid color, defined by the current background color. */ + HS_SOLIDBKCLR(0x000A), + /** The hatch is not a pattern, but is a dithered color, defined by the current background color. */ + HS_DITHEREDBKCLR(0x000B) + ; int flag; HwmfHatchStyle(int flag) { diff --git a/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java b/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java index f0e259df4d..8d360d9aef 100644 --- a/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java +++ b/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java @@ -22,14 +22,24 @@ import static org.apache.poi.POITestCase.assertContains; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.geom.Dimension2D; import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; import java.io.InputStream; import java.util.HashSet; import java.util.List; import java.util.Set; +import javax.imageio.ImageIO; + import org.apache.poi.POIDataSamples; import org.apache.poi.hemf.record.emf.HemfComment; import org.apache.poi.hemf.record.emf.HemfComment.EmfComment; @@ -41,22 +51,60 @@ import org.apache.poi.hemf.record.emf.HemfRecordType; import org.apache.poi.hemf.record.emf.HemfText; import org.apache.poi.util.IOUtils; import org.apache.poi.util.RecordFormatException; +import org.apache.poi.util.Units; +import org.junit.Ignore; import org.junit.Test; public class HemfPictureTest { - private POIDataSamples samples = POIDataSamples.getSpreadSheetInstance(); + private static final POIDataSamples ss_samples = POIDataSamples.getSpreadSheetInstance(); + private static final POIDataSamples sl_samples = POIDataSamples.getSlideShowInstance(); + + @Test + @Ignore("Only for manual tests") + public void paint() throws IOException { + File f = sl_samples.getFile("wrench.emf"); + try (FileInputStream fis = new FileInputStream(f)) { + HemfPicture emf = new HemfPicture(fis); + + Dimension2D dim = emf.getSize(); + 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(); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); + g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); + + emf.draw(g, new Rectangle2D.Double(0,0,width,height)); + + g.dispose(); + + ImageIO.write(bufImg, "PNG", new File("bla.png")); + } + } + + + @Test public void testBasicWindows() throws Exception { - try (InputStream is = samples.openResourceAsStream("SimpleEMF_windows.emf")) { + try (InputStream is = ss_samples.openResourceAsStream("SimpleEMF_windows.emf")) { HemfPicture pic = new HemfPicture(is); HemfHeader header = pic.getHeader(); assertEquals(27864, header.getBytes()); assertEquals(31, header.getRecords()); assertEquals(3, header.getHandles()); - assertEquals(346000, header.getMicrometersX()); - assertEquals(194000, header.getMicrometersY()); + assertEquals(346000, header.getMicroDimension().getWidth()); + assertEquals(194000, header.getMicroDimension().getHeight()); List records = pic.getRecords(); @@ -66,7 +114,7 @@ public class HemfPictureTest { @Test public void testBasicMac() throws Exception { - try (InputStream is = samples.openResourceAsStream("SimpleEMF_mac.emf")) { + try (InputStream is = ss_samples.openResourceAsStream("SimpleEMF_mac.emf")) { HemfPicture pic = new HemfPicture(is); HemfHeader header = pic.getHeader(); @@ -102,7 +150,7 @@ public class HemfPictureTest { @Test public void testMacText() throws Exception { - try (InputStream is = samples.openResourceAsStream("SimpleEMF_mac.emf")) { + try (InputStream is = ss_samples.openResourceAsStream("SimpleEMF_mac.emf")) { HemfPicture pic = new HemfPicture(is); double lastY = -1; @@ -134,7 +182,7 @@ public class HemfPictureTest { @Test public void testWindowsText() throws Exception { - try (InputStream is = samples.openResourceAsStream("SimpleEMF_windows.emf")) { + try (InputStream is = ss_samples.openResourceAsStream("SimpleEMF_windows.emf")) { HemfPicture pic = new HemfPicture(is); double lastY = -1; double lastX = -1; @@ -173,7 +221,7 @@ public class HemfPictureTest { @Test(expected = RecordFormatException.class) public void testInfiniteLoopOnFile() throws Exception { - try (InputStream is = samples.openResourceAsStream("61294.emf")) { + try (InputStream is = ss_samples.openResourceAsStream("61294.emf")) { HemfPicture pic = new HemfPicture(is); for (HemfRecord record : pic) { @@ -183,7 +231,7 @@ public class HemfPictureTest { @Test(expected = RecordFormatException.class) public void testInfiniteLoopOnByteArray() throws Exception { - try (InputStream is = samples.openResourceAsStream("61294.emf")) { + try (InputStream is = ss_samples.openResourceAsStream("61294.emf")) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); IOUtils.copy(is, bos); is.close(); 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 72eee287c2..d071f2dbdc 100644 --- a/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestPicture.java +++ b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestPicture.java @@ -210,7 +210,6 @@ public final class TestPicture { } else { BufferedImage img = new BufferedImage(pg.width, pg.height, BufferedImage.TYPE_INT_ARGB); Graphics2D graphics = img.createGraphics(); - DrawFactory.getInstance(graphics).fixFonts(graphics); slide.draw(graphics); graphics.setColor(Color.BLACK); graphics.setStroke(new BasicStroke(1));