浏览代码

#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
tags/REL_4_1_0
Andreas Beeker 5 年前
父节点
当前提交
5073f22fce
共有 30 个文件被更改,包括 749 次插入181 次删除
  1. 0
    1
      src/integrationtest/org/apache/poi/stress/SlideShowHandler.java
  2. 15
    1
      src/java/org/apache/poi/sl/draw/BitmapImageRenderer.java
  3. 4
    34
      src/java/org/apache/poi/sl/draw/DrawFactory.java
  4. 16
    18
      src/java/org/apache/poi/sl/draw/DrawPictureShape.java
  5. 0
    8
      src/java/org/apache/poi/sl/draw/DrawTextParagraph.java
  6. 1
    4
      src/java/org/apache/poi/sl/draw/DrawTextShape.java
  7. 7
    0
      src/java/org/apache/poi/sl/draw/ImageRenderer.java
  8. 0
    1
      src/ooxml/java/org/apache/poi/xslf/util/PPTX2PNG.java
  9. 0
    2
      src/ooxml/testcases/org/apache/poi/sl/TestFonts.java
  10. 0
    1
      src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSimpleShape.java
  11. 1
    0
      src/resources/main/META-INF/services/org.apache.poi.sl.draw.ImageRenderer
  12. 2
    0
      src/resources/scratchpad/META-INF/services/org.apache.poi.sl.draw.ImageRenderer
  13. 15
    0
      src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java
  14. 49
    1
      src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java
  15. 126
    0
      src/scratchpad/src/org/apache/poi/hemf/draw/HemfImageRenderer.java
  16. 45
    0
      src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfBounded.java
  17. 181
    30
      src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java
  18. 31
    3
      src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java
  19. 11
    6
      src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfHeader.java
  20. 66
    18
      src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java
  21. 6
    1
      src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecord.java
  22. 2
    2
      src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java
  23. 45
    0
      src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java
  24. 9
    1
      src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java
  25. 6
    5
      src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java
  26. 10
    5
      src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfImageRenderer.java
  27. 30
    28
      src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java
  28. 14
    1
      src/scratchpad/src/org/apache/poi/hwmf/record/HwmfHatchStyle.java
  29. 57
    9
      src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java
  30. 0
    1
      src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestPicture.java

+ 0
- 1
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);

+ 15
- 1
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);

+ 4
- 34
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<DrawFactory> defaultFactory = new ThreadLocal<>();
private static final ThreadLocal<DrawFactory> 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<String,String> fontMap = (Map<String,String>)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
*

+ 16
- 18
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<ImageRenderer> 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<? extends ImageRenderer> irc = (Class<? extends ImageRenderer>)
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();
}

+ 0
- 8
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 {
* <li>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 <a href="https://blogs.msdn.microsoft.com/officeinteroperability/2013/04/22/office-open-xml-themes-schemes-and-fonts/">Office Open XML Themes, Schemes, and Fonts</a>
*/
private void processGlyphs(Graphics2D graphics, DrawFontManager dfm, List<AttributedStringData> attList, final int beginIndex, TextRun run, String runText) {

+ 1
- 4
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<?,? extends TextParagraph<?,?,? extends TextRun>> getShape() {
return (TextShape<?,? extends TextParagraph<?,?,? extends TextRun>>)shape;

+ 7
- 0
src/java/org/apache/poi/sl/draw/ImageRenderer.java 查看文件

@@ -75,6 +75,13 @@ import java.io.InputStream;
* </pre>
*/
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
*

+ 0
- 1
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);

+ 0
- 2
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();


+ 0
- 1
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);

+ 1
- 0
src/resources/main/META-INF/services/org.apache.poi.sl.draw.ImageRenderer 查看文件

@@ -0,0 +1 @@
org.apache.poi.sl.draw.BitmapImageRenderer

+ 2
- 0
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

+ 15
- 0
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;
}
}

+ 49
- 1
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<AffineTransform> 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());
}
}

+ 126
- 0
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;
}
}

}

+ 45
- 0
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);
}

