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);
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;
* 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);
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;
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);
}
return new DrawBackground(shape);
}
+ @SuppressWarnings("WeakerAccess")
public DrawTextFragment getTextFragment(TextLayout layout, AttributedString str) {
return new DrawTextFragment(layout, str);
}
}
- /**
- * 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
*
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);
}
/**
* 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();
}
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();
* <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) {
@Override
public void drawContent(Graphics2D graphics) {
- DrawFactory.getInstance(graphics).fixFonts(graphics);
-
TextShape<?,?> s = getShape();
Rectangle2D anchor = DrawShape.getAnchor(graphics, s);
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;
* </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
*
BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = img.createGraphics();
- DrawFactory.getInstance(graphics).fixFonts(graphics);
// default rendering options
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
graphics.setRenderingHint(Drawable.FONT_FALLBACK, fallbackMap);
graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
- DrawFactory.getInstance(graphics).fixFonts(graphics);
-
tb.resizeToFitText(graphics);
graphics.dispose();
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);
--- /dev/null
+org.apache.poi.sl.draw.BitmapImageRenderer
\ No newline at end of file
--- /dev/null
+org.apache.poi.hwmf.draw.HwmfImageRenderer
+org.apache.poi.hemf.draw.HemfImageRenderer
\ No newline at end of file
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;
}
}
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
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());
+ }
}
--- /dev/null
+/* ====================================================================
+ 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;
+ }
+ }
+
+}
--- /dev/null
+/* ====================================================================
+ 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);
+}
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;
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;
/** 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
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]);
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
*/
* 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
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
*/
}
@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;
}
}
}
@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;
}
}
/**
* 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
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();
+ }
}
/**
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);
+ }
}
/**
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);
+ }
}
/**
@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
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 {
}
}
+ /**
+ * 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() {
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() {
}
}
+ /**
+ * 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() {
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() {
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() {
}
}
+ /**
+ * 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() {
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());
}
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;
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;
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);
* 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
// 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);
+ }
+ }
}
}
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
", offPixelFormat=" + offPixelFormat +
", bOpenGL=" + bOpenGL +
", hasExtension2=" + hasExtension2 +
- ", micrometersX=" + getMicrometersX() +
- ", micrometersY=" + getMicrometersY() +
+ ", deviceDimension=" + deviceDimension +
+ ", microDimension=" + microDimension +
+ ", milliDimension=" + milliDimension +
'}';
}
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;
@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;
}
// 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
//
// 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);
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;
+ }
+ }
}
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;
*/
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);
+ }
+ }
}
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),
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;
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
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);
+ }
+ }
+
}
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;
}
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;
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;
+ }
}
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;
this.graphicsCtx = graphicsCtx;
this.bbox = (Rectangle2D)bbox.clone();
this.initialAT = graphicsCtx.getTransform();
- DrawFactory.getInstance(graphicsCtx).fixFonts(graphicsCtx);
}
public HwmfDrawProperties getProperties() {
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));
}
* 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);
}
/**
--- /dev/null
+/* ====================================================================
+ 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;
+ }
+ }
+}
+++ /dev/null
-/* ====================================================================
- 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;
- }
- }
-}
@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 {
}
}
- protected Path2D getShape(HwmfGraphics ctx) {
- return (Path2D)poly.clone();
- }
-
/**
* @return true, if the shape should be filled
*/
@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();
}
}
- if (isFill()) {
- ctx.fill(area);
- } else {
- ctx.draw(area);
- }
+ return area;
}
-
/**
* @return true, if the shape should be filled
*/
@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);
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);
}
}
}
}
- 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.
/** +++++ - 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) {
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;
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();
@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();
@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;
@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;
@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) {
@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();
} 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));