+ 181
- 30
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<pointCnt; i+=3) {
// x (4 bytes): A 32-bit signed integer that defines the horizontal (x) coordinate of the point.
// y (4 bytes): A 32-bit signed integer that defines the vertical (y) coordinate of the point.
if (i==0 && hasStartPoint()) {
size += readPoint(leis, pnt[0]);
poly.moveTo(pnt[0].getX(),pnt[0].getY());
if (i==0) {
if (hasStartPoint()) {
size += readPoint(leis, pnt[0]);
poly.moveTo(pnt[0].getX(), pnt[0].getY());
} else {
poly.moveTo(0, 0);
}
}

size += readPoint(leis, pnt[0]);
@@ -257,6 +262,19 @@ public class HemfDraw {
return size;
}

@Override
public Rectangle2D getRecordBounds() {
return bounds;
}

@Override
public Rectangle2D getShapeBounds(HemfGraphics ctx) {
if (!hasStartPoint()) {
throw new IllegalStateException("shape bounds not valid for path bracket based record: "+getClass().getName());
}
return poly.getBounds2D();
}

/**
* @return true, if start point is in the list of points. false, if start point is taken from the context
*/
@@ -285,7 +303,7 @@ public class HemfDraw {
* The EMR_POLYGON record specifies a polygon consisting of two or more vertexes connected by
* straight lines.
*/
public static class EmfPolygon extends HwmfDraw.WmfPolygon implements HemfRecord {
public static class EmfPolygon extends HwmfDraw.WmfPolygon implements HemfRecord, HemfBounded {
private final Rectangle2D bounds = new Rectangle2D.Double();

@Override
@@ -310,21 +328,34 @@ public class HemfDraw {
for (int i=0; i<points; i++) {
size += readPoint(leis, pnt);
if (i==0) {
poly.moveTo(pnt.getX(), pnt.getY());

if (hasStartPoint()) {
continue;
poly.moveTo(pnt.getX(), pnt.getY());
} else {
// if this path is connected to the current position (= has no start point)
// the first entry is a dummy entry and will be skipped later
poly.moveTo(0,0);
}

// if this path is connected to the current position (= has no start point)
// the first entry is a dummy entry and will be skipped later
} else {
poly.lineTo(pnt.getX(), pnt.getY());
}
poly.lineTo(pnt.getX(), pnt.getY());
}

return size;
}

@Override
public Rectangle2D getRecordBounds() {
return bounds;
}

@Override
public Rectangle2D getShapeBounds(HemfGraphics ctx) {
if (!hasStartPoint()) {
throw new IllegalStateException("shape bounds not valid for path bracket based record: "+getClass().getName());
}
return poly.getBounds2D();
}

/**
* @return true, if start point is in the list of points. false, if start point is taken from the context
*/
@@ -398,8 +429,15 @@ public class HemfDraw {
}

@Override
protected Path2D getShape(HwmfGraphics ctx) {
return polyTo(ctx, poly);
public void draw(HemfGraphics ctx) {
polyTo(ctx, poly);
}

@Override
public Rectangle2D getShapeBounds(HemfGraphics ctx) {
// should be called in a beginPath/endPath bracket, so the shape bounds
// of this path segment are irrelevant
return null;
}
}

@@ -432,8 +470,15 @@ public class HemfDraw {
}

@Override
protected Path2D getShape(HwmfGraphics ctx) {
return polyTo(ctx, poly);
public void draw(HemfGraphics ctx) {
polyTo(ctx, poly);
}

@Override
public Rectangle2D getShapeBounds(HemfGraphics ctx) {
// should be called in a beginPath/endPath bracket, so the shape bounds
// of this path segment are irrelevant
return null;
}
}

@@ -458,7 +503,7 @@ public class HemfDraw {
/**
* The EMR_POLYPOLYGON record specifies a series of closed polygons.
*/
public static class EmfPolyPolygon extends HwmfDraw.WmfPolyPolygon implements HemfRecord {
public static class EmfPolyPolygon extends HwmfDraw.WmfPolyPolygon implements HemfRecord, HemfBounded {
private final Rectangle2D bounds = new Rectangle2D.Double();

@Override
@@ -519,6 +564,17 @@ public class HemfDraw {
protected boolean isClosed() {
return true;
}

@Override
public Rectangle2D getRecordBounds() {
return bounds;
}

@Override
public Rectangle2D getShapeBounds(HemfGraphics ctx) {
Area area = getShape(ctx);
return area == null ? bounds : area.getBounds2D();
}
}

/**
@@ -601,6 +657,14 @@ public class HemfDraw {
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
return readPointL(leis, point);
}

@Override
public void draw(HemfGraphics ctx) {
HemfDrawProperties prop = ctx.getProperties();
final Path2D path = prop.getPath();
path.moveTo(point.getX(), point.getY());
prop.setLocation(point);
}
}

/**
@@ -736,6 +800,14 @@ public class HemfDraw {
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
return readPointL(leis, point);
}

@Override
public void draw(HemfGraphics ctx) {
final HemfDrawProperties prop = ctx.getProperties();
final Path2D path = prop.getPath();
path.lineTo(point.getX(), point.getY());
prop.setLocation(point);
}
}

/**
@@ -758,13 +830,15 @@ public class HemfDraw {

@Override
public void draw(HemfGraphics ctx) {
super.draw(ctx);
final Path2D path = ctx.getProperties().getPath();
Arc2D arc = getShape();
path.append(arc, true);
ctx.getProperties().setLocation(endPoint);
}
}

/** The EMR_POLYDRAW record specifies a set of line segments and Bezier curves. */
public static class EmfPolyDraw extends HwmfDraw.WmfPolygon implements HemfRecord {
public static class EmfPolyDraw extends HwmfDraw.WmfPolygon implements HemfRecord, HemfBounded {
private final Rectangle2D bounds = new Rectangle2D.Double();

@Override
@@ -837,6 +911,16 @@ public class HemfDraw {
size += count;
return size;
}

@Override
public Rectangle2D getRecordBounds() {
return bounds;
}

@Override
public Rectangle2D getShapeBounds(HemfGraphics ctx) {
return poly.getBounds2D();
}
}

public static class EmfPolyDraw16 extends EmfPolyDraw {
@@ -850,6 +934,16 @@ public class HemfDraw {
}
}

/**
* This record opens a path bracket in the current playback device context.
*
* After a path bracket is open, an application can begin processing records to define
* the points that lie in the path. An application MUST close an open path bracket by
* processing the EMR_ENDPATH record.
*
* When an application processes the EMR_BEGINPATH record, all previous paths
* MUST be discarded from the playback device context.
*/
public static class EmfBeginPath implements HemfRecord {
@Override
public HemfRecordType getEmfRecordType() {
@@ -860,8 +954,19 @@ public class HemfDraw {
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
return 0;
}

@Override
public void draw(HemfGraphics ctx) {
final HemfDrawProperties prop = ctx.getProperties();
final Path2D path = prop.getPath();
path.reset();
}
}

/**
* This record closes a path bracket and selects the path defined by the bracket into
* the playback device context.
*/
public static class EmfEndPath implements HemfRecord {
@Override
public HemfRecordType getEmfRecordType() {
@@ -874,6 +979,9 @@ public class HemfDraw {
}
}

/**
* This record aborts a path bracket or discards the path from a closed path bracket.
*/
public static class EmfAbortPath implements HemfRecord {
@Override
public HemfRecordType getEmfRecordType() {
@@ -884,8 +992,34 @@ public class HemfDraw {
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
return 0;
}

@Override
public void draw(HemfGraphics ctx) {
final HemfDrawProperties prop = ctx.getProperties();
final Path2D path = prop.getPath();
path.reset();
}
}

/**
* This record closes an open figure in a path.
*
* Processing the EMR_CLOSEFIGURE record MUST close the figure by drawing a line
* from the current position to the first point of the figure, and then it MUST connect
* the lines by using the line join style. If a figure is closed by processing the
* EMR_LINETO record instead of the EMR_CLOSEFIGURE record, end caps are
* used to create the corner instead of a join.
*
* The EMR_CLOSEFIGURE record SHOULD only be used if there is an open path
* bracket in the playback device context.
*
* A figure in a path is open unless it is explicitly closed by processing this record.
* Note: A figure can be open even if the current point and the starting point of the
* figure are the same.
*
* After processing the EMR_CLOSEFIGURE record, adding a line or curve to the path
* MUST start a new figure.
*/
public static class EmfCloseFigure implements HemfRecord {
@Override
public HemfRecordType getEmfRecordType() {
@@ -896,8 +1030,22 @@ public class HemfDraw {
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
return 0;
}


@Override
public void draw(HemfGraphics ctx) {
final HemfDrawProperties prop = ctx.getProperties();
final Path2D path = prop.getPath();
path.closePath();
prop.setLocation(path.getCurrentPoint());

}
}

/**
* This record transforms any curves in the selected path into the playback device
* context; each curve MUST be turned into a sequence of lines.
*/
public static class EmfFlattenPath implements HemfRecord {
@Override
public HemfRecordType getEmfRecordType() {
@@ -910,6 +1058,10 @@ public class HemfDraw {
}
}

/**
* This record redefines the current path as the area that would be painted if the path
* were drawn using the pen currently selected into the playback device context.
*/
public static class EmfWidenPath implements HemfRecord {
@Override
public HemfRecordType getEmfRecordType() {
@@ -988,16 +1140,15 @@ public class HemfDraw {
return 2*LittleEndianConsts.INT_SIZE;
}

private static Path2D polyTo(HwmfGraphics ctx, Path2D poly) {
Path2D polyCopy = new Path2D.Double();
Point2D start = ctx.getProperties().getLocation();
polyCopy.moveTo(start.getX(), start.getY());

PathIterator iter = poly.getPathIterator(null);
iter.next();

polyCopy.append(iter, true);
return polyCopy;
private static void polyTo(HemfGraphics ctx, Path2D poly) {
final HemfDrawProperties prop = ctx.getProperties();
final Path2D path = prop.getPath();
PathIterator pi = poly.getPathIterator(null);
// ignore dummy start point (moveTo)
pi.next();
assert(!pi.isDone());
path.append(pi, true);
prop.setLocation(path.getCurrentPoint());
}



+ 31
- 3
src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java 查看文件

@@ -17,12 +17,13 @@

package org.apache.poi.hemf.record.emf;

import static org.apache.poi.hemf.record.emf.HemfDraw.readRectL;
import static org.apache.poi.hemf.record.emf.HemfDraw.readPointL;
import static org.apache.poi.hemf.record.emf.HemfDraw.readRectL;
import static org.apache.poi.hemf.record.emf.HemfRecordIterator.HEADER_SIZE;

import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.ByteArrayInputStream;
@@ -31,8 +32,11 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

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.HwmfBitmapDib;
import org.apache.poi.hwmf.record.HwmfBrushStyle;
import org.apache.poi.hwmf.record.HwmfColorRef;
import org.apache.poi.hwmf.record.HwmfDraw;
import org.apache.poi.hwmf.record.HwmfFill;
@@ -515,7 +519,7 @@ public class HemfFill {
static long readBitmap(final LittleEndianInputStream leis, final HwmfBitmapDib bitmap,
final int startIdx, final int offBmiSrc, final int cbBmiSrc, final int offBitsSrc, int cbBitsSrc)
throws IOException {
final int offCurr = leis.getReadIndex()-(startIdx-HEADER_SIZE);
final int offCurr = leis.getReadIndex()-startIdx;
final int undefinedSpace1 = offBmiSrc-offCurr;
assert(undefinedSpace1 >= 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);
}
}
}
}

+ 11
- 6
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 +
'}';
}


+ 66
- 18
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;
}
}
}

+ 6
- 1
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);
}
}
}

+ 2
- 2
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),

+ 45
- 0
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<HemfRecord> {
public void forEach(Consumer<? super HemfRecord> 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);
}
}

}

+ 9
- 1
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;
}
}

+ 6
- 5
src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java 查看文件

@@ -54,9 +54,9 @@ public class HwmfGraphics {

protected final List<HwmfDrawProperties> 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<HwmfObjectTableEntry> 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);
}
/**

src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfSLImageRenderer.java → src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfImageRenderer.java 查看文件

@@ -31,20 +31,25 @@ 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.sl.usermodel.PictureData.PictureType;
import org.apache.poi.util.Units;

/**
* Helper class which is instantiated by {@link DrawPictureShape}
* via reflection
*/
public class HwmfSLImageRenderer implements ImageRenderer {
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 (!PictureData.PictureType.WMF.contentType.equals(contentType)) {
if (!PictureType.WMF.contentType.equals(contentType)) {
throw new IOException("Invalid picture type");
}
image = new HwmfPicture(data);
@@ -52,7 +57,7 @@ public class HwmfSLImageRenderer implements ImageRenderer {

@Override
public void loadImage(byte[] data, String contentType) throws IOException {
if (!PictureData.PictureType.WMF.contentType.equals(contentType)) {
if (!PictureType.WMF.contentType.equals(contentType)) {
throw new IOException("Invalid picture type");
}
image = new HwmfPicture(new ByteArrayInputStream(data));

+ 30
- 28
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.

+ 14
- 1
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) {

+ 57
- 9
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<HemfRecord> 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();

+ 0
- 1
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));

正在加载...
取消
保存