From da099f37a60f144471d267004f25f954d6645913 Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Sun, 2 Sep 2018 10:07:27 +0000 Subject: [PATCH 01/21] branch to work on EMF parser/handling git-svn-id: https://svn.apache.org/repos/asf/poi/branches/hemf@1839876 13f79535-47bb-0310-9956-ffa450edef68 From 33a4b2f3fbc4b83e844dd0e33becbc03635afd67 Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Fri, 14 Sep 2018 21:35:43 +0000 Subject: [PATCH 02/21] Buffered LittleEndianInputStream git-svn-id: https://svn.apache.org/repos/asf/poi/branches/hemf@1840955 13f79535-47bb-0310-9956-ffa450edef68 --- .../org/apache/poi/hssf/dev/BiffViewer.java | 12 ++-- .../poi/hssf/record/RecordInputStream.java | 45 ++++++-------- src/java/org/apache/poi/util/IOUtils.java | 31 ++++++++-- .../poi/util/LittleEndianInputStream.java | 59 +++++++++++++++++-- 4 files changed, 102 insertions(+), 45 deletions(-) diff --git a/src/java/org/apache/poi/hssf/dev/BiffViewer.java b/src/java/org/apache/poi/hssf/dev/BiffViewer.java index cb17d84b56..631be8fd2d 100644 --- a/src/java/org/apache/poi/hssf/dev/BiffViewer.java +++ b/src/java/org/apache/poi/hssf/dev/BiffViewer.java @@ -527,20 +527,16 @@ public final class BiffViewer { } @Override public int read(byte[] b, int off, int len) throws IOException { + if (b == null || off < 0 || len < 0 || b.length < off+len) { + throw new IllegalArgumentException(); + } if (_currentPos >= _currentSize) { fillNextBuffer(); } if (_currentPos >= _currentSize) { return -1; } - int availSize = _currentSize - _currentPos; - int result; - if (len > availSize) { - System.err.println("Unexpected request to read past end of current biff record"); - result = availSize; - } else { - result = len; - } + final int result = Math.min(len, _currentSize - _currentPos); System.arraycopy(_data, _currentPos, b, off, result); _currentPos += result; _overallStreamPos += result; diff --git a/src/java/org/apache/poi/hssf/record/RecordInputStream.java b/src/java/org/apache/poi/hssf/record/RecordInputStream.java index 4f5f589032..3c9b977c79 100644 --- a/src/java/org/apache/poi/hssf/record/RecordInputStream.java +++ b/src/java/org/apache/poi/hssf/record/RecordInputStream.java @@ -105,8 +105,8 @@ public final class RecordInputStream implements LittleEndianInput { private final LittleEndianInput _lei; - public SimpleHeaderInput(InputStream in) { - _lei = getLEI(in); + private SimpleHeaderInput(LittleEndianInput lei) { + _lei = lei; } @Override public int available() { @@ -128,8 +128,12 @@ public final class RecordInputStream implements LittleEndianInput { public RecordInputStream(InputStream in, EncryptionInfo key, int initialOffset) throws RecordFormatException { if (key == null) { - _dataInput = getLEI(in); - _bhi = new SimpleHeaderInput(in); + _dataInput = (in instanceof LittleEndianInput) + // accessing directly is an optimisation + ? (LittleEndianInput)in + // less optimal, but should work OK just the same. Often occurs in junit tests. + : new LittleEndianInputStream(in); + _bhi = new SimpleHeaderInput(_dataInput); } else { Biff8DecryptingStream bds = new Biff8DecryptingStream(in, initialOffset, key); _dataInput = bds; @@ -138,15 +142,6 @@ public final class RecordInputStream implements LittleEndianInput { _nextSid = readNextSid(); } - static LittleEndianInput getLEI(InputStream is) { - if (is instanceof LittleEndianInput) { - // accessing directly is an optimisation - return (LittleEndianInput) is; - } - // less optimal, but should work OK just the same. Often occurs in junit tests. - return new LittleEndianInputStream(is); - } - /** * @return the number of bytes available in the current BIFF record * @see #remaining() @@ -194,11 +189,9 @@ public final class RecordInputStream implements LittleEndianInput { private int readNextSid() { int nAvailable = _bhi.available(); if (nAvailable < EOFRecord.ENCODED_SIZE) { - if (nAvailable > 0) { - // some scrap left over? - // ex45582-22397.xls has one extra byte after the last record - // Excel reads that file OK - } + // some scrap left over, if nAvailable > 0? + // ex45582-22397.xls has one extra byte after the last record + // Excel reads that file OK return INVALID_SID_VALUE; } int result = _bhi.readRecordSID(); @@ -302,17 +295,13 @@ public final class RecordInputStream implements LittleEndianInput { return _dataInput.readUShort(); } + /** + * + * @return a double - might return NaN + */ @Override public double readDouble() { - long valueLongBits = readLong(); - double result = Double.longBitsToDouble(valueLongBits); - if (Double.isNaN(result)) { - // YK: Excel doesn't write NaN but instead converts the cell type into {@link CellType#ERROR}. - // HSSF prior to version 3.7 had a bug: it could write Double.NaN but could not read such a file back. - // This behavior was fixed in POI-3.7. - //throw new RuntimeException("Did not expect to read NaN"); // (Because Excel typically doesn't write NaN) - } - return result; + return Double.longBitsToDouble(readLong()); } public void readPlain(byte[] buf, int off, int len) { @@ -329,7 +318,7 @@ public final class RecordInputStream implements LittleEndianInput { readFully(buf, off, len, false); } - protected void readFully(byte[] buf, int off, int len, boolean isPlain) { + private void readFully(byte[] buf, int off, int len, boolean isPlain) { int origLen = len; if (buf == null) { throw new NullPointerException(); diff --git a/src/java/org/apache/poi/util/IOUtils.java b/src/java/org/apache/poi/util/IOUtils.java index 839663cda3..035c6ee762 100644 --- a/src/java/org/apache/poi/util/IOUtils.java +++ b/src/java/org/apache/poi/util/IOUtils.java @@ -50,6 +50,7 @@ public final class IOUtils { * @param maxOverride The number of bytes that should be possible to be allocated in one step. * @since 4.0.0 */ + @SuppressWarnings("unused") public static void setByteArrayMaxOverride(int maxOverride) { BYTE_ARRAY_MAX_OVERRIDE = maxOverride; } @@ -395,13 +396,35 @@ public final class IOUtils { * @throws IOException If copying the data fails. */ public static long copy(InputStream inp, OutputStream out) throws IOException { + return copy(inp, out, -1); + } + + /** + * Copies all the data from the given InputStream to the OutputStream. It + * leaves both streams open, so you will still need to close them once done. + * + * @param inp The {@link InputStream} which provides the data + * @param out The {@link OutputStream} to write the data to + * @param limit limit the copied bytes - use {@code -1} for no limit + * @return the amount of bytes copied + * + * @throws IOException If copying the data fails. + */ + public static long copy(InputStream inp, OutputStream out, long limit) throws IOException { final byte[] buff = new byte[4096]; long totalCount = 0; - for (int count; (count = inp.read(buff)) != -1; totalCount += count) { - if (count > 0) { - out.write(buff, 0, count); + int readBytes = -1; + do { + int todoBytes = (int)((limit < 0) ? buff.length : Math.min(limit-totalCount, buff.length)); + if (todoBytes > 0) { + readBytes = inp.read(buff, 0, todoBytes); + if (readBytes > 0) { + out.write(buff, 0, readBytes); + totalCount += readBytes; + } } - } + } while (readBytes >= 0 && (limit == -1 || totalCount < limit)); + return totalCount; } diff --git a/src/java/org/apache/poi/util/LittleEndianInputStream.java b/src/java/org/apache/poi/util/LittleEndianInputStream.java index 886720f3f1..6199c4dd12 100644 --- a/src/java/org/apache/poi/util/LittleEndianInputStream.java +++ b/src/java/org/apache/poi/util/LittleEndianInputStream.java @@ -17,6 +17,7 @@ package org.apache.poi.util; +import java.io.BufferedInputStream; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; @@ -29,12 +30,16 @@ import java.io.InputStream; */ public class LittleEndianInputStream extends FilterInputStream implements LittleEndianInput { + private static final int BUFFERED_SIZE = 8096; + private static final int EOF = -1; + private int readIndex = 0; + private int markIndex = -1; public LittleEndianInputStream(InputStream is) { - super(is); + super(is.markSupported() ? is : new BufferedInputStream(is, BUFFERED_SIZE)); } - + @Override @SuppressForbidden("just delegating") public int available() { @@ -60,7 +65,18 @@ public class LittleEndianInputStream extends FilterInputStream implements Little } return LittleEndian.getUByte(buf); } - + + /** + * get a float value, reads it in little endian format + * then converts the resulting revolting IEEE 754 (curse them) floating + * point number to a happy java float + * + * @return the float (32-bit) value + */ + public float readFloat() { + return Float.intBitsToFloat( readInt() ); + } + @Override public double readDouble() { return Double.longBitsToDouble(readLong()); @@ -137,14 +153,42 @@ public class LittleEndianInputStream extends FilterInputStream implements Little } } - //Makes repeated calls to super.read() until length is read or EOF is reached + @Override + public int read(byte[] b, int off, int len) throws IOException { + int readBytes = super.read(b, off, len); + readIndex += readBytes; + return readBytes; + } + + @Override + public synchronized void mark(int readlimit) { + super.mark(readlimit); + markIndex = readIndex; + } + + @Override + public synchronized void reset() throws IOException { + super.reset(); + if (markIndex > -1) { + readIndex = markIndex; + markIndex = -1; + } + } + + public int getReadIndex() { + return readIndex; + } + + + + //Makes repeated calls to super.read() until length is read or EOF is reached private int _read(byte[] buffer, int offset, int length) throws IOException { //lifted directly from org.apache.commons.io.IOUtils 2.4 int remaining = length; while (remaining > 0) { int location = length - remaining; int count = read(buffer, offset + location, remaining); - if (EOF == count) { // EOF + if (EOF == count) { break; } remaining -= count; @@ -157,4 +201,9 @@ public class LittleEndianInputStream extends FilterInputStream implements Little public void readPlain(byte[] buf, int off, int len) { readFully(buf, off, len); } + + + public void skipFully(int len) throws IOException { + IOUtils.skipFully(this, len); + } } From a6a55a6ca514f9e76a784aadbec0d6d774ba8b9a Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Fri, 14 Sep 2018 21:37:37 +0000 Subject: [PATCH 03/21] #60656 - Support export file that contains emf and render it correctly git-svn-id: https://svn.apache.org/repos/asf/poi/branches/hemf@1840956 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/poi/util/Dimension2DDouble.java} | 14 +- .../apache/poi/xdgf/usermodel/XDGFPage.java | 8 +- .../org/apache/poi/xdgf/util/VsdxToPng.java | 4 +- .../HemfDrawProperties.java} | 17 +- .../HemfGraphics.java} | 16 +- .../poi/hemf/extractor/HemfExtractor.java | 115 --- .../hemfplus/record/HemfPlusRecordType.java | 97 --- .../poi/hemf/record/AbstractHemfComment.java | 39 - .../poi/hemf/record/HemfCommentEMFPlus.java | 111 --- .../poi/hemf/record/HemfCommentPublic.java | 177 ---- .../poi/hemf/record/HemfCommentRecord.java | 154 ---- .../poi/hemf/record/HemfRecordType.java | 159 ---- .../org/apache/poi/hemf/record/HemfText.java | 262 ------ .../poi/hemf/record/emf/HemfComment.java | 444 ++++++++++ .../apache/poi/hemf/record/emf/HemfDraw.java | 783 ++++++++++++++++++ .../apache/poi/hemf/record/emf/HemfFill.java | 620 ++++++++++++++ .../apache/poi/hemf/record/emf/HemfFont.java | 464 +++++++++++ .../poi/hemf/record/{ => emf}/HemfHeader.java | 102 ++- .../apache/poi/hemf/record/emf/HemfMisc.java | 447 ++++++++++ .../poi/hemf/record/emf/HemfPalette.java | 138 +++ .../poi/hemf/record/{ => emf}/HemfRecord.java | 15 +- .../hemf/record/emf/HemfRecordIterator.java | 82 ++ .../poi/hemf/record/emf/HemfRecordType.java | 165 ++++ .../apache/poi/hemf/record/emf/HemfText.java | 317 +++++++ .../poi/hemf/record/emf/HemfWindowing.java | 200 +++++ .../{ => emf}/UnimplementedHemfRecord.java | 6 +- .../emfplus}/HemfPlusHeader.java | 25 +- .../emfplus}/HemfPlusRecord.java | 22 +- .../emfplus/HemfPlusRecordIterator.java | 98 +++ .../record/emfplus/HemfPlusRecordType.java | 100 +++ .../emfplus}/UnimplementedHemfPlusRecord.java | 16 +- .../poi/hemf/usermodel/HemfPicture.java | 77 ++ .../poi/hwmf/draw/HwmfDrawProperties.java | 58 +- .../poi/hwmf/record/HwmfBrushStyle.java | 2 +- .../org/apache/poi/hwmf/record/HwmfDraw.java | 372 ++++----- .../apache/poi/hwmf/record/HwmfEscape.java | 2 +- .../org/apache/poi/hwmf/record/HwmfFill.java | 551 +++--------- .../org/apache/poi/hwmf/record/HwmfFont.java | 126 ++- .../poi/hwmf/record/HwmfHatchStyle.java | 2 +- .../apache/poi/hwmf/record/HwmfMapMode.java | 2 +- .../org/apache/poi/hwmf/record/HwmfMisc.java | 150 ++-- .../apache/poi/hwmf/record/HwmfPalette.java | 33 +- .../apache/poi/hwmf/record/HwmfPenStyle.java | 19 +- .../apache/poi/hwmf/record/HwmfRecord.java | 2 +- .../poi/hwmf/record/HwmfRecordType.java | 146 ++-- .../org/apache/poi/hwmf/record/HwmfText.java | 214 +++-- .../apache/poi/hwmf/record/HwmfWindowing.java | 202 ++--- .../poi/hwmf/usermodel/HwmfPicture.java | 14 +- .../poi/hemf/extractor/HemfExtractorTest.java | 198 ----- .../extractor/HemfPlusExtractorTest.java | 32 +- .../poi/hemf/usermodel/HemfPictureTest.java | 201 +++++ .../org/apache/poi/hwmf/TestHwmfParsing.java | 8 +- 52 files changed, 5099 insertions(+), 2529 deletions(-) rename src/{ooxml/java/org/apache/poi/xdgf/geom/Dimension2dDouble.java => java/org/apache/poi/util/Dimension2DDouble.java} (83%) rename src/scratchpad/src/org/apache/poi/hemf/{record/HemfCommentEMFSpool.java => draw/HemfDrawProperties.java} (77%) rename src/scratchpad/src/org/apache/poi/hemf/{record/HemfComment.java => draw/HemfGraphics.java} (76%) delete mode 100644 src/scratchpad/src/org/apache/poi/hemf/extractor/HemfExtractor.java delete mode 100644 src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusRecordType.java delete mode 100644 src/scratchpad/src/org/apache/poi/hemf/record/AbstractHemfComment.java delete mode 100644 src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentEMFPlus.java delete mode 100644 src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentPublic.java delete mode 100644 src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentRecord.java delete mode 100644 src/scratchpad/src/org/apache/poi/hemf/record/HemfRecordType.java delete mode 100644 src/scratchpad/src/org/apache/poi/hemf/record/HemfText.java create mode 100644 src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java create mode 100644 src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java create mode 100644 src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java create mode 100644 src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFont.java rename src/scratchpad/src/org/apache/poi/hemf/record/{ => emf}/HemfHeader.java (53%) create mode 100644 src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java create mode 100644 src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfPalette.java rename src/scratchpad/src/org/apache/poi/hemf/record/{ => emf}/HemfRecord.java (73%) create mode 100644 src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordIterator.java create mode 100644 src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java create mode 100644 src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java create mode 100644 src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java rename src/scratchpad/src/org/apache/poi/hemf/record/{ => emf}/UnimplementedHemfRecord.java (89%) rename src/scratchpad/src/org/apache/poi/hemf/{hemfplus/record => record/emfplus}/HemfPlusHeader.java (75%) rename src/scratchpad/src/org/apache/poi/hemf/{hemfplus/record => record/emfplus}/HemfPlusRecord.java (65%) create mode 100644 src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecordIterator.java create mode 100644 src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecordType.java rename src/scratchpad/src/org/apache/poi/hemf/{hemfplus/record => record/emfplus}/UnimplementedHemfPlusRecord.java (76%) create mode 100644 src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java delete mode 100644 src/scratchpad/testcases/org/apache/poi/hemf/extractor/HemfExtractorTest.java create mode 100644 src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java diff --git a/src/ooxml/java/org/apache/poi/xdgf/geom/Dimension2dDouble.java b/src/java/org/apache/poi/util/Dimension2DDouble.java similarity index 83% rename from src/ooxml/java/org/apache/poi/xdgf/geom/Dimension2dDouble.java rename to src/java/org/apache/poi/util/Dimension2DDouble.java index 94b887e04e..d08906433c 100644 --- a/src/ooxml/java/org/apache/poi/xdgf/geom/Dimension2dDouble.java +++ b/src/java/org/apache/poi/util/Dimension2DDouble.java @@ -15,21 +15,21 @@ limitations under the License. ==================================================================== */ -package org.apache.poi.xdgf.geom; +package org.apache.poi.util; import java.awt.geom.Dimension2D; -public class Dimension2dDouble extends Dimension2D { +public class Dimension2DDouble extends Dimension2D { double width; double height; - public Dimension2dDouble() { + public Dimension2DDouble() { width = 0d; height = 0d; } - public Dimension2dDouble(double width, double height) { + public Dimension2DDouble(double width, double height) { this.width = width; this.height = height; } @@ -52,8 +52,8 @@ public class Dimension2dDouble extends Dimension2D { @Override public boolean equals(Object obj) { - if (obj instanceof Dimension2dDouble) { - Dimension2dDouble other = (Dimension2dDouble) obj; + if (obj instanceof Dimension2DDouble) { + Dimension2DDouble other = (Dimension2DDouble) obj; return width == other.width && height == other.height; } @@ -68,6 +68,6 @@ public class Dimension2dDouble extends Dimension2D { @Override public String toString() { - return "Dimension2dDouble[" + width + ", " + height + "]"; + return "Dimension2DDouble[" + width + ", " + height + "]"; } } diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFPage.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFPage.java index 43559203fe..c094a6edbf 100644 --- a/src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFPage.java +++ b/src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFPage.java @@ -22,7 +22,7 @@ import java.awt.geom.Rectangle2D; import org.apache.poi.ooxml.POIXMLException; import org.apache.poi.util.Internal; -import org.apache.poi.xdgf.geom.Dimension2dDouble; +import org.apache.poi.util.Dimension2DDouble; import com.microsoft.schemas.office.visio.x2012.main.PageType; @@ -75,14 +75,14 @@ public class XDGFPage { /** * @return width/height of page */ - public Dimension2dDouble getPageSize() { + public Dimension2DDouble getPageSize() { XDGFCell w = _pageSheet.getCell("PageWidth"); XDGFCell h = _pageSheet.getCell("PageHeight"); if (w == null || h == null) throw new POIXMLException("Cannot determine page size"); - return new Dimension2dDouble(Double.parseDouble(w.getValue()), + return new Dimension2DDouble(Double.parseDouble(w.getValue()), Double.parseDouble(h.getValue())); } @@ -109,7 +109,7 @@ public class XDGFPage { * @return bounding box of page */ public Rectangle2D getBoundingBox() { - Dimension2dDouble sz = getPageSize(); + Dimension2DDouble sz = getPageSize(); Point2D.Double offset = getPageOffset(); return new Rectangle2D.Double(-offset.getX(), -offset.getY(), diff --git a/src/ooxml/java/org/apache/poi/xdgf/util/VsdxToPng.java b/src/ooxml/java/org/apache/poi/xdgf/util/VsdxToPng.java index 7706ca19e3..c0ddf5fb0b 100644 --- a/src/ooxml/java/org/apache/poi/xdgf/util/VsdxToPng.java +++ b/src/ooxml/java/org/apache/poi/xdgf/util/VsdxToPng.java @@ -25,7 +25,7 @@ import java.io.*; import javax.imageio.ImageIO; -import org.apache.poi.xdgf.geom.Dimension2dDouble; +import org.apache.poi.util.Dimension2DDouble; import org.apache.poi.xdgf.usermodel.XDGFPage; import org.apache.poi.xdgf.usermodel.XmlVisioDocument; import org.apache.poi.xdgf.usermodel.shape.ShapeDebuggerRenderer; @@ -58,7 +58,7 @@ public class VsdxToPng { public static void renderToPng(XDGFPage page, File outFile, double scale, ShapeRenderer renderer) throws IOException { - Dimension2dDouble sz = page.getPageSize(); + Dimension2DDouble sz = page.getPageSize(); int width = (int) (scale * sz.getWidth()); int height = (int) (scale * sz.getHeight()); diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentEMFSpool.java b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java similarity index 77% rename from src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentEMFSpool.java rename to src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java index 009974d10b..f2e1957a28 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentEMFSpool.java +++ b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java @@ -15,17 +15,16 @@ limitations under the License. ==================================================================== */ -package org.apache.poi.hemf.record; +package org.apache.poi.hemf.draw; -import org.apache.poi.util.Internal; +import org.apache.poi.hwmf.draw.HwmfDrawProperties; -/** - * Not yet implemented - */ -@Internal -public class HemfCommentEMFSpool extends AbstractHemfComment { +public class HemfDrawProperties extends HwmfDrawProperties { - public HemfCommentEMFSpool(byte[] rawBytes) { - super(rawBytes); + public HemfDrawProperties() { + } + + public HemfDrawProperties(HemfDrawProperties other) { + super(other); } } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/HemfComment.java b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java similarity index 76% rename from src/scratchpad/src/org/apache/poi/hemf/record/HemfComment.java rename to src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java index 5d45927bb9..89a764af14 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/HemfComment.java +++ b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java @@ -15,17 +15,15 @@ limitations under the License. ==================================================================== */ -package org.apache.poi.hemf.record; +package org.apache.poi.hemf.draw; -import org.apache.poi.util.Internal; +import java.awt.Graphics2D; +import java.awt.geom.Rectangle2D; -/** - * Contains arbitrary data - */ -@Internal -public class HemfComment extends AbstractHemfComment { +import org.apache.poi.hwmf.draw.HwmfGraphics; - public HemfComment(byte[] rawBytes) { - super(rawBytes); +public class HemfGraphics extends HwmfGraphics { + public HemfGraphics(Graphics2D graphicsCtx, Rectangle2D bbox) { + super(graphicsCtx,bbox); } } diff --git a/src/scratchpad/src/org/apache/poi/hemf/extractor/HemfExtractor.java b/src/scratchpad/src/org/apache/poi/hemf/extractor/HemfExtractor.java deleted file mode 100644 index ab4add4eba..0000000000 --- a/src/scratchpad/src/org/apache/poi/hemf/extractor/HemfExtractor.java +++ /dev/null @@ -1,115 +0,0 @@ -/* ==================================================================== - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -==================================================================== */ - -package org.apache.poi.hemf.extractor; - - -import java.io.IOException; -import java.io.InputStream; -import java.util.Iterator; - -import org.apache.poi.hemf.record.HemfHeader; -import org.apache.poi.hemf.record.HemfRecord; -import org.apache.poi.hemf.record.HemfRecordType; -import org.apache.poi.util.Internal; -import org.apache.poi.util.LittleEndianInputStream; -import org.apache.poi.util.RecordFormatException; - -/** - * Read-only EMF extractor. Lots remain - */ -@Internal -public class HemfExtractor implements Iterable { - - private HemfHeader header; - private final LittleEndianInputStream stream; - - public HemfExtractor(InputStream is) throws IOException { - stream = new LittleEndianInputStream(is); - header = new HemfHeader(); - long recordId = stream.readUInt(); - long recordSize = stream.readUInt(); - - header = new HemfHeader(); - header.init(stream, recordId, recordSize-8); - } - - @Override - public Iterator iterator() { - return new HemfRecordIterator(); - } - - public HemfHeader getHeader() { - return header; - } - - private class HemfRecordIterator implements Iterator { - - private HemfRecord currentRecord; - - HemfRecordIterator() { - //queue the first non-header record - currentRecord = _next(); - } - - @Override - public boolean hasNext() { - return currentRecord != null; - } - - @Override - public HemfRecord next() { - HemfRecord toReturn = currentRecord; - currentRecord = _next(); - return toReturn; - } - - private HemfRecord _next() { - if (currentRecord != null && currentRecord.getRecordType().equals(HemfRecordType.eof)) { - return null; - } - long recordId = stream.readUInt(); - long recordSize = stream.readUInt(); - - HemfRecord record = null; - HemfRecordType type = HemfRecordType.getById(recordId); - if (type == null) { - throw new RuntimeException("Undefined record of type:"+recordId); - } - try { - record = type.clazz.newInstance(); - } catch (InstantiationException e) { - throw new RuntimeException(e); - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } - try { - record.init(stream, recordId, recordSize-8); - } catch (IOException e) { - throw new RecordFormatException(e); - } - - return record; - } - - @Override - public void remove() { - throw new UnsupportedOperationException("Remove not supported"); - } - - } -} diff --git a/src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusRecordType.java b/src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusRecordType.java deleted file mode 100644 index 70837628e1..0000000000 --- a/src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusRecordType.java +++ /dev/null @@ -1,97 +0,0 @@ -/* ==================================================================== - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -==================================================================== */ - -package org.apache.poi.hemf.hemfplus.record; - -import org.apache.poi.util.Internal; - -@Internal -public enum HemfPlusRecordType { - header(0x4001, HemfPlusHeader.class), - endOfFile(0x4002, UnimplementedHemfPlusRecord.class), - comment(0x4003, UnimplementedHemfPlusRecord.class), - getDC(0x4004, UnimplementedHemfPlusRecord.class), - multiFormatStart(0x4005, UnimplementedHemfPlusRecord.class), - multiFormatSection(0x4006, UnimplementedHemfPlusRecord.class), - multiFormatEnd(0x4007, UnimplementedHemfPlusRecord.class), - object(0x4008, UnimplementedHemfPlusRecord.class), - clear(0x4009, UnimplementedHemfPlusRecord.class), - fillRects(0x400A, UnimplementedHemfPlusRecord.class), - drawRects(0x400B, UnimplementedHemfPlusRecord.class), - fillPolygon(0x400C, UnimplementedHemfPlusRecord.class), - drawLines(0x400D, UnimplementedHemfPlusRecord.class), - fillEllipse(0x400E, UnimplementedHemfPlusRecord.class), - drawEllipse(0x400F, UnimplementedHemfPlusRecord.class), - fillPie(0x4010, UnimplementedHemfPlusRecord.class), - drawPie(0x4011, UnimplementedHemfPlusRecord.class), - drawArc(0x4012, UnimplementedHemfPlusRecord.class), - fillRegion(0x4013, UnimplementedHemfPlusRecord.class), - fillPath(0x4014, UnimplementedHemfPlusRecord.class), - drawPath(0x4015, UnimplementedHemfPlusRecord.class), - fillClosedCurve(0x4016, UnimplementedHemfPlusRecord.class), - drawClosedCurve(0x4017, UnimplementedHemfPlusRecord.class), - drawCurve(0x4018, UnimplementedHemfPlusRecord.class), - drawBeziers(0x4019, UnimplementedHemfPlusRecord.class), - drawImage(0x401A, UnimplementedHemfPlusRecord.class), - drawImagePoints(0x401B, UnimplementedHemfPlusRecord.class), - drawString(0x401C, UnimplementedHemfPlusRecord.class), - setRenderingOrigin(0x401D, UnimplementedHemfPlusRecord.class), - setAntiAliasMode(0x401E, UnimplementedHemfPlusRecord.class), - setTextRenderingHint(0x401F, UnimplementedHemfPlusRecord.class), - setTextContrast(0x4020, UnimplementedHemfPlusRecord.class), - setInterpolationMode(0x4021, UnimplementedHemfPlusRecord.class), - setPixelOffsetMode(0x4022, UnimplementedHemfPlusRecord.class), - setComositingMode(0x4023, UnimplementedHemfPlusRecord.class), - setCompositingQuality(0x4024, UnimplementedHemfPlusRecord.class), - save(0x4025, UnimplementedHemfPlusRecord.class), - restore(0x4026, UnimplementedHemfPlusRecord.class), - beginContainer(0x4027, UnimplementedHemfPlusRecord.class), - beginContainerNoParams(0x428, UnimplementedHemfPlusRecord.class), - endContainer(0x4029, UnimplementedHemfPlusRecord.class), - setWorldTransform(0x402A, UnimplementedHemfPlusRecord.class), - resetWorldTransform(0x402B, UnimplementedHemfPlusRecord.class), - multiplyWorldTransform(0x402C, UnimplementedHemfPlusRecord.class), - translateWorldTransform(0x402D, UnimplementedHemfPlusRecord.class), - scaleWorldTransform(0x402E, UnimplementedHemfPlusRecord.class), - rotateWorldTransform(0x402F, UnimplementedHemfPlusRecord.class), - setPageTransform(0x4030, UnimplementedHemfPlusRecord.class), - resetClip(0x4031, UnimplementedHemfPlusRecord.class), - setClipRect(0x4032, UnimplementedHemfPlusRecord.class), - setClipRegion(0x4033, UnimplementedHemfPlusRecord.class), - setClipPath(0x4034, UnimplementedHemfPlusRecord.class), - offsetClip(0x4035, UnimplementedHemfPlusRecord.class), - drawDriverstring(0x4036, UnimplementedHemfPlusRecord.class), - strokeFillPath(0x4037, UnimplementedHemfPlusRecord.class), - serializableObject(0x4038, UnimplementedHemfPlusRecord.class), - setTSGraphics(0x4039, UnimplementedHemfPlusRecord.class), - setTSClip(0x403A, UnimplementedHemfPlusRecord.class); - - public final long id; - public final Class clazz; - - HemfPlusRecordType(long id, Class clazz) { - this.id = id; - this.clazz = clazz; - } - - public static HemfPlusRecordType getById(long id) { - for (HemfPlusRecordType wrt : values()) { - if (wrt.id == id) return wrt; - } - return null; - } -} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/AbstractHemfComment.java b/src/scratchpad/src/org/apache/poi/hemf/record/AbstractHemfComment.java deleted file mode 100644 index 7ffff6b016..0000000000 --- a/src/scratchpad/src/org/apache/poi/hemf/record/AbstractHemfComment.java +++ /dev/null @@ -1,39 +0,0 @@ -/* ==================================================================== - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -==================================================================== */ - -package org.apache.poi.hemf.record; - -import org.apache.poi.util.Internal; - -/** - * Syntactic utility to allow for four different - * comment classes - */ -@Internal -public abstract class AbstractHemfComment { - - private final byte[] rawBytes; - - public AbstractHemfComment(byte[] rawBytes) { - this.rawBytes = rawBytes; - } - - public byte[] getRawBytes() { - return rawBytes; - } - -} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentEMFPlus.java b/src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentEMFPlus.java deleted file mode 100644 index d5d4c10e84..0000000000 --- a/src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentEMFPlus.java +++ /dev/null @@ -1,111 +0,0 @@ -/* ==================================================================== - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -==================================================================== */ - -package org.apache.poi.hemf.record; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import org.apache.poi.hemf.hemfplus.record.HemfPlusRecord; -import org.apache.poi.hemf.hemfplus.record.HemfPlusRecordType; -import org.apache.poi.util.IOUtils; -import org.apache.poi.util.Internal; -import org.apache.poi.util.LittleEndian; -import org.apache.poi.util.RecordFormatException; - -/** - * An HemfCommentEMFPlus may contain one or more EMFPlus records - */ -@Internal -public class HemfCommentEMFPlus extends AbstractHemfComment { - - private static final int MAX_RECORD_LENGTH = 1_000_000; - - - long dataSize; - public HemfCommentEMFPlus(byte[] rawBytes) { - //these rawBytes contain only the EMFPlusRecord(s?) - //the EmfComment type, size, datasize and comment identifier have all been stripped. - //The EmfPlus type, flags, size, data size should start at rawBytes[0] - super(rawBytes); - - } - - public List getRecords() { - return HemfPlusParser.parse(getRawBytes()); - } - - private static class HemfPlusParser { - - public static List parse(byte[] bytes) { - List records = new ArrayList<>(); - int offset = 0; - while (offset < bytes.length) { - if (offset + 12 > bytes.length) { - //if header will go beyond bytes, stop now - //TODO: log or throw - break; - } - int type = LittleEndian.getUShort(bytes, offset); offset += LittleEndian.SHORT_SIZE; - int flags = LittleEndian.getUShort(bytes, offset); offset += LittleEndian.SHORT_SIZE; - long sizeLong = LittleEndian.getUInt(bytes, offset); offset += LittleEndian.INT_SIZE; - if (sizeLong >= Integer.MAX_VALUE) { - throw new RecordFormatException("size of emf record >= Integer.MAX_VALUE"); - } - int size = (int)sizeLong; - long dataSizeLong = LittleEndian.getUInt(bytes, offset); offset += LittleEndian.INT_SIZE; - if (dataSizeLong >= Integer.MAX_VALUE) { - throw new RuntimeException("data size of emfplus record cannot be >= Integer.MAX_VALUE"); - } - int dataSize = (int)dataSizeLong; - if (dataSize + offset > bytes.length) { - //TODO: log or throw? - break; - } - HemfPlusRecord record = buildRecord(type, flags, dataSize, offset, bytes); - records.add(record); - offset += dataSize; - } - return records; - } - - private static HemfPlusRecord buildRecord(int recordId, int flags, int size, int offset, byte[] bytes) { - HemfPlusRecord record = null; - HemfPlusRecordType type = HemfPlusRecordType.getById(recordId); - if (type == null) { - throw new RuntimeException("Undefined record of type:"+recordId); - } - try { - record = type.clazz.newInstance(); - } catch (InstantiationException e) { - throw new RuntimeException(e); - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } - byte[] dataBytes = IOUtils.safelyAllocate(size, MAX_RECORD_LENGTH); - System.arraycopy(bytes, offset, dataBytes, 0, size); - try { - record.init(dataBytes, recordId, flags); - } catch (IOException e) { - throw new RuntimeException(e); - } - return record; - - } - } -} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentPublic.java b/src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentPublic.java deleted file mode 100644 index 5ca5c375ce..0000000000 --- a/src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentPublic.java +++ /dev/null @@ -1,177 +0,0 @@ -/* ==================================================================== - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -==================================================================== */ - -package org.apache.poi.hemf.record; - - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; - -import org.apache.poi.util.IOUtils; -import org.apache.poi.util.Internal; -import org.apache.poi.util.LittleEndian; -import org.apache.poi.util.LittleEndianConsts; -import org.apache.poi.util.RecordFormatException; - -/** - * Container class for four subtypes of HemfCommentPublic: BeginGroup, EndGroup, MultiFormats - * and Windows Metafile. - */ -@Internal -public class HemfCommentPublic { - - private static final int MAX_RECORD_LENGTH = 1_000_000; - - - /** - * Stub, to be implemented - */ - public static class BeginGroup extends AbstractHemfComment { - - public BeginGroup(byte[] rawBytes) { - super(rawBytes); - } - - } - - /** - * Stub, to be implemented - */ - public static class EndGroup extends AbstractHemfComment { - - public EndGroup(byte[] rawBytes) { - super(rawBytes); - } - } - - public static class MultiFormats extends AbstractHemfComment { - - public MultiFormats(byte[] rawBytes) { - super(rawBytes); - } - - /** - * - * @return a list of HemfMultFormatsData - */ - public List getData() { - - byte[] rawBytes = getRawBytes(); - //note that raw bytes includes the public comment identifier - int currentOffset = 4 + 16;//4 public comment identifier, 16 for outputrect - long countFormats = LittleEndian.getUInt(rawBytes, currentOffset); - currentOffset += LittleEndianConsts.INT_SIZE; - List emrFormatList = new ArrayList<>(); - for (long i = 0; i < countFormats; i++) { - emrFormatList.add(new EmrFormat(rawBytes, currentOffset)); - currentOffset += 4 * LittleEndianConsts.INT_SIZE; - } - List list = new ArrayList<>(); - for (EmrFormat emrFormat : emrFormatList) { - byte[] data = IOUtils.safelyAllocate(emrFormat.size, MAX_RECORD_LENGTH); - System.arraycopy(rawBytes, emrFormat.offset-4, data, 0, emrFormat.size); - list.add(new HemfMultiFormatsData(emrFormat.signature, emrFormat.version, data)); - } - return list; - } - - private static class EmrFormat { - long signature; - long version; - int size; - int offset; - - public EmrFormat(byte[] rawBytes, int currentOffset) { - signature = LittleEndian.getUInt(rawBytes, currentOffset); currentOffset += LittleEndianConsts.INT_SIZE; - version = LittleEndian.getUInt(rawBytes, currentOffset); currentOffset += LittleEndianConsts.INT_SIZE; - //spec says this must be a 32bit "aligned" typo for "signed"? - //realistically, this has to be an int... - size = LittleEndian.getInt(rawBytes, currentOffset); currentOffset += LittleEndianConsts.INT_SIZE; - //y, can be long, but realistically? - offset = LittleEndian.getInt(rawBytes, currentOffset); currentOffset += LittleEndianConsts.INT_SIZE; - if (size < 0) { - throw new RecordFormatException("size for emrformat must be > 0"); - } - if (offset < 0) { - throw new RecordFormatException("offset for emrformat must be > 0"); - } - } - } - } - - /** - * Stub, to be implemented - */ - public static class WindowsMetafile extends AbstractHemfComment { - - private final byte[] wmfBytes; - public WindowsMetafile(byte[] rawBytes) { - super(rawBytes); - int offset = LittleEndianConsts.INT_SIZE;//public comment identifier - int version = LittleEndian.getUShort(rawBytes, offset); offset += LittleEndianConsts.SHORT_SIZE; - int reserved = LittleEndian.getUShort(rawBytes, offset); offset += LittleEndianConsts.SHORT_SIZE; - offset += LittleEndianConsts.INT_SIZE; //checksum - offset += LittleEndianConsts.INT_SIZE; //flags - long winMetafileSizeLong = LittleEndian.getUInt(rawBytes, offset); offset += LittleEndianConsts.INT_SIZE; - if (winMetafileSizeLong == 0L) { - wmfBytes = new byte[0]; - return; - } - wmfBytes = IOUtils.safelyAllocate(winMetafileSizeLong, MAX_RECORD_LENGTH); - System.arraycopy(rawBytes, offset, wmfBytes, 0, wmfBytes.length); - } - - /** - * - * @return an InputStream for the embedded WMF file - */ - public InputStream getWmfInputStream() { - return new ByteArrayInputStream(wmfBytes); - } - } - - /** - * This encapulates a single record stored within - * a HemfCommentPublic.MultiFormats record. - */ - public static class HemfMultiFormatsData { - - long signature; - long version; - byte[] data; - - public HemfMultiFormatsData(long signature, long version, byte[] data) { - this.signature = signature; - this.version = version; - this.data = data; - } - - public long getSignature() { - return signature; - } - - public long getVersion() { - return version; - } - - public byte[] getData() { - return data; - } - } -} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentRecord.java b/src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentRecord.java deleted file mode 100644 index af8aa28572..0000000000 --- a/src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentRecord.java +++ /dev/null @@ -1,154 +0,0 @@ -/* ==================================================================== - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -==================================================================== */ - -package org.apache.poi.hemf.record; - - -import java.io.IOException; - -import org.apache.poi.util.IOUtils; -import org.apache.poi.util.Internal; -import org.apache.poi.util.LittleEndian; -import org.apache.poi.util.LittleEndianInputStream; -import org.apache.poi.util.RecordFormatException; - -/** - * This is the outer comment record that is recognized - * by the initial parse by {@link HemfRecordType#comment}. - * However, there are four types of comment: EMR_COMMENT, - * EMR_COMMENT_EMFPLUS, EMR_COMMENT_EMFSPOOL, and EMF_COMMENT_PUBLIC. - * To get the underlying comment, call {@link #getComment()}. - * - */ -@Internal -public class HemfCommentRecord implements HemfRecord { - private static final int MAX_RECORD_LENGTH = 1_000_000; - - public final static long COMMENT_EMFSPOOL = 0x00000000; - public final static long COMMENT_EMFPLUS = 0x2B464D45; - public final static long COMMENT_PUBLIC = 0x43494447; - - - private AbstractHemfComment comment; - @Override - public HemfRecordType getRecordType() { - return HemfRecordType.comment; - } - - @Override - public long init(LittleEndianInputStream leis, long recordId, long recordSize) throws IOException { - long dataSize = leis.readUInt(); recordSize -= LittleEndian.INT_SIZE; - - byte[] optionalCommentIndentifierBuffer = new byte[4]; - leis.readFully(optionalCommentIndentifierBuffer); - dataSize = dataSize-LittleEndian.INT_SIZE; //size minus the first int which could be a comment identifier - recordSize -= LittleEndian.INT_SIZE; - long optionalCommentIdentifier = LittleEndian.getInt(optionalCommentIndentifierBuffer) & 0x00FFFFFFFFL; - if (optionalCommentIdentifier == COMMENT_EMFSPOOL) { - comment = new HemfCommentEMFSpool(readToByteArray(leis, dataSize, recordSize)); - } else if (optionalCommentIdentifier == COMMENT_EMFPLUS) { - comment = new HemfCommentEMFPlus(readToByteArray(leis, dataSize, recordSize)); - } else if (optionalCommentIdentifier == COMMENT_PUBLIC) { - comment = CommentPublicParser.parse(readToByteArray(leis, dataSize, recordSize)); - } else { - comment = new HemfComment(readToByteArray(optionalCommentIndentifierBuffer, leis, dataSize, recordSize)); - } - - return recordSize; - } - - //this prepends the initial "int" which turned out NOT to be - //a signifier of emfplus, spool, public. - private byte[] readToByteArray(byte[] initialBytes, LittleEndianInputStream leis, - long remainingDataSize, long remainingRecordSize) throws IOException { - if (remainingDataSize > Integer.MAX_VALUE) { - throw new RecordFormatException("Data size can't be > Integer.MAX_VALUE"); - } - - if (remainingRecordSize > Integer.MAX_VALUE) { - throw new RecordFormatException("Record size can't be > Integer.MAX_VALUE"); - } - if (remainingRecordSize == 0) { - return new byte[0]; - } - - int dataSize = (int)remainingDataSize; - int recordSize = (int)remainingRecordSize; - byte[] arr = IOUtils.safelyAllocate(dataSize+initialBytes.length, MAX_RECORD_LENGTH); - System.arraycopy(initialBytes,0,arr, 0, initialBytes.length); - long read = IOUtils.readFully(leis, arr, initialBytes.length, dataSize); - if (read != dataSize) { - throw new RecordFormatException("InputStream ended before full record could be read"); - } - long toSkip = recordSize-dataSize; - long skipped = IOUtils.skipFully(leis, toSkip); - if (toSkip != skipped) { - throw new RecordFormatException("InputStream ended before full record could be read"); - } - - return arr; - } - - private byte[] readToByteArray(LittleEndianInputStream leis, long dataSize, long recordSize) throws IOException { - - if (recordSize == 0) { - return new byte[0]; - } - - byte[] arr = IOUtils.safelyAllocate(dataSize, MAX_RECORD_LENGTH); - - long read = IOUtils.readFully(leis, arr); - if (read != dataSize) { - throw new RecordFormatException("InputStream ended before full record could be read"); - } - long toSkip = recordSize-dataSize; - long skipped = IOUtils.skipFully(leis, recordSize-dataSize); - if (toSkip != skipped) { - throw new RecordFormatException("InputStream ended before full record could be read"); - } - return arr; - } - - public AbstractHemfComment getComment() { - return comment; - } - - private static class CommentPublicParser { - private static final long WINDOWS_METAFILE = 0x80000001L; //wmf - private static final long BEGINGROUP = 0x00000002; //beginning of a group of drawing records - private static final long ENDGROUP = 0x00000003; //end of a group of drawing records - private static final long MULTIFORMATS = 0x40000004; //allows multiple definitions of an image, including encapsulated postscript - private static final long UNICODE_STRING = 0x00000040; //reserved. must not be used - private static final long UNICODE_END = 0x00000080; //reserved, must not be used - - private static AbstractHemfComment parse(byte[] bytes) { - long publicCommentIdentifier = LittleEndian.getUInt(bytes, 0); - if (publicCommentIdentifier == WINDOWS_METAFILE) { - return new HemfCommentPublic.WindowsMetafile(bytes); - } else if (publicCommentIdentifier == BEGINGROUP) { - return new HemfCommentPublic.BeginGroup(bytes); - } else if (publicCommentIdentifier == ENDGROUP) { - return new HemfCommentPublic.EndGroup(bytes); - } else if (publicCommentIdentifier == MULTIFORMATS) { - return new HemfCommentPublic.MultiFormats(bytes); - } else if (publicCommentIdentifier == UNICODE_STRING || publicCommentIdentifier == UNICODE_END) { - throw new RuntimeException("UNICODE_STRING/UNICODE_END values are reserved in CommentPublic records"); - } - throw new RuntimeException("Unrecognized public comment type:" +publicCommentIdentifier + " ; " + WINDOWS_METAFILE); - } - } -} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/HemfRecordType.java b/src/scratchpad/src/org/apache/poi/hemf/record/HemfRecordType.java deleted file mode 100644 index b1c5857114..0000000000 --- a/src/scratchpad/src/org/apache/poi/hemf/record/HemfRecordType.java +++ /dev/null @@ -1,159 +0,0 @@ -/* ==================================================================== - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -==================================================================== */ - -package org.apache.poi.hemf.record; - -import org.apache.poi.util.Internal; - -@Internal -public enum HemfRecordType { - - header(0x00000001, UnimplementedHemfRecord.class), - polybeizer(0x00000002, UnimplementedHemfRecord.class), - polygon(0x00000003, UnimplementedHemfRecord.class), - polyline(0x00000004, UnimplementedHemfRecord.class), - polybezierto(0x00000005, UnimplementedHemfRecord.class), - polylineto(0x00000006, UnimplementedHemfRecord.class), - polypolyline(0x00000007, UnimplementedHemfRecord.class), - polypolygon(0x00000008, UnimplementedHemfRecord.class), - setwindowextex(0x00000009, UnimplementedHemfRecord.class), - setwindoworgex(0x0000000A, UnimplementedHemfRecord.class), - setviewportextex(0x0000000B, UnimplementedHemfRecord.class), - setviewportorgex(0x0000000C, UnimplementedHemfRecord.class), - setbrushorgex(0x0000000D, UnimplementedHemfRecord.class), - eof(0x0000000E, UnimplementedHemfRecord.class), - setpixelv(0x0000000F, UnimplementedHemfRecord.class), - setmapperflags(0x00000010, UnimplementedHemfRecord.class), - setmapmode(0x00000011, UnimplementedHemfRecord.class), - setbkmode(0x00000012, UnimplementedHemfRecord.class), - setpolyfillmode(0x00000013, UnimplementedHemfRecord.class), - setrop2(0x00000014, UnimplementedHemfRecord.class), - setstretchbltmode(0x00000015, UnimplementedHemfRecord.class), - settextalign(0x00000016, HemfText.SetTextAlign.class), - setcoloradjustment(0x00000017, UnimplementedHemfRecord.class), - settextcolor(0x00000018, HemfText.SetTextColor.class), - setbkcolor(0x00000019, UnimplementedHemfRecord.class), - setoffsetcliprgn(0x0000001A, UnimplementedHemfRecord.class), - setmovetoex(0x0000001B, UnimplementedHemfRecord.class), - setmetargn(0x0000001C, UnimplementedHemfRecord.class), - setexcludecliprect(0x0000001D, UnimplementedHemfRecord.class), - setintersectcliprect(0x0000001E, UnimplementedHemfRecord.class), - scaleviewportextex(0x0000001F, UnimplementedHemfRecord.class), - scalewindowextex(0x00000020, UnimplementedHemfRecord.class), - savedc(0x00000021, UnimplementedHemfRecord.class), - restoredc(0x00000022, UnimplementedHemfRecord.class), - setworldtransform(0x00000023, UnimplementedHemfRecord.class), - modifyworldtransform(0x00000024, UnimplementedHemfRecord.class), - selectobject(0x00000025, UnimplementedHemfRecord.class), - createpen(0x00000026, UnimplementedHemfRecord.class), - createbrushindirect(0x00000027, UnimplementedHemfRecord.class), - deleteobject(0x00000028, UnimplementedHemfRecord.class), - anglearc(0x00000029, UnimplementedHemfRecord.class), - ellipse(0x0000002A, UnimplementedHemfRecord.class), - rectangle(0x0000002B, UnimplementedHemfRecord.class), - roundirect(0x0000002C, UnimplementedHemfRecord.class), - arc(0x0000002D, UnimplementedHemfRecord.class), - chord(0x0000002E, UnimplementedHemfRecord.class), - pie(0x0000002F, UnimplementedHemfRecord.class), - selectpalette(0x00000030, UnimplementedHemfRecord.class), - createpalette(0x00000031, UnimplementedHemfRecord.class), - setpaletteentries(0x00000032, UnimplementedHemfRecord.class), - resizepalette(0x00000033, UnimplementedHemfRecord.class), - realizepalette(0x0000034, UnimplementedHemfRecord.class), - extfloodfill(0x00000035, UnimplementedHemfRecord.class), - lineto(0x00000036, UnimplementedHemfRecord.class), - arcto(0x00000037, UnimplementedHemfRecord.class), - polydraw(0x00000038, UnimplementedHemfRecord.class), - setarcdirection(0x00000039, UnimplementedHemfRecord.class), - setmiterlimit(0x0000003A, UnimplementedHemfRecord.class), - beginpath(0x0000003B, UnimplementedHemfRecord.class), - endpath(0x0000003C, UnimplementedHemfRecord.class), - closefigure(0x0000003D, UnimplementedHemfRecord.class), - fillpath(0x0000003E, UnimplementedHemfRecord.class), - strokeandfillpath(0x0000003F, UnimplementedHemfRecord.class), - strokepath(0x00000040, UnimplementedHemfRecord.class), - flattenpath(0x00000041, UnimplementedHemfRecord.class), - widenpath(0x00000042, UnimplementedHemfRecord.class), - selectclippath(0x00000043, UnimplementedHemfRecord.class), - abortpath(0x00000044, UnimplementedHemfRecord.class), //no 45?! - comment(0x00000046, HemfCommentRecord.class), - fillrgn(0x00000047, UnimplementedHemfRecord.class), - framergn(0x00000048, UnimplementedHemfRecord.class), - invertrgn(0x00000049, UnimplementedHemfRecord.class), - paintrgn(0x0000004A, UnimplementedHemfRecord.class), - extselectciprrgn(0x0000004B, UnimplementedHemfRecord.class), - bitblt(0x0000004C, UnimplementedHemfRecord.class), - stretchblt(0x0000004D, UnimplementedHemfRecord.class), - maskblt(0x0000004E, UnimplementedHemfRecord.class), - plgblt(0x0000004F, UnimplementedHemfRecord.class), - setbitstodevice(0x00000050, UnimplementedHemfRecord.class), - stretchdibits(0x00000051, UnimplementedHemfRecord.class), - extcreatefontindirectw(0x00000052, HemfText.ExtCreateFontIndirectW.class), - exttextouta(0x00000053, HemfText.ExtTextOutA.class), - exttextoutw(0x00000054, HemfText.ExtTextOutW.class), - polybezier16(0x00000055, UnimplementedHemfRecord.class), - polygon16(0x00000056, UnimplementedHemfRecord.class), - polyline16(0x00000057, UnimplementedHemfRecord.class), - polybezierto16(0x00000058, UnimplementedHemfRecord.class), - polylineto16(0x00000059, UnimplementedHemfRecord.class), - polypolyline16(0x0000005A, UnimplementedHemfRecord.class), - polypolygon16(0x0000005B, UnimplementedHemfRecord.class), - polydraw16(0x0000005C, UnimplementedHemfRecord.class), - createmonobrush16(0x0000005D, UnimplementedHemfRecord.class), - createdibpatternbrushpt(0x0000005E, UnimplementedHemfRecord.class), - extcreatepen(0x0000005F, UnimplementedHemfRecord.class), - polytextouta(0x00000060, HemfText.PolyTextOutA.class), - polytextoutw(0x00000061, HemfText.PolyTextOutW.class), - seticmmode(0x00000062, UnimplementedHemfRecord.class), - createcolorspace(0x00000063, UnimplementedHemfRecord.class), - setcolorspace(0x00000064, UnimplementedHemfRecord.class), - deletecolorspace(0x00000065, UnimplementedHemfRecord.class), - glsrecord(0x00000066, UnimplementedHemfRecord.class), - glsboundedrecord(0x00000067, UnimplementedHemfRecord.class), - pixelformat(0x00000068, UnimplementedHemfRecord.class), - drawescape(0x00000069, UnimplementedHemfRecord.class), - extescape(0x0000006A, UnimplementedHemfRecord.class),//no 6b?! - smalltextout(0x0000006C, UnimplementedHemfRecord.class), - forceufimapping(0x0000006D, UnimplementedHemfRecord.class), - namedescape(0x0000006E, UnimplementedHemfRecord.class), - colorcorrectpalette(0x0000006F, UnimplementedHemfRecord.class), - seticmprofilea(0x00000070, UnimplementedHemfRecord.class), - seticmprofilew(0x00000071, UnimplementedHemfRecord.class), - alphablend(0x00000072, UnimplementedHemfRecord.class), - setlayout(0x00000073, UnimplementedHemfRecord.class), - transparentblt(0x00000074, UnimplementedHemfRecord.class), - gradientfill(0x00000076, UnimplementedHemfRecord.class), //no 75?! - setlinkdufis(0x00000077, UnimplementedHemfRecord.class), - settextjustification(0x00000078, HemfText.SetTextJustification.class), - colormatchtargetw(0x00000079, UnimplementedHemfRecord.class), - createcolorspacew(0x0000007A, UnimplementedHemfRecord.class); - - public final long id; - public final Class clazz; - - HemfRecordType(long id, Class clazz) { - this.id = id; - this.clazz = clazz; - } - - public static HemfRecordType getById(long id) { - for (HemfRecordType wrt : values()) { - if (wrt.id == id) return wrt; - } - return null; - } -} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/HemfText.java b/src/scratchpad/src/org/apache/poi/hemf/record/HemfText.java deleted file mode 100644 index 74b8212846..0000000000 --- a/src/scratchpad/src/org/apache/poi/hemf/record/HemfText.java +++ /dev/null @@ -1,262 +0,0 @@ -/* ==================================================================== - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -==================================================================== */ - -package org.apache.poi.hemf.record; - -import static java.nio.charset.StandardCharsets.UTF_16LE; - -import java.io.ByteArrayInputStream; -import java.io.EOFException; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.Reader; -import java.nio.charset.Charset; - -import org.apache.poi.util.IOUtils; -import org.apache.poi.util.Internal; -import org.apache.poi.util.LittleEndian; -import org.apache.poi.util.LittleEndianInputStream; -import org.apache.poi.util.RecordFormatException; - -/** - * Container class to gather all text-related commands - * This is starting out as read only, and very little is actually - * implemented at this point! - */ -@Internal -public class HemfText { - - private static final int MAX_RECORD_LENGTH = 1_000_000; - - public static class ExtCreateFontIndirectW extends UnimplementedHemfRecord { - } - - public static class ExtTextOutA implements HemfRecord { - - private long left,top,right,bottom; - - //TODO: translate this to a graphicsmode enum - private long graphicsMode; - - private long exScale; - private long eyScale; - EmrTextObject textObject; - - @Override - public HemfRecordType getRecordType() { - return HemfRecordType.exttextouta; - } - - @Override - public long init(LittleEndianInputStream leis, long recordId, long recordSize) throws IOException { - //note that the first 2 uInts have been read off and the recordsize has - //been decreased by 8 - left = leis.readInt(); - top = leis.readInt(); - right = leis.readInt(); - bottom = leis.readInt(); - graphicsMode = leis.readUInt(); - exScale = leis.readUInt(); - eyScale = leis.readUInt(); - - int recordSizeInt = -1; - if (recordSize < Integer.MAX_VALUE) { - recordSizeInt = (int)recordSize; - } else { - throw new RecordFormatException("can't have text length > Integer.MAX_VALUE"); - } - //guarantee to read the rest of the EMRTextObjectRecord - //emrtextbytes start after 7*4 bytes read above - byte[] emrTextBytes = IOUtils.safelyAllocate(recordSizeInt-(7*LittleEndian.INT_SIZE), MAX_RECORD_LENGTH); - IOUtils.readFully(leis, emrTextBytes); - textObject = new EmrTextObject(emrTextBytes, getEncodingHint(), 20);//should be 28, but recordSizeInt has already subtracted 8 - return recordSize; - } - - protected Charset getEncodingHint() { - return null; - } - - /** - * - * To be implemented! We need to get the current character set - * from the current font for {@link ExtTextOutA}, - * which has to be tracked in the playback device. - * - * For {@link ExtTextOutW}, the charset is "UTF-16LE" - * - * @param charset the charset to be used to decode the character bytes - * @return text from this text element - * @throws IOException - */ - public String getText(Charset charset) throws IOException { - return textObject.getText(charset); - } - - /** - * - * @return the x offset for the EmrTextObject - */ - public long getX() { - return textObject.x; - } - - /** - * - * @return the y offset for the EmrTextObject - */ - public long getY() { - return textObject.y; - } - - public long getLeft() { - return left; - } - - public long getTop() { - return top; - } - - public long getRight() { - return right; - } - - public long getBottom() { - return bottom; - } - - public long getGraphicsMode() { - return graphicsMode; - } - - public long getExScale() { - return exScale; - } - - public long getEyScale() { - return eyScale; - } - - } - - public static class ExtTextOutW extends ExtTextOutA { - - @Override - public HemfRecordType getRecordType() { - return HemfRecordType.exttextoutw; - } - - @Override - protected Charset getEncodingHint() { - return UTF_16LE; - } - - public String getText() throws IOException { - return getText(UTF_16LE); - } - } - - /** - * Needs to be implemented. Couldn't find example. - */ - public static class PolyTextOutA extends UnimplementedHemfRecord { - - } - - /** - * Needs to be implemented. Couldn't find example. - */ - public static class PolyTextOutW extends UnimplementedHemfRecord { - - } - - public static class SetTextAlign extends UnimplementedHemfRecord { - } - - public static class SetTextColor extends UnimplementedHemfRecord { - } - - - public static class SetTextJustification extends UnimplementedHemfRecord { - - } - - private static class EmrTextObject { - long x; - long y; - int numChars; - byte[] rawTextBytes;//this stores _all_ of the bytes to the end of the EMRTextObject record. - //Because of potential variable length encodings, must - //carefully read only the numChars from this byte array. - - EmrTextObject(byte[] emrTextObjBytes, Charset charsetHint, int readSoFar) throws IOException { - - int offset = 0; - x = LittleEndian.getUInt(emrTextObjBytes, offset); offset+= LittleEndian.INT_SIZE; - y = LittleEndian.getUInt(emrTextObjBytes, offset); offset+= LittleEndian.INT_SIZE; - long numCharsLong = LittleEndian.getUInt(emrTextObjBytes, offset); offset += LittleEndian.INT_SIZE; - long offString = LittleEndian.getUInt(emrTextObjBytes, offset); offset += LittleEndian.INT_SIZE; - int start = (int)offString-offset-readSoFar; - - if (numCharsLong == 0) { - rawTextBytes = new byte[0]; - numChars = 0; - return; - } - if (numCharsLong > Integer.MAX_VALUE) { - throw new RecordFormatException("Number of characters can't be > Integer.MAX_VALUE"); - } else if (numCharsLong < 0) { - throw new RecordFormatException("Number of characters can't be < 0"); - } - - numChars = (int)numCharsLong; - rawTextBytes = IOUtils.safelyAllocate(emrTextObjBytes.length-start, MAX_RECORD_LENGTH); - System.arraycopy(emrTextObjBytes, start, rawTextBytes, 0, emrTextObjBytes.length-start); - } - - String getText(Charset charset) throws IOException { - StringBuilder sb = new StringBuilder(); - try (Reader r = new InputStreamReader(new ByteArrayInputStream(rawTextBytes), charset)) { - for (int i = 0; i < numChars; i++) { - sb.appendCodePoint(readCodePoint(r)); - } - } - return sb.toString(); - } - - //TODO: move this to IOUtils? - private int readCodePoint(Reader r) throws IOException { - int c1 = r.read(); - if (c1 == -1) { - throw new EOFException("Tried to read beyond byte array"); - } - if (!Character.isHighSurrogate((char)c1)) { - return c1; - } - int c2 = r.read(); - if (c2 == -1) { - throw new EOFException("Tried to read beyond byte array"); - } - if (!Character.isLowSurrogate((char)c2)) { - throw new RecordFormatException("Expected low surrogate after high surrogate"); - } - return Character.toCodePoint((char)c1, (char)c2); - } - } - - -} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java new file mode 100644 index 0000000000..0a459e3cd0 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java @@ -0,0 +1,444 @@ +/* ==================================================================== + 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 java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.function.Supplier; + +import org.apache.poi.hemf.record.emfplus.HemfPlusRecord; +import org.apache.poi.hemf.record.emfplus.HemfPlusRecordIterator; +import org.apache.poi.util.IOUtils; +import org.apache.poi.util.Internal; +import org.apache.poi.util.LittleEndianConsts; +import org.apache.poi.util.LittleEndianInputStream; +import org.apache.poi.util.RecordFormatException; + +/** + * Contains arbitrary data + */ +@Internal +public class HemfComment { + private static final int MAX_RECORD_LENGTH = 1_000_000; + + public enum HemfCommentRecordType { + emfGeneric(-1, EmfCommentDataGeneric::new, false), + emfSpool(0x00000000, EmfCommentDataGeneric::new, false), + emfPlus(0x2B464D45, EmfCommentDataPlus::new, false), + emfPublic(0x43494447, null, false), + emfBeginGroup(0x00000002, EmfCommentDataBeginGroup::new, true), + emfEndGroup(0x00000003, EmfCommentDataEndGroup::new, true), + emfMultiFormats(0x40000004, EmfCommentDataMultiformats::new, true), + emfWMF(0x80000001, EmfCommentDataWMF::new, true), + emfUnicodeString(0x00000040, EmfCommentDataUnicode::new, true), + emfUnicodeEnd(0x00000080, EmfCommentDataUnicode::new, true) + ; + + + public final long id; + public final Supplier constructor; + public final boolean isEmfPublic; + + HemfCommentRecordType(long id, Supplier constructor, boolean isEmfPublic) { + this.id = id; + this.constructor = constructor; + this.isEmfPublic = isEmfPublic; + } + + public static HemfCommentRecordType getById(long id, boolean isEmfPublic) { + for (HemfCommentRecordType wrt : values()) { + if (wrt.id == id && wrt.isEmfPublic == isEmfPublic) return wrt; + } + return emfGeneric; + } + } + + public interface EmfCommentData { + HemfCommentRecordType getCommentRecordType(); + + long init(LittleEndianInputStream leis, long dataSize) throws IOException; + } + + public static class EmfComment implements HemfRecord { + private EmfCommentData data; + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.comment; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + int startIdx = leis.getReadIndex(); + data = new EmfCommentDataIterator(leis, (int)recordSize, true).next(); + return leis.getReadIndex()-startIdx; + } + + public EmfCommentData getCommentData() { + return data; + } + } + + public static class EmfCommentDataIterator implements Iterator { + + private final LittleEndianInputStream leis; + private final int startIdx; + private final int limit; + private EmfCommentData currentRecord; + /** is the caller the EmfComment */ + private final boolean emfParent; + + public EmfCommentDataIterator(LittleEndianInputStream leis, int limit, boolean emfParent) { + this.leis = leis; + this.limit = limit; + this.emfParent = emfParent; + startIdx = leis.getReadIndex(); + //queue the first non-header record + currentRecord = _next(); + } + + @Override + public boolean hasNext() { + return currentRecord != null; + } + + @Override + public EmfCommentData next() { + EmfCommentData toReturn = currentRecord; + final boolean isEOF = (limit == -1 || leis.getReadIndex() >= startIdx+limit); + // (currentRecord instanceof HemfPlusMisc.EmfEof) + currentRecord = isEOF ? null : _next(); + return toReturn; + } + + private EmfCommentData _next() { + long type, recordSize; + if (currentRecord == null && emfParent) { + type = HemfRecordType.comment.id; + recordSize = limit; + } else { + // A 32-bit unsigned integer from the RecordType enumeration that identifies this record + // as a comment record. This value MUST be 0x00000046. + type = leis.readUInt(); + assert(type == HemfRecordType.comment.id); + // A 32-bit unsigned integer that specifies the size in bytes of this record in the + // metafile. This value MUST be a multiple of 4 bytes. + recordSize = leis.readUInt(); + } + + // A 32-bit unsigned integer that specifies the size, in bytes, of the CommentIdentifier and + // CommentRecordParm fields in the RecordBuffer field that follows. + // It MUST NOT include the size of itself or the size of the AlignmentPadding field, if present. + long dataSize = leis.readUInt(); + + try { + leis.mark(2*LittleEndianConsts.INT_SIZE); + // An optional, 32-bit unsigned integer that identifies the type of comment record. + // See the preceding table for descriptions of these record types. + // Valid comment identifier values are listed in the following table. + // + // If this field contains any other value, the comment record MUST be an EMR_COMMENT record + final int commentIdentifier = (int)leis.readUInt(); + // A 32-bit unsigned integer that identifies the type of public comment record. + final int publicCommentIdentifier = (int)leis.readUInt(); + + final boolean isEmfPublic = (commentIdentifier == HemfCommentRecordType.emfPublic.id); + leis.reset(); + + final HemfCommentRecordType commentType = HemfCommentRecordType.getById + (isEmfPublic ? publicCommentIdentifier : commentIdentifier, isEmfPublic); + assert(commentType != null); + final EmfCommentData record = commentType.constructor.get(); + + long readBytes = record.init(leis, dataSize); + final int skipBytes = (int)(recordSize-4-readBytes); + assert (skipBytes >= 0); + leis.skipFully(skipBytes); + + return record; + } catch (IOException e) { + throw new RecordFormatException(e); + } + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Remove not supported"); + } + + } + + + + /** + * Private data is unknown to EMF; it is meaningful only to applications that know the format of the + * data and how to use it. EMR_COMMENT private data records MAY be ignored. + */ + public static class EmfCommentDataGeneric implements EmfCommentData { + private byte[] privateData; + + @Override + public HemfCommentRecordType getCommentRecordType() { + return HemfCommentRecordType.emfGeneric; + } + + @Override + public long init(LittleEndianInputStream leis, long dataSize) throws IOException { + privateData = IOUtils.safelyAllocate(dataSize, MAX_RECORD_LENGTH); + leis.readFully(privateData); + return privateData.length; + } + } + + /** The EMR_COMMENT_EMFPLUS record contains embedded EMF+ records. */ + public static class EmfCommentDataPlus implements EmfCommentData { + private final List records = new ArrayList<>(); + + @Override + public HemfCommentRecordType getCommentRecordType() { + return HemfCommentRecordType.emfPlus; + } + + @Override + public long init(final LittleEndianInputStream leis, final long dataSize) + throws IOException { + long startIdx = leis.getReadIndex(); + int commentIdentifier = leis.readInt(); + assert (commentIdentifier == HemfCommentRecordType.emfPlus.id); + new HemfPlusRecordIterator(leis, (int)dataSize-LittleEndianConsts.INT_SIZE).forEachRemaining(records::add); + return leis.getReadIndex()-startIdx; + } + + public List getRecords() { + return Collections.unmodifiableList(records); + } + } + + public static class EmfCommentDataBeginGroup implements EmfCommentData { + private final Rectangle2D bounds = new Rectangle2D.Double(); + private String description; + + @Override + public HemfCommentRecordType getCommentRecordType() { + return HemfCommentRecordType.emfBeginGroup; + } + + @Override + public long init(final LittleEndianInputStream leis, final long dataSize) + throws IOException { + final int startIdx = leis.getReadIndex(); + final int commentIdentifier = (int)leis.readUInt(); + assert(commentIdentifier == HemfCommentRecordType.emfPublic.id); + final int publicCommentIdentifier = (int)leis.readUInt(); + assert(publicCommentIdentifier == HemfCommentRecordType.emfBeginGroup.id); + HemfDraw.readRectL(leis, bounds); + + // The number of Unicode characters in the optional description string that follows. + int nDescription = (int)leis.readUInt(); + + byte[] buf = IOUtils.safelyAllocate(nDescription*2, MAX_RECORD_LENGTH); + leis.readFully(buf); + description = new String(buf, StandardCharsets.UTF_16LE); + + return leis.getReadIndex()-startIdx; + } + } + + public static class EmfCommentDataEndGroup implements EmfCommentData { + @Override + public HemfCommentRecordType getCommentRecordType() { + return HemfCommentRecordType.emfEndGroup; + } + + @Override + public long init(final LittleEndianInputStream leis, final long dataSize) + throws IOException { + final int startIdx = leis.getReadIndex(); + final int commentIdentifier = (int)leis.readUInt(); + assert(commentIdentifier == HemfCommentRecordType.emfPublic.id); + final int publicCommentIdentifier = (int)leis.readUInt(); + assert(publicCommentIdentifier == HemfCommentRecordType.emfEndGroup.id); + return leis.getReadIndex()-startIdx; + } + } + + public static class EmfCommentDataMultiformats implements EmfCommentData { + private final Rectangle2D bounds = new Rectangle2D.Double(); + private final List formats = new ArrayList<>(); + + @Override + public HemfCommentRecordType getCommentRecordType() { + return HemfCommentRecordType.emfMultiFormats; + } + + @Override + public long init(final LittleEndianInputStream leis, final long dataSize) + throws IOException { + final int startIdx = leis.getReadIndex(); + final int commentIdentifier = (int)leis.readUInt(); + assert(commentIdentifier == HemfCommentRecordType.emfPublic.id); + final int publicCommentIdentifier = (int)leis.readUInt(); + assert(publicCommentIdentifier == HemfCommentRecordType.emfMultiFormats.id); + HemfDraw.readRectL(leis, bounds); + + // A 32-bit unsigned integer that specifies the number of graphics formats contained in this record. + int countFormats = (int)leis.readUInt(); + for (int i=0; i getFormats() { + return Collections.unmodifiableList(formats); + } + } + + public enum EmfFormatSignature { + ENHMETA_SIGNATURE(0x464D4520), + EPS_SIGNATURE(0x46535045); + + int id; + + EmfFormatSignature(int flag) { + this.id = id; + } + + public static EmfFormatSignature getById(int id) { + for (EmfFormatSignature wrt : values()) { + if (wrt.id == id) return wrt; + } + return null; + } + + } + + public static class EmfCommentDataFormat { + private EmfFormatSignature signature; + private int version; + private int sizeData; + private int offData; + private byte[] rawData; + + public long init(final LittleEndianInputStream leis, final long dataSize, long startIdx) throws IOException { + // A 32-bit unsigned integer that specifies the format of the image data. + signature = EmfFormatSignature.getById(leis.readInt()); + + // A 32-bit unsigned integer that specifies the format version number. + // If the Signature field specifies encapsulated PostScript (EPS), this value MUST be 0x00000001; + // otherwise, this value MUST be ignored. + version = leis.readInt(); + + // A 32-bit unsigned integer that specifies the size of the data in bytes. + sizeData = leis.readInt(); + + // A 32-bit unsigned integer that specifies the offset to the data from the start + // of the identifier field in an EMR_COMMENT_PUBLIC record. The offset MUST be 32-bit aligned. + offData = leis.readInt(); + if (sizeData < 0) { + throw new RecordFormatException("size for emrformat must be > 0"); + } + if (offData < 0) { + throw new RecordFormatException("offset for emrformat must be > 0"); + } + + return 4*LittleEndianConsts.INT_SIZE; + } + + public byte[] getRawData() { + return rawData; + } + } + + public static class EmfCommentDataWMF implements EmfCommentData { + private final Rectangle2D bounds = new Rectangle2D.Double(); + private final List formats = new ArrayList<>(); + + @Override + public HemfCommentRecordType getCommentRecordType() { + return HemfCommentRecordType.emfWMF; + } + + @Override + public long init(final LittleEndianInputStream leis, final long dataSize) + throws IOException { + final int startIdx = leis.getReadIndex(); + final int commentIdentifier = (int)leis.readUInt(); + assert(commentIdentifier == HemfCommentRecordType.emfPublic.id); + final int publicCommentIdentifier = (int)leis.readUInt(); + assert(publicCommentIdentifier == HemfCommentRecordType.emfWMF.id); + + // A 16-bit unsigned integer that specifies the WMF metafile version in terms + //of support for device-independent bitmaps (DIBs) + int version = leis.readUShort(); + + // A 16-bit value that MUST be 0x0000 and MUST be ignored. + leis.skipFully(LittleEndianConsts.SHORT_SIZE); + + // A 32-bit unsigned integer that specifies the checksum for this record. + int checksum = leis.readInt(); + + // A 32-bit value that MUST be 0x00000000 and MUST be ignored. + int flags = leis.readInt(); + + // A 32-bit unsigned integer that specifies the size, in bytes, of the + // WMF metafile in the WinMetafile field. + int winMetafileSize = (int)leis.readUInt(); + + byte[] winMetafile = IOUtils.safelyAllocate(winMetafileSize, MAX_RECORD_LENGTH); + leis.readFully(winMetafile); + + return leis.getReadIndex()-startIdx; + } + } + + public static class EmfCommentDataUnicode implements EmfCommentData { + private final Rectangle2D bounds = new Rectangle2D.Double(); + private final List formats = new ArrayList<>(); + + @Override + public HemfCommentRecordType getCommentRecordType() { + return HemfCommentRecordType.emfUnicodeString; + } + + @Override + public long init(final LittleEndianInputStream leis, final long dataSize) + throws IOException { + throw new RecordFormatException("UNICODE_STRING/UNICODE_END values are reserved in CommentPublic records"); + } + } +} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java new file mode 100644 index 0000000000..688a9ff66b --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java @@ -0,0 +1,783 @@ +/* ==================================================================== + 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.AffineTransform; +import java.awt.geom.Dimension2D; +import java.awt.geom.Path2D; +import java.awt.geom.PathIterator; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.io.IOException; + +import org.apache.poi.hemf.draw.HemfGraphics; +import org.apache.poi.hwmf.draw.HwmfGraphics; +import org.apache.poi.hwmf.record.HwmfDraw; +import org.apache.poi.hwmf.record.HwmfDraw.WmfSelectObject; +import org.apache.poi.util.LittleEndianConsts; +import org.apache.poi.util.LittleEndianInputStream; + +public class HemfDraw { + /** + * The EMR_SELECTOBJECT record adds a graphics object to the current metafile playback device + * context. The object is specified either by its index in the EMF Object Table or by its + * value from the StockObject enumeration. + */ + public static class EmfSelectObject extends WmfSelectObject implements HemfRecord { + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.selectObject; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + // A 32-bit unsigned integer that specifies either the index of a graphics object in the + // EMF Object Table or the index of a stock object from the StockObject enumeration. + objectIndex = leis.readInt(); + return LittleEndianConsts.INT_SIZE; + } + } + + + /** The EMR_POLYBEZIER record specifies one or more Bezier curves. */ + public static class EmfPolyBezier extends HwmfDraw.WmfPolygon implements HemfRecord { + private final Rectangle2D bounds = new Rectangle2D.Double(); + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.polyBezier; + } + + protected long readPoint(LittleEndianInputStream leis, Point2D point) { + return readPointL(leis, point); + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + long size = readRectL(leis, bounds); + + /* A 32-bit unsigned integer that specifies the number of points in the points + * array. This value MUST be one more than three times the number of curves to + * be drawn, because each Bezier curve requires two control points and an + * endpoint, and the initial curve requires an additional starting point. + * + * Line width | Device supports wideline | Maximum points allowed + * 1 | n/a | 16K + * > 1 | yes | 16K + * > 1 | no | 1360 + * + * Any extra points MUST be ignored. + */ + final int count = (int)leis.readUInt(); + final int points = Math.min(count, 16384); + size += LittleEndianConsts.INT_SIZE; + + poly.reset(); + + /* Cubic Bezier curves are defined using the endpoints and control points + * specified by the points field. The first curve is drawn from the first + * point to the fourth point, using the second and third points as control + * points. Each subsequent curve in the sequence needs exactly three more points: + * the ending point of the previous curve is used as the starting point, + * the next two points in the sequence are control points, + * and the third is the ending point. + * The cubic Bezier curves SHOULD be drawn using the current pen. + */ + + Point2D pnt[] = { new Point2D.Double(), new Point2D.Double(), new Point2D.Double() }; + + // points-1 because of the first point + final int pointCnt = hasStartPoint() ? points-1 : points; + for (int i=0; i+3= 0); + leis.skipFully(undefinedSpace1); + size += undefinedSpace1; + + bmiSrc = IOUtils.safelyAllocate(cbBmiSrc, MAX_RECORD_LENGTH); + leis.readFully(bmiSrc); + size += cbBmiSrc; + + final int undefinedSpace2 = (int)(offBitsSrc - size - HEADER_SIZE); + assert(undefinedSpace2 >= 0); + leis.skipFully(undefinedSpace2); + size += undefinedSpace2; + + bitsSrc = IOUtils.safelyAllocate(cbBitsSrc, MAX_RECORD_LENGTH); + leis.readFully(bitsSrc); + size += cbBitsSrc; + + return size; + } + + protected boolean srcEqualsDstDimension() { + return false; + } + } + + /** + * The EMR_BITBLT record specifies a block transfer of pixels from a source bitmap to a destination rectangle, + * optionally in combination with a brush pattern, according to a specified raster operation. + */ + public static class EmfBitBlt extends EmfStretchBlt { + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.bitBlt; + } + + @Override + protected boolean srcEqualsDstDimension() { + return false; + } + } + + + /** The EMR_FRAMERGN record draws a border around the specified region using the specified brush. */ + public static class EmfFrameRgn extends HwmfDraw.WmfFrameRegion implements HemfRecord { + private final Rectangle2D bounds = new Rectangle2D.Double(); + private final List rgnRects = new ArrayList<>(); + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.frameRgn; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + long size = readRectL(leis, bounds); + // A 32-bit unsigned integer that specifies the size of region data, in bytes. + long rgnDataSize = leis.readUInt(); + // A 32-bit unsigned integer that specifies the brush EMF Object Table index. + brushIndex = (int)leis.readUInt(); + // A 32-bit signed integer that specifies the width of the vertical brush stroke, in logical units. + width = leis.readInt(); + // A 32-bit signed integer that specifies the height of the horizontal brush stroke, in logical units. + height = leis.readInt(); + size += 4*LittleEndianConsts.INT_SIZE; + size += readRgnData(leis, rgnRects); + return size; + } + + @Override + public void draw(HwmfGraphics ctx) { + ctx.applyObjectTableEntry(brushIndex); + + Area frame = new Area(); + for (Rectangle2D rct : rgnRects) { + frame.add(new Area(rct)); + } + Rectangle2D frameBounds = frame.getBounds2D(); + AffineTransform at = new AffineTransform(); + at.translate(bounds.getX()-frameBounds.getX(), bounds.getY()-frameBounds.getY()); + at.scale(bounds.getWidth()/frameBounds.getWidth(), bounds.getHeight()/frameBounds.getHeight()); + frame.transform(at); + + ctx.fill(frame); + } + } + + /** The EMR_INVERTRGN record inverts the colors in the specified region. */ + public static class EmfInvertRgn implements HemfRecord { + protected final Rectangle2D bounds = new Rectangle2D.Double(); + protected final List rgnRects = new ArrayList<>(); + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.invertRgn; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + long size = readRectL(leis, bounds); + // A 32-bit unsigned integer that specifies the size of region data, in bytes. + long rgnDataSize = leis.readUInt(); + size += LittleEndianConsts.INT_SIZE; + size += readRgnData(leis, rgnRects); + return size; + } + } + + /** + * The EMR_PAINTRGN record paints the specified region by using the brush currently selected into the + * playback device context. + */ + public static class EmfPaintRgn extends EmfInvertRgn { + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.paintRgn; + } + } + + /** The EMR_FILLRGN record fills the specified region by using the specified brush. */ + public static class EmfFillRgn extends HwmfFill.WmfFillRegion implements HemfRecord { + protected final Rectangle2D bounds = new Rectangle2D.Double(); + protected final List rgnRects = new ArrayList<>(); + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.fillRgn; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + long size = readRectL(leis, bounds); + // A 32-bit unsigned integer that specifies the size of region data, in bytes. + long rgnDataSize = leis.readUInt(); + brushIndex = (int)leis.readUInt(); + size += 2*LittleEndianConsts.INT_SIZE; + size += readRgnData(leis, rgnRects); + return size; + } + } + + public static class EmfExtSelectClipRgn implements HemfRecord { + protected HemfRegionMode regionMode; + protected final List rgnRects = new ArrayList<>(); + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.extSelectClipRgn; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + // A 32-bit unsigned integer that specifies the size of region data in bytes + long rgnDataSize = leis.readUInt(); + // A 32-bit unsigned integer that specifies the way to use the region. + regionMode = HemfRegionMode.valueOf((int)leis.readUInt()); + long size = 2* LittleEndianConsts.INT_SIZE; + + // If RegionMode is RGN_COPY, this data can be omitted and the clip region + // SHOULD be set to the default (NULL) clip region. + if (regionMode != HemfRegionMode.RGN_COPY) { + size += readRgnData(leis, rgnRects); + } + return size; + } + } + + public static class EmfAlphaBlend implements HemfRecord { + /** the destination bounding rectangle in device units */ + protected final Rectangle2D bounds = new Rectangle2D.Double(); + /** the destination rectangle */ + protected final Rectangle2D destRect = new Rectangle2D.Double(); + /** the source rectangle */ + protected final Rectangle2D srcRect = new Rectangle2D.Double(); + /** + * The blend operation code. The only source and destination blend operation that has been defined + * is 0x00, which specifies that the source bitmap MUST be combined with the destination bitmap based + * on the alpha transparency values of the source pixels. + */ + protected byte blendOperation; + /** This value MUST be 0x00 and MUST be ignored. */ + protected byte blendFlags; + /** + * An 8-bit unsigned integer that specifies alpha transparency, which determines the blend of the source + * and destination bitmaps. This value MUST be used on the entire source bitmap. The minimum alpha + * transparency value, zero, corresponds to completely transparent; the maximum value, 0xFF, corresponds + * to completely opaque. In effect, a value of 0xFF specifies that the per-pixel alpha values determine + * the blend of the source and destination bitmaps. + */ + protected int srcConstantAlpha; + /** + * A byte that specifies how source and destination pixels are interpreted with respect to alpha transparency. + * + * 0x00: + * The pixels in the source bitmap do not specify alpha transparency. + * In this case, the SrcConstantAlpha value determines the blend of the source and destination bitmaps. + * Note that in the following equations SrcConstantAlpha is divided by 255, + * which produces a value in the range 0 to 1. + * + * 0x01: "AC_SRC_ALPHA" + * Indicates that the source bitmap is 32 bits-per-pixel and specifies an alpha transparency value + * for each pixel. + */ + protected byte alphaFormat; + /** a world-space to page-space transform to apply to the source bitmap. */ + protected final AffineTransform xFormSrc = new AffineTransform(); + /** the background color of the source bitmap. */ + protected final HwmfColorRef bkColorSrc = new HwmfColorRef(); + /** + * A 32-bit unsigned integer that specifies how to interpret values in the + * color table in the source bitmap header. + */ + protected ColorUsage usageSrc; + + protected final HwmfBitmapDib bitmap = new HwmfBitmapDib(); + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.alphaBlend; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + final int startIdx = leis.getReadIndex(); + + long size = readRectL(leis, bounds); + size += readBounds2(leis, destRect); + + blendOperation = leis.readByte(); + assert (blendOperation == 0); + blendFlags = leis.readByte(); + assert (blendOperation == 0); + srcConstantAlpha = leis.readUByte(); + alphaFormat = leis.readByte(); + + // A 32-bit signed integer that specifies the logical x-coordinate of the upper-left + // corner of the source rectangle. + final int xSrc = leis.readInt(); + // A 32-bit signed integer that specifies the logical y-coordinate of the upper-left + // corner of the source rectangle. + final int ySrc = leis.readInt(); + + size += 3*LittleEndianConsts.INT_SIZE; + size += readXForm(leis, xFormSrc); + size += bkColorSrc.init(leis); + + usageSrc = ColorUsage.valueOf((int)leis.readUInt()); + + + // A 32-bit unsigned integer that specifies the offset, in bytes, from the + // start of this record to the source bitmap header in the BitmapBuffer field. + final int offBmiSrc = (int)leis.readUInt(); + + // A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap header. + final int cbBmiSrc = (int)leis.readUInt(); + // A 32-bit unsigned integer that specifies the offset, in bytes, from the + // start of this record to the source bitmap bits in the BitmapBuffer field. + final int offBitsSrc = (int)leis.readUInt(); + // A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap bits. + final int cbBitsSrc = (int)leis.readUInt(); + + // A 32-bit signed integer that specifies the logical width of the source rectangle. + // This value MUST be greater than zero. + final int cxSrc = leis.readInt(); + // A 32-bit signed integer that specifies the logical height of the source rectangle. + // This value MUST be greater than zero. + final int cySrc = leis.readInt(); + + srcRect.setRect(xSrc, ySrc, cxSrc, cySrc); + + size += 7 * LittleEndianConsts.INT_SIZE; + + size += readBitmap(leis, bitmap, startIdx, offBmiSrc, cbBmiSrc, offBitsSrc, cbBitsSrc); + + return size; + } + } + + public static class EmfSetDiBitsToDevice implements HemfRecord { + protected final Rectangle2D bounds = new Rectangle2D.Double(); + protected final Point2D dest = new Point2D.Double(); + protected final Rectangle2D src = new Rectangle2D.Double(); + protected ColorUsage usageSrc; + protected final HwmfBitmapDib bitmap = new HwmfBitmapDib(); + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.setDiBitsToDevice; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + int startIdx = leis.getReadIndex(); + + // A WMF RectL object that defines the destination bounding rectangle in device units. + long size = readRectL(leis, bounds); + // the logical x/y-coordinate of the upper-left corner of the destination rectangle. + size += readPointL(leis, dest); + // the source rectangle + size += readBounds2(leis, src); + // A 32-bit unsigned integer that specifies the offset, in bytes, from the + // start of this record to the source bitmap header in the BitmapBuffer field. + final int offBmiSrc = (int)leis.readUInt(); + // A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap header. + final int cbBmiSrc = (int)leis.readUInt(); + // A 32-bit unsigned integer that specifies the offset, in bytes, from the + // start of this record to the source bitmap bits in the BitmapBuffer field. + final int offBitsSrc = (int)leis.readUInt(); + // A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap bits. + final int cbBitsSrc = (int)leis.readUInt(); + // A 32-bit unsigned integer that specifies how to interpret values in the color table + // in the source bitmap header. This value MUST be in the DIBColors enumeration + usageSrc = ColorUsage.valueOf((int)leis.readUInt()); + // A 32-bit unsigned integer that specifies the first scan line in the array. + final int iStartScan = (int)leis.readUInt(); + // A 32-bit unsigned integer that specifies the number of scan lines. + final int cScans = (int)leis.readUInt(); + size += 7*LittleEndianConsts.INT_SIZE; + + size += readBitmap(leis, bitmap, startIdx, offBmiSrc, cbBmiSrc, offBitsSrc, cbBitsSrc); + + return size; + } + } + + 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 undefinedSpace1 = offBmiSrc-offCurr; + assert(undefinedSpace1 >= 0); + + final int undefinedSpace2 = offBitsSrc-offCurr-cbBmiSrc-undefinedSpace1; + assert(undefinedSpace2 >= 0); + + leis.skipFully(undefinedSpace1); + + if (cbBmiSrc == 0 || cbBitsSrc == 0) { + return undefinedSpace1; + } + + final LittleEndianInputStream leisDib; + if (undefinedSpace2 == 0) { + leisDib = leis; + } else { + final ByteArrayOutputStream bos = new ByteArrayOutputStream(cbBmiSrc+cbBitsSrc); + final long cbBmiSrcAct = IOUtils.copy(leis, bos, cbBmiSrc); + assert (cbBmiSrcAct == cbBmiSrc); + leis.skipFully(undefinedSpace2); + final long cbBitsSrcAct = IOUtils.copy(leis, bos, cbBitsSrc); + assert (cbBitsSrcAct == cbBitsSrc); + leisDib = new LittleEndianInputStream(new ByteArrayInputStream(bos.toByteArray())); + } + final int dibSize = cbBmiSrc+cbBitsSrc; + final int dibSizeAct = bitmap.init(leisDib, dibSize); + assert (dibSizeAct <= dibSize); + return undefinedSpace1 + cbBmiSrc + undefinedSpace2 + cbBitsSrc; + } + + + static long readRgnData(final LittleEndianInputStream leis, final List rgnRects) { + // *** RegionDataHeader *** + // A 32-bit unsigned integer that specifies the size of this object in bytes. This MUST be 0x00000020. + long rgnHdrSiez = leis.readUInt(); + assert(rgnHdrSiez == 0x20); + // A 32-bit unsigned integer that specifies the region type. This SHOULD be RDH_RECTANGLES (0x00000001) + long rgnHdrType = leis.readUInt(); + assert(rgnHdrType == 1); + // A 32-bit unsigned integer that specifies the number of rectangles in this region. + long rgnCntRect = leis.readUInt(); + // A 32-bit unsigned integer that specifies the size of the buffer of rectangles in bytes. + long rgnCntBytes = leis.readUInt(); + long size = 4*LittleEndianConsts.INT_SIZE; + // A 128-bit WMF RectL object, which specifies the bounds of the region. + Rectangle2D rgnBounds = new Rectangle2D.Double(); + size += readRectL(leis, rgnBounds); + for (int i=0; i = : + // m00 (scaleX) = eM11 (Horizontal scaling component) + // m11 (scaleY) = eM22 (Vertical scaling component) + // m01 (shearX) = eM12 (Horizontal proportionality constant) + // m10 (shearY) = eM21 (Vertical proportionality constant) + // m02 (translateX) = eDx (The horizontal translation component, in logical units.) + // m12 (translateY) = eDy (The vertical translation component, in logical units.) + + // A 32-bit floating-point value of the transform matrix. + double eM11 = leis.readFloat(); + + // A 32-bit floating-point value of the transform matrix. + double eM12 = leis.readFloat(); + + // A 32-bit floating-point value of the transform matrix. + double eM21 = leis.readFloat(); + + // A 32-bit floating-point value of the transform matrix. + double eM22 = leis.readFloat(); + + // A 32-bit floating-point value that contains a horizontal translation component, in logical units. + double eDx = leis.readFloat(); + + // A 32-bit floating-point value that contains a vertical translation component, in logical units. + double eDy = leis.readFloat(); + + xform.setTransform(eM11, eM21, eM12, eM22, eDx, eDy); + + return 6 * LittleEndian.INT_SIZE; + } +} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFont.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFont.java new file mode 100644 index 0000000000..8690855f16 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFont.java @@ -0,0 +1,464 @@ +/* ==================================================================== + 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.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import org.apache.poi.common.usermodel.fonts.FontCharset; +import org.apache.poi.hwmf.record.HwmfFont; +import org.apache.poi.util.LittleEndian; +import org.apache.poi.util.LittleEndianConsts; +import org.apache.poi.util.LittleEndianInputStream; + +public class HemfFont extends HwmfFont { + private static final int LOGFONT_SIZE = 92; + private static final int LOGFONTPANOSE_SIZE = 320; + + protected interface LogFontDetails {} + + protected static class LogFontExDv implements LogFontDetails { + protected int[] designVector; + } + + protected static class LogFontPanose implements LogFontDetails { + enum FamilyType { + PAN_ANY, + PAN_NO_FIT, + PAN_FAMILY_TEXT_DISPLAY, + PAN_FAMILY_SCRIPT, + PAN_FAMILY_DECORATIVE, + PAN_FAMILY_PICTORIAL + } + + enum SerifType { + PAN_ANY, + PAN_NO_FIT, + PAN_SERIF_COVE, + PAN_SERIF_OBTUSE_COVE, + PAN_SERIF_SQUARE_COVE, + PAN_SERIF_OBTUSE_SQUARE_COVE, + PAN_SERIF_SQUARE, + PAN_SERIF_THIN, + PAN_SERIF_BONE, + PAN_SERIF_EXAGGERATED, + PAN_SERIF_TRIANGLE, + PAN_SERIF_NORMAL_SANS, + PAN_SERIF_OBTUSE_SANS, + PAN_SERIF_PERP_SANS, + PAN_SERIF_FLARED, + PAN_SERIF_ROUNDED + } + + enum FontWeight { + PAN_ANY, + PAN_NO_FIT, + PAN_WEIGHT_VERY_LIGHT, + PAN_WEIGHT_LIGHT, + PAN_WEIGHT_THIN, + PAN_WEIGHT_BOOK, + PAN_WEIGHT_MEDIUM, + PAN_WEIGHT_DEMI, + PAN_WEIGHT_BOLD, + PAN_WEIGHT_HEAVY, + PAN_WEIGHT_BLACK, + PAN_WEIGHT_NORD + } + + enum Proportion { + PAN_ANY, + PAN_NO_FIT, + PAN_PROP_OLD_STYLE, + PAN_PROP_MODERN, + PAN_PROP_EVEN_WIDTH, + PAN_PROP_EXPANDED, + PAN_PROP_CONDENSED, + PAN_PROP_VERY_EXPANDED, + PAN_PROP_VERY_CONDENSED, + PAN_PROP_MONOSPACED + } + + enum Contrast { + PAN_ANY, + PAN_NO_FIT, + PAN_CONTRAST_NONE, + PAN_CONTRAST_VERY_LOW, + PAN_CONTRAST_LOW, + PAN_CONTRAST_MEDIUM_LOW, + PAN_CONTRAST_MEDIUM, + PAN_CONTRAST_MEDIUM_HIGH, + PAN_CONTRAST_HIGH, + PAN_CONTRAST_VERY_HIGH + } + + enum StrokeVariation { + PAN_ANY, + PAN_NO_FIT, + PAN_STROKE_GRADUAL_DIAG, + PAN_STROKE_GRADUAL_TRAN, + PAN_STROKE_GRADUAL_VERT, + PAN_STROKE_GRADUAL_HORZ, + PAN_STROKE_RAPID_VERT, + PAN_STROKE_RAPID_HORZ, + PAN_STROKE_INSTANT_VERT + } + + enum ArmStyle { + PAN_ANY, + PAN_NO_FIT, + PAN_STRAIGHT_ARMS_HORZ, + PAN_STRAIGHT_ARMS_WEDGE, + PAN_STRAIGHT_ARMS_VERT, + PAN_STRAIGHT_ARMS_SINGLE_SERIF, + PAN_STRAIGHT_ARMS_DOUBLE_SERIF, + PAN_BENT_ARMS_HORZ, + PAN_BENT_ARMS_WEDGE, + PAN_BENT_ARMS_VERT, + PAN_BENT_ARMS_SINGLE_SERIF, + PAN_BENT_ARMS_DOUBLE_SERIF + } + + enum Letterform { + PAN_ANY, + PAN_NO_FIT, + PAN_LETT_NORMAL_CONTACT, + PAN_LETT_NORMAL_WEIGHTED, + PAN_LETT_NORMAL_BOXED, + PAN_LETT_NORMAL_FLATTENED, + PAN_LETT_NORMAL_ROUNDED, + PAN_LETT_NORMAL_OFF_CENTER, + PAN_LETT_NORMAL_SQUARE, + PAN_LETT_OBLIQUE_CONTACT, + PAN_LETT_OBLIQUE_WEIGHTED, + PAN_LETT_OBLIQUE_BOXED, + PAN_LETT_OBLIQUE_FLATTENED, + PAN_LETT_OBLIQUE_ROUNDED, + PAN_LETT_OBLIQUE_OFF_CENTER, + PAN_LETT_OBLIQUE_SQUARE + } + + enum MidLine { + PAN_ANY, + PAN_NO_FIT, + PAN_MIDLINE_STANDARD_TRIMMED, + PAN_MIDLINE_STANDARD_POINTED, + PAN_MIDLINE_STANDARD_SERIFED, + PAN_MIDLINE_HIGH_TRIMMED, + PAN_MIDLINE_HIGH_POINTED, + PAN_MIDLINE_HIGH_SERIFED, + PAN_MIDLINE_CONSTANT_TRIMMED, + PAN_MIDLINE_CONSTANT_POINTED, + PAN_MIDLINE_CONSTANT_SERIFED, + PAN_MIDLINE_LOW_TRIMMED, + PAN_MIDLINE_LOW_POINTED, + PAN_MIDLINE_LOW_SERIFED + } + + enum XHeight { + PAN_ANY, + PAN_NO_FIT, + PAN_XHEIGHT_CONSTANT_SMALL, + PAN_XHEIGHT_CONSTANT_STD, + PAN_XHEIGHT_CONSTANT_LARGE, + PAN_XHEIGHT_DUCKING_SMALL, + PAN_XHEIGHT_DUCKING_STD, + PAN_XHEIGHT_DUCKING_LARGE + } + + protected int styleSize; + protected int vendorId; + protected int culture; + protected FamilyType familyType; + protected SerifType serifStyle; + protected FontWeight weight; + protected Proportion proportion; + protected Contrast contrast; + protected StrokeVariation strokeVariation; + protected ArmStyle armStyle; + protected Letterform letterform; + protected MidLine midLine; + protected XHeight xHeight; + } + + protected String fullname; + protected String style; + protected String script; + + protected LogFontDetails details; + + @Override + public int init(LittleEndianInputStream leis, long recordSize) throws IOException { + // A 32-bit signed integer that specifies the height, in logical units, of the font's + // character cell or character. The character height value, also known as the em size, is the + // character cell height value minus the internal leading value. The font mapper SHOULD + // interpret the value specified in the Height field in the following manner. + // + // 0x00000000 < value: + // The font mapper transforms this value into device units and matches it against + // the cell height of the available fonts. + // + // 0x00000000 + // The font mapper uses a default height value when it searches for a match. + // + // value < 0x00000000: + // The font mapper transforms this value into device units and matches its + // absolute value against the character height of the available fonts. + // + // For all height comparisons, the font mapper SHOULD look for the largest font that does not + // exceed the requested size. + height = leis.readInt(); + + // A 32-bit signed integer that specifies the average width, in logical units, of + // characters in the font. If the Width field value is zero, an appropriate value SHOULD be + // calculated from other LogFont values to find a font that has the typographer's intended + // aspect ratio. + width = leis.readInt(); + + // A 32-bit signed integer that specifies the angle, in tenths of degrees, + // between the escapement vector and the x-axis of the device. The escapement vector is + // parallel to the baseline of a row of text. + // + // When the graphics mode is set to GM_ADVANCED, the escapement angle of the string can + // be specified independently of the orientation angle of the string's characters. + escapement = leis.readInt(); + + // A 32-bit signed integer that specifies the angle, in tenths of degrees, + // between each character's baseline and the x-axis of the device. + orientation = leis.readInt(); + + // A 32-bit signed integer that specifies the weight of the font in the range zero through 1000. + // For example, 400 is normal and 700 is bold. If this value is zero, a default weight can be used. + weight = leis.readInt(); + + // An 8-bit unsigned integer that specifies an italic font if set to 0x01; + // otherwise, it MUST be set to 0x00. + italic = (leis.readUByte() == 0x01); + + // An 8-bit unsigned integer that specifies an underlined font if set to 0x01; + // otherwise, it MUST be set to 0x00. + underline = (leis.readUByte() == 0x01); + + // An 8-bit unsigned integer that specifies a strikeout font if set to 0x01; + // otherwise, it MUST be set to 0x00. + strikeOut = (leis.readUByte() == 0x01); + + // An 8-bit unsigned integer that specifies the set of character glyphs. + // It MUST be a value in the WMF CharacterSet enumeration. + // If the character set is unknown, metafile processing SHOULD NOT attempt + // to translate or interpret strings that are rendered with that font. + // If a typeface name is specified in the Facename field, the CharSet field + // value MUST match the character set of that typeface. + charSet = FontCharset.valueOf(leis.readUByte()); + + // An 8-bit unsigned integer that specifies the output precision. + // The output precision defines how closely the font is required to match the requested height, width, + // character orientation, escapement, pitch, and font type. + // It MUST be a value from the WMF OutPrecision enumeration. + // Applications can use the output precision to control how the font mapper chooses a font when the + // operating system contains more than one font with a specified name. For example, if an operating + // system contains a font named Symbol in rasterized and TrueType forms, an output precision value + // of OUT_TT_PRECIS forces the font mapper to choose the TrueType version. + // A value of OUT_TT_ONLY_PRECIS forces the font mapper to choose a TrueType font, even if it is + // necessary to substitute a TrueType font with another name. + outPrecision = WmfOutPrecision.valueOf(leis.readUByte()); + + // An 8-bit unsigned integer that specifies the clipping precision. + // The clipping precision defines how to clip characters that are partially outside the clipping region. + // It can be one or more of the WMF ClipPrecision Flags + clipPrecision.init(leis); + + // An 8-bit unsigned integer that specifies the output quality. The output quality defines how closely + // to attempt to match the logical-font attributes to those of an actual physical font. + // It MUST be one of the values in the WMF FontQuality enumeration + quality = WmfFontQuality.valueOf(leis.readUByte()); + + // A WMF PitchAndFamily object that specifies the pitch and family of the font. + // Font families describe the look of a font in a general way. + // They are intended for specifying a font when the specified typeface is not available. + pitchAndFamily = leis.readUByte(); + + int size = 5* LittleEndianConsts.INT_SIZE+8*LittleEndianConsts.BYTE_SIZE; + + StringBuilder sb = new StringBuilder(); + + // A string of no more than 32 Unicode characters that specifies the typeface name of the font. + // If the length of this string is less than 32 characters, a terminating NULL MUST be present, + // after which the remainder of this field MUST be ignored. + int readBytes = readString(leis, sb, 32); + if (readBytes == -1) { + throw new IOException("Font facename can't be determined."); + } + facename = sb.toString(); + size += readBytes; + + if (recordSize <= LOGFONT_SIZE) { + return size; + } + + // A string of 64 Unicode characters that contains the font's full name. + // Ifthe length of this string is less than 64 characters, a terminating + // NULL MUST be present, after which the remainder of this field MUST be ignored. + readBytes = readString(leis, sb, 64); + if (readBytes == -1) { + throw new IOException("Font fullname can't be determined."); + } + fullname = sb.toString(); + size += readBytes; + + // A string of 32 Unicode characters that defines the font's style. If the length of + // this string is less than 32 characters, a terminating NULL MUST be present, + // after which the remainder of this field MUST be ignored. + readBytes = readString(leis, sb, 32); + if (readBytes == -1) { + throw new IOException("Font style can't be determined."); + } + style = sb.toString(); + size += readBytes; + + if (recordSize == LOGFONTPANOSE_SIZE) { + // LogFontPanose Object + + LogFontPanose logPan = new LogFontPanose(); + details = logPan; + + int version = leis.readInt(); + + // A 32-bit unsigned integer that specifies the point size at which font + //hinting is performed. If set to zero, font hinting is performed at the point size corresponding + //to the Height field in the LogFont object in the LogFont field. + logPan.styleSize = (int)leis.readUInt(); + + int match = leis.readInt(); + + int reserved = leis.readInt(); + + logPan.vendorId = leis.readInt(); + + logPan.culture = leis.readInt(); + + // An 8-bit unsigned integer that specifies the family type. + // The value MUST be in the FamilyType enumeration table. + logPan.familyType = LogFontPanose.FamilyType.values()[leis.readUByte()]; + + // An 8-bit unsigned integer that specifies the serif style. + // The value MUST be in the SerifType enumeration table. + logPan.serifStyle = LogFontPanose.SerifType.values()[leis.readUByte()]; + + // An 8-bit unsigned integer that specifies the weight of the font. + // The value MUST be in the Weight enumeration table. + logPan.weight = LogFontPanose.FontWeight.values()[leis.readUByte()]; + + // An 8-bit unsigned integer that specifies the proportion of the font. + // The value MUST be in the Proportion enumeration table. + logPan.proportion = LogFontPanose.Proportion.values()[leis.readUByte()]; + + // An 8-bit unsigned integer that specifies the proportion of the font. + // The value MUST be in the Proportion enumeration table. + logPan.contrast = LogFontPanose.Contrast.values()[leis.readUByte()]; + + // An 8-bit unsigned integer that specifies the stroke variation for the font. + // The value MUST be in the StrokeVariation enumeration table. + logPan.strokeVariation = LogFontPanose.StrokeVariation.values()[leis.readUByte()]; + + // An 8-bit unsigned integer that specifies the arm style of the font. + // The value MUST be in the ArmStyle enumeration table. + logPan.armStyle = LogFontPanose.ArmStyle.values()[leis.readUByte()]; + + // An 8-bit unsigned integer that specifies the letterform of the font. + // The value MUST be in the Letterform enumeration table. + logPan.letterform = LogFontPanose.Letterform.values()[leis.readUByte()]; + + // An 8-bit unsigned integer that specifies the midline of the font. + // The value MUST be in the MidLine enumeration table. + logPan.midLine = LogFontPanose.MidLine.values()[leis.readUByte()]; + + // An 8-bit unsigned integer that specifies the x height of the font. + // The value MUST be in the XHeight enumeration table. + logPan.xHeight = LogFontPanose.XHeight.values()[leis.readUByte()]; + + // skip 2 byte to ensure 32-bit alignment of this structure. + leis.skip(2); + + size += 6*LittleEndianConsts.INT_SIZE+10* LittleEndianConsts.BYTE_SIZE+2; + } else { + // LogFontExDv Object + + LogFontExDv logEx = new LogFontExDv(); + details = logEx; + + // A string of 32 Unicode characters that defines the character set of the font. + // If the length of this string is less than 32 characters, a terminating NULL MUST be present, + // after which the remainder of this field MUST be ignored. + readBytes = readString(leis, sb, 32); + if (readBytes == -1) { + throw new IOException("Font script can't be determined."); + } + script = sb.toString(); + size += readBytes; + + // Design Vector + + // A 32-bit unsigned integer that MUST be set to the value 0x08007664. + int signature = leis.readInt(); + assert (signature == 0x08007664); + + // A 32-bit unsigned integer that specifies the number of elements in the + // Values array. It MUST be in the range 0 to 16, inclusive. + int numAxes = leis.readInt(); + assert (0 <= numAxes && numAxes <= 16); + + // An optional array of 32-bit signed integers that specify the values of the font axes of a + // multiple master, OpenType font. The maximum number of values in the array is 16. + if (numAxes > 0) { + logEx.designVector = new int[numAxes]; + for (int i=0; i= 100) { + size += readDimensionInt(leis, deviceDimension); + size += readDimensionInt(leis, milliDimension); + + if (size+12 <= recordSize) { hasExtension1 = true; - cbPixelFormat = LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE; - offPixelFormat = LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE; - bOpenGL= LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE; + cbPixelFormat = leis.readUInt(); + offPixelFormat = leis.readUInt(); + bOpenGL = leis.readUInt(); + size += 3*LittleEndianConsts.INT_SIZE; } - if (recordSize+8 >= 108) { + if (size+8 <= recordSize) { hasExtension2 = true; - micrometersX = LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE; - micrometersY = LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE; + size += readDimensionInt(leis, microDimension); } - return recordSize; + return size; } } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java new file mode 100644 index 0000000000..ab3d06bfe7 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java @@ -0,0 +1,447 @@ +/* ==================================================================== + 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 static org.apache.poi.hemf.record.emf.HemfDraw.readDimensionInt; +import static org.apache.poi.hemf.record.emf.HemfFill.readBitmap; +import static org.apache.poi.hemf.record.emf.HemfRecordIterator.HEADER_SIZE; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.poi.hemf.draw.HemfGraphics; +import org.apache.poi.hwmf.record.HwmfBinaryRasterOp; +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.HwmfHatchStyle; +import org.apache.poi.hwmf.record.HwmfMapMode; +import org.apache.poi.hwmf.record.HwmfMisc; +import org.apache.poi.hwmf.record.HwmfMisc.WmfSetBkMode; +import org.apache.poi.hwmf.record.HwmfPalette.PaletteEntry; +import org.apache.poi.hwmf.record.HwmfPenStyle; +import org.apache.poi.hwmf.record.HwmfPenStyle.HwmfLineDash; +import org.apache.poi.util.IOUtils; +import org.apache.poi.util.LittleEndianConsts; +import org.apache.poi.util.LittleEndianInputStream; + +public class HemfMisc { + private static final int MAX_RECORD_LENGTH = 10_000_000; + + public static class EmfEof implements HemfRecord { + protected final List palette = new ArrayList<>(); + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.eof; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + + // A 32-bit unsigned integer that specifies the number of palette entries. + 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(); + + 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= 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); + + return size; + } + } + + /** + * The EMF_SAVEDC record saves the playback device context for later retrieval. + */ + public static class EmfSaveDc implements HemfRecord { + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.saveDc; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + return 0; + } + + @Override + public void draw(HemfGraphics ctx) { + ctx.saveProperties(); + } + } + + /** + * The EMF_RESTOREDC record restores the playback device context from a previously saved device + * context. + */ + public static class EmfRestoreDc implements HemfRecord { + + /** + * SavedDC (4 bytes): A 32-bit signed integer that specifies the saved state to restore relative to + * the current state. This value MUST be negative; –1 represents the state that was most + * recently saved on the stack, –2 the one before that, etc. + */ + private int nSavedDC; + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.restoreDc; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + nSavedDC = leis.readInt(); + return LittleEndianConsts.INT_SIZE; + } + + @Override + public void draw(HemfGraphics ctx) { + ctx.restoreProperties(nSavedDC); + } + } + + /** + * The META_SETBKCOLOR record sets the background color in the playback device context to a + * specified color, or to the nearest physical color if the device cannot represent the specified color. + */ + public static class EmfSetBkColor implements HemfRecord { + + private HwmfColorRef colorRef; + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.setBkColor; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + colorRef = new HwmfColorRef(); + return colorRef.init(leis); + } + + @Override + public void draw(HemfGraphics ctx) { + ctx.getProperties().setBackgroundColor(colorRef); + } + } + + + /** + * The EMR_SETBKMODE record specifies the background mix mode of the playback device context. + * The background mix mode is used with text, hatched brushes, and pen styles that are not solid + * lines. + */ + public static class EmfSetBkMode extends WmfSetBkMode implements HemfRecord { + public HemfRecordType getEmfRecordType() { + return HemfRecordType.setBkMode; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + /* + * A 32-bit unsigned integer that specifies the background mode + * and MUST be in the BackgroundMode (section 2.1.4) enumeration + */ + bkMode = HwmfBkMode.valueOf((int)leis.readUInt()); + return LittleEndianConsts.INT_SIZE; + } + } + + /** + * The EMR_SETMAPPERFLAGS record specifies parameters of the process of matching logical fonts to + * physical fonts, which is performed by the font mapper. + */ + public static class EmfSetMapperFlags extends HwmfMisc.WmfSetMapperFlags implements HemfRecord { + public HemfRecordType getEmfRecordType() { + return HemfRecordType.setMapperFlags; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + return super.init(leis, recordSize, (int)recordId); + } + } + + /** + * The EMR_SETMAPMODE record specifies the mapping mode of the playback device context. The + * mapping mode specifies the unit of measure used to transform page space units into device space + * units, and also specifies the orientation of the device's x-axis and y-axis. + */ + public static class EmfSetMapMode extends HwmfMisc.WmfSetMapMode implements HemfRecord { + public HemfRecordType getEmfRecordType() { + return HemfRecordType.setMapMode; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + // A 32-bit unsigned integer whose definition MUST be in the MapMode enumeration + mapMode = HwmfMapMode.valueOf((int)leis.readUInt()); + return LittleEndianConsts.INT_SIZE; + } + } + + /** + * The EMR_SETROP2 record defines a binary raster operation mode. + */ + public static class EmfSetRop2 extends HwmfMisc.WmfSetRop2 implements HemfRecord { + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.setRop2; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + // A 32-bit unsigned integer that specifies the raster operation mode and + // MUST be in the WMF Binary Raster Op enumeration + drawMode = HwmfBinaryRasterOp.valueOf((int)leis.readUInt()); + return LittleEndianConsts.INT_SIZE; + } + } + + + /** + * The EMR_SETSTRETCHBLTMODE record specifies bitmap stretch mode. + */ + public static class EmfSetStretchBltMode extends HwmfMisc.WmfSetStretchBltMode implements HemfRecord { + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.setStretchBltMode; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + // A 32-bit unsigned integer that specifies the stretch mode and MAY be + // in the StretchMode enumeration. + stretchBltMode = StretchBltMode.valueOf((int)leis.readUInt()); + return LittleEndianConsts.INT_SIZE; + } + } + + /** The EMR_CREATEBRUSHINDIRECT record defines a logical brush for graphics operations. */ + public static class EmfCreateBrushIndirect extends HwmfMisc.WmfCreateBrushIndirect implements HemfRecord { + /** + * A 32-bit unsigned integer that specifies the index of the logical brush object in the + * EMF Object Table. This index MUST be saved so that this object can be reused or modified. + */ + private int brushIdx; + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.createBrushIndirect; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + brushIdx = (int)leis.readUInt(); + + brushStyle = HwmfBrushStyle.valueOf((int)leis.readUInt()); + colorRef = new HwmfColorRef(); + int size = colorRef.init(leis); + brushHatch = HwmfHatchStyle.valueOf((int)leis.readUInt()); + return size+3*LittleEndianConsts.INT_SIZE; + + } + } + + /** + * The EMR_DELETEOBJECT record deletes a graphics object, which is specified by its index + * in the EMF Object Table + */ + public static class EmfDeleteObject extends HwmfMisc.WmfDeleteObject implements HemfRecord { + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.deleteobject; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + objectIndex = (int)leis.readUInt(); + return LittleEndianConsts.INT_SIZE; + } + } + + /** The EMR_CREATEPEN record defines a logical pen for graphics operations. */ + public static class EmfCreatePen extends HwmfMisc.WmfCreatePenIndirect implements HemfRecord { + /** + * A 32-bit unsigned integer that specifies the index of the logical palette object + * in the EMF Object Table. This index MUST be saved so that this object can be + * reused or modified. + */ + protected int penIndex; + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.createPen; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + penIndex = (int)leis.readUInt(); + + // A 32-bit unsigned integer that specifies the PenStyle. + // The value MUST be defined from the PenStyle enumeration table + penStyle = HwmfPenStyle.valueOf((int)leis.readUInt()); + + int widthX = leis.readInt(); + int widthY = leis.readInt(); + dimension.setSize(widthX, widthY); + + int size = colorRef.init(leis); + + return size + 4*LittleEndianConsts.INT_SIZE; + } + } + + public static class EmfExtCreatePen extends EmfCreatePen { + protected HwmfBrushStyle brushStyle; + protected HwmfHatchStyle hatchStyle; + + protected int[] styleEntry; + + protected final HwmfBitmapDib bitmap = new HwmfBitmapDib(); + + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.extCreatePen; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + final int startIdx = leis.getReadIndex(); + + penIndex = (int)leis.readUInt(); + + // A 32-bit unsigned integer that specifies the offset from the start of this + // record to the DIB header, if the record contains a DIB. + int offBmi = (int)leis.readUInt(); + + // A 32-bit unsigned integer that specifies the size of the DIB header, if the + // record contains a DIB. + int cbBmi = (int)leis.readUInt(); + + // A 32-bit unsigned integer that specifies the offset from the start of this + // record to the DIB bits, if the record contains a DIB. + int offBits = (int)leis.readUInt(); + + // A 32-bit unsigned integer that specifies the size of the DIB bits, if the record + // contains a DIB. + int cbBits = (int)leis.readUInt(); + + // A 32-bit unsigned integer that specifies the PenStyle. + // The value MUST be defined from the PenStyle enumeration table + penStyle = HwmfPenStyle.valueOf((int)leis.readUInt()); + + // A 32-bit unsigned integer that specifies the width of the line drawn by the pen. + // If the pen type in the PenStyle field is PS_GEOMETRIC, this value is the width in logical + // units; otherwise, the width is specified in device units. If the pen type in the PenStyle field is + // PS_COSMETIC, this value MUST be 0x00000001. + long width = leis.readUInt(); + dimension.setSize(width, 0); + + // A 32-bit unsigned integer that specifies a brush style for the pen from the WMF BrushStyle enumeration + // + // If the pen type in the PenStyle field is PS_GEOMETRIC, this value MUST be either BS_SOLID or BS_HATCHED. + // The value of this field can be BS_NULL, but only if the line style specified in PenStyle is PS_NULL. + // 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 += colorRef.init(leis); + + hatchStyle = HwmfHatchStyle.valueOf(leis.readInt()); + + // 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; + + assert(numStyleEntries == 0 || penStyle.getLineDash() == HwmfLineDash.USERSTYLE); + + // An optional array of 32-bit unsigned integers that defines the lengths of + // dashes and gaps in the line drawn by this pen, when the value of PenStyle is + // PS_USERSTYLE line style for the pen. The array contains a number of entries specified by + // NumStyleEntries, but it is used as if it repeated indefinitely. + // The first entry in the array specifies the length of the first dash. The second entry specifies + // the length of the first gap. Thereafter, lengths of dashes and gaps alternate. + // If the pen type in the PenStyle field is PS_GEOMETRIC, the lengths are specified in logical + // units; otherwise, the lengths are specified in device units. + + styleEntry = new int[numStyleEntries]; + + for (int i=0; i { + + static final int HEADER_SIZE = 2*LittleEndianConsts.INT_SIZE; + + private final LittleEndianInputStream stream; + private HemfRecord currentRecord; + + public HemfRecordIterator(LittleEndianInputStream leis) { + stream = leis; + //queue the first non-header record + currentRecord = _next(); + } + + @Override + public boolean hasNext() { + return currentRecord != null; + } + + @Override + public HemfRecord next() { + HemfRecord toReturn = currentRecord; + currentRecord = (currentRecord instanceof HemfMisc.EmfEof) ? null : _next(); + return toReturn; + } + + private HemfRecord _next() { + if (currentRecord != null && HemfRecordType.eof == currentRecord.getEmfRecordType()) { + return null; + } + long recordId = stream.readUInt(); + long recordSize = stream.readUInt(); + + HemfRecordType type = HemfRecordType.getById(recordId); + if (type == null) { + throw new RecordFormatException("Undefined record of type:"+recordId); + } + final HemfRecord record = type.constructor.get(); + + try { + long remBytes = recordSize-HEADER_SIZE; + long readBytes = record.init(stream, remBytes, recordId); + assert (readBytes <= remBytes); + stream.skipFully((int)(remBytes-readBytes)); + } catch (IOException|RuntimeException e) { + throw new RecordFormatException(e); + } + + return record; + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Remove not supported"); + } + +} \ No newline at end of file diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java new file mode 100644 index 0000000000..0995291005 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java @@ -0,0 +1,165 @@ +/* ==================================================================== + 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.util.function.Supplier; + +import org.apache.poi.util.Internal; + +@Internal +public enum HemfRecordType { + + header(0x00000001, HemfHeader::new), + polyBezier(0x00000002, HemfDraw.EmfPolyBezier::new), + polygon(0x00000003, HemfDraw.EmfPolygon::new), + polyline(0x00000004, HemfDraw.EmfPolyline::new), + polyBezierTo(0x00000005, HemfDraw.EmfPolyBezierTo::new), + polylineTo(0x00000006, HemfDraw.EmfPolylineTo::new), + polyPolyline(0x00000007, HemfDraw.EmfPolyPolyline::new), + polyPolygon(0x00000008, HemfDraw.EmfPolyPolygon::new), + setWindowExtEx(0x00000009, HemfWindowing.EmfSetWindowExtEx::new), + setWindowOrgEx(0x0000000A, HemfWindowing.EmfSetWindowOrgEx::new), + setViewportExtEx(0x0000000B, HemfWindowing.EmfSetViewportExtEx::new), + setViewportOrgEx(0x0000000C, HemfWindowing.EmfSetViewportOrgEx::new), + setbrushorgex(0x0000000D, UnimplementedHemfRecord::new), + eof(0x0000000E, HemfMisc.EmfEof::new), + setPixelV(0x0000000F, HemfDraw.EmfSetPixelV::new), + setMapperFlags(0x00000010, HemfMisc.EmfSetMapperFlags::new), + setMapMode(0x00000011, HemfMisc.EmfSetMapMode::new), + setBkMode(0x00000012, HemfMisc.EmfSetBkMode::new), + setPolyfillMode(0x00000013, HemfFill.EmfSetPolyfillMode::new), + setRop2(0x00000014, HemfMisc.EmfSetRop2::new), + setStretchBltMode(0x00000015, HemfMisc.EmfSetStretchBltMode::new), + setTextAlign(0x00000016, HemfText.EmfSetTextAlign::new), + setcoloradjustment(0x00000017, UnimplementedHemfRecord::new), + setTextColor(0x00000018, HemfText.SetTextColor::new), + setBkColor(0x00000019, HemfMisc.EmfSetBkColor::new), + setOffsetClipRgn(0x0000001A, HemfWindowing.EmfSetOffsetClipRgn::new), + setMoveToEx(0x0000001B, HemfDraw.EmfSetMoveToEx::new), + setmetargn(0x0000001C, UnimplementedHemfRecord::new), + setExcludeClipRect(0x0000001D, HemfWindowing.EmfSetExcludeClipRect::new), + setIntersectClipRect(0x0000001E, HemfWindowing.EmfSetIntersectClipRect::new), + scaleViewportExtEx(0x0000001F, HemfWindowing.EmfScaleViewportExtEx::new), + scaleWindowExtEx(0x00000020, HemfWindowing.EmfScaleWindowExtEx::new), + saveDc(0x00000021, HemfMisc.EmfSaveDc::new), + restoreDc(0x00000022, HemfMisc.EmfRestoreDc::new), + setworldtransform(0x00000023, UnimplementedHemfRecord::new), + modifyworldtransform(0x00000024, UnimplementedHemfRecord::new), + selectObject(0x00000025, HemfDraw.EmfSelectObject::new), + createPen(0x00000026, HemfMisc.EmfCreatePen::new), + createBrushIndirect(0x00000027, HemfMisc.EmfCreateBrushIndirect::new), + deleteobject(0x00000028, HemfMisc.EmfDeleteObject::new), + anglearc(0x00000029, UnimplementedHemfRecord::new), + ellipse(0x0000002A, HemfDraw.EmfEllipse::new), + rectangle(0x0000002B, HemfDraw.EmfRectangle::new), + roundRect(0x0000002C, HemfDraw.EmfRoundRect::new), + arc(0x0000002D, HemfDraw.EmfArc::new), + chord(0x0000002E, HemfDraw.EmfChord::new), + pie(0x0000002F, HemfDraw.EmfPie::new), + selectPalette(0x00000030, HemfPalette.EmfSelectPalette::new), + createPalette(0x00000031, HemfPalette.EmfCreatePalette::new), + setPaletteEntries(0x00000032, HemfPalette.EmfSetPaletteEntries::new), + resizePalette(0x00000033, HemfPalette.EmfResizePalette::new), + realizePalette(0x0000034, HemfPalette.EmfRealizePalette::new), + extFloodFill(0x00000035, HemfFill.EmfExtFloodFill::new), + lineTo(0x00000036, HemfDraw.EmfLineTo::new), + arcTo(0x00000037, HemfDraw.EmfArcTo::new), + polyDraw(0x00000038, HemfDraw.EmfPolyDraw::new), + setarcdirection(0x00000039, UnimplementedHemfRecord::new), + setMiterLimit(0x0000003A, HemfMisc.EmfSetMiterLimit::new), + beginpath(0x0000003B, UnimplementedHemfRecord::new), + endpath(0x0000003C, UnimplementedHemfRecord::new), + closefigure(0x0000003D, UnimplementedHemfRecord::new), + fillpath(0x0000003E, UnimplementedHemfRecord::new), + strokeandfillpath(0x0000003F, UnimplementedHemfRecord::new), + strokepath(0x00000040, UnimplementedHemfRecord::new), + flattenpath(0x00000041, UnimplementedHemfRecord::new), + widenpath(0x00000042, UnimplementedHemfRecord::new), + selectclippath(0x00000043, UnimplementedHemfRecord::new), + abortpath(0x00000044, UnimplementedHemfRecord::new), + // no 45 ?! + comment(0x00000046, HemfComment.EmfComment::new), + fillRgn(0x00000047, HemfFill.EmfFillRgn::new), + frameRgn(0x00000048, HemfFill.EmfFrameRgn::new), + invertRgn(0x00000049, HemfFill.EmfInvertRgn::new), + paintRgn(0x0000004A, HemfFill.EmfPaintRgn::new), + extSelectClipRgn(0x0000004B, HemfFill.EmfExtSelectClipRgn::new), + bitBlt(0x0000004C, HemfFill.EmfBitBlt::new), + stretchBlt(0x0000004D, HemfFill.EmfStretchBlt::new), + maskblt(0x0000004E, UnimplementedHemfRecord::new), + plgblt(0x0000004F, UnimplementedHemfRecord::new), + setDiBitsToDevice(0x00000050, HemfFill.EmfSetDiBitsToDevice::new), + stretchdibits(0x00000051, UnimplementedHemfRecord::new), + extCreateFontIndirectW(0x00000052, HemfText.ExtCreateFontIndirectW::new), + exttextouta(0x00000053, HemfText.ExtTextOutA::new), + exttextoutw(0x00000054, HemfText.ExtTextOutW::new), + polyBezier16(0x00000055, HemfDraw.EmfPolyBezier16::new), + polygon16(0x00000056, HemfDraw.EmfPolygon16::new), + polyline16(0x00000057, HemfDraw.EmfPolyline16::new), + polyBezierTo16(0x00000058, HemfDraw.EmfPolyBezierTo16::new), + polylineTo16(0x00000059, HemfDraw.EmfPolylineTo16::new), + polyPolyline16(0x0000005A, HemfDraw.EmfPolyPolyline16::new), + polyPolygon16(0x0000005B, HemfDraw.EmfPolyPolygon16::new), + polyDraw16(0x0000005C, HemfDraw.EmfPolyDraw16::new), + createmonobrush16(0x0000005D, UnimplementedHemfRecord::new), + createdibpatternbrushpt(0x0000005E, UnimplementedHemfRecord::new), + extCreatePen(0x0000005F, HemfMisc.EmfExtCreatePen::new), + polytextouta(0x00000060, HemfText.PolyTextOutA::new), + polytextoutw(0x00000061, HemfText.PolyTextOutW::new), + seticmmode(0x00000062, UnimplementedHemfRecord::new), + createcolorspace(0x00000063, UnimplementedHemfRecord::new), + setcolorspace(0x00000064, UnimplementedHemfRecord::new), + deletecolorspace(0x00000065, UnimplementedHemfRecord::new), + glsrecord(0x00000066, UnimplementedHemfRecord::new), + glsboundedrecord(0x00000067, UnimplementedHemfRecord::new), + pixelformat(0x00000068, UnimplementedHemfRecord::new), + drawescape(0x00000069, UnimplementedHemfRecord::new), + extescape(0x0000006A, UnimplementedHemfRecord::new), + // no 6b ?! + smalltextout(0x0000006C, UnimplementedHemfRecord::new), + forceufimapping(0x0000006D, UnimplementedHemfRecord::new), + namedescape(0x0000006E, UnimplementedHemfRecord::new), + colorcorrectpalette(0x0000006F, UnimplementedHemfRecord::new), + seticmprofilea(0x00000070, UnimplementedHemfRecord::new), + seticmprofilew(0x00000071, UnimplementedHemfRecord::new), + alphaBlend(0x00000072, HemfFill.EmfAlphaBlend::new), + setlayout(0x00000073, UnimplementedHemfRecord::new), + transparentblt(0x00000074, UnimplementedHemfRecord::new), + // no 75 ?! + gradientfill(0x00000076, UnimplementedHemfRecord::new), + setlinkdufis(0x00000077, UnimplementedHemfRecord::new), + settextjustification(0x00000078, HemfText.SetTextJustification::new), + colormatchtargetw(0x00000079, UnimplementedHemfRecord::new), + createcolorspacew(0x0000007A, UnimplementedHemfRecord::new); + + + public final long id; + public final Supplier constructor; + + HemfRecordType(long id, Supplier constructor) { + this.id = id; + this.constructor = constructor; + } + + public static HemfRecordType getById(long id) { + for (HemfRecordType wrt : values()) { + if (wrt.id == id) return wrt; + } + return null; + } +} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java new file mode 100644 index 0000000000..95c110df10 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java @@ -0,0 +1,317 @@ +/* ==================================================================== + 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 static java.nio.charset.StandardCharsets.UTF_16LE; +import static org.apache.poi.hemf.record.emf.HemfDraw.readRectL; +import static org.apache.poi.hemf.record.emf.HemfDraw.readDimensionFloat; +import static org.apache.poi.hemf.record.emf.HemfDraw.readPointL; + +import java.awt.geom.Dimension2D; +import java.awt.geom.Rectangle2D; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; + +import org.apache.poi.hwmf.record.HwmfColorRef; +import org.apache.poi.hwmf.record.HwmfText; +import org.apache.poi.hwmf.record.HwmfText.WmfSetTextAlign; +import org.apache.poi.util.Dimension2DDouble; +import org.apache.poi.util.IOUtils; +import org.apache.poi.util.Internal; +import org.apache.poi.util.LittleEndianConsts; +import org.apache.poi.util.LittleEndianInputStream; +import org.apache.poi.util.RecordFormatException; + +/** + * Container class to gather all text-related commands + * This is starting out as read only, and very little is actually + * implemented at this point! + */ +@Internal +public class HemfText { + + private static final int MAX_RECORD_LENGTH = 1_000_000; + + public enum EmfGraphicsMode { + GM_COMPATIBLE, GM_ADVANCED + } + + public static class ExtTextOutA implements HemfRecord { + + protected final Rectangle2D boundsIgnored = new Rectangle2D.Double(); + + protected EmfGraphicsMode graphicsMode; + + /** + * The scale factor to apply along the X/Y axis to convert from page space units to .01mm units. + * This SHOULD be used only if the graphics mode specified by iGraphicsMode is GM_COMPATIBLE. + */ + protected final Dimension2D scale = new Dimension2DDouble(); + + protected final EmrTextObject textObject; + + public ExtTextOutA() { + this(false); + } + + protected ExtTextOutA(boolean isUnicode) { + textObject = new EmrTextObject(isUnicode); + } + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.exttextouta; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + if (recordSize < 0 || Integer.MAX_VALUE <= recordSize) { + throw new RecordFormatException("recordSize must be a positive integer (0-0x7FFFFFFF)"); + } + + // A WMF RectL object. It is not used and MUST be ignored on receipt. + long size = readRectL(leis, boundsIgnored); + + // A 32-bit unsigned integer that specifies the graphics mode from the GraphicsMode enumeration + graphicsMode = EmfGraphicsMode.values()[leis.readInt()-1]; + size += LittleEndianConsts.INT_SIZE; + + size += readDimensionFloat(leis, scale); + + // guarantee to read the rest of the EMRTextObjectRecord + size += textObject.init(leis, recordSize, (int)size); + + return size; + } + + /** + * + * To be implemented! We need to get the current character set + * from the current font for {@link ExtTextOutA}, + * which has to be tracked in the playback device. + * + * For {@link ExtTextOutW}, the charset is "UTF-16LE" + * + * @param charset the charset to be used to decode the character bytes + * @return text from this text element + * @throws IOException + */ + public String getText(Charset charset) throws IOException { + return textObject.getText(charset); + } + + /** + * + * @return the x offset for the EmrTextObject + */ + public EmrTextObject getTextObject() { + return textObject; + } + + public EmfGraphicsMode getGraphicsMode() { + return graphicsMode; + } + + public Dimension2D getScale() { + return scale; + } + } + + public static class ExtTextOutW extends ExtTextOutA { + + public ExtTextOutW() { + super(true); + } + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.exttextoutw; + } + + public String getText() throws IOException { + return getText(UTF_16LE); + } + } + + /** + * The EMR_SETTEXTALIGN record specifies text alignment. + */ + public static class EmfSetTextAlign extends WmfSetTextAlign implements HemfRecord { + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.setTextAlign; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + /** + * A 32-bit unsigned integer that specifies text alignment by using a mask of text alignment flags. + * These are either WMF TextAlignmentMode Flags for text with a horizontal baseline, + * or WMF VerticalTextAlignmentMode Flags for text with a vertical baseline. + * Only one value can be chosen from those that affect horizontal and vertical alignment. + */ + textAlignmentMode = (int)leis.readUInt(); + return LittleEndianConsts.INT_SIZE; + } + } + + /** + * The EMR_SETTEXTCOLOR record defines the current text color. + */ + public static class SetTextColor implements HemfRecord { + /** A WMF ColorRef object that specifies the text color value. */ + private final HwmfColorRef colorRef = new HwmfColorRef(); + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.setTextColor; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + return colorRef.init(leis); + } + } + + public static class EmrTextObject extends HwmfText.WmfExtTextOut { + protected final boolean isUnicode; + protected final List outputDx = new ArrayList<>(); + + public EmrTextObject(boolean isUnicode) { + super(new EmfExtTextOutOptions()); + this.isUnicode = isUnicode; + } + + @Override + public int init(LittleEndianInputStream leis, final long recordSize, final int offset) throws IOException { + // A WMF PointL object that specifies the coordinates of the reference point used to position the string. + // The reference point is defined by the last EMR_SETTEXTALIGN record. + // If no such record has been set, the default alignment is TA_LEFT,TA_TOP. + long size = readPointL(leis, reference); + // A 32-bit unsigned integer that specifies the number of characters in the string. + stringLength = (int)leis.readUInt(); + // A 32-bit unsigned integer that specifies the offset to the output string, in bytes, + // from the start of the record in which this object is contained. + // This value MUST be 8- or 16-bit aligned, according to the character format. + int offString = (int)leis.readUInt(); + size += 2*LittleEndianConsts.INT_SIZE; + + size += options.init(leis); + // An optional WMF RectL object that defines a clipping and/or opaquing rectangle in logical units. + // This rectangle is applied to the text output performed by the containing record. + if (options.isClipped() || options.isOpaque()) { + size += readRectL(leis, bounds); + } + + // A 32-bit unsigned integer that specifies the offset to an intercharacter spacing array, in bytes, + // from the start of the record in which this object is contained. This value MUST be 32-bit aligned. + int offDx = (int)leis.readUInt(); + size += LittleEndianConsts.INT_SIZE; + + int undefinedSpace1 = (int)(offString-offset-size-2*LittleEndianConsts.INT_SIZE); + assert (undefinedSpace1 >= 0); + leis.skipFully(undefinedSpace1); + size += undefinedSpace1; + + rawTextBytes = IOUtils.safelyAllocate(stringLength*(isUnicode?2:1), MAX_RECORD_LENGTH); + leis.readFully(rawTextBytes); + size += rawTextBytes.length; + + outputDx.clear(); + if (offDx > 0) { + int undefinedSpace2 = (int) (offDx - offset - size - 2 * LittleEndianConsts.INT_SIZE); + assert (undefinedSpace2 >= 0); + leis.skipFully(undefinedSpace2); + size += undefinedSpace2; + + // An array of 32-bit unsigned integers that specify the output spacing between the origins of adjacent + // character cells in logical units. The location of this field is specified by the value of offDx + // in bytes from the start of this record. If spacing is defined, this field contains the same number + // of values as characters in the output string. + // + // If the Options field of the EmrText object contains the ETO_PDY flag, then this buffer + // contains twice as many values as there are characters in the output string, one + // horizontal and one vertical offset for each, in that order. + // + // If ETO_RTLREADING is specified, characters are laid right to left instead of left to right. + // No other options affect the interpretation of this field. + while (size < recordSize) { + outputDx.add((int) leis.readUInt()); + size += LittleEndianConsts.INT_SIZE; + } + } + + return (int)size; + } + } + + + public static class ExtCreateFontIndirectW extends HwmfText.WmfCreateFontIndirect + implements HemfRecord { + int fontIdx; + + public ExtCreateFontIndirectW() { + super(new HemfFont()); + } + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.extCreateFontIndirectW; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + // A 32-bit unsigned integer that specifies the index of the logical font object + // in the EMF Object Table + fontIdx = (int)leis.readUInt(); + int size = font.init(leis, (int)(recordSize-LittleEndianConsts.INT_SIZE)); + return size+LittleEndianConsts.INT_SIZE; + } + } + + public static class EmfExtTextOutOptions extends HwmfText.WmfExtTextOutOptions { + @Override + public int init(LittleEndianInputStream leis) { + // A 32-bit unsigned integer that specifies how to use the rectangle specified in the Rectangle field. + // This field can be a combination of more than one ExtTextOutOptions enumeration + flag = (int)leis.readUInt(); + return LittleEndianConsts.INT_SIZE; + } + } + + public static class SetTextJustification extends UnimplementedHemfRecord { + + } + + /** + * Needs to be implemented. Couldn't find example. + */ + public static class PolyTextOutA extends UnimplementedHemfRecord { + + } + + /** + * Needs to be implemented. Couldn't find example. + */ + public static class PolyTextOutW extends UnimplementedHemfRecord { + + } + +} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java new file mode 100644 index 0000000000..bd48f1b39d --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java @@ -0,0 +1,200 @@ +/* ==================================================================== + 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.io.IOException; + +import org.apache.poi.hwmf.record.HwmfWindowing; +import org.apache.poi.util.LittleEndianConsts; +import org.apache.poi.util.LittleEndianInputStream; + +public class HemfWindowing { + + /** + * The EMR_SETWINDOWEXTEX record defines the window extent. + */ + public static class EmfSetWindowExtEx extends HwmfWindowing.WmfSetWindowExt implements HemfRecord { + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.setWindowExtEx; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + // cx (4 bytes): A 32-bit unsigned integer that defines the x-coordinate of the point. + width = (int)leis.readUInt(); + // cy (4 bytes): A 32-bit unsigned integer that defines the y-coordinate of the point. + height = (int)leis.readUInt(); + + return 2*LittleEndianConsts.INT_SIZE; + } + } + + /** + * The EMR_SETWINDOWORGEX record defines the window origin. + */ + public static class EmfSetWindowOrgEx extends HwmfWindowing.WmfSetWindowOrg implements HemfRecord { + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.setWindowOrgEx; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + // x (4 bytes): A 32-bit signed integer that defines the horizontal (x) coordinate of the point. + x = leis.readInt(); + // y (4 bytes): A 32-bit signed integer that defines the vertical (y) coordinate of the point. + y = leis.readInt(); + + return 2*LittleEndianConsts.INT_SIZE; + } + } + + /** + * The EMR_SETVIEWPORTEXTEX record defines the viewport extent. + */ + public static class EmfSetViewportExtEx extends HwmfWindowing.WmfSetViewportExt implements HemfRecord { + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.setViewportExtEx; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + // cx (4 bytes): A 32-bit unsigned integer that defines the x-coordinate of the point. + width = (int)leis.readUInt(); + // cy (4 bytes): A 32-bit unsigned integer that defines the y-coordinate of the point. + height = (int)leis.readUInt(); + + return 2*LittleEndianConsts.INT_SIZE; + } + } + + /** + * The EMR_SETVIEWPORTORGEX record defines the viewport origin. + */ + public static class EmfSetViewportOrgEx extends HwmfWindowing.WmfSetViewportOrg implements HemfRecord { + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.setViewportOrgEx; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + // x (4 bytes): A 32-bit signed integer that defines the horizontal (x) coordinate of the point. + x = leis.readInt(); + // y (4 bytes): A 32-bit signed integer that defines the vertical (y) coordinate of the point. + y = leis.readInt(); + + return 2*LittleEndianConsts.INT_SIZE; + } + } + + /** + * The EMR_OFFSETCLIPRGN record moves the current clipping region in the playback device context + * by the specified offsets. + */ + public static class EmfSetOffsetClipRgn extends HwmfWindowing.WmfOffsetClipRgn implements HemfRecord { + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.setOffsetClipRgn; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + // x (4 bytes): A 32-bit signed integer that defines the horizontal (x) coordinate of the point. + xOffset = leis.readInt(); + // y (4 bytes): A 32-bit signed integer that defines the vertical (y) coordinate of the point. + yOffset = leis.readInt(); + + return 2*LittleEndianConsts.INT_SIZE; + } + } + + /** + * The EMR_EXCLUDECLIPRECT record specifies a new clipping region that consists of the existing + * clipping region minus the specified rectangle. + */ + public static class EmfSetExcludeClipRect extends HwmfWindowing.WmfExcludeClipRect implements HemfRecord { + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.setExcludeClipRect; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + return HemfDraw.readRectL(leis, bounds); + } + } + + /** + * The EMR_INTERSECTCLIPRECT record specifies a new clipping region from the intersection of the + * current clipping region and the specified rectangle. + */ + public static class EmfSetIntersectClipRect extends HwmfWindowing.WmfIntersectClipRect implements HemfRecord { + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.setIntersectClipRect; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + return HemfDraw.readRectL(leis, bounds); + } + } + + /** + * The EMR_SCALEVIEWPORTEXTEX record respecifies the viewport for a device context by using the + * ratios formed by the specified multiplicands and divisors. + */ + public static class EmfScaleViewportExtEx extends HwmfWindowing.WmfScaleViewportExt implements HemfRecord { + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.scaleViewportExtEx; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + xNum = leis.readInt(); + xDenom = leis.readInt(); + yNum = leis.readInt(); + yDenom = leis.readInt(); + return 4*LittleEndianConsts.INT_SIZE; + } + } + + /** + * The EMR_SCALEWINDOWEXTEX record respecifies the window for a playback device context by + * using the ratios formed by the specified multiplicands and divisors. + */ + public static class EmfScaleWindowExtEx extends HwmfWindowing.WmfScaleWindowExt implements HemfRecord { + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.scaleWindowExtEx; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + xNum = leis.readInt(); + xDenom = leis.readInt(); + yNum = leis.readInt(); + yDenom = leis.readInt(); + return 4*LittleEndianConsts.INT_SIZE; + } + } +} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/UnimplementedHemfRecord.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/UnimplementedHemfRecord.java similarity index 89% rename from src/scratchpad/src/org/apache/poi/hemf/record/UnimplementedHemfRecord.java rename to src/scratchpad/src/org/apache/poi/hemf/record/emf/UnimplementedHemfRecord.java index 6f3ded48d5..e66e2b40d8 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/UnimplementedHemfRecord.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/UnimplementedHemfRecord.java @@ -15,7 +15,7 @@ limitations under the License. ==================================================================== */ -package org.apache.poi.hemf.record; +package org.apache.poi.hemf.record.emf; import java.io.IOException; @@ -33,12 +33,12 @@ public class UnimplementedHemfRecord implements HemfRecord { } @Override - public HemfRecordType getRecordType() { + public HemfRecordType getEmfRecordType() { return HemfRecordType.getById(recordId); } @Override - public long init(LittleEndianInputStream leis, long recordId, long recordSize) throws IOException { + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { this.recordId = recordId; long skipped = IOUtils.skipFully(leis, recordSize); if (skipped < recordSize) { diff --git a/src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusHeader.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusHeader.java similarity index 75% rename from src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusHeader.java rename to src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusHeader.java index 25947937b4..72a8d31528 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusHeader.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusHeader.java @@ -15,13 +15,14 @@ limitations under the License. ==================================================================== */ -package org.apache.poi.hemf.hemfplus.record; +package org.apache.poi.hemf.record.emfplus; import java.io.IOException; import org.apache.poi.util.Internal; -import org.apache.poi.util.LittleEndian; +import org.apache.poi.util.LittleEndianConsts; +import org.apache.poi.util.LittleEndianInputStream; @Internal public class HemfPlusHeader implements HemfPlusRecord { @@ -42,15 +43,19 @@ public class HemfPlusHeader implements HemfPlusRecord { } @Override - public void init(byte[] dataBytes, int recordId, int flags) throws IOException { - //assert record id == header + public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException { this.flags = flags; - int offset = 0; - this.version = LittleEndian.getUInt(dataBytes, offset); offset += LittleEndian.INT_SIZE; - this.emfPlusFlags = LittleEndian.getUInt(dataBytes, offset); offset += LittleEndian.INT_SIZE; - this.logicalDpiX = LittleEndian.getUInt(dataBytes, offset); offset += LittleEndian.INT_SIZE; - this.logicalDpiY = LittleEndian.getUInt(dataBytes, offset); + version = leis.readUInt(); + // verify MetafileSignature (20 bits) == 0xDBC01 and + // GraphicsVersion (12 bits) in (1 or 2) + assert((version & 0xFFFFFA00) == 0xDBC01000L && ((version & 0x3FF) == 1 || (version & 0x3FF) == 2)); + + emfPlusFlags = leis.readUInt(); + + logicalDpiX = leis.readUInt(); + logicalDpiY = leis.readUInt(); + return 4* LittleEndianConsts.INT_SIZE; } public long getVersion() { @@ -79,4 +84,4 @@ public class HemfPlusHeader implements HemfPlusRecord { ", logicalDpiY=" + logicalDpiY + '}'; } -} +} \ No newline at end of file diff --git a/src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusRecord.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecord.java similarity index 65% rename from src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusRecord.java rename to src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecord.java index 09af3d5cf2..c53db9855b 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusRecord.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecord.java @@ -15,12 +15,14 @@ limitations under the License. ==================================================================== */ -package org.apache.poi.hemf.hemfplus.record; +package org.apache.poi.hemf.record.emfplus; import java.io.IOException; +import org.apache.poi.hemf.record.emf.HemfRecordType; import org.apache.poi.util.Internal; +import org.apache.poi.util.LittleEndianInputStream; @Internal public interface HemfPlusRecord { @@ -30,15 +32,17 @@ public interface HemfPlusRecord { int getFlags(); /** + * Init record from stream * - * @param dataBytes these are the bytes that start after the id, flags, record size - * and go to the end of the record; they do not include any required padding - * at the end. - * @param recordId record type id - * @param flags flags - * @throws IOException, RecordFormatException + * @param leis the little endian input stream + * @param dataSize the size limit for this record + * @param recordId the id of the {@link HemfPlusRecordType} + * @param flags the record flags + * + * @return count of processed bytes + * + * @throws IOException when the inputstream is malformed */ - void init(byte[] dataBytes, int recordId, int flags) throws IOException; - + long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException; } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecordIterator.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecordIterator.java new file mode 100644 index 0000000000..a3b56fa8ab --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecordIterator.java @@ -0,0 +1,98 @@ +/* ==================================================================== + 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.emfplus; + +import java.io.IOException; +import java.util.Iterator; + +import org.apache.poi.util.LittleEndianInputStream; +import org.apache.poi.util.RecordFormatException; + +public class HemfPlusRecordIterator implements Iterator { + + private final LittleEndianInputStream leis; + private final int startIdx; + private final int limit; + private HemfPlusRecord currentRecord; + + public HemfPlusRecordIterator(LittleEndianInputStream leis) { + this(leis, -1); + } + + public HemfPlusRecordIterator(LittleEndianInputStream leis, int limit) { + this.leis = leis; + this.limit = limit; + startIdx = leis.getReadIndex(); + //queue the first non-header record + currentRecord = _next(); + } + + @Override + public boolean hasNext() { + return currentRecord != null; + } + + @Override + public HemfPlusRecord next() { + HemfPlusRecord toReturn = currentRecord; + final boolean isEOF = (limit == -1 || leis.getReadIndex()-startIdx >= limit); + // (currentRecord instanceof HemfPlusMisc.EmfEof) + currentRecord = isEOF ? null : _next(); + return toReturn; + } + + private HemfPlusRecord _next() { + if (currentRecord != null && HemfPlusRecordType.eof == currentRecord.getRecordType()) { + return null; + } + // A 16-bit unsigned integer that identifies this record type + int recordId = leis.readUShort(); + // A 16-bit unsigned integer that provides information about how the operation is + // to be performed, and about the structure of the record. + int flags = leis.readUShort(); + // A 32-bit unsigned integer that specifies the 32-bit-aligned size of the entire + // record in bytes, including the 12-byte record header and record-specific data. + int recordSize = (int)leis.readUInt(); + // A 32-bit unsigned integer that specifies the 32-bit-aligned number of bytes of data + // in the record-specific data that follows. This number does not include the size of + // the invariant part of this record. + int dataSize = (int)leis.readUInt(); + + HemfPlusRecordType type = HemfPlusRecordType.getById(recordId); + if (type == null) { + throw new RecordFormatException("Undefined record of type:"+recordId); + } + final HemfPlusRecord record = type.constructor.get(); + + try { + long readBytes = record.init(leis, dataSize, recordId, flags); + assert (readBytes <= recordSize-12); + leis.skipFully((int)(recordSize-12-readBytes)); + } catch (IOException e) { + throw new RecordFormatException(e); + } + + return record; + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Remove not supported"); + } + +} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecordType.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecordType.java new file mode 100644 index 0000000000..2fc1926ff6 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecordType.java @@ -0,0 +1,100 @@ +/* ==================================================================== + 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.emfplus; + +import java.util.function.Supplier; + +import org.apache.poi.util.Internal; + +@Internal +public enum HemfPlusRecordType { + header(0x4001, HemfPlusHeader::new), + eof(0x4002, UnimplementedHemfPlusRecord::new), + comment(0x4003, UnimplementedHemfPlusRecord::new), + getDC(0x4004, UnimplementedHemfPlusRecord::new), + multiFormatStart(0x4005, UnimplementedHemfPlusRecord::new), + multiFormatSection(0x4006, UnimplementedHemfPlusRecord::new), + multiFormatEnd(0x4007, UnimplementedHemfPlusRecord::new), + object(0x4008, UnimplementedHemfPlusRecord::new), + clear(0x4009, UnimplementedHemfPlusRecord::new), + fillRects(0x400A, UnimplementedHemfPlusRecord::new), + drawRects(0x400B, UnimplementedHemfPlusRecord::new), + fillPolygon(0x400C, UnimplementedHemfPlusRecord::new), + drawLines(0x400D, UnimplementedHemfPlusRecord::new), + fillEllipse(0x400E, UnimplementedHemfPlusRecord::new), + drawEllipse(0x400F, UnimplementedHemfPlusRecord::new), + fillPie(0x4010, UnimplementedHemfPlusRecord::new), + drawPie(0x4011, UnimplementedHemfPlusRecord::new), + drawArc(0x4012, UnimplementedHemfPlusRecord::new), + fillRegion(0x4013, UnimplementedHemfPlusRecord::new), + fillPath(0x4014, UnimplementedHemfPlusRecord::new), + drawPath(0x4015, UnimplementedHemfPlusRecord::new), + fillClosedCurve(0x4016, UnimplementedHemfPlusRecord::new), + drawClosedCurve(0x4017, UnimplementedHemfPlusRecord::new), + drawCurve(0x4018, UnimplementedHemfPlusRecord::new), + drawBeziers(0x4019, UnimplementedHemfPlusRecord::new), + drawImage(0x401A, UnimplementedHemfPlusRecord::new), + drawImagePoints(0x401B, UnimplementedHemfPlusRecord::new), + drawString(0x401C, UnimplementedHemfPlusRecord::new), + setRenderingOrigin(0x401D, UnimplementedHemfPlusRecord::new), + setAntiAliasMode(0x401E, UnimplementedHemfPlusRecord::new), + setTextRenderingHint(0x401F, UnimplementedHemfPlusRecord::new), + setTextContrast(0x4020, UnimplementedHemfPlusRecord::new), + setInterpolationMode(0x4021, UnimplementedHemfPlusRecord::new), + setPixelOffsetMode(0x4022, UnimplementedHemfPlusRecord::new), + setComositingMode(0x4023, UnimplementedHemfPlusRecord::new), + setCompositingQuality(0x4024, UnimplementedHemfPlusRecord::new), + save(0x4025, UnimplementedHemfPlusRecord::new), + restore(0x4026, UnimplementedHemfPlusRecord::new), + beginContainer(0x4027, UnimplementedHemfPlusRecord::new), + beginContainerNoParams(0x428, UnimplementedHemfPlusRecord::new), + endContainer(0x4029, UnimplementedHemfPlusRecord::new), + setWorldTransform(0x402A, UnimplementedHemfPlusRecord::new), + resetWorldTransform(0x402B, UnimplementedHemfPlusRecord::new), + multiplyWorldTransform(0x402C, UnimplementedHemfPlusRecord::new), + translateWorldTransform(0x402D, UnimplementedHemfPlusRecord::new), + scaleWorldTransform(0x402E, UnimplementedHemfPlusRecord::new), + rotateWorldTransform(0x402F, UnimplementedHemfPlusRecord::new), + setPageTransform(0x4030, UnimplementedHemfPlusRecord::new), + resetClip(0x4031, UnimplementedHemfPlusRecord::new), + setClipRect(0x4032, UnimplementedHemfPlusRecord::new), + setClipRegion(0x4033, UnimplementedHemfPlusRecord::new), + setClipPath(0x4034, UnimplementedHemfPlusRecord::new), + offsetClip(0x4035, UnimplementedHemfPlusRecord::new), + drawDriverstring(0x4036, UnimplementedHemfPlusRecord::new), + strokeFillPath(0x4037, UnimplementedHemfPlusRecord::new), + serializableObject(0x4038, UnimplementedHemfPlusRecord::new), + setTSGraphics(0x4039, UnimplementedHemfPlusRecord::new), + setTSClip(0x403A, UnimplementedHemfPlusRecord::new); + + + public final long id; + public final Supplier constructor; + + HemfPlusRecordType(long id, Supplier constructor) { + this.id = id; + this.constructor = constructor; + } + + public static HemfPlusRecordType getById(long id) { + for (HemfPlusRecordType wrt : values()) { + if (wrt.id == id) return wrt; + } + return null; + } +} diff --git a/src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/UnimplementedHemfPlusRecord.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/UnimplementedHemfPlusRecord.java similarity index 76% rename from src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/UnimplementedHemfPlusRecord.java rename to src/scratchpad/src/org/apache/poi/hemf/record/emfplus/UnimplementedHemfPlusRecord.java index 7e3cbcff4c..21d0e14424 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/UnimplementedHemfPlusRecord.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/UnimplementedHemfPlusRecord.java @@ -15,17 +15,21 @@ limitations under the License. ==================================================================== */ -package org.apache.poi.hemf.hemfplus.record; +package org.apache.poi.hemf.record.emfplus; import java.io.IOException; +import org.apache.poi.util.IOUtils; import org.apache.poi.util.Internal; +import org.apache.poi.util.LittleEndianInputStream; @Internal public class UnimplementedHemfPlusRecord implements HemfPlusRecord { - private int recordId; + private static final int MAX_RECORD_LENGTH = 1_000_000; + + private long recordId; private int flags; private byte[] recordBytes; @@ -40,14 +44,16 @@ public class UnimplementedHemfPlusRecord implements HemfPlusRecord { } @Override - public void init(byte[] recordBytes, int recordId, int flags) throws IOException { + public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException { this.recordId = recordId; this.flags = flags; - this.recordBytes = recordBytes; + recordBytes = IOUtils.safelyAllocate(dataSize, MAX_RECORD_LENGTH); + leis.readFully(recordBytes); + return recordBytes.length; } public byte[] getRecordBytes() { //should probably defensively return a copy. return recordBytes; } -} +} \ No newline at end of file diff --git a/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java b/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java new file mode 100644 index 0000000000..40b04d716a --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java @@ -0,0 +1,77 @@ +/* ==================================================================== + 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.usermodel; + + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Spliterator; +import java.util.function.Consumer; + +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.Internal; +import org.apache.poi.util.LittleEndianInputStream; + +/** + * Read-only EMF extractor. Lots remain + */ +@Internal +public class HemfPicture implements Iterable { + + private final LittleEndianInputStream stream; + private final List records = new ArrayList<>(); + + public HemfPicture(InputStream is) throws IOException { + this(new LittleEndianInputStream(is)); + } + + public HemfPicture(LittleEndianInputStream is) throws IOException { + stream = is; + } + + public HemfHeader getHeader() { + return (HemfHeader)getRecords().get(0); + } + + public List getRecords() { + if (records.isEmpty()) { + new HemfRecordIterator(stream).forEachRemaining(records::add); + } + return records; + } + + @Override + public Iterator iterator() { + return getRecords().iterator(); + } + + @Override + public Spliterator spliterator() { + return getRecords().spliterator(); + } + + @Override + public void forEach(Consumer action) { + getRecords().forEach(action); + } +} diff --git a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java index bd8c9ff9a9..cdbfefe71e 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java @@ -43,32 +43,48 @@ public class HwmfDrawProperties { private final Rectangle2D window; private Rectangle2D viewport; private final Point2D location; - private HwmfMapMode mapMode = HwmfMapMode.MM_ANISOTROPIC; - private HwmfColorRef backgroundColor = new HwmfColorRef(Color.BLACK); - private HwmfBrushStyle brushStyle = HwmfBrushStyle.BS_SOLID; - private HwmfColorRef brushColor = new HwmfColorRef(Color.BLACK); - private HwmfHatchStyle brushHatch = HwmfHatchStyle.HS_HORIZONTAL; + private HwmfMapMode mapMode; + private HwmfColorRef backgroundColor; + private HwmfBrushStyle brushStyle; + private HwmfColorRef brushColor; + private HwmfHatchStyle brushHatch; private BufferedImage brushBitmap; - private double penWidth = 1; - private HwmfPenStyle penStyle = HwmfPenStyle.valueOf(0); - private HwmfColorRef penColor = new HwmfColorRef(Color.BLACK); - private double penMiterLimit = 10; - private HwmfBkMode bkMode = HwmfBkMode.OPAQUE; - private HwmfPolyfillMode polyfillMode = HwmfPolyfillMode.WINDING; + private double penWidth; + private HwmfPenStyle penStyle; + private HwmfColorRef penColor; + private double penMiterLimit; + private HwmfBkMode bkMode; + private HwmfPolyfillMode polyfillMode; private Shape region; private List palette; private int paletteOffset; private HwmfFont font; - private HwmfColorRef textColor = new HwmfColorRef(Color.BLACK); - private HwmfTextAlignment textAlignLatin = HwmfTextAlignment.LEFT; - private HwmfTextVerticalAlignment textVAlignLatin = HwmfTextVerticalAlignment.TOP; - private HwmfTextAlignment textAlignAsian = HwmfTextAlignment.RIGHT; - private HwmfTextVerticalAlignment textVAlignAsian = HwmfTextVerticalAlignment.TOP; + private HwmfColorRef textColor; + private HwmfTextAlignment textAlignLatin; + private HwmfTextVerticalAlignment textVAlignLatin; + private HwmfTextAlignment textAlignAsian; + private HwmfTextVerticalAlignment textVAlignAsian; public HwmfDrawProperties() { window = new Rectangle2D.Double(0, 0, 1, 1); viewport = null; location = new Point2D.Double(0,0); + mapMode = HwmfMapMode.MM_ANISOTROPIC; + backgroundColor = new HwmfColorRef(Color.BLACK); + brushStyle = HwmfBrushStyle.BS_SOLID; + brushColor = new HwmfColorRef(Color.BLACK); + brushHatch = HwmfHatchStyle.HS_HORIZONTAL; + penWidth = 1; + penStyle = HwmfPenStyle.valueOf(0); + penColor = new HwmfColorRef(Color.BLACK); + penMiterLimit = 10; + bkMode = HwmfBkMode.OPAQUE; + polyfillMode = HwmfPolyfillMode.WINDING; + textColor = new HwmfColorRef(Color.BLACK); + textAlignLatin = HwmfTextAlignment.LEFT; + textVAlignLatin = HwmfTextVerticalAlignment.TOP; + textAlignAsian = HwmfTextAlignment.RIGHT; + textVAlignAsian = HwmfTextVerticalAlignment.TOP; } public HwmfDrawProperties(HwmfDrawProperties other) { @@ -86,7 +102,7 @@ public class HwmfDrawProperties { WritableRaster raster = other.brushBitmap.copyData(null); this.brushBitmap = new BufferedImage(cm, raster, isAlphaPremultiplied, null); } - this.penWidth = 1; + this.penWidth = other.penWidth; this.penStyle = (other.penStyle == null) ? null : other.penStyle.clone(); this.penColor = (other.penColor == null) ? null : other.penColor.clone(); this.penMiterLimit = other.penMiterLimit; @@ -101,6 +117,10 @@ public class HwmfDrawProperties { this.paletteOffset = other.paletteOffset; this.font = other.font; this.textColor = (other.textColor == null) ? null : other.textColor.clone(); + this.textAlignLatin = other.textAlignLatin; + this.textVAlignLatin = other.textVAlignLatin; + this.textAlignAsian = other.textAlignAsian; + this.textVAlignAsian = other.textVAlignAsian; } public void setViewportExt(double width, double height) { @@ -149,6 +169,10 @@ public class HwmfDrawProperties { location.setLocation(x, y); } + public void setLocation(Point2D point) { + location.setLocation(point); + } + public Point2D getLocation() { return (Point2D)location.clone(); } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBrushStyle.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBrushStyle.java index 2e8122fb15..903ba592ac 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBrushStyle.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBrushStyle.java @@ -71,7 +71,7 @@ public enum HwmfBrushStyle { this.flag = flag; } - static HwmfBrushStyle valueOf(int flag) { + public static HwmfBrushStyle valueOf(int flag) { for (HwmfBrushStyle bs : values()) { if (bs.flag == flag) return bs; } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java index 48bcc60b44..fb78158a9d 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java @@ -41,31 +41,21 @@ public class HwmfDraw { */ public static class WmfMoveTo implements HwmfRecord { - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units. - */ - private int y; - - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units. - */ - private int x; + protected final Point2D point = new Point2D.Double(); @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.moveTo; } @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { - y = leis.readShort(); - x = leis.readShort(); - return 2*LittleEndianConsts.SHORT_SIZE; + return readPointS(leis, point); } @Override public void draw(HwmfGraphics ctx) { - ctx.getProperties().setLocation(x, y); + ctx.getProperties().setLocation(point); } } @@ -75,36 +65,24 @@ public class HwmfDraw { */ public static class WmfLineTo implements HwmfRecord { - /** - * A 16-bit signed integer that defines the vertical component of the drawing - * destination position, in logical units. - */ - private int y; - - /** - * A 16-bit signed integer that defines the horizontal component of the drawing - * destination position, in logical units. - */ - private int x; + protected final Point2D point = new Point2D.Double(); @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.lineTo; } @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { - y = leis.readShort(); - x = leis.readShort(); - return 2*LittleEndianConsts.SHORT_SIZE; + return readPointS(leis, point); } @Override public void draw(HwmfGraphics ctx) { Point2D start = ctx.getProperties().getLocation(); - Line2D line = new Line2D.Double(start.getX(), start.getY(), x, y); + Line2D line = new Line2D.Double(start, point); ctx.draw(line); - ctx.getProperties().setLocation(x, y); + ctx.getProperties().setLocation(point); } } @@ -115,10 +93,10 @@ public class HwmfDraw { */ public static class WmfPolygon implements HwmfRecord { - private Path2D poly = new Path2D.Double(); + protected Path2D poly = new Path2D.Double(); @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.polygon; } @@ -146,16 +124,26 @@ public class HwmfDraw { @Override public void draw(HwmfGraphics ctx) { - Path2D shape = getShape(); -// shape.closePath(); - Path2D p = (Path2D)shape.clone(); + Path2D p = getShape(ctx); + // don't close the path p.setWindingRule(getWindingRule(ctx)); - ctx.fill(p); + if (isFill()) { + ctx.fill(p); + } else { + ctx.draw(p); + } } - protected Path2D getShape() { + protected Path2D getShape(HwmfGraphics ctx) { return (Path2D)poly.clone(); } + + /** + * @return true, if the shape should be filled + */ + protected boolean isFill() { + return true; + } } /** @@ -165,16 +153,13 @@ public class HwmfDraw { public static class WmfPolyline extends WmfPolygon { @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.polyline; } @Override - public void draw(HwmfGraphics ctx) { - Path2D shape = getShape(); - Path2D p = (Path2D)shape.clone(); - p.setWindingRule(getWindingRule(ctx)); - ctx.draw(p); + protected boolean isFill() { + return false; } } @@ -184,48 +169,21 @@ public class HwmfDraw { * are defined in the playback device context. */ public static class WmfEllipse implements HwmfRecord { - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of - * the lower-right corner of the bounding rectangle. - */ - private int bottomRect; - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of - * the lower-right corner of the bounding rectangle. - */ - private int rightRect; - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the - * upper-left corner of the bounding rectangle. - */ - private int topRect; - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of - * the upper-left corner of the bounding rectangle. - */ - private int leftRect; + protected final Rectangle2D bounds = new Rectangle2D.Double(); @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.ellipse; } @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { - bottomRect = leis.readShort(); - rightRect = leis.readShort(); - topRect = leis.readShort(); - leftRect = leis.readShort(); - return 4*LittleEndianConsts.SHORT_SIZE; + return readBounds(leis, bounds); } @Override public void draw(HwmfGraphics ctx) { - int x = Math.min(leftRect, rightRect); - int y = Math.min(topRect, bottomRect); - int w = Math.abs(leftRect - rightRect - 1); - int h = Math.abs(topRect - bottomRect - 1); - Shape s = new Ellipse2D.Double(x, y, w, h); + Shape s = new Ellipse2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight()); ctx.fill(s); } } @@ -239,25 +197,25 @@ public class HwmfDraw { * A 16-bit unsigned integer used to index into the WMF Object Table to get * the region to be framed. */ - private int regionIndex; + protected int regionIndex; /** * A 16-bit unsigned integer used to index into the WMF Object Table to get the * Brush to use for filling the region. */ - private int brushIndex; + protected int brushIndex; /** * A 16-bit signed integer that defines the height, in logical units, of the * region frame. */ - private int height; + protected int height; /** * A 16-bit signed integer that defines the width, in logical units, of the * region frame. */ - private int width; + protected int width; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.frameRegion; } @@ -293,10 +251,10 @@ public class HwmfDraw { */ public static class WmfPolyPolygon implements HwmfRecord { - private List polyList = new ArrayList<>(); + protected List polyList = new ArrayList<>(); @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.polyPolygon; } @@ -361,7 +319,20 @@ public class HwmfDraw { area.exclusiveOr(newArea); } } - ctx.fill(area); + + if (isFill()) { + ctx.fill(area); + } else { + ctx.draw(area); + } + } + + + /** + * @return true, if the shape should be filled + */ + protected boolean isFill() { + return true; } } @@ -370,92 +341,50 @@ public class HwmfDraw { * filled by using the brush that are defined in the playback device context. */ public static class WmfRectangle implements HwmfRecord { - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of - * the lower-right corner of the rectangle. - */ - private int bottomRect; - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of - * the lower-right corner of the rectangle. - */ - private int rightRect; - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the - * upper-left corner of the rectangle. - */ - private int topRect; - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of - * the upper-left corner of the rectangle. - */ - private int leftRect; + protected final Rectangle2D bounds = new Rectangle2D.Double(); @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.frameRegion; } @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { - bottomRect = leis.readShort(); - rightRect = leis.readShort(); - topRect = leis.readShort(); - leftRect = leis.readShort(); - return 4*LittleEndianConsts.SHORT_SIZE; + return readBounds(leis, bounds); } @Override public void draw(HwmfGraphics ctx) { - int x = Math.min(leftRect, rightRect); - int y = Math.min(topRect, bottomRect); - int w = Math.abs(leftRect - rightRect - 1); - int h = Math.abs(topRect - bottomRect - 1); - Shape s = new Rectangle2D.Double(x, y, w, h); - ctx.fill(s); + ctx.fill(bounds); } } /** - * The META_RECTANGLE record paints a rectangle. The rectangle is outlined by using the pen and - * filled by using the brush that are defined in the playback device context. + * The META_SETPIXEL record sets the pixel at the specified coordinates to the specified color. */ public static class WmfSetPixel implements HwmfRecord { /** * A ColorRef Object that defines the color value. */ - HwmfColorRef colorRef; + protected final HwmfColorRef colorRef = new HwmfColorRef(); - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the point - * to be set. - */ - private int y; - - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of the point - * to be set. - */ - private int x; + protected final Point2D point = new Point2D.Double(); @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.setPixel; } @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { - colorRef = new HwmfColorRef(); int size = colorRef.init(leis); - y = leis.readShort(); - x = leis.readShort(); - return 2*LittleEndianConsts.SHORT_SIZE+size; + return size+ readPointS(leis, point); } @Override public void draw(HwmfGraphics ctx) { - Shape s = new Rectangle2D.Double(x, y, 1, 1); + Shape s = new Rectangle2D.Double(point.getX(), point.getY(), 1, 1); ctx.fill(s); } } @@ -469,41 +398,19 @@ public class HwmfDraw { * A 16-bit signed integer that defines the height, in logical coordinates, of the * ellipse used to draw the rounded corners. */ - private int height; + protected int height; /** * A 16-bit signed integer that defines the width, in logical coordinates, of the * ellipse used to draw the rounded corners. */ - private int width; + protected int width; - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of - * the lower-right corner of the rectangle. - */ - private int bottomRect; - - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of - * the lower-right corner of the rectangle. - */ - private int rightRect; - - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the - * upper-left corner of the rectangle. - */ - private int topRect; - - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of - * the upper-left corner of the rectangle. - */ - private int leftRect; + protected final Rectangle2D bounds = new Rectangle2D.Double(); @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.roundRect; } @@ -511,20 +418,12 @@ public class HwmfDraw { public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { height = leis.readShort(); width = leis.readShort(); - bottomRect = leis.readShort(); - rightRect = leis.readShort(); - topRect = leis.readShort(); - leftRect = leis.readShort(); - return 6*LittleEndianConsts.SHORT_SIZE; + return 2*LittleEndianConsts.SHORT_SIZE+readBounds(leis, bounds); } @Override public void draw(HwmfGraphics ctx) { - int x = Math.min(leftRect, rightRect); - int y = Math.min(topRect, bottomRect); - int w = Math.abs(leftRect - rightRect - 1); - int h = Math.abs(topRect - bottomRect - 1); - Shape s = new RoundRectangle2D.Double(x, y, w, h, width, height); + Shape s = new RoundRectangle2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), width, height); ctx.fill(s); } } @@ -534,73 +433,34 @@ public class HwmfDraw { * The META_ARC record draws an elliptical arc. */ public static class WmfArc implements HwmfRecord { - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of - * the ending point of the radial line defining the ending point of the arc. - */ - private int yEndArc; - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of - * the ending point of the radial line defining the ending point of the arc. - */ - private int xEndArc; - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of - * the ending point of the radial line defining the starting point of the arc. - */ - private int yStartArc; - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of - * the ending point of the radial line defining the starting point of the arc. - */ - private int xStartArc; - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of - * the lower-right corner of the bounding rectangle. - */ - private int bottomRect; - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of - * the lower-right corner of the bounding rectangle. - */ - private int rightRect; - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the - * upper-left corner of the bounding rectangle. - */ - private int topRect; - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of - * the upper-left corner of the bounding rectangle. - */ - private int leftRect; + /** starting point of the arc */ + protected final Point2D startPoint = new Point2D.Double(); + + /** ending point of the arc */ + protected final Point2D endPoint = new Point2D.Double(); + + /** the bounding rectangle */ + protected final Rectangle2D bounds = new Rectangle2D.Double(); + @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.arc; } @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { - yEndArc = leis.readShort(); - xEndArc = leis.readShort(); - yStartArc = leis.readShort(); - xStartArc = leis.readShort(); - bottomRect = leis.readShort(); - rightRect = leis.readShort(); - topRect = leis.readShort(); - leftRect = leis.readShort(); + readPointS(leis, endPoint); + readPointS(leis, startPoint); + readBounds(leis, bounds); + return 8*LittleEndianConsts.SHORT_SIZE; } @Override public void draw(HwmfGraphics ctx) { - int x = Math.min(leftRect, rightRect); - int y = Math.min(topRect, bottomRect); - int w = Math.abs(leftRect - rightRect - 1); - int h = Math.abs(topRect - bottomRect - 1); - double startAngle = Math.toDegrees(Math.atan2(-(yStartArc - (topRect + h / 2.)), xStartArc - (leftRect + w / 2.))); - double endAngle = Math.toDegrees(Math.atan2(-(yEndArc - (topRect + h / 2.)), xEndArc - (leftRect + w / 2.))); + 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); if (startAngle < 0) { startAngle += 360; @@ -608,7 +468,7 @@ public class HwmfDraw { boolean fillShape; int arcClosure; - switch (getRecordType()) { + switch (getWmfRecordType()) { default: case arc: arcClosure = Arc2D.OPEN; @@ -624,7 +484,7 @@ public class HwmfDraw { break; } - Shape s = new Arc2D.Double(x, y, w, h, startAngle, arcAngle, arcClosure); + Shape s = new Arc2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), startAngle, arcAngle, arcClosure); if (fillShape) { ctx.fill(s); } else { @@ -641,7 +501,7 @@ public class HwmfDraw { public static class WmfPie extends WmfArc { @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.pie; } } @@ -654,7 +514,7 @@ public class HwmfDraw { public static class WmfChord extends WmfArc { @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.chord; } } @@ -673,10 +533,10 @@ public class HwmfDraw { * A 16-bit unsigned integer used to index into the WMF Object Table to * get the object to be selected. */ - private int objectIndex; + protected int objectIndex; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.selectObject; } @@ -695,4 +555,50 @@ 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. + */ + int bottom = leis.readShort(); + int right = leis.readShort(); + int top = leis.readShort(); + int left = leis.readShort(); + + int x = Math.min(left, right); + int y = Math.min(top, bottom); + int w = Math.abs(left - right - 1); + int h = Math.abs(top - bottom - 1); + + bounds.setRect(x, y, w, h); + + return 4 * LittleEndianConsts.SHORT_SIZE; + } + + static int readRectS(LittleEndianInputStream leis, Rectangle2D bounds) { + /** + * The 16-bit signed integers that defines the corners of the bounding rectangle. + */ + int left = leis.readShort(); + int top = leis.readShort(); + int right = leis.readShort(); + int bottom = leis.readShort(); + + int x = Math.min(left, right); + int y = Math.min(top, bottom); + int w = Math.abs(left - right - 1); + int h = Math.abs(top - bottom - 1); + + bounds.setRect(x, y, w, h); + + return 4 * LittleEndianConsts.SHORT_SIZE; + } + + static int readPointS(LittleEndianInputStream leis, Point2D point) { + /** a signed integer that defines the x/y-coordinate, in logical units. */ + int y = leis.readShort(); + int x = leis.readShort(); + point.setLocation(x, y); + return 2*LittleEndianConsts.SHORT_SIZE; + } +} diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfEscape.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfEscape.java index 6c7ef213b0..a1de4dca46 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfEscape.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfEscape.java @@ -185,7 +185,7 @@ public class HwmfEscape implements HwmfRecord { private byte escapeData[]; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.escape; } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java index ec4d070540..26aa55696b 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java @@ -17,8 +17,12 @@ package org.apache.poi.hwmf.record; +import static org.apache.poi.hwmf.record.HwmfDraw.readPointS; + import java.awt.Shape; import java.awt.geom.Path2D; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.io.IOException; @@ -62,7 +66,7 @@ public class HwmfFill { this.flag = flag; } - static ColorUsage valueOf(int flag) { + public static ColorUsage valueOf(int flag) { for (ColorUsage bs : values()) { if (bs.flag == flag) return bs; } @@ -80,16 +84,16 @@ public class HwmfFill { * A 16-bit unsigned integer used to index into the WMF Object Table to get * the region to be filled. */ - private int regionIndex; + protected int regionIndex; /** * A 16-bit unsigned integer used to index into the WMF Object Table to get the * brush to use for filling the region. */ - private int brushIndex; + protected int brushIndex; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.fillRegion; } @@ -125,7 +129,7 @@ public class HwmfFill { */ int regionIndex; - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.paintRegion; } @@ -155,31 +159,21 @@ public class HwmfFill { /** * A 32-bit ColorRef Object that defines the color value. */ - private HwmfColorRef colorRef; - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the - * point where filling is to start. - */ - private int yStart; - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of the - * point where filling is to start. - */ - private int xStart; - - + protected final HwmfColorRef colorRef = new HwmfColorRef(); + + /** the point where filling is to start. */ + protected final Point2D start = new Point2D.Double(); + @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.floodFill; } @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { - colorRef = new HwmfColorRef(); int size = colorRef.init(leis); - yStart = leis.readShort(); - xStart = leis.readShort(); - return size+2*LittleEndianConsts.SHORT_SIZE; + size += readPointS(leis, start); + return size; } @Override @@ -215,22 +209,22 @@ public class HwmfFill { this.awtFlag = awtFlag; } - static HwmfPolyfillMode valueOf(int wmfFlag) { + public static HwmfPolyfillMode valueOf(int wmfFlag) { for (HwmfPolyfillMode pm : values()) { if (pm.wmfFlag == wmfFlag) return pm; } return null; } } - + /** - * A 16-bit unsigned integer that defines polygon fill mode. + * An unsigned integer that defines polygon fill mode. * This MUST be one of the values: ALTERNATE = 0x0001, WINDING = 0x0002 */ - private HwmfPolyfillMode polyfillMode; + protected HwmfPolyfillMode polyfillMode; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.setPolyFillMode; } @@ -251,7 +245,7 @@ public class HwmfFill { * The META_EXTFLOODFILL record fills an area with the brush that is defined in * the playback device context. */ - public static class WmfExtFloodFill implements HwmfRecord { + public static class WmfExtFloodFill extends WmfFloodFill { /** * A 16-bit unsigned integer that defines the fill operation to be performed. This @@ -266,38 +260,17 @@ public class HwmfFill { * Filling continues outward in all directions as long as the color is encountered. * This style is useful for filling areas with multicolored boundaries. */ - private int mode; - - /** - * A 32-bit ColorRef Object that defines the color value. - */ - private HwmfColorRef colorRef; - - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the point - * to be set. - */ - private int y; - - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of the point - * to be set. - */ - private int x; + protected int mode; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.extFloodFill; } @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { mode = leis.readUShort(); - colorRef = new HwmfColorRef(); - int size = colorRef.init(leis); - y = leis.readShort(); - x = leis.readShort(); - return size+3*LittleEndianConsts.SHORT_SIZE; + return super.init(leis, recordSize, recordFunction)+LittleEndianConsts.SHORT_SIZE; } @Override @@ -318,7 +291,7 @@ public class HwmfFill { private int region; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.invertRegion; } @@ -348,30 +321,10 @@ public class HwmfFill { */ private HwmfTernaryRasterOp rasterOperation; - /** - * A 16-bit signed integer that defines the height, in logical units, of the rectangle. - */ - private int height; - - /** - * A 16-bit signed integer that defines the width, in logical units, of the rectangle. - */ - private int width; - - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the - * upper-left corner of the rectangle to be filled. - */ - private int yLeft; - - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of the - * upper-left corner of the rectangle to be filled. - */ - private int xLeft; - + private final Rectangle2D bounds = new Rectangle2D.Double(); + @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.patBlt; } @@ -383,12 +336,7 @@ public class HwmfFill { rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex); assert(rasterOpCode == rasterOperation.opCode); - height = leis.readShort(); - width = leis.readShort(); - yLeft = leis.readShort(); - xLeft = leis.readShort(); - - return 6*LittleEndianConsts.SHORT_SIZE; + return readBounds2(leis, bounds)+2*LittleEndianConsts.SHORT_SIZE; } @Override @@ -414,53 +362,22 @@ public class HwmfFill { * in the playback device context, and the destination pixels are to be combined to form the new * image. This code MUST be one of the values in the Ternary Raster Operation Enumeration */ - private HwmfTernaryRasterOp rasterOperation; - - /** - * A 16-bit signed integer that defines the height, in logical units, of the source rectangle. - */ - private int srcHeight; - /** - * A 16-bit signed integer that defines the width, in logical units, of the source rectangle. - */ - private int srcWidth; - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the upper-left corner - * of the source rectangle. - */ - private int ySrc; - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of the upper-left corner - * of the source rectangle. - */ - private int xSrc; - /** - * A 16-bit signed integer that defines the height, in logical units, of the destination rectangle. - */ - private int destHeight; - /** - * A 16-bit signed integer that defines the width, in logical units, of the destination rectangle. - */ - private int destWidth; - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the upper-left - * corner of the destination rectangle. - */ - private int yDest; - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of the upper-left - * corner of the destination rectangle. - */ - private int xDest; - + protected HwmfTernaryRasterOp rasterOperation; + + /** the source rectangle */ + protected final Rectangle2D srcBounds = new Rectangle2D.Double(); + + /** the destination rectangle */ + protected final Rectangle2D dstBounds = new Rectangle2D.Double(); + /** * A variable-sized Bitmap16 Object that defines source image content. * This object MUST be specified, even if the raster operation does not require a source. */ - HwmfBitmap16 target; + protected HwmfBitmap16 target; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.stretchBlt; } @@ -469,27 +386,23 @@ public class HwmfFill { public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { boolean hasBitmap = (recordSize > ((recordFunction >> 8) + 3)); - int size = 0; int rasterOpCode = leis.readUShort(); int rasterOpIndex = leis.readUShort(); rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex); assert(rasterOpCode == rasterOperation.opCode); - srcHeight = leis.readShort(); - srcWidth = leis.readShort(); - ySrc = leis.readShort(); - xSrc = leis.readShort(); - size = 6*LittleEndianConsts.SHORT_SIZE; + int size = 2*LittleEndianConsts.SHORT_SIZE; + + size += readBounds2(leis, srcBounds); + if (!hasBitmap) { /*int reserved =*/ leis.readShort(); size += LittleEndianConsts.SHORT_SIZE; } - destHeight = leis.readShort(); - destWidth = leis.readShort(); - yDest = leis.readShort(); - xDest = leis.readShort(); - size += 4*LittleEndianConsts.SHORT_SIZE; + + size += readBounds2(leis, dstBounds); + if (hasBitmap) { target = new HwmfBitmap16(); size += target.init(leis); @@ -524,46 +437,13 @@ public class HwmfFill { * DIB contains explicit RGB values or indexes into a palette. */ private ColorUsage colorUsage; - /** - * A 16-bit signed integer that defines the height, in logical units, of the - * source rectangle. - */ - private int srcHeight; - /** - * A 16-bit signed integer that defines the width, in logical units, of the - * source rectangle. - */ - private int srcWidth; - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the - * source rectangle. - */ - private int ySrc; - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of the - * source rectangle. - */ - private int xSrc; - /** - * A 16-bit signed integer that defines the height, in logical units, of the - * destination rectangle. - */ - private int destHeight; - /** - * A 16-bit signed integer that defines the width, in logical units, of the - * destination rectangle. - */ - private int destWidth; - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the - * upper-left corner of the destination rectangle. - */ - private int yDst; - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of the - * upper-left corner of the destination rectangle. - */ - private int xDst; + + /** the source rectangle. */ + protected final Rectangle2D srcBounds = new Rectangle2D.Double(); + + /** the destination rectangle. */ + protected final Rectangle2D dstBounds = new Rectangle2D.Double(); + /** * A variable-sized DeviceIndependentBitmap Object (section 2.2.2.9) that is the * source of the color data. @@ -571,7 +451,7 @@ public class HwmfFill { private HwmfBitmapDib dib; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.stretchDib; } @@ -585,16 +465,12 @@ public class HwmfFill { assert(rasterOpCode == rasterOperation.opCode); colorUsage = ColorUsage.valueOf(leis.readUShort()); - srcHeight = leis.readShort(); - srcWidth = leis.readShort(); - ySrc = leis.readShort(); - xSrc = leis.readShort(); - destHeight = leis.readShort(); - destWidth = leis.readShort(); - yDst = leis.readShort(); - xDst = leis.readShort(); - - int size = 11*LittleEndianConsts.SHORT_SIZE; + + int size = 3*LittleEndianConsts.SHORT_SIZE; + + size += readBounds2(leis, srcBounds); + size += readBounds2(leis, dstBounds); + dib = new HwmfBitmapDib(); size += dib.init(leis, (int)(recordSize-6-size)); @@ -617,53 +493,10 @@ public class HwmfFill { } } - public static class WmfBitBlt implements HwmfRecord { - - /** - * A 32-bit unsigned integer that defines how the source pixels, the current brush in the playback - * device context, and the destination pixels are to be combined to form the new image. - */ - private HwmfTernaryRasterOp rasterOperation; - - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the upper-left corner - of the source rectangle. - */ - private int ySrc; - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of the upper-left corner - of the source rectangle. - */ - private int xSrc; - /** - * A 16-bit signed integer that defines the height, in logical units, of the source and - destination rectangles. - */ - private int height; - /** - * A 16-bit signed integer that defines the width, in logical units, of the source and destination - rectangles. - */ - private int width; - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the upper-left - corner of the destination rectangle. - */ - private int yDest; - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of the upper-left - corner of the destination rectangle. - */ - private int xDest; - - /** - * A variable-sized Bitmap16 Object that defines source image content. - * This object MUST be specified, even if the raster operation does not require a source. - */ - private HwmfBitmap16 target; + public static class WmfBitBlt extends WmfStretchBlt { @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.bitBlt; } @@ -671,41 +504,33 @@ public class HwmfFill { public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { boolean hasBitmap = (recordSize/2 != ((recordFunction >> 8) + 3)); - int size = 0; int rasterOpCode = leis.readUShort(); int rasterOpIndex = leis.readUShort(); rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex); assert(rasterOpCode == rasterOperation.opCode); - ySrc = leis.readShort(); - xSrc = leis.readShort(); + int size = 2*LittleEndianConsts.SHORT_SIZE; + + final Point2D srcPnt = new Point2D.Double(); + size += readPointS(leis, srcPnt); - size = 4*LittleEndianConsts.SHORT_SIZE; - if (!hasBitmap) { /*int reserved =*/ leis.readShort(); size += LittleEndianConsts.SHORT_SIZE; } - - height = leis.readShort(); - width = leis.readShort(); - yDest = leis.readShort(); - xDest = leis.readShort(); - size += 4*LittleEndianConsts.SHORT_SIZE; + size += readBounds2(leis, dstBounds); + if (hasBitmap) { target = new HwmfBitmap16(); size += target.init(leis); } + + srcBounds.setRect(srcPnt.getX(), srcPnt.getY(), dstBounds.getWidth(), dstBounds.getHeight()); return size; } - - @Override - public void draw(HwmfGraphics ctx) { - - } } @@ -729,36 +554,13 @@ public class HwmfFill { * A 16-bit unsigned integer that defines the starting scan line in the source. */ private int startScan; - /** - * A 16-bit unsigned integer that defines the y-coordinate, in logical units, of the - * source rectangle. - */ - private int yDib; - /** - * A 16-bit unsigned integer that defines the x-coordinate, in logical units, of the - * source rectangle. - */ - private int xDib; - /** - * A 16-bit unsigned integer that defines the height, in logical units, of the - * source and destination rectangles. - */ - private int height; - /** - * A 16-bit unsigned integer that defines the width, in logical units, of the - * source and destination rectangles. - */ - private int width; - /** - * A 16-bit unsigned integer that defines the y-coordinate, in logical units, of the - * upper-left corner of the destination rectangle. - */ - private int yDest; - /** - * A 16-bit unsigned integer that defines the x-coordinate, in logical units, of the - * upper-left corner of the destination rectangle. - */ - private int xDest; + + /** the source rectangle */ + protected final Rectangle2D srcBounds = new Rectangle2D.Double(); + + /** the destination rectangle, having the same dimension as the source rectangle */ + protected final Rectangle2D dstBounds = new Rectangle2D.Double(); + /** * A variable-sized DeviceIndependentBitmap Object that is the source of the color data. */ @@ -766,7 +568,7 @@ public class HwmfFill { @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.setDibToDev; } @@ -775,17 +577,19 @@ public class HwmfFill { colorUsage = ColorUsage.valueOf(leis.readUShort()); scanCount = leis.readUShort(); startScan = leis.readUShort(); - yDib = leis.readUShort(); - xDib = leis.readUShort(); - height = leis.readUShort(); - width = leis.readUShort(); - yDest = leis.readUShort(); - xDest = leis.readUShort(); - - int size = 9*LittleEndianConsts.SHORT_SIZE; + + int size = 3*LittleEndianConsts.SHORT_SIZE; + + final Point2D srcPnt = new Point2D.Double(); + size += readPointS(leis, srcPnt); + + size += readBounds2(leis, dstBounds); + dib = new HwmfBitmapDib(); size += dib.init(leis, (int)(recordSize-6-size)); - + + srcBounds.setRect(srcPnt.getX(), srcPnt.getY(), dstBounds.getWidth(), dstBounds.getHeight()); + return size; } @@ -806,52 +610,9 @@ public class HwmfFill { } - public static class WmfDibBitBlt implements HwmfRecord, HwmfImageRecord, HwmfObjectTableEntry { - - /** - * A 32-bit unsigned integer that defines how the source pixels, the current brush - * in the playback device context, and the destination pixels are to be combined to form the - * new image. This code MUST be one of the values in the Ternary Raster Operation Enumeration. - */ - HwmfTernaryRasterOp rasterOperation; - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the source rectangle. - */ - private int ySrc; - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of the source rectangle. - */ - private int xSrc; - /** - * A 16-bit signed integer that defines the height, in logical units, of the source and - * destination rectangles. - */ - private int height; - /** - * A 16-bit signed integer that defines the width, in logical units, of the source and destination - * rectangles. - */ - private int width; - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the upper-left - * corner of the destination rectangle. - */ - private int yDest; - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of the upper-left - * corner of the destination rectangle. - */ - private int xDest; - - /** - * A variable-sized DeviceIndependentBitmap Object that defines image content. - * This object MUST be specified, even if the raster operation does not require a source. - */ - private HwmfBitmapDib target; - - + public static class WmfDibBitBlt extends WmfDibStretchBlt { @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.dibBitBlt; } @@ -859,48 +620,32 @@ public class HwmfFill { public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { boolean hasBitmap = (recordSize/2 != ((recordFunction >> 8) + 3)); - int size = 0; int rasterOpCode = leis.readUShort(); int rasterOpIndex = leis.readUShort(); rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex); assert(rasterOpCode == rasterOperation.opCode); - ySrc = leis.readShort(); - xSrc = leis.readShort(); - size = 4*LittleEndianConsts.SHORT_SIZE; + int size = 2*LittleEndianConsts.SHORT_SIZE; + + final Point2D srcPnt = new Point2D.Double(); + size += readPointS(leis, srcPnt); if (!hasBitmap) { /*int reserved =*/ leis.readShort(); size += LittleEndianConsts.SHORT_SIZE; } - height = leis.readShort(); - width = leis.readShort(); - yDest = leis.readShort(); - xDest = leis.readShort(); - - size += 4*LittleEndianConsts.SHORT_SIZE; + + size += readBounds2(leis, dstBounds); if (hasBitmap) { target = new HwmfBitmapDib(); size += target.init(leis, (int)(recordSize-6-size)); } - + + // the destination rectangle, having the same dimension as the source rectangle + srcBounds.setRect(srcPnt.getX(), srcPnt.getY(), dstBounds.getWidth(), dstBounds.getHeight()); + return size; } - - @Override - public void draw(HwmfGraphics ctx) { - ctx.addObjectTableEntry(this); - } - - @Override - public void applyObject(HwmfGraphics ctx) { - - } - - @Override - public BufferedImage getImage() { - return (target == null) ? null : target.getImage(); - } } public static class WmfDibStretchBlt implements HwmfRecord, HwmfImageRecord, HwmfObjectTableEntry { @@ -909,53 +654,22 @@ public class HwmfFill { * in the playback device context, and the destination pixels are to be combined to form the * new image. This code MUST be one of the values in the Ternary Raster Operation Enumeration. */ - private HwmfTernaryRasterOp rasterOperation; - /** - * A 16-bit signed integer that defines the height, in logical units, of the source rectangle. - */ - private int srcHeight; - /** - * A 16-bit signed integer that defines the width, in logical units, of the source rectangle. - */ - private int srcWidth; - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the - * upper-left corner of the source rectangle. - */ - private int ySrc; - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of the - * upper-left corner of the source rectangle. - */ - private int xSrc; - /** - * A 16-bit signed integer that defines the height, in logical units, of the - * destination rectangle. - */ - private int destHeight; - /** - * A 16-bit signed integer that defines the width, in logical units, of the - * destination rectangle. - */ - private int destWidth; - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, - * of the upper-left corner of the destination rectangle. - */ - private int yDest; - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, - * of the upper-left corner of the destination rectangle. - */ - private int xDest; + protected HwmfTernaryRasterOp rasterOperation; + + /** the source rectangle */ + protected final Rectangle2D srcBounds = new Rectangle2D.Double(); + + /** the destination rectangle */ + protected final Rectangle2D dstBounds = new Rectangle2D.Double(); + /** * A variable-sized DeviceIndependentBitmap Object that defines image content. * This object MUST be specified, even if the raster operation does not require a source. */ - HwmfBitmapDib target; - + protected HwmfBitmapDib target; + @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.dibStretchBlt; } @@ -963,27 +677,21 @@ public class HwmfFill { public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { boolean hasBitmap = (recordSize > ((recordFunction >> 8) + 3)); - int size = 0; int rasterOpCode = leis.readUShort(); int rasterOpIndex = leis.readUShort(); rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex); assert(rasterOpCode == rasterOperation.opCode); - srcHeight = leis.readShort(); - srcWidth = leis.readShort(); - ySrc = leis.readShort(); - xSrc = leis.readShort(); - size = 6*LittleEndianConsts.SHORT_SIZE; + int size = 2*LittleEndianConsts.SHORT_SIZE; + + size += readBounds2(leis, srcBounds); if (!hasBitmap) { /*int reserved =*/ leis.readShort(); size += LittleEndianConsts.SHORT_SIZE; } - destHeight = leis.readShort(); - destWidth = leis.readShort(); - yDest = leis.readShort(); - xDest = leis.readShort(); - size += 4*LittleEndianConsts.SHORT_SIZE; + + size += readBounds2(leis, dstBounds); if (hasBitmap) { target = new HwmfBitmapDib(); size += target.init(leis, (int)(recordSize-6-size)); @@ -996,15 +704,30 @@ public class HwmfFill { public void draw(HwmfGraphics ctx) { ctx.addObjectTableEntry(this); } - + @Override public void applyObject(HwmfGraphics ctx) { - + } @Override public BufferedImage getImage() { - return target.getImage(); + return (target == null) ? null : target.getImage(); } } + + static int readBounds2(LittleEndianInputStream leis, Rectangle2D bounds) { + /** + * The 16-bit signed integers that defines the corners of the bounding rectangle. + */ + int h = leis.readShort(); + int w = leis.readShort(); + int y = leis.readShort(); + int x = leis.readShort(); + + bounds.setRect(x, y, w, h); + + return 4 * LittleEndianConsts.SHORT_SIZE; + } + } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFont.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFont.java index f094ae1959..932358740f 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFont.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFont.java @@ -24,6 +24,8 @@ import org.apache.poi.common.usermodel.fonts.FontCharset; import org.apache.poi.common.usermodel.fonts.FontFamily; import org.apache.poi.common.usermodel.fonts.FontInfo; import org.apache.poi.common.usermodel.fonts.FontPitch; +import org.apache.poi.util.BitField; +import org.apache.poi.util.BitFieldFactory; import org.apache.poi.util.LittleEndianConsts; import org.apache.poi.util.LittleEndianInputStream; @@ -90,7 +92,7 @@ public class HwmfFont implements FontInfo { this.flag = flag; } - static WmfOutPrecision valueOf(int flag) { + public static WmfOutPrecision valueOf(int flag) { for (WmfOutPrecision op : values()) { if (op.flag == flag) { return op; @@ -104,22 +106,17 @@ public class HwmfFont implements FontInfo { * ClipPrecision Flags specify clipping precision, which defines how to clip characters that are * partially outside a clipping region. These flags can be combined to specify multiple options. */ - public enum WmfClipPrecision { + public static class WmfClipPrecision { - /** - * Specifies that default clipping MUST be used. - */ - CLIP_DEFAULT_PRECIS (0x00000000), + /** Specifies that default clipping MUST be used. */ + private static final BitField CLIP_DEFAULT_PRECIS = BitFieldFactory.getInstance(0x0000); - /** - * This value SHOULD NOT be used. - */ - CLIP_CHARACTER_PRECIS (0x00000001), - /** - * This value MAY be returned when enumerating rasterized, TrueType and vector fonts. - */ - CLIP_STROKE_PRECIS (0x00000002), + /** This value SHOULD NOT be used. */ + private static final BitField CLIP_CHARACTER_PRECIS = BitFieldFactory.getInstance(0x0001); + + /** This value MAY be returned when enumerating rasterized, TrueType and vector fonts. */ + private static final BitField CLIP_STROKE_PRECIS = BitFieldFactory.getInstance(0x0002); /** * This value is used to control font rotation, as follows: @@ -129,37 +126,25 @@ public class HwmfFont implements FontInfo { * If clear, device fonts SHOULD rotate counterclockwise, but the rotation of other fonts * SHOULD be determined by the orientation of the coordinate system. */ - CLIP_LH_ANGLES (0x00000010), + private static final BitField CLIP_LH_ANGLES = BitFieldFactory.getInstance(0x0010); - /** - * This value SHOULD NOT be used. - */ - CLIP_TT_ALWAYS (0x00000020), + /** This value SHOULD NOT be used. */ + private static final BitField CLIP_TT_ALWAYS = BitFieldFactory.getInstance(0x0020); - /** - * This value specifies that font association SHOULD< be turned off. - */ - CLIP_DFA_DISABLE (0x00000040), + /** This value specifies that font association SHOULD< be turned off. */ + private static final BitField CLIP_DFA_DISABLE = BitFieldFactory.getInstance(0x0040); /** * This value specifies that font embedding MUST be used to render document content; * embedded fonts are read-only. */ - CLIP_EMBEDDED (0x00000080); - + private static final BitField CLIP_EMBEDDED = BitFieldFactory.getInstance(0x0080); int flag; - WmfClipPrecision(int flag) { - this.flag = flag; - } - static WmfClipPrecision valueOf(int flag) { - for (WmfClipPrecision cp : values()) { - if (cp.flag == flag) { - return cp; - } - } - return null; + public int init(LittleEndianInputStream leis) { + flag = leis.readUByte(); + return LittleEndianConsts.BYTE_SIZE; } } @@ -210,7 +195,7 @@ public class HwmfFont implements FontInfo { this.flag = flag; } - static WmfFontQuality valueOf(int flag) { + public static WmfFontQuality valueOf(int flag) { for (WmfFontQuality fq : values()) { if (fq.flag == flag) { return fq; @@ -240,7 +225,7 @@ public class HwmfFont implements FontInfo { * For all height comparisons, the font mapper SHOULD find the largest physical * font that does not exceed the requested size. */ - int height; + protected int height; /** * A 16-bit signed integer that defines the average width, in logical units, of @@ -248,45 +233,45 @@ public class HwmfFont implements FontInfo { * against the digitization aspect ratio of the available fonts to find the closest match, * determined by the absolute value of the difference. */ - int width; + protected int width; /** * A 16-bit signed integer that defines the angle, in tenths of degrees, between the * escapement vector and the x-axis of the device. The escapement vector is parallel * to the base line of a row of text. */ - int escapement; + protected int escapement; /** * A 16-bit signed integer that defines the angle, in tenths of degrees, * between each character's base line and the x-axis of the device. */ - int orientation; + protected int orientation; /** * A 16-bit signed integer that defines the weight of the font in the range 0 * through 1000. For example, 400 is normal and 700 is bold. If this value is 0x0000, * a default weight SHOULD be used. */ - int weight; + protected int weight; /** * A 8-bit Boolean value that specifies the italic attribute of the font. * 0 = not italic / 1 = italic. */ - boolean italic; + protected boolean italic; /** * An 8-bit Boolean value that specifies the underline attribute of the font. * 0 = not underlined / 1 = underlined */ - boolean underline; + protected boolean underline; /** * An 8-bit Boolean value that specifies the strike out attribute of the font. * 0 = not striked out / 1 = striked out */ - boolean strikeOut; + protected boolean strikeOut; /** * An 8-bit unsigned integer that defines the character set. @@ -299,12 +284,12 @@ public class HwmfFont implements FontInfo { * If a typeface name in the FaceName field is specified, the CharSet value MUST match the * character set of that typeface. */ - FontCharset charSet; + protected FontCharset charSet; /** * An 8-bit unsigned integer that defines the output precision. */ - WmfOutPrecision outPrecision; + protected WmfOutPrecision outPrecision; /** * An 8-bit unsigned integer that defines the clipping precision. @@ -312,40 +297,40 @@ public class HwmfFont implements FontInfo { * * @see WmfClipPrecision */ - WmfClipPrecision clipPrecision; + protected final WmfClipPrecision clipPrecision = new WmfClipPrecision(); /** * An 8-bit unsigned integer that defines the output quality. */ - WmfFontQuality quality; + protected WmfFontQuality quality; /** * A PitchAndFamily object that defines the pitch and the family of the font. * Font families specify the look of fonts in a general way and are intended for * specifying fonts when the exact typeface wanted is not available. */ - int pitchAndFamily; + protected int pitchAndFamily; /** * Font families specify the look of fonts in a general way and are * intended for specifying fonts when the exact typeface wanted is not available. * (LSB 4 bits) */ - FontFamily family; + protected FontFamily family; /** * A property of a font that describes the pitch (MSB 2 bits) */ - FontPitch pitch; + protected FontPitch pitch; /** * A null-terminated string of 8-bit Latin-1 [ISO/IEC-8859-1] ANSI * characters that specifies the typeface name of the font. The length of this string MUST NOT * exceed 32 8-bit characters, including the terminating null. */ - String facename; + protected String facename; - public int init(LittleEndianInputStream leis) throws IOException { + public int init(LittleEndianInputStream leis, long recordSize) throws IOException { height = leis.readShort(); width = leis.readShort(); escapement = leis.readShort(); @@ -356,21 +341,17 @@ public class HwmfFont implements FontInfo { strikeOut = leis.readByte() != 0; charSet = FontCharset.valueOf(leis.readUByte()); outPrecision = WmfOutPrecision.valueOf(leis.readUByte()); - clipPrecision = WmfClipPrecision.valueOf(leis.readUByte()); + clipPrecision.init(leis); quality = WmfFontQuality.valueOf(leis.readUByte()); pitchAndFamily = leis.readUByte(); - - byte buf[] = new byte[32], b, readBytes = 0; - do { - if (readBytes == 32) { - throw new IOException("Font facename can't be determined."); - } - buf[readBytes++] = b = leis.readByte(); - } while (b != 0 && b != -1 && readBytes <= 32); - - facename = new String(buf, 0, readBytes-1, StandardCharsets.ISO_8859_1); - + StringBuilder sb = new StringBuilder(); + int readBytes = readString(leis, sb, 32); + if (readBytes == -1) { + throw new IOException("Font facename can't be determined."); + } + facename = sb.toString(); + return 5*LittleEndianConsts.SHORT_SIZE+8*LittleEndianConsts.BYTE_SIZE+readBytes; } @@ -471,4 +452,19 @@ public class HwmfFont implements FontInfo { public void setCharset(FontCharset charset) { throw new UnsupportedOperationException("setCharset not supported by HwmfFont."); } + + protected int readString(LittleEndianInputStream leis, StringBuilder sb, int limit) throws IOException { + byte buf[] = new byte[limit], b, readBytes = 0; + do { + if (readBytes == limit) { + return -1; + } + + buf[readBytes++] = b = leis.readByte(); + } while (b != 0 && b != -1 && readBytes <= limit); + + sb.append(new String(buf, 0, readBytes-1, StandardCharsets.ISO_8859_1)); + + return readBytes; + } } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfHatchStyle.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfHatchStyle.java index 02f896849a..e364d83b3b 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfHatchStyle.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfHatchStyle.java @@ -39,7 +39,7 @@ public enum HwmfHatchStyle { this.flag = flag; } - static HwmfHatchStyle valueOf(int flag) { + public static HwmfHatchStyle valueOf(int flag) { for (HwmfHatchStyle hs : values()) { if (hs.flag == flag) return hs; } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMapMode.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMapMode.java index f4f9a65c27..2e09606bdd 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMapMode.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMapMode.java @@ -105,7 +105,7 @@ public enum HwmfMapMode { this.scale = scale; } - static HwmfMapMode valueOf(int flag) { + public static HwmfMapMode valueOf(int flag) { for (HwmfMapMode mm : values()) { if (mm.flag == flag) return mm; } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java index f5ab077d22..631383d6e4 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java @@ -17,6 +17,7 @@ package org.apache.poi.hwmf.record; +import java.awt.geom.Dimension2D; import java.awt.image.BufferedImage; import java.io.IOException; @@ -24,6 +25,7 @@ import org.apache.poi.hwmf.draw.HwmfDrawProperties; import org.apache.poi.hwmf.draw.HwmfGraphics; import org.apache.poi.hwmf.record.HwmfFill.ColorUsage; import org.apache.poi.hwmf.record.HwmfFill.HwmfImageRecord; +import org.apache.poi.util.Dimension2DDouble; import org.apache.poi.util.LittleEndianConsts; import org.apache.poi.util.LittleEndianInputStream; @@ -34,7 +36,7 @@ public class HwmfMisc { */ public static class WmfSaveDc implements HwmfRecord { @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.saveDc; } @@ -53,7 +55,7 @@ public class HwmfMisc { * The META_SETRELABS record is reserved and not supported. */ public static class WmfSetRelabs implements HwmfRecord { - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.setRelabs; } @@ -81,7 +83,7 @@ public class HwmfMisc { private int nSavedDC; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.restoreDc; } @@ -106,7 +108,7 @@ public class HwmfMisc { private HwmfColorRef colorRef; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.setBkColor; } @@ -140,7 +142,7 @@ public class HwmfMisc { this.flag = flag; } - static HwmfBkMode valueOf(int flag) { + public static HwmfBkMode valueOf(int flag) { for (HwmfBkMode bs : values()) { if (bs.flag == flag) return bs; } @@ -148,9 +150,9 @@ public class HwmfMisc { } } - private HwmfBkMode bkMode; + protected HwmfBkMode bkMode; - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.setBkMode; } @@ -180,7 +182,7 @@ public class HwmfMisc { private int layout; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.setLayout; } @@ -205,10 +207,10 @@ public class HwmfMisc { */ public static class WmfSetMapMode implements HwmfRecord { - private HwmfMapMode mapMode; + protected HwmfMapMode mapMode; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.setMapMode; } @@ -234,12 +236,13 @@ public class HwmfMisc { /** * A 32-bit unsigned integer that defines whether the font mapper should attempt to * match a font's aspect ratio to the current device's aspect ratio. If bit 0 is - * set, the mapper selects only matching fonts. + * set, the font mapper SHOULD select only fonts that match the aspect ratio of the + * output device, as it is currently defined in the playback device context. */ private long mapperValues; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.setMapperFlags; } @@ -262,14 +265,11 @@ public class HwmfMisc { */ public static class WmfSetRop2 implements HwmfRecord { - /** - * A 16-bit unsigned integer that defines the foreground binary raster - * operation mixing mode - */ - private HwmfBinaryRasterOp drawMode; + /** An unsigned integer that defines the foreground binary raster operation mixing mode */ + protected HwmfBinaryRasterOp drawMode; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.setRop2; } @@ -291,24 +291,63 @@ public class HwmfMisc { */ public static class WmfSetStretchBltMode implements HwmfRecord { - /** - * A 16-bit unsigned integer that defines bitmap stretching mode. - * This MUST be one of the values: - * BLACKONWHITE = 0x0001, - * WHITEONBLACK = 0x0002, - * COLORONCOLOR = 0x0003, - * HALFTONE = 0x0004 - */ - private int setStretchBltMode; + public enum StretchBltMode { + /** + * Performs a Boolean AND operation by using the color values for the eliminated and existing pixels. + * If the bitmap is a monochrome bitmap, this mode preserves black pixels at the expense of white pixels. + * + * EMF name: STRETCH_ANDSCANS + */ + BLACKONWHITE(0x0001), + /** + * Performs a Boolean OR operation by using the color values for the eliminated and existing pixels. + * If the bitmap is a monochrome bitmap, this mode preserves white pixels at the expense of black pixels. + * + * EMF name: STRETCH_ORSCANS + */ + WHITEONBLACK(0x0002), + /** + * Deletes the pixels. This mode deletes all eliminated lines of pixels without trying + * to preserve their information. + * + * EMF name: STRETCH_DELETESCANS + */ + COLORONCOLOR(0x0003), + /** + * Maps pixels from the source rectangle into blocks of pixels in the destination rectangle. + * The average color over the destination block of pixels approximates the color of the source + * pixels. + * + * After setting the HALFTONE stretching mode, the brush origin MUST be set to avoid misalignment + * artifacts - in EMF this is done via EmfSetBrushOrgEx + * + * EMF name: STRETCH_HALFTONE + */ + HALFTONE(0x0004); + + public final int flag; + StretchBltMode(int flag) { + this.flag = flag; + } + + public static StretchBltMode valueOf(int flag) { + for (StretchBltMode bs : values()) { + if (bs.flag == flag) return bs; + } + return null; + } + } + + protected StretchBltMode stretchBltMode; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.setStretchBltMode; } @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { - setStretchBltMode = leis.readUShort(); + stretchBltMode = StretchBltMode.valueOf(leis.readUShort()); return LittleEndianConsts.SHORT_SIZE; } @@ -341,7 +380,7 @@ public class HwmfMisc { private HwmfBitmap16 pattern16; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.dibCreatePatternBrush; } @@ -403,10 +442,10 @@ public class HwmfMisc { * A 16-bit unsigned integer used to index into the WMF Object Table to * get the object to be deleted. */ - private int objectIndex; + protected int objectIndex; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.deleteObject; } @@ -418,6 +457,12 @@ public class HwmfMisc { @Override public void draw(HwmfGraphics ctx) { + /* TODO: + * The object specified by this record MUST be deleted from the EMF Object Table. + * If the deleted object is currently selected in the playback device context, + * the default object for that graphics property MUST be restored. + */ + ctx.unsetObjectTableEntry(objectIndex); } } @@ -427,7 +472,7 @@ public class HwmfMisc { private HwmfBitmap16 pattern; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.createPatternBrush; } @@ -452,30 +497,28 @@ public class HwmfMisc { public static class WmfCreatePenIndirect implements HwmfRecord, HwmfObjectTableEntry { - private HwmfPenStyle penStyle; - /** - * A 32-bit PointS Object that specifies a point for the object dimensions. - * The x-coordinate is the pen width. The y-coordinate is ignored. - */ - private int xWidth; - @SuppressWarnings("unused") - private int yWidth; + protected HwmfPenStyle penStyle; + + protected final Dimension2D dimension = new Dimension2DDouble(); /** * A 32-bit ColorRef Object that specifies the pen color value. */ - private HwmfColorRef colorRef; + protected final HwmfColorRef colorRef = new HwmfColorRef(); @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.createPenIndirect; } @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { penStyle = HwmfPenStyle.valueOf(leis.readUShort()); - xWidth = leis.readShort(); - yWidth = leis.readShort(); - colorRef = new HwmfColorRef(); + // A 32-bit PointS Object that specifies a point for the object dimensions. + // The x-coordinate is the pen width. The y-coordinate is ignored. + int xWidth = leis.readShort(); + int yWidth = leis.readShort(); + dimension.setSize(xWidth, yWidth); + int size = colorRef.init(leis); return size+3*LittleEndianConsts.SHORT_SIZE; } @@ -490,7 +533,7 @@ public class HwmfMisc { HwmfDrawProperties p = ctx.getProperties(); p.setPenStyle(penStyle); p.setPenColor(colorRef); - p.setPenWidth(xWidth); + p.setPenWidth(dimension.getWidth()); } } @@ -540,19 +583,14 @@ public class HwmfMisc { * */ public static class WmfCreateBrushIndirect implements HwmfRecord, HwmfObjectTableEntry { - private HwmfBrushStyle brushStyle; + protected HwmfBrushStyle brushStyle; - private HwmfColorRef colorRef; + protected HwmfColorRef colorRef; - /** - * A 16-bit field that specifies the brush hatch type. - * Its interpretation depends on the value of BrushStyle. - * - */ - private HwmfHatchStyle brushHatch; + protected HwmfHatchStyle brushHatch; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.createBrushIndirect; } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPalette.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPalette.java index cb80c454ee..cef75f698d 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPalette.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPalette.java @@ -39,12 +39,12 @@ public class HwmfPalette { private int values; private Color colorRef; - private PaletteEntry() { + public PaletteEntry() { this.values = PC_RESERVED.set(0); this.colorRef = Color.BLACK; } - private PaletteEntry(PaletteEntry other) { + public PaletteEntry(PaletteEntry other) { this.values = other.values; this.colorRef = other.colorRef; } @@ -100,19 +100,24 @@ public class HwmfPalette { * used with the META_SETPALENTRIES and META_ANIMATEPALETTE record types. * When used with META_CREATEPALETTE, it MUST be 0x0300 */ - private int start; + protected int start; - private List palette = new ArrayList<>(); + protected final List palette = new ArrayList<>(); @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { start = leis.readUShort(); + int size = readPaletteEntries(leis, -1); + return size + LittleEndianConsts.SHORT_SIZE; + } + + protected int readPaletteEntries(LittleEndianInputStream leis, int nbrOfEntries) throws IOException { /** * NumberOfEntries (2 bytes): A 16-bit unsigned integer that defines the number of objects in * aPaletteEntries. */ - int numberOfEntries = leis.readUShort(); - int size = 2*LittleEndianConsts.SHORT_SIZE; + final int numberOfEntries = (nbrOfEntries > -1) ? nbrOfEntries : leis.readUShort(); + int size = (nbrOfEntries > -1) ? 0 : LittleEndianConsts.SHORT_SIZE; for (int i=0; i clazz; + public final Supplier constructor; - HwmfRecordType(int id, Class clazz) { + HwmfRecordType(int id, Supplier constructor) { this.id = id; - this.clazz = clazz; + this.constructor = constructor; } public static HwmfRecordType getById(int id) { diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java index 9c6ed1e7f4..787f444beb 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java @@ -17,8 +17,16 @@ package org.apache.poi.hwmf.record; +import static org.apache.poi.hwmf.record.HwmfDraw.readPointS; +import static org.apache.poi.hwmf.record.HwmfDraw.readRectS; + +import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; +import java.io.ByteArrayInputStream; +import java.io.EOFException; import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; import java.nio.charset.Charset; import org.apache.poi.hwmf.draw.HwmfDrawProperties; @@ -31,6 +39,7 @@ import org.apache.poi.util.LittleEndianConsts; import org.apache.poi.util.LittleEndianInputStream; import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; +import org.apache.poi.util.RecordFormatException; public class HwmfText { private static final POILogger logger = POILogFactory.getLogger(HwmfText.class); @@ -52,7 +61,7 @@ public class HwmfText { private int charExtra; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.setTextCharExtra; } @@ -76,7 +85,7 @@ public class HwmfText { private HwmfColorRef colorRef; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.setTextColor; } @@ -112,7 +121,7 @@ public class HwmfText { private int breakExtra; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.setBkColor; } @@ -159,7 +168,7 @@ public class HwmfText { private int xStart; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.textOut; } @@ -195,40 +204,47 @@ public class HwmfText { return ret; } } - - /** - * The META_EXTTEXTOUT record outputs text by using the font, background color, and text color that - * are defined in the playback device context. Optionally, dimensions can be provided for clipping, - * opaquing, or both. - */ - public static class WmfExtTextOut implements HwmfRecord { + public static class WmfExtTextOutOptions { /** - * Indicates that the background color that is defined in the playback device context + * Indicates that the background color that is defined in the playback device context * SHOULD be used to fill the rectangle. - */ + */ private static final BitField ETO_OPAQUE = BitFieldFactory.getInstance(0x0002); - + /** * Indicates that the text SHOULD be clipped to the rectangle. */ private static final BitField ETO_CLIPPED = BitFieldFactory.getInstance(0x0004); /** - * Indicates that the string to be output SHOULD NOT require further processing - * with respect to the placement of the characters, and an array of character - * placement values SHOULD be provided. This character placement process is + * Indicates that the string to be output SHOULD NOT require further processing + * with respect to the placement of the characters, and an array of character + * placement values SHOULD be provided. This character placement process is * useful for fonts in which diacritical characters affect character spacing. */ private static final BitField ETO_GLYPH_INDEX = BitFieldFactory.getInstance(0x0010); /** - * Indicates that the text MUST be laid out in right-to-left reading order, instead of - * the default left-to-right order. This SHOULD be applied only when the font that is + * Indicates that the text MUST be laid out in right-to-left reading order, instead of + * the default left-to-right order. This SHOULD be applied only when the font that is * defined in the playback device context is either Hebrew or Arabic. */ private static final BitField ETO_RTLREADING = BitFieldFactory.getInstance(0x0080); + /** + * This bit indicates that the record does not specify a bounding rectangle for the + * text output. + */ + private static final BitField ETO_NO_RECT = BitFieldFactory.getInstance(0x0100); + + /** + * This bit indicates that the codes for characters in an output text string are 8 bits, + * derived from the low bytes of 16-bit Unicode UTF16-LE character codes, in which + * the high byte is assumed to be 0. + */ + private static final BitField ETO_SMALL_CHARS = BitFieldFactory.getInstance(0x0200); + /** * Indicates that to display numbers, digits appropriate to the locale SHOULD be used. */ @@ -240,32 +256,58 @@ public class HwmfText { private static final BitField ETO_NUMERICSLATIN = BitFieldFactory.getInstance(0x0800); /** - * Indicates that both horizontal and vertical character displacement values + * This bit indicates that no special operating system processing for glyph placement + * should be performed on right-to-left strings; that is, all glyph positioning + * SHOULD be taken care of by drawing and state records in the metafile + */ + private static final BitField ETO_IGNORELANGUAGE = BitFieldFactory.getInstance(0x1000); + + /** + * Indicates that both horizontal and vertical character displacement values * SHOULD be provided. */ private static final BitField ETO_PDY = BitFieldFactory.getInstance(0x2000); + /** This bit is reserved and SHOULD NOT be used. */ + private static final BitField ETO_REVERSE_INDEX_MAP = BitFieldFactory.getInstance(0x10000); + + protected int flag; + + public int init(LittleEndianInputStream leis) { + flag = leis.readUShort(); + return LittleEndianConsts.SHORT_SIZE; + } + + public boolean isOpaque() { + return ETO_OPAQUE.isSet(flag); + } + + public boolean isClipped() { + return ETO_CLIPPED.isSet(flag); + } + } + + /** + * The META_EXTTEXTOUT record outputs text by using the font, background color, and text color that + * are defined in the playback device context. Optionally, dimensions can be provided for clipping, + * opaquing, or both. + */ + public static class WmfExtTextOut implements HwmfRecord { /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, where the - text string is to be located. + * The location, in logical units, where the text string is to be placed. */ - private int y; - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, where the - text string is to be located. - */ - private int x; + protected final Point2D reference = new Point2D.Double(); + /** * A 16-bit signed integer that defines the length of the string. */ - private int stringLength; - - /** - * A 16-bit unsigned integer that defines the use of the application-defined - * rectangle. This member can be a combination of one or more values in the - * ExtTextOutOptions Flags (ETO_*) - */ - private int fwOpts; + protected int stringLength; + /** + * A 16-bit unsigned integer that defines the use of the application-defined + * rectangle. This member can be a combination of one or more values in the + * ExtTextOutOptions Flags (ETO_*) + */ + protected final WmfExtTextOutOptions options; /** * An optional 8-byte Rect Object (section 2.2.2.18) that defines the * dimensions, in logical coordinates, of a rectangle that is used for clipping, opaquing, or both. @@ -274,14 +316,14 @@ public class HwmfText { * Each value is a 16-bit signed integer that defines the coordinate, in logical coordinates, of * the upper-left corner of the rectangle */ - private int left,top,right,bottom; + protected final Rectangle2D bounds = new Rectangle2D.Double(); /** * A variable-length string that specifies the text to be drawn. The string does * not need to be null-terminated, because StringLength specifies the length of the string. If * the length is odd, an extra byte is placed after it so that the following member (optional Dx) is * aligned on a 16-bit boundary. */ - private byte[] rawTextBytes; + protected byte[] rawTextBytes; /** * An optional array of 16-bit signed integers that indicate the distance between * origins of adjacent character cells. For example, Dx[i] logical units separate the origins of @@ -289,9 +331,17 @@ public class HwmfText { * number of values as there are characters in the string. */ private int dx[]; - + + public WmfExtTextOut() { + this(new WmfExtTextOutOptions()); + } + + protected WmfExtTextOut(WmfExtTextOutOptions options) { + this.options = options; + } + @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.extTextOut; } @@ -299,22 +349,17 @@ public class HwmfText { public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { // -6 bytes of record function and length header final int remainingRecordSize = (int)(recordSize-6); - - y = leis.readShort(); - x = leis.readShort(); + + int size = readPointS(leis, reference); + stringLength = leis.readShort(); - fwOpts = leis.readUShort(); - - int size = 4*LittleEndianConsts.SHORT_SIZE; - + size += LittleEndianConsts.SHORT_SIZE; + size += options.init(leis); + // Check if we have a rectangle - if ((ETO_OPAQUE.isSet(fwOpts) || ETO_CLIPPED.isSet(fwOpts)) && size+8<=remainingRecordSize) { - // the bounding rectangle is optional and only read when fwOpts are given - left = leis.readShort(); - top = leis.readShort(); - right = leis.readShort(); - bottom = leis.readShort(); - size += 4*LittleEndianConsts.SHORT_SIZE; + if ((options.isOpaque() || options.isClipped()) && size+8<=remainingRecordSize) { + // the bounding rectangle is optional and only read when options are given + size += readRectS(leis, bounds); } rawTextBytes = IOUtils.safelyAllocate(stringLength+(stringLength&1), MAX_RECORD_LENGTH); @@ -342,12 +387,46 @@ public class HwmfText { @Override public void draw(HwmfGraphics ctx) { - Rectangle2D bounds = new Rectangle2D.Double(x, y, 0, 0); + Rectangle2D bounds = new Rectangle2D.Double(reference.getX(), reference.getY(), 0, 0); ctx.drawString(getTextBytes(), bounds, dx); } - public String getText(Charset charset) { - return new String(getTextBytes(), charset); + + public String getText(Charset charset) throws IOException { + StringBuilder sb = new StringBuilder(); + try (Reader r = new InputStreamReader(new ByteArrayInputStream(rawTextBytes), charset)) { + for (int i = 0; i < stringLength; i++) { + sb.appendCodePoint(readCodePoint(r)); + } + } + return sb.toString(); + } + + //TODO: move this to IOUtils? + private int readCodePoint(Reader r) throws IOException { + int c1 = r.read(); + if (c1 == -1) { + throw new EOFException("Tried to read beyond byte array"); + } + if (!Character.isHighSurrogate((char)c1)) { + return c1; + } + int c2 = r.read(); + if (c2 == -1) { + throw new EOFException("Tried to read beyond byte array"); + } + if (!Character.isLowSurrogate((char)c2)) { + throw new RecordFormatException("Expected low surrogate after high surrogate"); + } + return Character.toCodePoint((char)c1, (char)c2); + } + + public Point2D getReference() { + return reference; + } + + public Rectangle2D getBounds() { + return bounds; } /** @@ -482,10 +561,10 @@ public class HwmfText { * for text with a horizontal baseline, and VerticalTextAlignmentMode Flags * for text with a vertical baseline. */ - private int textAlignmentMode; + protected int textAlignmentMode; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.setTextAlign; } @@ -533,17 +612,24 @@ public class HwmfText { } public static class WmfCreateFontIndirect implements HwmfRecord, HwmfObjectTableEntry { - private HwmfFont font; - + protected final HwmfFont font; + + public WmfCreateFontIndirect() { + this(new HwmfFont()); + } + + protected WmfCreateFontIndirect(HwmfFont font) { + this.font = font; + } + @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.createFontIndirect; } @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { - font = new HwmfFont(); - return font.init(leis); + return font.init(leis, recordSize); } @Override diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java index 42913aad64..389cfbc7dd 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java @@ -33,18 +33,14 @@ public class HwmfWindowing { */ public static class WmfSetViewportOrg implements HwmfRecord { - /** - * A 16-bit signed integer that defines the vertical offset, in device units. - */ - private int y; + /** A signed integer that defines the vertical offset, in device units. */ + protected int y; - /** - * A 16-bit signed integer that defines the horizontal offset, in device units. - */ - private int x; + /** A signed integer that defines the horizontal offset, in device units. */ + protected int x; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.setViewportOrg; } @@ -67,20 +63,14 @@ public class HwmfWindowing { */ public static class WmfSetViewportExt implements HwmfRecord { - /** - * A 16-bit signed integer that defines the vertical extent - * of the viewport in device units. - */ - private int height; + /** A signed integer that defines the vertical extent of the viewport in device units. */ + protected int height; - /** - * A 16-bit signed integer that defines the horizontal extent - * of the viewport in device units. - */ - private int width; + /** A signed integer that defines the horizontal extent of the viewport in device units. */ + protected int width; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.setViewportExt; } @@ -114,7 +104,7 @@ public class HwmfWindowing { private int xOffset; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.offsetViewportOrg; } @@ -139,18 +129,14 @@ public class HwmfWindowing { */ public static class WmfSetWindowOrg implements HwmfRecord { - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units. - */ - private int y; + /** A signed integer that defines the y-coordinate, in logical units. */ + protected int y; - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units. - */ - private int x; + /** A signed integer that defines the x-coordinate, in logical units. */ + protected int x; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.setWindowOrg; } @@ -182,20 +168,14 @@ public class HwmfWindowing { */ public static class WmfSetWindowExt implements HwmfRecord { - /** - * A 16-bit signed integer that defines the vertical extent of - * the window in logical units. - */ - private int height; + /** A signed integer that defines the vertical extent of the window in logical units. */ + protected int height; - /** - * A 16-bit signed integer that defines the horizontal extent of - * the window in logical units. - */ - private int width; + /** A signed integer that defines the horizontal extent of the window in logical units. */ + protected int width; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.setWindowExt; } @@ -238,7 +218,7 @@ public class HwmfWindowing { private int xOffset; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.offsetWindowOrg; } @@ -264,31 +244,31 @@ public class HwmfWindowing { public static class WmfScaleWindowExt implements HwmfRecord { /** - * A 16-bit signed integer that defines the amount by which to divide the + * A signed integer that defines the amount by which to divide the * result of multiplying the current y-extent by the value of the yNum member. */ - private int yDenom; + protected int yDenom; /** - * A 16-bit signed integer that defines the amount by which to multiply the + * A signed integer that defines the amount by which to multiply the * current y-extent. */ - private int yNum; + protected int yNum; /** - * A 16-bit signed integer that defines the amount by which to divide the + * A signed integer that defines the amount by which to divide the * result of multiplying the current x-extent by the value of the xNum member. */ - private int xDenom; + protected int xDenom; /** - * A 16-bit signed integer that defines the amount by which to multiply the + * A signed integer that defines the amount by which to multiply the * current x-extent. */ - private int xNum; + protected int xNum; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.scaleWindowExt; } @@ -320,31 +300,31 @@ public class HwmfWindowing { public static class WmfScaleViewportExt implements HwmfRecord { /** - * A 16-bit signed integer that defines the amount by which to divide the + * A signed integer that defines the amount by which to divide the * result of multiplying the current y-extent by the value of the yNum member. */ - private int yDenom; + protected int yDenom; /** - * A 16-bit signed integer that defines the amount by which to multiply the + * A signed integer that defines the amount by which to multiply the * current y-extent. */ - private int yNum; + protected int yNum; /** - * A 16-bit signed integer that defines the amount by which to divide the + * A signed integer that defines the amount by which to divide the * result of multiplying the current x-extent by the value of the xNum member. */ - private int xDenom; + protected int xDenom; /** - * A 16-bit signed integer that defines the amount by which to multiply the + * A signed integer that defines the amount by which to multiply the * current x-extent. */ - private int xNum; + protected int xNum; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.scaleViewportExt; } @@ -376,17 +356,17 @@ public class HwmfWindowing { public static class WmfOffsetClipRgn implements HwmfRecord, HwmfObjectTableEntry { /** - * A 16-bit signed integer that defines the number of logical units to move up or down. + * A signed integer that defines the number of logical units to move up or down. */ - private int yOffset; + protected int yOffset; /** - * A 16-bit signed integer that defines the number of logical units to move left or right. + * A signed integer that defines the number of logical units to move left or right. */ - private int xOffset; + protected int xOffset; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.offsetClipRgn; } @@ -413,41 +393,29 @@ public class HwmfWindowing { */ public static class WmfExcludeClipRect implements HwmfRecord, HwmfObjectTableEntry { - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the - * lower-right corner of the rectangle. - */ - private int bottom; - - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of the - * lower-right corner of the rectangle. - */ - private int right; - - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the - * upper-left corner of the rectangle. - */ - private int top; - - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of the - * upper-left corner of the rectangle. - */ - private int left; + /** a rectangle in logical units */ + protected final Rectangle2D bounds = new Rectangle2D.Double(); @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.excludeClipRect; } @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { - bottom = leis.readShort(); - right = leis.readShort(); - top = leis.readShort(); - left = leis.readShort(); + // A 16-bit signed integer that defines the y-coordinate, in logical units, of the + // lower-right corner of the rectangle. + final int bottom = leis.readShort(); + // A 16-bit signed integer that defines the x-coordinate, in logical units, of the + // lower-right corner of the rectangle. + final int right = leis.readShort(); + // A 16-bit signed integer that defines the y-coordinate, in logical units, of the + // upper-left corner of the rectangle. + final int top = leis.readShort(); + // A 16-bit signed integer that defines the x-coordinate, in logical units, of the + // upper-left corner of the rectangle. + final int left = leis.readShort(); + bounds.setRect(left, top, right-left, bottom-top); return 4*LittleEndianConsts.SHORT_SIZE; } @@ -468,41 +436,29 @@ public class HwmfWindowing { */ public static class WmfIntersectClipRect implements HwmfRecord, HwmfObjectTableEntry { - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the - * lower-right corner of the rectangle. - */ - private int bottom; - - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of the - * lower-right corner of the rectangle. - */ - private int right; - - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the - * upper-left corner of the rectangle. - */ - private int top; - - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of the - * upper-left corner of the rectangle. - */ - private int left; + /** a rectangle in logical units */ + protected final Rectangle2D bounds = new Rectangle2D.Double(); @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.intersectClipRect; } @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { - bottom = leis.readShort(); - right = leis.readShort(); - top = leis.readShort(); - left = leis.readShort(); + // A 16-bit signed integer that defines the y-coordinate, in logical units, of the + // lower-right corner of the rectangle. + final int bottom = leis.readShort(); + // A 16-bit signed integer that defines the x-coordinate, in logical units, of the + // lower-right corner of the rectangle. + final int right = leis.readShort(); + // A 16-bit signed integer that defines the y-coordinate, in logical units, of the + // upper-left corner of the rectangle. + final int top = leis.readShort(); + // A 16-bit signed integer that defines the x-coordinate, in logical units, of the + // upper-left corner of the rectangle. + final int left = leis.readShort(); + bounds.setRect(left, top, right-left, bottom-top); return 4*LittleEndianConsts.SHORT_SIZE; } @@ -528,7 +484,7 @@ public class HwmfWindowing { private int region; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.selectClipRegion; } @@ -650,7 +606,7 @@ public class HwmfWindowing { private WmfScanObject scanObjects[]; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.createRegion; } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfPicture.java b/src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfPicture.java index fa59f4bc31..b380d46143 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfPicture.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfPicture.java @@ -51,8 +51,7 @@ public class HwmfPicture { public HwmfPicture(InputStream inputStream) throws IOException { - try (BufferedInputStream bis = new BufferedInputStream(inputStream, 10000); - LittleEndianInputStream leis = new LittleEndianInputStream(bis)) { + try (LittleEndianInputStream leis = new LittleEndianInputStream(inputStream)) { placeableHeader = HwmfPlaceableHeader.readHeader(leis); header = new HwmfHeader(leis); @@ -82,17 +81,12 @@ public class HwmfPicture { if (wrt == HwmfRecordType.eof) { break; } - if (wrt.clazz == null) { + if (wrt.constructor == null) { throw new IOException("unsupported record type: "+recordFunction); } - HwmfRecord wr; - try { - wr = wrt.clazz.newInstance(); - records.add(wr); - } catch (Exception e) { - throw (IOException)new IOException("can't create wmf record").initCause(e); - } + final HwmfRecord wr = wrt.constructor.get(); + records.add(wr); consumedSize += wr.init(leis, recordSize, recordFunction); int remainingSize = (int)(recordSize - consumedSize); diff --git a/src/scratchpad/testcases/org/apache/poi/hemf/extractor/HemfExtractorTest.java b/src/scratchpad/testcases/org/apache/poi/hemf/extractor/HemfExtractorTest.java deleted file mode 100644 index 8d32b21a5e..0000000000 --- a/src/scratchpad/testcases/org/apache/poi/hemf/extractor/HemfExtractorTest.java +++ /dev/null @@ -1,198 +0,0 @@ -/* ==================================================================== - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -==================================================================== */ - - -package org.apache.poi.hemf.extractor; - -import static org.apache.poi.POITestCase.assertContains; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.InputStream; -import java.util.HashSet; -import java.util.Set; - -import org.apache.poi.POIDataSamples; -import org.apache.poi.hemf.record.AbstractHemfComment; -import org.apache.poi.hemf.record.HemfCommentPublic; -import org.apache.poi.hemf.record.HemfCommentRecord; -import org.apache.poi.hemf.record.HemfHeader; -import org.apache.poi.hemf.record.HemfRecord; -import org.apache.poi.hemf.record.HemfRecordType; -import org.apache.poi.hemf.record.HemfText; -import org.apache.poi.util.IOUtils; -import org.apache.poi.util.RecordFormatException; -import org.junit.Test; - -public class HemfExtractorTest { - - @Test - public void testBasicWindows() throws Exception { - InputStream is = POIDataSamples.getSpreadSheetInstance().openResourceAsStream("SimpleEMF_windows.emf"); - HemfExtractor ex = new HemfExtractor(is); - HemfHeader header = ex.getHeader(); - assertEquals(27864, header.getBytes()); - assertEquals(31, header.getRecords()); - assertEquals(3, header.getHandles()); - assertEquals(346000, header.getMicrometersX()); - assertEquals(194000, header.getMicrometersY()); - - int records = 0; - for (HemfRecord record : ex) { - records++; - } - - assertEquals(header.getRecords() - 1, records); - } - - @Test - public void testBasicMac() throws Exception { - InputStream is = - POIDataSamples.getSpreadSheetInstance().openResourceAsStream("SimpleEMF_mac.emf"); - HemfExtractor ex = new HemfExtractor(is); - HemfHeader header = ex.getHeader(); - - int records = 0; - boolean extractedData = false; - for (HemfRecord record : ex) { - if (record.getRecordType() == HemfRecordType.comment) { - AbstractHemfComment comment = ((HemfCommentRecord) record).getComment(); - if (comment instanceof HemfCommentPublic.MultiFormats) { - for (HemfCommentPublic.HemfMultiFormatsData d : ((HemfCommentPublic.MultiFormats) comment).getData()) { - byte[] data = d.getData(); - //make sure header starts at 0 - assertEquals('%', data[0]); - assertEquals('P', data[1]); - assertEquals('D', data[2]); - assertEquals('F', data[3]); - - //make sure byte array ends at EOF\n - assertEquals('E', data[data.length - 4]); - assertEquals('O', data[data.length - 3]); - assertEquals('F', data[data.length - 2]); - assertEquals('\n', data[data.length - 1]); - extractedData = true; - } - } - } - records++; - } - assertTrue(extractedData); - assertEquals(header.getRecords() - 1, records); - } - - @Test - public void testMacText() throws Exception { - InputStream is = - POIDataSamples.getSpreadSheetInstance().openResourceAsStream("SimpleEMF_mac.emf"); - HemfExtractor ex = new HemfExtractor(is); - - long lastY = -1; - long lastX = -1; - long fudgeFactorX = 1000;//derive this from the font information! - StringBuilder sb = new StringBuilder(); - for (HemfRecord record : ex) { - if (record.getRecordType().equals(HemfRecordType.exttextoutw)) { - HemfText.ExtTextOutW extTextOutW = (HemfText.ExtTextOutW) record; - if (lastY > -1 && lastY != extTextOutW.getY()) { - sb.append("\n"); - lastX = -1; - } - if (lastX > -1 && extTextOutW.getX() - lastX > fudgeFactorX) { - sb.append(" "); - } - sb.append(extTextOutW.getText()); - lastY = extTextOutW.getY(); - lastX = extTextOutW.getX(); - } - } - String txt = sb.toString(); - assertContains(txt, "Tika http://incubator.apache.org"); - assertContains(txt, "Latest News\n"); - } - - @Test - public void testWindowsText() throws Exception { - InputStream is = POIDataSamples.getSpreadSheetInstance().openResourceAsStream("SimpleEMF_windows.emf"); - HemfExtractor ex = new HemfExtractor(is); - long lastY = -1; - long lastX = -1; - long fudgeFactorX = 1000;//derive this from the font or frame/bounds information - StringBuilder sb = new StringBuilder(); - Set expectedParts = new HashSet<>(); - expectedParts.add("C:\\Users\\tallison\\"); - expectedParts.add("testPDF.pdf"); - int foundExpected = 0; - for (HemfRecord record : ex) { - if (record.getRecordType().equals(HemfRecordType.exttextoutw)) { - HemfText.ExtTextOutW extTextOutW = (HemfText.ExtTextOutW) record; - if (lastY > -1 && lastY != extTextOutW.getY()) { - sb.append("\n"); - lastX = -1; - } - if (lastX > -1 && extTextOutW.getX() - lastX > fudgeFactorX) { - sb.append(" "); - } - String txt = extTextOutW.getText(); - if (expectedParts.contains(txt)) { - foundExpected++; - } - sb.append(txt); - lastY = extTextOutW.getY(); - lastX = extTextOutW.getX(); - } - } - String txt = sb.toString(); - assertContains(txt, "C:\\Users\\tallison\\\n"); - assertContains(txt, "asf2-git-1.x\\tika-\n"); - assertEquals(expectedParts.size(), foundExpected); - } - - @Test(expected = RecordFormatException.class) - public void testInfiniteLoopOnFile() throws Exception { - InputStream is = null; - try { - is = POIDataSamples.getSpreadSheetInstance().openResourceAsStream("61294.emf"); - - HemfExtractor ex = new HemfExtractor(is); - for (HemfRecord record : ex) { - - } - } finally { - IOUtils.closeQuietly(is); - } - } - - @Test(expected = RecordFormatException.class) - public void testInfiniteLoopOnByteArray() throws Exception { - InputStream is = POIDataSamples.getSpreadSheetInstance().openResourceAsStream("61294.emf"); - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - IOUtils.copy(is, bos); - is.close(); - - HemfExtractor ex = new HemfExtractor(new ByteArrayInputStream(bos.toByteArray())); - for (HemfRecord record : ex) { - - } - } - - /* - govdocs1 064213.doc-0.emf contains an example of extextouta - */ -} \ No newline at end of file diff --git a/src/scratchpad/testcases/org/apache/poi/hemf/hemfplus/extractor/HemfPlusExtractorTest.java b/src/scratchpad/testcases/org/apache/poi/hemf/hemfplus/extractor/HemfPlusExtractorTest.java index f3bdbf7b4f..5811accac5 100644 --- a/src/scratchpad/testcases/org/apache/poi/hemf/hemfplus/extractor/HemfPlusExtractorTest.java +++ b/src/scratchpad/testcases/org/apache/poi/hemf/hemfplus/extractor/HemfPlusExtractorTest.java @@ -25,13 +25,13 @@ import java.util.ArrayList; import java.util.List; import org.apache.poi.POIDataSamples; -import org.apache.poi.hemf.extractor.HemfExtractor; -import org.apache.poi.hemf.hemfplus.record.HemfPlusHeader; -import org.apache.poi.hemf.hemfplus.record.HemfPlusRecord; -import org.apache.poi.hemf.hemfplus.record.HemfPlusRecordType; -import org.apache.poi.hemf.record.HemfCommentEMFPlus; -import org.apache.poi.hemf.record.HemfCommentRecord; -import org.apache.poi.hemf.record.HemfRecord; +import org.apache.poi.hemf.record.emf.HemfComment.EmfComment; +import org.apache.poi.hemf.record.emf.HemfComment.EmfCommentDataPlus; +import org.apache.poi.hemf.record.emf.HemfRecord; +import org.apache.poi.hemf.record.emfplus.HemfPlusHeader; +import org.apache.poi.hemf.record.emfplus.HemfPlusRecord; +import org.apache.poi.hemf.record.emfplus.HemfPlusRecordType; +import org.apache.poi.hemf.usermodel.HemfPicture; import org.junit.Test; public class HemfPlusExtractorTest { @@ -39,7 +39,7 @@ public class HemfPlusExtractorTest { @Test public void testBasic() throws Exception { //test header - HemfCommentEMFPlus emfPlus = getCommentRecord("SimpleEMF_windows.emf", 0); + EmfCommentDataPlus emfPlus = getCommentRecord("SimpleEMF_windows.emf", 0); List records = emfPlus.getRecords(); assertEquals(1, records.size()); assertEquals(HemfPlusRecordType.header, records.get(0).getRecordType()); @@ -72,24 +72,20 @@ public class HemfPlusExtractorTest { } - private HemfCommentEMFPlus getCommentRecord(String testFileName, int recordIndex) throws Exception { - InputStream is = null; - HemfCommentEMFPlus returnRecord = null; + private EmfCommentDataPlus getCommentRecord(String testFileName, int recordIndex) throws Exception { + EmfCommentDataPlus returnRecord = null; - try { - is = POIDataSamples.getSpreadSheetInstance().openResourceAsStream(testFileName); - HemfExtractor ex = new HemfExtractor(is); + try (InputStream is = POIDataSamples.getSpreadSheetInstance().openResourceAsStream(testFileName)) { + HemfPicture ex = new HemfPicture(is); int i = 0; for (HemfRecord record : ex) { if (i == recordIndex) { - HemfCommentRecord commentRecord = ((HemfCommentRecord) record); - returnRecord = (HemfCommentEMFPlus) commentRecord.getComment(); + EmfComment commentRecord = ((EmfComment) record); + returnRecord = (EmfCommentDataPlus) commentRecord.getCommentData(); break; } i++; } - } finally { - is.close(); } return returnRecord; } diff --git a/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java b/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java new file mode 100644 index 0000000000..f0e259df4d --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java @@ -0,0 +1,201 @@ +/* ==================================================================== + 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.usermodel; + +import static org.apache.poi.POITestCase.assertContains; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.awt.geom.Point2D; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +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.HemfComment.EmfCommentDataFormat; +import org.apache.poi.hemf.record.emf.HemfComment.EmfCommentDataMultiformats; +import org.apache.poi.hemf.record.emf.HemfHeader; +import org.apache.poi.hemf.record.emf.HemfRecord; +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.junit.Test; + +public class HemfPictureTest { + + private POIDataSamples samples = POIDataSamples.getSpreadSheetInstance(); + + @Test + public void testBasicWindows() throws Exception { + try (InputStream is = 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()); + + List records = pic.getRecords(); + + assertEquals(31, records.size()); + } + } + + @Test + public void testBasicMac() throws Exception { + try (InputStream is = samples.openResourceAsStream("SimpleEMF_mac.emf")) { + HemfPicture pic = new HemfPicture(is); + HemfHeader header = pic.getHeader(); + + int records = 0; + boolean extractedData = false; + for (HemfRecord record : pic) { + if (record.getEmfRecordType() == HemfRecordType.comment) { + HemfComment.EmfCommentData comment = ((EmfComment) record).getCommentData(); + if (comment instanceof EmfCommentDataMultiformats) { + for (EmfCommentDataFormat d : ((EmfCommentDataMultiformats) comment).getFormats()) { + byte[] data = d.getRawData(); + //make sure header starts at 0 + assertEquals('%', data[0]); + assertEquals('P', data[1]); + assertEquals('D', data[2]); + assertEquals('F', data[3]); + + //make sure byte array ends at EOF\n + assertEquals('E', data[data.length - 4]); + assertEquals('O', data[data.length - 3]); + assertEquals('F', data[data.length - 2]); + assertEquals('\n', data[data.length - 1]); + extractedData = true; + } + } + } + records++; + } + assertTrue(extractedData); + assertEquals(header.getRecords(), records); + } + } + + @Test + public void testMacText() throws Exception { + try (InputStream is = samples.openResourceAsStream("SimpleEMF_mac.emf")) { + HemfPicture pic = new HemfPicture(is); + + double lastY = -1; + double lastX = -1; + //derive this from the font information! + long fudgeFactorX = 1000; + StringBuilder sb = new StringBuilder(); + for (HemfRecord record : pic) { + if (record.getEmfRecordType().equals(HemfRecordType.exttextoutw)) { + HemfText.ExtTextOutW extTextOutW = (HemfText.ExtTextOutW) record; + Point2D reference = extTextOutW.getTextObject().getReference(); + if (lastY > -1 && lastY != reference.getY()) { + sb.append("\n"); + lastX = -1; + } + if (lastX > -1 && reference.getX() - lastX > fudgeFactorX) { + sb.append(" "); + } + sb.append(extTextOutW.getText()); + lastY = reference.getY(); + lastX = reference.getX(); + } + } + String txt = sb.toString(); + assertContains(txt, "Tika http://incubator.apache.org"); + assertContains(txt, "Latest News\n"); + } + } + + @Test + public void testWindowsText() throws Exception { + try (InputStream is = samples.openResourceAsStream("SimpleEMF_windows.emf")) { + HemfPicture pic = new HemfPicture(is); + double lastY = -1; + double lastX = -1; + long fudgeFactorX = 1000;//derive this from the font or frame/bounds information + StringBuilder sb = new StringBuilder(); + Set expectedParts = new HashSet<>(); + expectedParts.add("C:\\Users\\tallison\\"); + expectedParts.add("testPDF.pdf"); + int foundExpected = 0; + for (HemfRecord record : pic) { + if (record.getEmfRecordType().equals(HemfRecordType.exttextoutw)) { + HemfText.ExtTextOutW extTextOutW = (HemfText.ExtTextOutW) record; + Point2D reference = extTextOutW.getTextObject().getReference(); + if (lastY > -1 && lastY != reference.getY()) { + sb.append("\n"); + lastX = -1; + } + if (lastX > -1 && reference.getX() - lastX > fudgeFactorX) { + sb.append(" "); + } + String txt = extTextOutW.getText(); + if (expectedParts.contains(txt)) { + foundExpected++; + } + sb.append(txt); + lastY = reference.getY(); + lastX = reference.getX(); + } + } + String txt = sb.toString(); + assertContains(txt, "C:\\Users\\tallison\\\n"); + assertContains(txt, "asf2-git-1.x\\tika-\n"); + assertEquals(expectedParts.size(), foundExpected); + } + } + + @Test(expected = RecordFormatException.class) + public void testInfiniteLoopOnFile() throws Exception { + try (InputStream is = 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")) { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + IOUtils.copy(is, bos); + is.close(); + + HemfPicture pic = new HemfPicture(new ByteArrayInputStream(bos.toByteArray())); + for (HemfRecord record : pic) { + + } + } + } + + /* + govdocs1 064213.doc-0.emf contains an example of extextouta + */ +} \ No newline at end of file diff --git a/src/scratchpad/testcases/org/apache/poi/hwmf/TestHwmfParsing.java b/src/scratchpad/testcases/org/apache/poi/hwmf/TestHwmfParsing.java index 1667b67b49..df084cdad3 100644 --- a/src/scratchpad/testcases/org/apache/poi/hwmf/TestHwmfParsing.java +++ b/src/scratchpad/testcases/org/apache/poi/hwmf/TestHwmfParsing.java @@ -222,11 +222,11 @@ public class TestHwmfParsing { //this happens to work on this test file, but you need to //do what Graphics does by maintaining the stack, etc.! for (HwmfRecord r : wmf.getRecords()) { - if (r.getRecordType().equals(HwmfRecordType.createFontIndirect)) { + if (r.getWmfRecordType().equals(HwmfRecordType.createFontIndirect)) { HwmfFont font = ((HwmfText.WmfCreateFontIndirect)r).getFont(); charset = (font.getCharset().getCharset() == null) ? LocaleUtil.CHARSET_1252 : font.getCharset().getCharset(); } - if (r.getRecordType().equals(HwmfRecordType.extTextOut)) { + if (r.getWmfRecordType().equals(HwmfRecordType.extTextOut)) { HwmfText.WmfExtTextOut textOut = (HwmfText.WmfExtTextOut)r; sb.append(textOut.getText(charset)).append("\n"); } @@ -250,11 +250,11 @@ public class TestHwmfParsing { //this happens to work on this test file, but you need to //do what Graphics does by maintaining the stack, etc.! for (HwmfRecord r : wmf.getRecords()) { - if (r.getRecordType().equals(HwmfRecordType.createFontIndirect)) { + if (r.getWmfRecordType().equals(HwmfRecordType.createFontIndirect)) { HwmfFont font = ((HwmfText.WmfCreateFontIndirect)r).getFont(); charset = (font.getCharset().getCharset() == null) ? LocaleUtil.CHARSET_1252 : font.getCharset().getCharset(); } - if (r.getRecordType().equals(HwmfRecordType.extTextOut)) { + if (r.getWmfRecordType().equals(HwmfRecordType.extTextOut)) { HwmfText.WmfExtTextOut textOut = (HwmfText.WmfExtTextOut)r; sb.append(textOut.getText(charset)).append("\n"); } From e6e1f622d8289b5eb08633c6edcb45a0ba3f48e6 Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Mon, 17 Sep 2018 19:25:40 +0000 Subject: [PATCH 04/21] #60656 - Support export file that contains emf and render it correctly git-svn-id: https://svn.apache.org/repos/asf/poi/branches/hemf@1841134 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/poi/hemf/draw/HemfGraphics.java | 16 ++ .../apache/poi/hemf/record/emf/HemfDraw.java | 225 +++++++++++++++++- .../apache/poi/hemf/record/emf/HemfFill.java | 19 ++ .../apache/poi/hemf/record/emf/HemfMisc.java | 93 +++++--- .../poi/hemf/record/emf/HemfRecordType.java | 20 +- .../poi/hemf/record/emf/HemfWindowing.java | 26 +- .../record/emf/UnimplementedHemfRecord.java | 9 +- .../hemf/record/emfplus/HemfPlusHeader.java | 2 +- .../hemf/record/emfplus/HemfPlusRecord.java | 2 +- .../emfplus/HemfPlusRecordIterator.java | 2 +- .../emfplus/UnimplementedHemfPlusRecord.java | 8 +- .../apache/poi/hwmf/draw/HwmfGraphics.java | 59 ++--- .../extractor/HemfPlusExtractorTest.java | 4 +- 13 files changed, 393 insertions(+), 92 deletions(-) diff --git a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java index 89a764af14..ff190652e1 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java +++ b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java @@ -20,10 +20,26 @@ package org.apache.poi.hemf.draw; import java.awt.Graphics2D; import java.awt.geom.Rectangle2D; +import org.apache.poi.hwmf.draw.HwmfDrawProperties; import org.apache.poi.hwmf.draw.HwmfGraphics; public class HemfGraphics extends HwmfGraphics { public HemfGraphics(Graphics2D graphicsCtx, Rectangle2D bbox) { super(graphicsCtx,bbox); } + + @Override + public HemfDrawProperties getProperties() { + if (prop == null) { + prop = new HemfDrawProperties(); + } + return (HemfDrawProperties)prop; + } + + @Override + public void saveProperties() { + assert(prop != null); + propStack.add(prop); + prop = new HemfDrawProperties((HemfDrawProperties)prop); + } } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java index 688a9ff66b..77ddf7e39e 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java @@ -17,7 +17,10 @@ package org.apache.poi.hemf.record.emf; -import java.awt.geom.AffineTransform; +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.Dimension2D; import java.awt.geom.Path2D; import java.awt.geom.PathIterator; @@ -25,10 +28,13 @@ import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; 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; +import org.apache.poi.hwmf.record.HwmfPenStyle; import org.apache.poi.util.LittleEndianConsts; import org.apache.poi.util.LittleEndianInputStream; @@ -40,6 +46,12 @@ public class HemfDraw { */ public static class EmfSelectObject extends WmfSelectObject implements HemfRecord { + private static final HwmfColorRef WHITE = new HwmfColorRef(Color.WHITE); + private static final HwmfColorRef LTGRAY = new HwmfColorRef(new Color(0x00C0C0C0)); + private static final HwmfColorRef GRAY = new HwmfColorRef(new Color(0x00808080)); + private static final HwmfColorRef DKGRAY = new HwmfColorRef(new Color(0x00404040)); + private static final HwmfColorRef BLACK = new HwmfColorRef(Color.BLACK); + @Override public HemfRecordType getEmfRecordType() { return HemfRecordType.selectObject; @@ -52,6 +64,125 @@ public class HemfDraw { objectIndex = leis.readInt(); return LittleEndianConsts.INT_SIZE; } + + @Override + public void draw(HemfGraphics ctx) { + if ((objectIndex & 0x80000000) != 0) { + selectStockObject(ctx); + } else { + super.draw(ctx); + } + } + + private void selectStockObject(HemfGraphics ctx) { + final HemfDrawProperties prop = ctx.getProperties(); + switch (objectIndex) { + case 0x80000000: + // WHITE_BRUSH - A white, solid-color brush + // BrushStyle: BS_SOLID + // Color: 0x00FFFFFF + prop.setBrushColor(WHITE); + prop.setBrushStyle(BS_SOLID); + break; + case 0x80000001: + // LTGRAY_BRUSH - A light gray, solid-color brush + // BrushStyle: BS_SOLID + // Color: 0x00C0C0C0 + prop.setBrushColor(LTGRAY); + prop.setBrushStyle(BS_SOLID); + break; + case 0x80000002: + // GRAY_BRUSH - A gray, solid-color brush + // BrushStyle: BS_SOLID + // Color: 0x00808080 + prop.setBrushColor(GRAY); + prop.setBrushStyle(BS_SOLID); + break; + case 0x80000003: + // DKGRAY_BRUSH - A dark gray, solid color brush + // BrushStyle: BS_SOLID + // Color: 0x00404040 + prop.setBrushColor(DKGRAY); + prop.setBrushStyle(BS_SOLID); + break; + case 0x80000004: + // BLACK_BRUSH - A black, solid color brush + // BrushStyle: BS_SOLID + // Color: 0x00000000 + prop.setBrushColor(BLACK); + prop.setBrushStyle(BS_SOLID); + break; + case 0x80000005: + // NULL_BRUSH - A null brush + // BrushStyle: BS_NULL + prop.setBrushStyle(BS_NULL); + break; + case 0x80000006: + // WHITE_PEN - A white, solid-color pen + // PenStyle: PS_COSMETIC + PS_SOLID + // ColorRef: 0x00FFFFFF + prop.setPenStyle(HwmfPenStyle.valueOf(0)); + prop.setPenWidth(1); + prop.setPenColor(WHITE); + break; + case 0x80000007: + // BLACK_PEN - A black, solid-color pen + // PenStyle: PS_COSMETIC + PS_SOLID + // ColorRef: 0x00000000 + prop.setPenStyle(HwmfPenStyle.valueOf(0)); + prop.setPenWidth(1); + prop.setPenColor(BLACK); + break; + case 0x80000008: + // NULL_PEN - A null pen + // PenStyle: PS_NULL + prop.setPenStyle(HwmfPenStyle.valueOf(HwmfPenStyle.HwmfLineDash.NULL.wmfFlag)); + break; + case 0x8000000A: + // OEM_FIXED_FONT - A fixed-width, OEM character set + // Charset: OEM_CHARSET + // PitchAndFamily: FF_DONTCARE + FIXED_PITCH + break; + case 0x8000000B: + // ANSI_FIXED_FONT - A fixed-width font + // Charset: ANSI_CHARSET + // PitchAndFamily: FF_DONTCARE + FIXED_PITCH + break; + case 0x8000000C: + // ANSI_VAR_FONT - A variable-width font + // Charset: ANSI_CHARSET + // PitchAndFamily: FF_DONTCARE + VARIABLE_PITCH + break; + case 0x8000000D: + // SYSTEM_FONT - A font that is guaranteed to be available in the operating system + break; + case 0x8000000E: + // DEVICE_DEFAULT_FONT + // The default font that is provided by the graphics device driver for the current output device + break; + case 0x8000000F: + // DEFAULT_PALETTE + // The default palette that is defined for the current output device. + break; + case 0x80000010: + // SYSTEM_FIXED_FONT + // A fixed-width font that is guaranteed to be available in the operating system. + break; + case 0x80000011: + // DEFAULT_GUI_FONT + // The default font that is used for user interface objects such as menus and dialog boxes. + break; + case 0x80000012: + // DC_BRUSH + // The solid-color brush that is currently selected in the playback device context. + break; + case 0x80000013: + // DC_PEN + // The solid-color pen that is currently selected in the playback device context. + break; + } + } + } @@ -717,8 +848,98 @@ public class HemfDraw { protected long readPoint(LittleEndianInputStream leis, Point2D point) { return readPointS(leis, point); } - } + + public static class EmfBeginPath implements HemfRecord { + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.beginPath; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + return 0; + } + } + + public static class EmfEndPath implements HemfRecord { + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.endPath; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + return 0; + } + } + + public static class EmfAbortPath implements HemfRecord { + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.abortPath; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + return 0; + } + } + + public static class EmfCloseFigure implements HemfRecord { + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.closeFigure; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + return 0; + } + } + + public static class EmfFlattenPath implements HemfRecord { + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.flattenPath; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + return 0; + } + } + + public static class EmfWidenPath implements HemfRecord { + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.widenPath; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + return 0; + } + } + + /** + * The EMR_STROKEPATH record renders the specified path by using the current pen. + */ + public static class EmfStrokePath implements HemfRecord { + protected final Rectangle2D bounds = new Rectangle2D.Double(); + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.strokePath; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + // A 128-bit WMF RectL object, which specifies bounding rectangle, in device units + return readRectL(leis, bounds); + } + } + static long readRectL(LittleEndianInputStream leis, Rectangle2D bounds) { /* A 32-bit signed integer that defines the x coordinate, in logical coordinates, * of the ... corner of the rectangle. diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java index 5e4b972982..f832a7abeb 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java @@ -617,4 +617,23 @@ public class HemfFill { return 6 * LittleEndian.INT_SIZE; } + + /** + * 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 { + protected final Rectangle2D bounds = new Rectangle2D.Double(); + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.fillPath; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + // A 128-bit WMF RectL object, which specifies bounding rectangle, in device units + return readRectL(leis, bounds); + } + } } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java index ab3d06bfe7..e843dc4f11 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java @@ -17,10 +17,11 @@ package org.apache.poi.hemf.record.emf; -import static org.apache.poi.hemf.record.emf.HemfDraw.readDimensionInt; +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.HemfRecordIterator.HEADER_SIZE; +import java.awt.geom.Point2D; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -37,7 +38,6 @@ import org.apache.poi.hwmf.record.HwmfMisc.WmfSetBkMode; import org.apache.poi.hwmf.record.HwmfPalette.PaletteEntry; import org.apache.poi.hwmf.record.HwmfPenStyle; import org.apache.poi.hwmf.record.HwmfPenStyle.HwmfLineDash; -import org.apache.poi.util.IOUtils; import org.apache.poi.util.LittleEndianConsts; import org.apache.poi.util.LittleEndianInputStream; @@ -56,22 +56,22 @@ public class HemfMisc { public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { // A 32-bit unsigned integer that specifies the number of palette entries. - int nPalEntries = (int)leis.readUInt(); + 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(); + int offPalEntries = (int) leis.readUInt(); - int size = 2*LittleEndianConsts.INT_SIZE; - int undefinedSpace1 = (int)(offPalEntries - size - HEADER_SIZE); + 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= 0); leis.skipFully(undefinedSpace2); size += undefinedSpace2; @@ -81,7 +81,7 @@ public class HemfMisc { // LogPaletteEntry objects, if they exist, MUST precede this field. long sizeLast = leis.readUInt(); size += LittleEndianConsts.INT_SIZE; - assert ((sizeLast-HEADER_SIZE) == recordSize && recordSize == size); + assert ((sizeLast - HEADER_SIZE) == recordSize && recordSize == size); return size; } @@ -179,7 +179,7 @@ public class HemfMisc { * A 32-bit unsigned integer that specifies the background mode * and MUST be in the BackgroundMode (section 2.1.4) enumeration */ - bkMode = HwmfBkMode.valueOf((int)leis.readUInt()); + bkMode = HwmfBkMode.valueOf((int) leis.readUInt()); return LittleEndianConsts.INT_SIZE; } } @@ -195,7 +195,7 @@ public class HemfMisc { @Override public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { - return super.init(leis, recordSize, (int)recordId); + return super.init(leis, recordSize, (int) recordId); } } @@ -212,7 +212,7 @@ public class HemfMisc { @Override public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { // A 32-bit unsigned integer whose definition MUST be in the MapMode enumeration - mapMode = HwmfMapMode.valueOf((int)leis.readUInt()); + mapMode = HwmfMapMode.valueOf((int) leis.readUInt()); return LittleEndianConsts.INT_SIZE; } } @@ -230,7 +230,7 @@ public class HemfMisc { public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { // A 32-bit unsigned integer that specifies the raster operation mode and // MUST be in the WMF Binary Raster Op enumeration - drawMode = HwmfBinaryRasterOp.valueOf((int)leis.readUInt()); + drawMode = HwmfBinaryRasterOp.valueOf((int) leis.readUInt()); return LittleEndianConsts.INT_SIZE; } } @@ -250,12 +250,14 @@ public class HemfMisc { public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { // A 32-bit unsigned integer that specifies the stretch mode and MAY be // in the StretchMode enumeration. - stretchBltMode = StretchBltMode.valueOf((int)leis.readUInt()); + stretchBltMode = StretchBltMode.valueOf((int) leis.readUInt()); return LittleEndianConsts.INT_SIZE; } } - /** The EMR_CREATEBRUSHINDIRECT record defines a logical brush for graphics operations. */ + /** + * The EMR_CREATEBRUSHINDIRECT record defines a logical brush for graphics operations. + */ public static class EmfCreateBrushIndirect extends HwmfMisc.WmfCreateBrushIndirect implements HemfRecord { /** * A 32-bit unsigned integer that specifies the index of the logical brush object in the @@ -270,13 +272,13 @@ public class HemfMisc { @Override public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { - brushIdx = (int)leis.readUInt(); + brushIdx = (int) leis.readUInt(); - brushStyle = HwmfBrushStyle.valueOf((int)leis.readUInt()); + brushStyle = HwmfBrushStyle.valueOf((int) leis.readUInt()); colorRef = new HwmfColorRef(); int size = colorRef.init(leis); - brushHatch = HwmfHatchStyle.valueOf((int)leis.readUInt()); - return size+3*LittleEndianConsts.INT_SIZE; + brushHatch = HwmfHatchStyle.valueOf((int) leis.readUInt()); + return size + 3 * LittleEndianConsts.INT_SIZE; } } @@ -294,12 +296,14 @@ public class HemfMisc { @Override public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { - objectIndex = (int)leis.readUInt(); + objectIndex = (int) leis.readUInt(); return LittleEndianConsts.INT_SIZE; } } - /** The EMR_CREATEPEN record defines a logical pen for graphics operations. */ + /** + * The EMR_CREATEPEN record defines a logical pen for graphics operations. + */ public static class EmfCreatePen extends HwmfMisc.WmfCreatePenIndirect implements HemfRecord { /** * A 32-bit unsigned integer that specifies the index of the logical palette object @@ -315,11 +319,11 @@ public class HemfMisc { @Override public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { - penIndex = (int)leis.readUInt(); + penIndex = (int) leis.readUInt(); // A 32-bit unsigned integer that specifies the PenStyle. // The value MUST be defined from the PenStyle enumeration table - penStyle = HwmfPenStyle.valueOf((int)leis.readUInt()); + penStyle = HwmfPenStyle.valueOf((int) leis.readUInt()); int widthX = leis.readInt(); int widthY = leis.readInt(); @@ -327,7 +331,7 @@ public class HemfMisc { int size = colorRef.init(leis); - return size + 4*LittleEndianConsts.INT_SIZE; + return size + 4 * LittleEndianConsts.INT_SIZE; } } @@ -349,27 +353,27 @@ public class HemfMisc { public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { final int startIdx = leis.getReadIndex(); - penIndex = (int)leis.readUInt(); + penIndex = (int) leis.readUInt(); // A 32-bit unsigned integer that specifies the offset from the start of this // record to the DIB header, if the record contains a DIB. - int offBmi = (int)leis.readUInt(); + int offBmi = (int) leis.readUInt(); // A 32-bit unsigned integer that specifies the size of the DIB header, if the // record contains a DIB. - int cbBmi = (int)leis.readUInt(); + int cbBmi = (int) leis.readUInt(); // A 32-bit unsigned integer that specifies the offset from the start of this // record to the DIB bits, if the record contains a DIB. - int offBits = (int)leis.readUInt(); + int offBits = (int) leis.readUInt(); // A 32-bit unsigned integer that specifies the size of the DIB bits, if the record // contains a DIB. - int cbBits = (int)leis.readUInt(); + int cbBits = (int) leis.readUInt(); // A 32-bit unsigned integer that specifies the PenStyle. // The value MUST be defined from the PenStyle enumeration table - penStyle = HwmfPenStyle.valueOf((int)leis.readUInt()); + penStyle = HwmfPenStyle.valueOf((int) leis.readUInt()); // A 32-bit unsigned integer that specifies the width of the line drawn by the pen. // If the pen type in the PenStyle field is PS_GEOMETRIC, this value is the width in logical @@ -383,7 +387,7 @@ public class HemfMisc { // If the pen type in the PenStyle field is PS_GEOMETRIC, this value MUST be either BS_SOLID or BS_HATCHED. // The value of this field can be BS_NULL, but only if the line style specified in PenStyle is PS_NULL. // The BS_NULL style SHOULD be used to specify a brush that has no effect - brushStyle = HwmfBrushStyle.valueOf((int)leis.readUInt()); + brushStyle = HwmfBrushStyle.valueOf((int) leis.readUInt()); int size = 8 * LittleEndianConsts.INT_SIZE; @@ -393,10 +397,10 @@ public class HemfMisc { // 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; + final int numStyleEntries = (int) leis.readUInt(); + size += 2 * LittleEndianConsts.INT_SIZE; - assert(numStyleEntries == 0 || penStyle.getLineDash() == HwmfLineDash.USERSTYLE); + assert (numStyleEntries == 0 || penStyle.getLineDash() == HwmfLineDash.USERSTYLE); // An optional array of 32-bit unsigned integers that defines the lengths of // dashes and gaps in the line drawn by this pen, when the value of PenStyle is @@ -409,8 +413,8 @@ public class HemfMisc { styleEntry = new int[numStyleEntries]; - for (int i=0; i { } private HemfPlusRecord _next() { - if (currentRecord != null && HemfPlusRecordType.eof == currentRecord.getRecordType()) { + if (currentRecord != null && HemfPlusRecordType.eof == currentRecord.getEmfPlusRecordType()) { return null; } // A 16-bit unsigned integer that identifies this record type diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/UnimplementedHemfPlusRecord.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/UnimplementedHemfPlusRecord.java index 21d0e14424..bc7152cd7e 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/UnimplementedHemfPlusRecord.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/UnimplementedHemfPlusRecord.java @@ -29,13 +29,13 @@ public class UnimplementedHemfPlusRecord implements HemfPlusRecord { private static final int MAX_RECORD_LENGTH = 1_000_000; - private long recordId; + private HemfPlusRecordType recordType; private int flags; private byte[] recordBytes; @Override - public HemfPlusRecordType getRecordType() { - return HemfPlusRecordType.getById(recordId); + public HemfPlusRecordType getEmfPlusRecordType() { + return recordType; } @Override @@ -45,7 +45,7 @@ public class UnimplementedHemfPlusRecord implements HemfPlusRecord { @Override public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException { - this.recordId = recordId; + recordType = HemfPlusRecordType.getById(recordId); this.flags = flags; recordBytes = IOUtils.safelyAllocate(dataSize, MAX_RECORD_LENGTH); leis.readFully(recordBytes); diff --git a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java index 5eccd5d7a9..1789337d2a 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java @@ -52,11 +52,12 @@ import org.apache.poi.util.LocaleUtil; public class HwmfGraphics { + protected final List propStack = new LinkedList<>(); + protected HwmfDrawProperties prop; + private static final Charset DEFAULT_CHARSET = LocaleUtil.CHARSET_1252; private final Graphics2D graphicsCtx; - private final List propStack = new LinkedList<>(); - private HwmfDrawProperties prop = new HwmfDrawProperties(); - private List objectTable = new ArrayList<>(); + private final List objectTable = new ArrayList<>(); /** Bounding box from the placeable header */ private final Rectangle2D bbox; private final AffineTransform initialAT; @@ -75,11 +76,14 @@ public class HwmfGraphics { } public HwmfDrawProperties getProperties() { + if (prop == null) { + prop = new HwmfDrawProperties(); + } return prop; } public void draw(Shape shape) { - HwmfLineDash lineDash = prop.getPenStyle().getLineDash(); + HwmfLineDash lineDash = getProperties().getPenStyle().getLineDash(); if (lineDash == HwmfLineDash.NULL) { // line is not drawn return; @@ -89,22 +93,22 @@ public class HwmfGraphics { // first draw a solid background line (depending on bkmode) // only makes sense if the line is not solid - if (prop.getBkMode() == HwmfBkMode.OPAQUE && (lineDash != HwmfLineDash.SOLID && lineDash != HwmfLineDash.INSIDEFRAME)) { + if (getProperties().getBkMode() == HwmfBkMode.OPAQUE && (lineDash != HwmfLineDash.SOLID && lineDash != HwmfLineDash.INSIDEFRAME)) { graphicsCtx.setStroke(new BasicStroke(stroke.getLineWidth())); - graphicsCtx.setColor(prop.getBackgroundColor().getColor()); + graphicsCtx.setColor(getProperties().getBackgroundColor().getColor()); graphicsCtx.draw(shape); } // then draw the (dashed) line graphicsCtx.setStroke(stroke); - graphicsCtx.setColor(prop.getPenColor().getColor()); + graphicsCtx.setColor(getProperties().getPenColor().getColor()); graphicsCtx.draw(shape); } public void fill(Shape shape) { - if (prop.getBrushStyle() != HwmfBrushStyle.BS_NULL) { + if (getProperties().getBrushStyle() != HwmfBrushStyle.BS_NULL) { // GeneralPath gp = new GeneralPath(shape); -// gp.setWindingRule(prop.getPolyfillMode().awtFlag); +// gp.setWindingRule(getProperties().getPolyfillMode().awtFlag); graphicsCtx.setPaint(getFill()); graphicsCtx.fill(shape); } @@ -114,14 +118,14 @@ public class HwmfGraphics { protected BasicStroke getStroke() { // TODO: fix line width calculation - float width = (float)prop.getPenWidth(); + float width = (float)getProperties().getPenWidth(); if (width == 0) { width = 1; } - HwmfPenStyle ps = prop.getPenStyle(); + HwmfPenStyle ps = getProperties().getPenStyle(); int cap = ps.getLineCap().awtFlag; int join = ps.getLineJoin().awtFlag; - float miterLimit = (float)prop.getPenMiterLimit(); + float miterLimit = (float)getProperties().getPenMiterLimit(); float dashes[] = ps.getLineDash().dashes; boolean dashAlt = ps.isAlternateDash(); // This value is not an integer index into the dash pattern array. @@ -132,7 +136,7 @@ public class HwmfGraphics { } protected Paint getFill() { - switch (prop.getBrushStyle()) { + switch (getProperties().getBrushStyle()) { default: case BS_INDEXED: case BS_PATTERN8X8: @@ -148,20 +152,20 @@ public class HwmfGraphics { } protected Paint getSolidFill() { - return prop.getBrushColor().getColor(); + return getProperties().getBrushColor().getColor(); } protected Paint getHatchedFill() { int dim = 7, mid = 3; BufferedImage bi = new BufferedImage(dim, dim, BufferedImage.TYPE_4BYTE_ABGR); Graphics2D g = bi.createGraphics(); - Color c = (prop.getBkMode() == HwmfBkMode.TRANSPARENT) + Color c = (getProperties().getBkMode() == HwmfBkMode.TRANSPARENT) ? new Color(0, true) - : prop.getBackgroundColor().getColor(); + : getProperties().getBackgroundColor().getColor(); g.setColor(c); g.fillRect(0, 0, dim, dim); - g.setColor(prop.getBrushColor().getColor()); - HwmfHatchStyle h = prop.getBrushHatch(); + g.setColor(getProperties().getBrushColor().getColor()); + HwmfHatchStyle h = getProperties().getBrushHatch(); if (h == HwmfHatchStyle.HS_HORIZONTAL || h == HwmfHatchStyle.HS_CROSS) { g.drawLine(0, mid, dim, mid); } @@ -179,7 +183,7 @@ public class HwmfGraphics { } protected Paint getPatternPaint() { - BufferedImage bi = prop.getBrushBitmap(); + BufferedImage bi = getProperties().getBrushBitmap(); return (bi == null) ? null : new TexturePaint(bi, new Rectangle(0,0,bi.getWidth(),bi.getHeight())); } @@ -244,8 +248,9 @@ public class HwmfGraphics { * Saves the current properties to the stack */ public void saveProperties() { + assert(prop != null); propStack.add(prop); - prop = new HwmfDrawProperties(prop); + prop = new HwmfDrawProperties(prop); } /** @@ -260,7 +265,7 @@ public class HwmfGraphics { } int stackIndex = index; if (stackIndex < 0) { - int curIdx = propStack.indexOf(prop); + int curIdx = propStack.indexOf(getProperties()); if (curIdx == -1) { // the current element is not pushed to the stacked, i.e. it's the last curIdx = propStack.size(); @@ -280,8 +285,8 @@ public class HwmfGraphics { * This methods gathers and sets the corresponding graphics transformations. */ public void updateWindowMapMode() { - Rectangle2D win = prop.getWindow(); - HwmfMapMode mapMode = prop.getMapMode(); + Rectangle2D win = getProperties().getWindow(); + HwmfMapMode mapMode = getProperties().getMapMode(); graphicsCtx.setTransform(initialAT); switch (mapMode) { @@ -320,7 +325,7 @@ public class HwmfGraphics { } public void drawString(byte[] text, Rectangle2D bounds, int dx[]) { - HwmfFont font = prop.getFont(); + HwmfFont font = getProperties().getFont(); if (font == null || text == null || text.length == 0) { return; } @@ -386,12 +391,12 @@ public class HwmfGraphics { try { graphicsCtx.translate(bounds.getX(), bounds.getY()+fontH); graphicsCtx.rotate(angle); - if (prop.getBkMode() == HwmfBkMode.OPAQUE) { + if (getProperties().getBkMode() == HwmfBkMode.OPAQUE) { // TODO: validate bounds - graphicsCtx.setBackground(prop.getBackgroundColor().getColor()); + graphicsCtx.setBackground(getProperties().getBackgroundColor().getColor()); graphicsCtx.fill(new Rectangle2D.Double(0, 0, bounds.getWidth(), bounds.getHeight())); } - graphicsCtx.setColor(prop.getTextColor().getColor()); + graphicsCtx.setColor(getProperties().getTextColor().getColor()); graphicsCtx.drawString(as.getIterator(), 0, 0); // (float)bounds.getX(), (float)bounds.getY()); } finally { graphicsCtx.setTransform(at); diff --git a/src/scratchpad/testcases/org/apache/poi/hemf/hemfplus/extractor/HemfPlusExtractorTest.java b/src/scratchpad/testcases/org/apache/poi/hemf/hemfplus/extractor/HemfPlusExtractorTest.java index 5811accac5..b42bac7d5d 100644 --- a/src/scratchpad/testcases/org/apache/poi/hemf/hemfplus/extractor/HemfPlusExtractorTest.java +++ b/src/scratchpad/testcases/org/apache/poi/hemf/hemfplus/extractor/HemfPlusExtractorTest.java @@ -42,7 +42,7 @@ public class HemfPlusExtractorTest { EmfCommentDataPlus emfPlus = getCommentRecord("SimpleEMF_windows.emf", 0); List records = emfPlus.getRecords(); assertEquals(1, records.size()); - assertEquals(HemfPlusRecordType.header, records.get(0).getRecordType()); + assertEquals(HemfPlusRecordType.header, records.get(0).getEmfPlusRecordType()); HemfPlusHeader header = (HemfPlusHeader)records.get(0); assertEquals(240, header.getLogicalDpiX()); @@ -67,7 +67,7 @@ public class HemfPlusExtractorTest { assertEquals(expected.size(), records.size()); for (int i = 0; i < expected.size(); i++) { - assertEquals(expected.get(i), records.get(i).getRecordType()); + assertEquals(expected.get(i), records.get(i).getEmfPlusRecordType()); } } From 5073f22fce96dcc74529baaa8ee6e7e3c732ee56 Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Sun, 23 Sep 2018 01:58:40 +0000 Subject: [PATCH 05/21] #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 --- .../apache/poi/stress/SlideShowHandler.java | 1 - .../poi/sl/draw/BitmapImageRenderer.java | 16 +- .../org/apache/poi/sl/draw/DrawFactory.java | 38 +--- .../apache/poi/sl/draw/DrawPictureShape.java | 34 ++- .../apache/poi/sl/draw/DrawTextParagraph.java | 8 - .../org/apache/poi/sl/draw/DrawTextShape.java | 5 +- .../org/apache/poi/sl/draw/ImageRenderer.java | 7 + .../org/apache/poi/xslf/util/PPTX2PNG.java | 1 - .../org/apache/poi/sl/TestFonts.java | 2 - .../xslf/usermodel/TestXSLFSimpleShape.java | 1 - .../org.apache.poi.sl.draw.ImageRenderer | 1 + .../org.apache.poi.sl.draw.ImageRenderer | 2 + .../poi/hemf/draw/HemfDrawProperties.java | 15 ++ .../apache/poi/hemf/draw/HemfGraphics.java | 50 ++++- .../poi/hemf/draw/HemfImageRenderer.java | 126 +++++++++++ .../poi/hemf/record/emf/HemfBounded.java | 45 ++++ .../apache/poi/hemf/record/emf/HemfDraw.java | 211 +++++++++++++++--- .../apache/poi/hemf/record/emf/HemfFill.java | 34 ++- .../poi/hemf/record/emf/HemfHeader.java | 17 +- .../apache/poi/hemf/record/emf/HemfMisc.java | 82 +++++-- .../poi/hemf/record/emf/HemfRecord.java | 7 +- .../poi/hemf/record/emf/HemfRecordType.java | 4 +- .../poi/hemf/usermodel/HemfPicture.java | 45 ++++ .../poi/hwmf/draw/HwmfDrawProperties.java | 10 +- .../apache/poi/hwmf/draw/HwmfGraphics.java | 11 +- ...geRenderer.java => HwmfImageRenderer.java} | 15 +- .../org/apache/poi/hwmf/record/HwmfDraw.java | 58 ++--- .../poi/hwmf/record/HwmfHatchStyle.java | 15 +- .../poi/hemf/usermodel/HemfPictureTest.java | 66 +++++- .../poi/hslf/usermodel/TestPicture.java | 1 - 30 files changed, 748 insertions(+), 180 deletions(-) create mode 100644 src/resources/main/META-INF/services/org.apache.poi.sl.draw.ImageRenderer create mode 100644 src/resources/scratchpad/META-INF/services/org.apache.poi.sl.draw.ImageRenderer create mode 100644 src/scratchpad/src/org/apache/poi/hemf/draw/HemfImageRenderer.java create mode 100644 src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfBounded.java rename src/scratchpad/src/org/apache/poi/hwmf/draw/{HwmfSLImageRenderer.java => HwmfImageRenderer.java} (91%) diff --git a/src/integrationtest/org/apache/poi/stress/SlideShowHandler.java b/src/integrationtest/org/apache/poi/stress/SlideShowHandler.java index 63ffdbd58c..20460cc2db 100644 --- a/src/integrationtest/org/apache/poi/stress/SlideShowHandler.java +++ b/src/integrationtest/org/apache/poi/stress/SlideShowHandler.java @@ -105,7 +105,6 @@ public abstract class SlideShowHandler extends POIFSFileHandler { for (Slide s : ss.getSlides()) { BufferedImage img = new BufferedImage(pgsize.width, pgsize.height, BufferedImage.TYPE_INT_ARGB); Graphics2D graphics = img.createGraphics(); - DrawFactory.getInstance(graphics).fixFonts(graphics); // default rendering options graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); diff --git a/src/java/org/apache/poi/sl/draw/BitmapImageRenderer.java b/src/java/org/apache/poi/sl/draw/BitmapImageRenderer.java index a92c8dc590..c37ce006a0 100644 --- a/src/java/org/apache/poi/sl/draw/BitmapImageRenderer.java +++ b/src/java/org/apache/poi/sl/draw/BitmapImageRenderer.java @@ -40,6 +40,7 @@ import javax.imageio.ImageTypeSpecifier; import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.MemoryCacheImageInputStream; +import org.apache.poi.sl.usermodel.PictureData.PictureType; import org.apache.poi.util.IOUtils; import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; @@ -48,10 +49,23 @@ import org.apache.poi.util.POILogger; * For now this class renders only images supported by the javax.imageio.ImageIO framework. **/ public class BitmapImageRenderer implements ImageRenderer { - private final static POILogger LOG = POILogFactory.getLogger(ImageRenderer.class); + private final static POILogger LOG = POILogFactory.getLogger(BitmapImageRenderer.class); protected BufferedImage img; + @Override + public boolean canRender(String contentType) { + PictureType[] pts = { + PictureType.JPEG, PictureType.PNG, PictureType.BMP, PictureType.GIF + }; + for (PictureType pt : pts) { + if (pt.contentType.equalsIgnoreCase(contentType)) { + return true; + } + } + return false; + } + @Override public void loadImage(InputStream data, String contentType) throws IOException { img = readImage(data, contentType); diff --git a/src/java/org/apache/poi/sl/draw/DrawFactory.java b/src/java/org/apache/poi/sl/draw/DrawFactory.java index 98c41ed993..99c9942b67 100644 --- a/src/java/org/apache/poi/sl/draw/DrawFactory.java +++ b/src/java/org/apache/poi/sl/draw/DrawFactory.java @@ -22,8 +22,6 @@ import java.awt.font.TextLayout; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.text.AttributedString; -import java.util.HashMap; -import java.util.Map; import org.apache.poi.sl.usermodel.Background; import org.apache.poi.sl.usermodel.ConnectorShape; @@ -40,18 +38,18 @@ import org.apache.poi.sl.usermodel.TableShape; import org.apache.poi.sl.usermodel.TextBox; import org.apache.poi.sl.usermodel.TextParagraph; import org.apache.poi.sl.usermodel.TextShape; -import org.apache.poi.util.JvmBugs; public class DrawFactory { - protected static final ThreadLocal defaultFactory = new ThreadLocal<>(); + private static final ThreadLocal defaultFactory = new ThreadLocal<>(); /** * Set a custom draw factory for the current thread. * This is a fallback, for operations where usercode can't set a graphics context. * Preferably use the rendering hint {@link Drawable#DRAW_FACTORY} to set the factory. * - * @param factory + * @param factory the custom factory */ + @SuppressWarnings("unused") public static void setDefaultFactory(DrawFactory factory) { defaultFactory.set(factory); } @@ -170,6 +168,7 @@ public class DrawFactory { return new DrawBackground(shape); } + @SuppressWarnings("WeakerAccess") public DrawTextFragment getTextFragment(TextLayout layout, AttributedString str) { return new DrawTextFragment(layout, str); } @@ -213,35 +212,6 @@ public class DrawFactory { } - /** - * Replace font families for Windows JVM 6, which contains a font rendering error. - * This is likely to be removed, when POI upgrades to JDK 7 - * - * @param graphics the graphics context which will contain the font mapping - */ - public void fixFonts(Graphics2D graphics) { - if (!JvmBugs.hasLineBreakMeasurerBug()) return; - @SuppressWarnings("unchecked") - Map fontMap = (Map)graphics.getRenderingHint(Drawable.FONT_MAP); - if (fontMap == null) { - fontMap = new HashMap<>(); - graphics.setRenderingHint(Drawable.FONT_MAP, fontMap); - } - - String fonts[][] = { - { "Calibri", "Lucida Sans" }, - { "Cambria", "Lucida Bright" }, - { "Times New Roman", "Lucida Bright" }, - { "serif", "Lucida Bright" } - }; - - for (String f[] : fonts) { - if (!fontMap.containsKey(f[0])) { - fontMap.put(f[0], f[1]); - } - } - } - /** * Return a FontManager, either registered beforehand or a default implementation * diff --git a/src/java/org/apache/poi/sl/draw/DrawPictureShape.java b/src/java/org/apache/poi/sl/draw/DrawPictureShape.java index fcca6d07a7..5c42d6fd13 100644 --- a/src/java/org/apache/poi/sl/draw/DrawPictureShape.java +++ b/src/java/org/apache/poi/sl/draw/DrawPictureShape.java @@ -22,19 +22,19 @@ import java.awt.Graphics2D; import java.awt.Insets; import java.awt.geom.Rectangle2D; import java.io.IOException; +import java.util.ServiceLoader; import org.apache.poi.sl.usermodel.PictureData; -import org.apache.poi.sl.usermodel.PictureData.PictureType; -import org.apache.poi.util.POILogFactory; -import org.apache.poi.util.POILogger; import org.apache.poi.sl.usermodel.PictureShape; import org.apache.poi.sl.usermodel.RectAlign; +import org.apache.poi.util.POILogFactory; +import org.apache.poi.util.POILogger; public class DrawPictureShape extends DrawSimpleShape { private static final POILogger LOG = POILogFactory.getLogger(DrawPictureShape.class); - private static final String WMF_IMAGE_RENDERER = "org.apache.poi.hwmf.draw.HwmfSLImageRenderer"; - + private static final ServiceLoader rendererLoader = ServiceLoader.load(ImageRenderer.class); + public DrawPictureShape(PictureShape shape) { super(shape); } @@ -59,28 +59,26 @@ public class DrawPictureShape extends DrawSimpleShape { /** * Returns an ImageRenderer for the PictureData * - * @param graphics + * @param graphics the graphics context * @return the image renderer */ + @SuppressWarnings("WeakerAccess") public static ImageRenderer getImageRenderer(Graphics2D graphics, String contentType) { ImageRenderer renderer = (ImageRenderer)graphics.getRenderingHint(Drawable.IMAGE_RENDERER); if (renderer != null) { return renderer; } - - if (PictureType.WMF.contentType.equals(contentType)) { - try { - @SuppressWarnings("unchecked") - Class irc = (Class) - DrawPictureShape.class.getClassLoader().loadClass(WMF_IMAGE_RENDERER); - return irc.newInstance(); - } catch (Exception e) { - // WMF image renderer is not on the classpath, continuing with BitmapRenderer - // although this doesn't make much sense ... - LOG.log(POILogger.ERROR, "WMF image renderer is not on the classpath - include poi-scratchpad jar!", e); + + for (ImageRenderer ir : rendererLoader) { + if (ir.canRender(contentType)) { + return ir; } } - + + LOG.log(POILogger.ERROR, "No suiteable image renderer found for content-type '"+ + contentType+"' - include poi-scratchpad jar!"); + + // falling back to BitmapImageRenderer although this doesn't make much sense ... return new BitmapImageRenderer(); } diff --git a/src/java/org/apache/poi/sl/draw/DrawTextParagraph.java b/src/java/org/apache/poi/sl/draw/DrawTextParagraph.java index 938d46230a..1ab4af3a1e 100644 --- a/src/java/org/apache/poi/sl/draw/DrawTextParagraph.java +++ b/src/java/org/apache/poi/sl/draw/DrawTextParagraph.java @@ -254,7 +254,6 @@ public class DrawTextParagraph implements Drawable { lines.clear(); DrawFactory fact = DrawFactory.getInstance(graphics); - fact.fixFonts(graphics); StringBuilder text = new StringBuilder(); AttributedString at = getAttributedString(graphics, text); boolean emptyParagraph = text.toString().trim().isEmpty(); @@ -635,13 +634,6 @@ public class DrawTextParagraph implements Drawable { *
  • determine the font group - a text run can have different font groups. Depending on the chars, * the correct font group needs to be used * - * @param graphics - * @param dfm - * @param attList - * @param beginIndex - * @param run - * @param runText - * * @see Office Open XML Themes, Schemes, and Fonts */ private void processGlyphs(Graphics2D graphics, DrawFontManager dfm, List attList, final int beginIndex, TextRun run, String runText) { diff --git a/src/java/org/apache/poi/sl/draw/DrawTextShape.java b/src/java/org/apache/poi/sl/draw/DrawTextShape.java index 413ab218c9..c4dd65bb75 100644 --- a/src/java/org/apache/poi/sl/draw/DrawTextShape.java +++ b/src/java/org/apache/poi/sl/draw/DrawTextShape.java @@ -40,8 +40,6 @@ public class DrawTextShape extends DrawSimpleShape { @Override public void drawContent(Graphics2D graphics) { - DrawFactory.getInstance(graphics).fixFonts(graphics); - TextShape s = getShape(); Rectangle2D anchor = DrawShape.getAnchor(graphics, s); @@ -219,10 +217,9 @@ public class DrawTextShape extends DrawSimpleShape { graphics.addRenderingHints(oldGraphics.getRenderingHints()); graphics.setTransform(oldGraphics.getTransform()); } - DrawFactory.getInstance(graphics).fixFonts(graphics); return drawParagraphs(graphics, 0, 0); } - + @Override protected TextShape> getShape() { return (TextShape>)shape; diff --git a/src/java/org/apache/poi/sl/draw/ImageRenderer.java b/src/java/org/apache/poi/sl/draw/ImageRenderer.java index 7ecc96a967..b2355b3012 100644 --- a/src/java/org/apache/poi/sl/draw/ImageRenderer.java +++ b/src/java/org/apache/poi/sl/draw/ImageRenderer.java @@ -75,6 +75,13 @@ import java.io.InputStream; * */ public interface ImageRenderer { + /** + * Determines if this image renderer implementation supports the given contentType + * @param contentType the image content type + * @return if the content type is supported + */ + boolean canRender(String contentType); + /** * Load and buffer the image * diff --git a/src/ooxml/java/org/apache/poi/xslf/util/PPTX2PNG.java b/src/ooxml/java/org/apache/poi/xslf/util/PPTX2PNG.java index 79327bce4f..c4fbb4ca3b 100644 --- a/src/ooxml/java/org/apache/poi/xslf/util/PPTX2PNG.java +++ b/src/ooxml/java/org/apache/poi/xslf/util/PPTX2PNG.java @@ -139,7 +139,6 @@ public class PPTX2PNG { BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); Graphics2D graphics = img.createGraphics(); - DrawFactory.getInstance(graphics).fixFonts(graphics); // default rendering options graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); diff --git a/src/ooxml/testcases/org/apache/poi/sl/TestFonts.java b/src/ooxml/testcases/org/apache/poi/sl/TestFonts.java index b7a6d16a54..846335ce90 100644 --- a/src/ooxml/testcases/org/apache/poi/sl/TestFonts.java +++ b/src/ooxml/testcases/org/apache/poi/sl/TestFonts.java @@ -131,8 +131,6 @@ public class TestFonts { graphics.setRenderingHint(Drawable.FONT_FALLBACK, fallbackMap); graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); - DrawFactory.getInstance(graphics).fixFonts(graphics); - tb.resizeToFitText(graphics); graphics.dispose(); diff --git a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSimpleShape.java b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSimpleShape.java index e35ed35a29..ddbfe26652 100644 --- a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSimpleShape.java +++ b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSimpleShape.java @@ -361,7 +361,6 @@ public class TestXSLFSimpleShape { BufferedImage img = new BufferedImage(pgsize.width, pgsize.height, BufferedImage.TYPE_INT_ARGB); Graphics2D graphics = img.createGraphics(); - DrawFactory.getInstance(graphics).fixFonts(graphics); // default rendering options graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); diff --git a/src/resources/main/META-INF/services/org.apache.poi.sl.draw.ImageRenderer b/src/resources/main/META-INF/services/org.apache.poi.sl.draw.ImageRenderer new file mode 100644 index 0000000000..1e7699fe57 --- /dev/null +++ b/src/resources/main/META-INF/services/org.apache.poi.sl.draw.ImageRenderer @@ -0,0 +1 @@ +org.apache.poi.sl.draw.BitmapImageRenderer \ No newline at end of file diff --git a/src/resources/scratchpad/META-INF/services/org.apache.poi.sl.draw.ImageRenderer b/src/resources/scratchpad/META-INF/services/org.apache.poi.sl.draw.ImageRenderer new file mode 100644 index 0000000000..b23f654157 --- /dev/null +++ b/src/resources/scratchpad/META-INF/services/org.apache.poi.sl.draw.ImageRenderer @@ -0,0 +1,2 @@ +org.apache.poi.hwmf.draw.HwmfImageRenderer +org.apache.poi.hemf.draw.HemfImageRenderer \ No newline at end of file diff --git a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java index f2e1957a28..8bb8af33bf 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java +++ b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java @@ -17,14 +17,29 @@ package org.apache.poi.hemf.draw; +import java.awt.geom.Path2D; + import org.apache.poi.hwmf.draw.HwmfDrawProperties; public class HemfDrawProperties extends HwmfDrawProperties { + /** Path for path bracket operations */ + protected final Path2D path; + + public HemfDrawProperties() { + path = new Path2D.Double(); } public HemfDrawProperties(HemfDrawProperties other) { super(other); + path = (Path2D)other.path.clone(); + } + + /** + * @return the current path used for bracket operations + */ + public Path2D getPath() { + return path; } } diff --git a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java index ff190652e1..05701ef823 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java +++ b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java @@ -18,14 +18,23 @@ package org.apache.poi.hemf.draw; import java.awt.Graphics2D; +import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; +import java.util.ArrayDeque; +import java.util.Deque; -import org.apache.poi.hwmf.draw.HwmfDrawProperties; +import org.apache.poi.hemf.record.emf.HemfBounded; +import org.apache.poi.hemf.record.emf.HemfRecord; import org.apache.poi.hwmf.draw.HwmfGraphics; public class HemfGraphics extends HwmfGraphics { + + private final Deque transforms = new ArrayDeque<>(); + public HemfGraphics(Graphics2D graphicsCtx, Rectangle2D bbox) { super(graphicsCtx,bbox); + // add dummy entry for object index 0, as emf is 1-based + addObjectTableEntry((ctx)->{}); } @Override @@ -42,4 +51,43 @@ public class HemfGraphics extends HwmfGraphics { propStack.add(prop); prop = new HemfDrawProperties((HemfDrawProperties)prop); } + + @Override + public void updateWindowMapMode() { + // ignore window settings + } + + public void draw(HemfRecord r) { + if (r instanceof HemfBounded) { + saveTransform(); + final HemfBounded bounded = (HemfBounded)r; + final Rectangle2D tgt = bounded.getRecordBounds(); + if (tgt != null && !tgt.isEmpty()) { + final Rectangle2D src = bounded.getShapeBounds(this); + if (src != null && !src.isEmpty()) { + graphicsCtx.translate(tgt.getCenterX() - src.getCenterX(), tgt.getCenterY() - src.getCenterY()); + graphicsCtx.translate(src.getCenterX(), src.getCenterY()); + graphicsCtx.scale(tgt.getWidth() / src.getWidth(), tgt.getHeight() / src.getHeight()); + graphicsCtx.translate(-src.getCenterX(), -src.getCenterY()); + } + } + } + + r.draw(this); + + if (r instanceof HemfBounded) { + restoreTransform(); + } + } + + + /** saves the current affine transform on the stack */ + private void saveTransform() { + transforms.push(graphicsCtx.getTransform()); + } + + /** restore the last saved affine transform */ + private void restoreTransform() { + graphicsCtx.setTransform(transforms.pop()); + } } diff --git a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfImageRenderer.java b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfImageRenderer.java new file mode 100644 index 0000000000..712c2e6c0e --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfImageRenderer.java @@ -0,0 +1,126 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hemf.draw; + +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.Insets; +import java.awt.RenderingHints; +import java.awt.geom.Dimension2D; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.awt.image.RescaleOp; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.apache.poi.hemf.usermodel.HemfPicture; +import org.apache.poi.sl.draw.ImageRenderer; +import org.apache.poi.sl.usermodel.PictureData; +import org.apache.poi.util.Units; + +public class HemfImageRenderer implements ImageRenderer { + HemfPicture image; + double alpha; + + @Override + public boolean canRender(String contentType) { + return PictureData.PictureType.EMF.contentType.equalsIgnoreCase(contentType); + } + + @Override + public void loadImage(InputStream data, String contentType) throws IOException { + if (!PictureData.PictureType.EMF.contentType.equals(contentType)) { + throw new IOException("Invalid picture type"); + } + image = new HemfPicture(data); + } + + @Override + public void loadImage(byte[] data, String contentType) throws IOException { + if (!PictureData.PictureType.EMF.contentType.equals(contentType)) { + throw new IOException("Invalid picture type"); + } + image = new HemfPicture(new ByteArrayInputStream(data)); + } + + @Override + public Dimension getDimension() { + int width = 0, height = 0; + if (image != null) { + Dimension2D dim = image.getSize(); + width = Units.pointsToPixel(dim.getWidth()); + // keep aspect ratio for height + height = Units.pointsToPixel(dim.getHeight()); + } + return new Dimension(width, height); + } + + @Override + public void setAlpha(double alpha) { + this.alpha = alpha; + } + + @Override + public BufferedImage getImage() { + return getImage(getDimension()); + } + + @Override + public BufferedImage getImage(Dimension dim) { + if (image == null) { + return new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); + } + + BufferedImage bufImg = new BufferedImage((int)dim.getWidth(), (int)dim.getHeight(), BufferedImage.TYPE_INT_ARGB); + Graphics2D g = bufImg.createGraphics(); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); + g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); + image.draw(g, new Rectangle2D.Double(0,0,dim.getWidth(),dim.getHeight())); + g.dispose(); + + if (alpha != 0) { + BufferedImage newImg = new BufferedImage((int)dim.getWidth(), (int)dim.getHeight(), BufferedImage.TYPE_INT_ARGB); + g = newImg.createGraphics(); + RescaleOp op = new RescaleOp(new float[]{1.0f, 1.0f, 1.0f, (float)alpha}, new float[]{0,0,0,0}, null); + g.drawImage(bufImg, op, 0, 0); + g.dispose(); + bufImg = newImg; + } + + return bufImg; + } + + @Override + public boolean drawImage(Graphics2D graphics, Rectangle2D anchor) { + return drawImage(graphics, anchor, null); + } + + @Override + public boolean drawImage(Graphics2D graphics, Rectangle2D anchor, Insets clip) { + if (image == null) { + return false; + } else { + image.draw(graphics, anchor); + return true; + } + } + +} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfBounded.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfBounded.java new file mode 100644 index 0000000000..dee8f94ffa --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfBounded.java @@ -0,0 +1,45 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hemf.record.emf; + +import java.awt.geom.Rectangle2D; + +import org.apache.poi.hemf.draw.HemfGraphics; + +/** + * In EMF, shape records bring their own bounding. + * The record bounding is in the same space as the global drawing context, + * but the specified shape points can have a different space and therefore + * need to be translated/normalized + */ +public interface HemfBounded { + /** + * Getter for the outer bounds which are given in the record + * + * @return the bounds specified in the record + */ + Rectangle2D getRecordBounds(); + + /** + * Getter for the inner bounds which are calculated by the shape points + * + * @param ctx the graphics context + * @return the bounds of the shape points + */ + Rectangle2D getShapeBounds(HemfGraphics ctx); +} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java index 77ddf7e39e..c2cc9c3de2 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java @@ -21,6 +21,8 @@ import static org.apache.poi.hwmf.record.HwmfBrushStyle.BS_NULL; import static org.apache.poi.hwmf.record.HwmfBrushStyle.BS_SOLID; import java.awt.Color; +import java.awt.geom.Arc2D; +import java.awt.geom.Area; import java.awt.geom.Dimension2D; import java.awt.geom.Path2D; import java.awt.geom.PathIterator; @@ -30,7 +32,6 @@ import java.io.IOException; import org.apache.poi.hemf.draw.HemfDrawProperties; import org.apache.poi.hemf.draw.HemfGraphics; -import org.apache.poi.hwmf.draw.HwmfGraphics; import org.apache.poi.hwmf.record.HwmfColorRef; import org.apache.poi.hwmf.record.HwmfDraw; import org.apache.poi.hwmf.record.HwmfDraw.WmfSelectObject; @@ -187,7 +188,7 @@ public class HemfDraw { /** The EMR_POLYBEZIER record specifies one or more Bezier curves. */ - public static class EmfPolyBezier extends HwmfDraw.WmfPolygon implements HemfRecord { + public static class EmfPolyBezier extends HwmfDraw.WmfPolygon implements HemfRecord, HemfBounded { private final Rectangle2D bounds = new Rectangle2D.Double(); @Override @@ -238,9 +239,13 @@ public class HemfDraw { for (int i=0; i+3= 0); @@ -622,7 +626,7 @@ public class HemfFill { * The EMR_FILLPATH record closes any open figures in the current path and fills the path's interior by * using the current brush and polygon-filling mode. */ - public static class EmfFillPath implements HemfRecord { + public static class EmfFillPath implements HemfRecord, HemfBounded { protected final Rectangle2D bounds = new Rectangle2D.Double(); @Override @@ -635,5 +639,29 @@ public class HemfFill { // A 128-bit WMF RectL object, which specifies bounding rectangle, in device units return readRectL(leis, bounds); } + + @Override + public Rectangle2D getRecordBounds() { + return bounds; + } + + @Override + public Rectangle2D getShapeBounds(HemfGraphics ctx) { + final HemfDrawProperties prop = ctx.getProperties(); + final Path2D path = prop.getPath(); + return path.getBounds2D(); + } + + @Override + public void draw(HemfGraphics ctx) { + final HemfDrawProperties prop = ctx.getProperties(); + final Path2D path = (Path2D)prop.getPath().clone(); + path.setWindingRule(ctx.getProperties().getWindingRule()); + if (prop.getBrushStyle() == HwmfBrushStyle.BS_NULL) { + ctx.draw(path); + } else { + ctx.fill(path); + } + } } } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfHeader.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfHeader.java index 0821d363ad..c1c0712c48 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfHeader.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfHeader.java @@ -111,12 +111,16 @@ public class HemfHeader implements HemfRecord { return hasExtension2; } - public long getMicrometersX() { - return (long)microDimension.getWidth(); + public Dimension2D getDeviceDimension() { + return deviceDimension; } - public long getMicrometersY() { - return (long)microDimension.getHeight(); + public Dimension2D getMilliDimension() { + return milliDimension; + } + + public Dimension2D getMicroDimension() { + return microDimension; } @Override @@ -135,8 +139,9 @@ public class HemfHeader implements HemfRecord { ", offPixelFormat=" + offPixelFormat + ", bOpenGL=" + bOpenGL + ", hasExtension2=" + hasExtension2 + - ", micrometersX=" + getMicrometersX() + - ", micrometersY=" + getMicrometersY() + + ", deviceDimension=" + deviceDimension + + ", microDimension=" + microDimension + + ", milliDimension=" + milliDimension + '}'; } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java index e843dc4f11..f3424d08d0 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java @@ -19,8 +19,10 @@ package org.apache.poi.hemf.record.emf; import static org.apache.poi.hemf.record.emf.HemfDraw.readPointL; import static org.apache.poi.hemf.record.emf.HemfFill.readBitmap; +import static org.apache.poi.hemf.record.emf.HemfFill.readXForm; import static org.apache.poi.hemf.record.emf.HemfRecordIterator.HEADER_SIZE; +import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.io.IOException; import java.util.ArrayList; @@ -54,34 +56,40 @@ public class HemfMisc { @Override public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + final int startIdx = leis.getReadIndex(); // A 32-bit unsigned integer that specifies the number of palette entries. - int nPalEntries = (int) leis.readUInt(); + final int nPalEntries = (int) leis.readUInt(); // A 32-bit unsigned integer that specifies the offset to the palette entries from the start of this record. - int offPalEntries = (int) leis.readUInt(); + final int offPalEntries = (int) leis.readUInt(); int size = 2 * LittleEndianConsts.INT_SIZE; - int undefinedSpace1 = (int) (offPalEntries - size - HEADER_SIZE); - assert (undefinedSpace1 >= 0); - leis.skipFully(undefinedSpace1); - size += undefinedSpace1; - for (int i = 0; i < nPalEntries; i++) { - PaletteEntry pe = new PaletteEntry(); - size += pe.init(leis); + 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; } - int undefinedSpace2 = (int) (recordSize - size - LittleEndianConsts.INT_SIZE); - assert (undefinedSpace2 >= 0); - leis.skipFully(undefinedSpace2); - size += undefinedSpace2; - // A 32-bit unsigned integer that MUST be the same as Size and MUST be the // last field of the record and hence the metafile. // LogPaletteEntry objects, if they exist, MUST precede this field. long sizeLast = leis.readUInt(); size += LittleEndianConsts.INT_SIZE; - assert ((sizeLast - HEADER_SIZE) == recordSize && recordSize == size); + // some files store the whole file size in sizeLast, other just the last record size + // assert (sizeLast == size+HEADER_SIZE); + assert (recordSize == size); return size; } @@ -381,6 +389,7 @@ public class HemfMisc { // PS_COSMETIC, this value MUST be 0x00000001. long width = leis.readUInt(); dimension.setSize(width, 0); + int size = 7 * LittleEndianConsts.INT_SIZE; // A 32-bit unsigned integer that specifies a brush style for the pen from the WMF BrushStyle enumeration // @@ -389,16 +398,17 @@ public class HemfMisc { // The BS_NULL style SHOULD be used to specify a brush that has no effect brushStyle = HwmfBrushStyle.valueOf((int) leis.readUInt()); - int size = 8 * LittleEndianConsts.INT_SIZE; + size += LittleEndianConsts.INT_SIZE; size += colorRef.init(leis); hatchStyle = HwmfHatchStyle.valueOf(leis.readInt()); + size += LittleEndianConsts.INT_SIZE; // The number of elements in the array specified in the StyleEntry // field. This value SHOULD be zero if PenStyle does not specify PS_USERSTYLE. final int numStyleEntries = (int) leis.readUInt(); - size += 2 * LittleEndianConsts.INT_SIZE; + size += LittleEndianConsts.INT_SIZE; assert (numStyleEntries == 0 || penStyle.getLineDash() == HwmfLineDash.USERSTYLE); @@ -463,4 +473,42 @@ public class HemfMisc { return readPointL(leis, origin); } } + + public static class EmfSetWorldTransform implements HemfRecord { + protected final AffineTransform xForm = new AffineTransform(); + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.setWorldTransform; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + return readXForm(leis, xForm); + } + } + + public static class EmfModifyWorldTransform implements HemfRecord { + protected final AffineTransform xForm = new AffineTransform(); + protected int modifyWorldTransformMode; + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.modifyWorldTransform; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + // An XForm object that defines a two-dimensional linear transform in logical units. + // This transform is used according to the ModifyWorldTransformMode to define a new value for + // the world-space to page-space transform in the playback device context. + int size = readXForm(leis, xForm); + + // A 32-bit unsigned integer that specifies how the transform specified in Xform is used. + // This value MUST be in the ModifyWorldTransformMode enumeration + modifyWorldTransformMode = (int)leis.readUInt(); + + return size + LittleEndianConsts.INT_SIZE; + } + } } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecord.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecord.java index 627bd73155..4f11906bbd 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecord.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecord.java @@ -21,6 +21,7 @@ package org.apache.poi.hemf.record.emf; import java.io.IOException; import org.apache.poi.hemf.draw.HemfGraphics; +import org.apache.poi.hwmf.record.HwmfRecord; import org.apache.poi.util.Internal; import org.apache.poi.util.LittleEndianInputStream; @@ -42,5 +43,9 @@ public interface HemfRecord { */ long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException; - default void draw(HemfGraphics ctx) {} + default void draw(HemfGraphics ctx) { + if (this instanceof HwmfRecord) { + ((HwmfRecord) this).draw(ctx); + } + } } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java index c3e97b8e75..6c858ccc53 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java @@ -58,8 +58,8 @@ public enum HemfRecordType { scaleWindowExtEx(0x00000020, HemfWindowing.EmfScaleWindowExtEx::new), saveDc(0x00000021, HemfMisc.EmfSaveDc::new), restoreDc(0x00000022, HemfMisc.EmfRestoreDc::new), - setworldtransform(0x00000023, UnimplementedHemfRecord::new), - modifyworldtransform(0x00000024, UnimplementedHemfRecord::new), + setWorldTransform(0x00000023, HemfMisc.EmfSetWorldTransform::new), + modifyWorldTransform(0x00000024, HemfMisc.EmfModifyWorldTransform::new), selectObject(0x00000025, HemfDraw.EmfSelectObject::new), createPen(0x00000026, HemfMisc.EmfCreatePen::new), createBrushIndirect(0x00000027, HemfMisc.EmfCreateBrushIndirect::new), diff --git a/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java b/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java index 40b04d716a..3a68547823 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java +++ b/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java @@ -18,6 +18,10 @@ package org.apache.poi.hemf.usermodel; +import java.awt.Graphics2D; +import java.awt.geom.AffineTransform; +import java.awt.geom.Dimension2D; +import java.awt.geom.Rectangle2D; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; @@ -26,11 +30,14 @@ import java.util.List; import java.util.Spliterator; import java.util.function.Consumer; +import org.apache.poi.hemf.draw.HemfGraphics; import org.apache.poi.hemf.record.emf.HemfHeader; import org.apache.poi.hemf.record.emf.HemfRecord; import org.apache.poi.hemf.record.emf.HemfRecordIterator; +import org.apache.poi.util.Dimension2DDouble; import org.apache.poi.util.Internal; import org.apache.poi.util.LittleEndianInputStream; +import org.apache.poi.util.Units; /** * Read-only EMF extractor. Lots remain @@ -74,4 +81,42 @@ public class HemfPicture implements Iterable { public void forEach(Consumer action) { getRecords().forEach(action); } + + /** + * Return the image size in points + * + * @return the image size in points + */ + public Dimension2D getSize() { + HemfHeader header = (HemfHeader)getRecords().get(0); + Rectangle2D dim = header.getFrameRectangle(); + + double coeff = (double)Units.EMU_PER_CENTIMETER/Units.EMU_PER_POINT/10.; + return new Dimension2DDouble(dim.getWidth()*coeff, dim.getHeight()*coeff); + } + + public void draw(Graphics2D ctx, Rectangle2D graphicsBounds) { + HemfHeader header = (HemfHeader)getRecords().get(0); + + AffineTransform at = ctx.getTransform(); + try { + Rectangle2D emfBounds = header.getBoundsRectangle(); + ctx.translate(graphicsBounds.getCenterX()-emfBounds.getCenterX(), graphicsBounds.getCenterY()-emfBounds.getCenterY()); + + // scale output bounds to image bounds + ctx.translate(emfBounds.getCenterX(), emfBounds.getCenterY()); + ctx.scale(graphicsBounds.getWidth()/emfBounds.getWidth(), graphicsBounds.getHeight()/emfBounds.getHeight()); + ctx.translate(-emfBounds.getCenterX(), -emfBounds.getCenterY()); + + + + HemfGraphics g = new HemfGraphics(ctx, emfBounds); + for (HemfRecord r : getRecords()) { + g.draw(r); + } + } finally { + ctx.setTransform(at); + } + } + } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java index cdbfefe71e..705f02705d 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java @@ -20,6 +20,7 @@ package org.apache.poi.hwmf.draw; import java.awt.Color; import java.awt.Shape; import java.awt.geom.Area; +import java.awt.geom.Path2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; @@ -88,7 +89,7 @@ public class HwmfDrawProperties { } public HwmfDrawProperties(HwmfDrawProperties other) { - this.window = (Rectangle2D)other.window.clone(); + this.window = (other.window == null) ? null : (Rectangle2D)other.window.clone(); this.viewport = (other.viewport == null) ? null : (Rectangle2D)other.viewport.clone(); this.location = (Point2D)other.location.clone(); this.mapMode = other.mapMode; @@ -367,4 +368,11 @@ public class HwmfDrawProperties { public void setTextVAlignAsian(HwmfTextVerticalAlignment textVAlignAsian) { this.textVAlignAsian = textVAlignAsian; } + + /** + * @return the current active winding rule ({@link Path2D#WIND_EVEN_ODD} or {@link Path2D#WIND_NON_ZERO}) + */ + public int getWindingRule() { + return getPolyfillMode().awtFlag; + } } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java index 1789337d2a..de348480ad 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java @@ -54,9 +54,9 @@ public class HwmfGraphics { protected final List propStack = new LinkedList<>(); protected HwmfDrawProperties prop; + protected final Graphics2D graphicsCtx; private static final Charset DEFAULT_CHARSET = LocaleUtil.CHARSET_1252; - private final Graphics2D graphicsCtx; private final List objectTable = new ArrayList<>(); /** Bounding box from the placeable header */ private final Rectangle2D bbox; @@ -72,7 +72,6 @@ public class HwmfGraphics { this.graphicsCtx = graphicsCtx; this.bbox = (Rectangle2D)bbox.clone(); this.initialAT = graphicsCtx.getTransform(); - DrawFactory.getInstance(graphicsCtx).fixFonts(graphicsCtx); } public HwmfDrawProperties getProperties() { @@ -178,6 +177,7 @@ public class HwmfGraphics { if (h == HwmfHatchStyle.HS_BDIAGONAL || h == HwmfHatchStyle.HS_DIAGCROSS) { g.drawLine(0, dim, dim, 0); } + // TODO: handle new HS_* enumeration values g.dispose(); return new TexturePaint(bi, new Rectangle(0,0,dim,dim)); } @@ -248,9 +248,10 @@ public class HwmfGraphics { * Saves the current properties to the stack */ public void saveProperties() { - assert(prop != null); - propStack.add(prop); - prop = new HwmfDrawProperties(prop); + final HwmfDrawProperties p = getProperties(); + assert(p != null); + propStack.add(p); + prop = new HwmfDrawProperties(p); } /** diff --git a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfSLImageRenderer.java b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfImageRenderer.java similarity index 91% rename from src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfSLImageRenderer.java rename to src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfImageRenderer.java index e2601bc65e..a87db042d6 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfSLImageRenderer.java +++ b/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)); diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java index fb78158a9d..2f052075b5 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java @@ -124,9 +124,9 @@ public class HwmfDraw { @Override public void draw(HwmfGraphics ctx) { - Path2D p = getShape(ctx); + Path2D p = (Path2D)poly.clone(); // don't close the path - p.setWindingRule(getWindingRule(ctx)); + p.setWindingRule(ctx.getProperties().getWindingRule()); if (isFill()) { ctx.fill(p); } else { @@ -134,10 +134,6 @@ public class HwmfDraw { } } - protected Path2D getShape(HwmfGraphics ctx) { - return (Path2D)poly.clone(); - } - /** * @return true, if the shape should be filled */ @@ -303,11 +299,20 @@ public class HwmfDraw { @Override public void draw(HwmfGraphics ctx) { - if (polyList.isEmpty()) { + Area area = getShape(ctx); + if (area == null) { return; } - int windingRule = getWindingRule(ctx); + if (isFill()) { + ctx.fill(area); + } else { + ctx.draw(area); + } + } + + protected Area getShape(HwmfGraphics ctx) { + int windingRule = ctx.getProperties().getWindingRule(); Area area = null; for (Path2D poly : polyList) { Path2D p = (Path2D)poly.clone(); @@ -320,14 +325,9 @@ public class HwmfDraw { } } - if (isFill()) { - ctx.fill(area); - } else { - ctx.draw(area); - } + return area; } - /** * @return true, if the shape should be filled */ @@ -459,6 +459,20 @@ public class HwmfDraw { @Override public void draw(HwmfGraphics ctx) { + Shape s = getShape(); + switch (getWmfRecordType()) { + default: + case arc: + ctx.draw(s); + break; + case chord: + case pie: + ctx.fill(s); + break; + } + } + + protected Arc2D getShape() { double startAngle = Math.toDegrees(Math.atan2(-(startPoint.getY() - bounds.getCenterY()), startPoint.getX() - bounds.getCenterX())); double endAngle = Math.toDegrees(Math.atan2(-(endPoint.getY() - bounds.getCenterY()), endPoint.getX() - bounds.getCenterX())); double arcAngle = (endAngle - startAngle) + (endAngle - startAngle > 0 ? 0 : 360); @@ -472,24 +486,16 @@ public class HwmfDraw { default: case arc: arcClosure = Arc2D.OPEN; - fillShape = false; break; case chord: arcClosure = Arc2D.CHORD; - fillShape = true; break; case pie: arcClosure = Arc2D.PIE; - fillShape = true; break; } - - Shape s = new Arc2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), startAngle, arcAngle, arcClosure); - if (fillShape) { - ctx.fill(s); - } else { - ctx.draw(s); - } + + return new Arc2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), startAngle, arcAngle, arcClosure); } } @@ -552,10 +558,6 @@ public class HwmfDraw { } } - private static int getWindingRule(HwmfGraphics ctx) { - return ctx.getProperties().getPolyfillMode().awtFlag; - } - static int readBounds(LittleEndianInputStream leis, Rectangle2D bounds) { /** * The 16-bit signed integers that defines the corners of the bounding rectangle. diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfHatchStyle.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfHatchStyle.java index e364d83b3b..6574deaed3 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfHatchStyle.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfHatchStyle.java @@ -32,7 +32,20 @@ public enum HwmfHatchStyle { /** +++++ - A horizontal and vertical cross-hatch. */ HS_CROSS(0x0004), /** xxxxx - A 45-degree crosshatch. */ - HS_DIAGCROSS(0x0005); + HS_DIAGCROSS(0x0005), + /** The hatch is not a pattern, but is a solid color. */ + HS_SOLIDCLR(0x0006), + /** The hatch is not a pattern, but is a dithered color. */ + HS_DITHEREDCLR(0x0007), + /** The hatch is not a pattern, but is a solid color, defined by the current text (foreground) color. */ + HS_SOLIDTEXTCLR(0x0008), + /** The hatch is not a pattern, but is a dithered color, defined by the current text (foreground) color. */ + HS_DITHEREDTEXTCLR(0x0009), + /** The hatch is not a pattern, but is a solid color, defined by the current background color. */ + HS_SOLIDBKCLR(0x000A), + /** The hatch is not a pattern, but is a dithered color, defined by the current background color. */ + HS_DITHEREDBKCLR(0x000B) + ; int flag; HwmfHatchStyle(int flag) { diff --git a/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java b/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java index f0e259df4d..8d360d9aef 100644 --- a/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java +++ b/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java @@ -22,14 +22,24 @@ import static org.apache.poi.POITestCase.assertContains; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.geom.Dimension2D; import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; import java.io.InputStream; import java.util.HashSet; import java.util.List; import java.util.Set; +import javax.imageio.ImageIO; + import org.apache.poi.POIDataSamples; import org.apache.poi.hemf.record.emf.HemfComment; import org.apache.poi.hemf.record.emf.HemfComment.EmfComment; @@ -41,22 +51,60 @@ import org.apache.poi.hemf.record.emf.HemfRecordType; import org.apache.poi.hemf.record.emf.HemfText; import org.apache.poi.util.IOUtils; import org.apache.poi.util.RecordFormatException; +import org.apache.poi.util.Units; +import org.junit.Ignore; import org.junit.Test; public class HemfPictureTest { - private POIDataSamples samples = POIDataSamples.getSpreadSheetInstance(); + private static final POIDataSamples ss_samples = POIDataSamples.getSpreadSheetInstance(); + private static final POIDataSamples sl_samples = POIDataSamples.getSlideShowInstance(); + + @Test + @Ignore("Only for manual tests") + public void paint() throws IOException { + File f = sl_samples.getFile("wrench.emf"); + try (FileInputStream fis = new FileInputStream(f)) { + HemfPicture emf = new HemfPicture(fis); + + Dimension2D dim = emf.getSize(); + int width = Units.pointsToPixel(dim.getWidth()); + // keep aspect ratio for height + int height = Units.pointsToPixel(dim.getHeight()); + double max = Math.max(width, height); + if (max > 1500) { + width *= 1500 / max; + height *= 1500 / max; + } + + BufferedImage bufImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = bufImg.createGraphics(); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); + g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); + + emf.draw(g, new Rectangle2D.Double(0,0,width,height)); + + g.dispose(); + + ImageIO.write(bufImg, "PNG", new File("bla.png")); + } + } + + + @Test public void testBasicWindows() throws Exception { - try (InputStream is = samples.openResourceAsStream("SimpleEMF_windows.emf")) { + try (InputStream is = ss_samples.openResourceAsStream("SimpleEMF_windows.emf")) { HemfPicture pic = new HemfPicture(is); HemfHeader header = pic.getHeader(); assertEquals(27864, header.getBytes()); assertEquals(31, header.getRecords()); assertEquals(3, header.getHandles()); - assertEquals(346000, header.getMicrometersX()); - assertEquals(194000, header.getMicrometersY()); + assertEquals(346000, header.getMicroDimension().getWidth()); + assertEquals(194000, header.getMicroDimension().getHeight()); List records = pic.getRecords(); @@ -66,7 +114,7 @@ public class HemfPictureTest { @Test public void testBasicMac() throws Exception { - try (InputStream is = samples.openResourceAsStream("SimpleEMF_mac.emf")) { + try (InputStream is = ss_samples.openResourceAsStream("SimpleEMF_mac.emf")) { HemfPicture pic = new HemfPicture(is); HemfHeader header = pic.getHeader(); @@ -102,7 +150,7 @@ public class HemfPictureTest { @Test public void testMacText() throws Exception { - try (InputStream is = samples.openResourceAsStream("SimpleEMF_mac.emf")) { + try (InputStream is = ss_samples.openResourceAsStream("SimpleEMF_mac.emf")) { HemfPicture pic = new HemfPicture(is); double lastY = -1; @@ -134,7 +182,7 @@ public class HemfPictureTest { @Test public void testWindowsText() throws Exception { - try (InputStream is = samples.openResourceAsStream("SimpleEMF_windows.emf")) { + try (InputStream is = ss_samples.openResourceAsStream("SimpleEMF_windows.emf")) { HemfPicture pic = new HemfPicture(is); double lastY = -1; double lastX = -1; @@ -173,7 +221,7 @@ public class HemfPictureTest { @Test(expected = RecordFormatException.class) public void testInfiniteLoopOnFile() throws Exception { - try (InputStream is = samples.openResourceAsStream("61294.emf")) { + try (InputStream is = ss_samples.openResourceAsStream("61294.emf")) { HemfPicture pic = new HemfPicture(is); for (HemfRecord record : pic) { @@ -183,7 +231,7 @@ public class HemfPictureTest { @Test(expected = RecordFormatException.class) public void testInfiniteLoopOnByteArray() throws Exception { - try (InputStream is = samples.openResourceAsStream("61294.emf")) { + try (InputStream is = ss_samples.openResourceAsStream("61294.emf")) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); IOUtils.copy(is, bos); is.close(); diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestPicture.java b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestPicture.java index 72eee287c2..d071f2dbdc 100644 --- a/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestPicture.java +++ b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestPicture.java @@ -210,7 +210,6 @@ public final class TestPicture { } else { BufferedImage img = new BufferedImage(pg.width, pg.height, BufferedImage.TYPE_INT_ARGB); Graphics2D graphics = img.createGraphics(); - DrawFactory.getInstance(graphics).fixFonts(graphics); slide.draw(graphics); graphics.setColor(Color.BLACK); graphics.setStroke(new BasicStroke(1)); From b3fc9f49572cb79ffa25f82d45987adb33b09e93 Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Mon, 24 Sep 2018 23:55:52 +0000 Subject: [PATCH 06/21] #60656 - Support export file that contains emf and render it correctly git-svn-id: https://svn.apache.org/repos/asf/poi/branches/hemf@1841897 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/poi/sl/draw/DrawPictureShape.java | 39 +++++++-- .../org.apache.poi.sl.draw.ImageRenderer | 1 - .../org.apache.poi.sl.draw.ImageRenderer | 2 - .../poi/hemf/draw/HemfDrawProperties.java | 22 ++++- .../apache/poi/hemf/draw/HemfGraphics.java | 58 +++++++++++++ .../poi/hemf/record/emf/HemfComment.java | 11 +++ .../apache/poi/hemf/record/emf/HemfDraw.java | 86 ++++++++++++------- .../apache/poi/hemf/record/emf/HemfFill.java | 3 + .../apache/poi/hemf/record/emf/HemfMisc.java | 46 ++++------ .../poi/hemf/record/emf/HemfPalette.java | 16 ++++ .../poi/hemf/record/emf/HemfRecordType.java | 6 +- .../apache/poi/hemf/record/emf/HemfText.java | 48 ++++++++--- .../apache/poi/hwmf/draw/HwmfGraphics.java | 4 +- .../apache/poi/hwmf/record/HwmfColorRef.java | 5 ++ .../org/apache/poi/hwmf/record/HwmfDraw.java | 56 +++++++++++- .../org/apache/poi/hwmf/record/HwmfFont.java | 33 +++++++ .../org/apache/poi/hwmf/record/HwmfMisc.java | 25 +++++- .../org/apache/poi/hwmf/record/HwmfText.java | 13 ++- .../apache/poi/hwmf/record/HwmfWindowing.java | 27 +++--- .../poi/hemf/usermodel/HemfPictureTest.java | 15 +++- 20 files changed, 397 insertions(+), 119 deletions(-) delete mode 100644 src/resources/main/META-INF/services/org.apache.poi.sl.draw.ImageRenderer delete mode 100644 src/resources/scratchpad/META-INF/services/org.apache.poi.sl.draw.ImageRenderer diff --git a/src/java/org/apache/poi/sl/draw/DrawPictureShape.java b/src/java/org/apache/poi/sl/draw/DrawPictureShape.java index 5c42d6fd13..3ccbc9f219 100644 --- a/src/java/org/apache/poi/sl/draw/DrawPictureShape.java +++ b/src/java/org/apache/poi/sl/draw/DrawPictureShape.java @@ -33,7 +33,10 @@ import org.apache.poi.util.POILogger; public class DrawPictureShape extends DrawSimpleShape { private static final POILogger LOG = POILogFactory.getLogger(DrawPictureShape.class); - private static final ServiceLoader rendererLoader = ServiceLoader.load(ImageRenderer.class); + private static final String[] KNOWN_RENDERER = { + "org.apache.poi.hwmf.draw.HwmfImageRenderer", + "org.apache.poi.hemf.draw.HemfImageRenderer" + }; public DrawPictureShape(PictureShape shape) { super(shape); @@ -62,24 +65,44 @@ public class DrawPictureShape extends DrawSimpleShape { * @param graphics the graphics context * @return the image renderer */ - @SuppressWarnings("WeakerAccess") + @SuppressWarnings({"WeakerAccess", "unchecked"}) public static ImageRenderer getImageRenderer(Graphics2D graphics, String contentType) { - ImageRenderer renderer = (ImageRenderer)graphics.getRenderingHint(Drawable.IMAGE_RENDERER); - if (renderer != null) { + final ImageRenderer renderer = (ImageRenderer)graphics.getRenderingHint(Drawable.IMAGE_RENDERER); + if (renderer != null && renderer.canRender(contentType)) { return renderer; } - for (ImageRenderer ir : rendererLoader) { + // first try with our default image renderer + final BitmapImageRenderer bir = new BitmapImageRenderer(); + if (bir.canRender(contentType)) { + return bir; + } + + // then iterate through the scratchpad renderers + // + // this could be nicely implemented via a j.u.ServiceLoader, but OSGi makes things complicated ... + // https://blog.osgi.org/2013/02/javautilserviceloader-in-osgi.html + // ... therefore falling back to classloading attempts + ClassLoader cl = ImageRenderer.class.getClassLoader(); + for (String kr : KNOWN_RENDERER) { + final ImageRenderer ir; + try { + ir = ((Class)cl.loadClass(kr)).newInstance(); + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { + // scratchpad was not on the path, ignore and continue + LOG.log(POILogger.INFO, "Known image renderer '"+kr+" not found/loaded - include poi-scratchpad jar!", e); + continue; + } if (ir.canRender(contentType)) { return ir; } } - LOG.log(POILogger.ERROR, "No suiteable image renderer found for content-type '"+ + LOG.log(POILogger.WARN, "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(); + // falling back to BitmapImageRenderer, at least it gracefully handles invalid images + return bir; } @Override diff --git a/src/resources/main/META-INF/services/org.apache.poi.sl.draw.ImageRenderer b/src/resources/main/META-INF/services/org.apache.poi.sl.draw.ImageRenderer deleted file mode 100644 index 1e7699fe57..0000000000 --- a/src/resources/main/META-INF/services/org.apache.poi.sl.draw.ImageRenderer +++ /dev/null @@ -1 +0,0 @@ -org.apache.poi.sl.draw.BitmapImageRenderer \ No newline at end of file diff --git a/src/resources/scratchpad/META-INF/services/org.apache.poi.sl.draw.ImageRenderer b/src/resources/scratchpad/META-INF/services/org.apache.poi.sl.draw.ImageRenderer deleted file mode 100644 index b23f654157..0000000000 --- a/src/resources/scratchpad/META-INF/services/org.apache.poi.sl.draw.ImageRenderer +++ /dev/null @@ -1,2 +0,0 @@ -org.apache.poi.hwmf.draw.HwmfImageRenderer -org.apache.poi.hemf.draw.HemfImageRenderer \ No newline at end of file diff --git a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java index 8bb8af33bf..b12a4caa6c 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java +++ b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java @@ -24,16 +24,15 @@ import org.apache.poi.hwmf.draw.HwmfDrawProperties; public class HemfDrawProperties extends HwmfDrawProperties { /** Path for path bracket operations */ - protected final Path2D path; + protected Path2D path = null; public HemfDrawProperties() { - path = new Path2D.Double(); } public HemfDrawProperties(HemfDrawProperties other) { super(other); - path = (Path2D)other.path.clone(); + path = (other.path != null) ? (Path2D)other.path.clone() : null; } /** @@ -42,4 +41,21 @@ public class HemfDrawProperties extends HwmfDrawProperties { public Path2D getPath() { return path; } + + /** + * Un-/Sets the bracket path + * @param path the bracket path + */ + public void setPath(Path2D path) { + this.path = path; + } + + /** + * Use path (bracket) or graphics context for drawing operations + * @return {@code true}, if the drawing should go to the path bracket, + * if {@code false} draw directly to the graphics context + */ + public boolean usePathBracket() { + return path != null; + } } diff --git a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java index 05701ef823..c4783950a6 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java +++ b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java @@ -19,13 +19,18 @@ package org.apache.poi.hemf.draw; import java.awt.Graphics2D; import java.awt.geom.AffineTransform; +import java.awt.geom.Path2D; +import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.ArrayDeque; import java.util.Deque; +import java.util.function.Consumer; import org.apache.poi.hemf.record.emf.HemfBounded; import org.apache.poi.hemf.record.emf.HemfRecord; import org.apache.poi.hwmf.draw.HwmfGraphics; +import org.apache.poi.hwmf.record.HwmfObjectTableEntry; +import org.apache.poi.util.Internal; public class HemfGraphics extends HwmfGraphics { @@ -80,6 +85,59 @@ public class HemfGraphics extends HwmfGraphics { } } + @Internal + public void draw(Consumer pathConsumer) { + final HemfDrawProperties prop = getProperties(); + final boolean useBracket = prop.usePathBracket(); + + final Path2D path; + if (useBracket) { + path = prop.getPath(); + } else { + path = new Path2D.Double(); + Point2D pnt = prop.getLocation(); + path.moveTo(pnt.getX(),pnt.getY()); + } + + pathConsumer.accept(path); + + prop.setLocation(path.getCurrentPoint()); + if (!useBracket) { + // TODO: when to use draw vs. fill? + graphicsCtx.draw(path); + } + + } + + /** + * Adds or sets an record of type {@link HwmfObjectTableEntry} to the object table. + * If the {@code index} is less than 1, the method acts the same as + * {@link HwmfGraphics#addObjectTableEntry(HwmfObjectTableEntry)}, otherwise the + * index is used to access the object table. + * As the table is filled successively, the index must be between 1 and size+1 + * + * @param entry the record to be stored + * @param index the index to be overwritten, regardless if its content was unset before + * + * @see HwmfGraphics#addObjectTableEntry(HwmfObjectTableEntry) + */ + public void addObjectTableEntry(HwmfObjectTableEntry entry, int index) { + if (index < 1) { + super.addObjectTableEntry(entry); + return; + } + + if (index > objectTable.size()) { + throw new IllegalStateException("object table hasn't grown to this index yet"); + } + + if (index == objectTable.size()) { + objectTable.add(entry); + } else { + objectTable.set(index, entry); + } + } + /** saves the current affine transform on the stack */ private void saveTransform() { diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java index 0a459e3cd0..fe098f2794 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java @@ -32,6 +32,7 @@ import org.apache.poi.util.IOUtils; import org.apache.poi.util.Internal; import org.apache.poi.util.LittleEndianConsts; import org.apache.poi.util.LittleEndianInputStream; +import org.apache.poi.util.LocaleUtil; import org.apache.poi.util.RecordFormatException; /** @@ -97,6 +98,11 @@ public class HemfComment { public EmfCommentData getCommentData() { return data; } + + @Override + public String toString() { + return "{ data: "+data+" }"; + } } public static class EmfCommentDataIterator implements Iterator { @@ -208,6 +214,11 @@ public class HemfComment { leis.readFully(privateData); return privateData.length; } + + @Override + public String toString() { + return "\""+new String(privateData, LocaleUtil.CHARSET_1252)+"\""; + } } /** The EMR_COMMENT_EMFPLUS record contains embedded EMF+ records. */ diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java index c2cc9c3de2..f9c6e975a2 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java @@ -53,6 +53,28 @@ public class HemfDraw { private static final HwmfColorRef DKGRAY = new HwmfColorRef(new Color(0x00404040)); private static final HwmfColorRef BLACK = new HwmfColorRef(Color.BLACK); + private static final String[] STOCK_IDS = { + "0x80000000 /* WHITE_BRUSH */", + "0x80000001 /* LTGRAY_BRUSH */", + "0x80000002 /* GRAY_BRUSH */", + "0x80000003 /* DKGRAY_BRUSH */", + "0x80000004 /* BLACK_BRUSH */", + "0x80000005 /* NULL_BRUSH */", + "0x80000006 /* WHITE_PEN */", + "0x80000007 /* BLACK_PEN */", + "0x80000008 /* NULL_PEN */", + "0x8000000A /* OEM_FIXED_FONT */", + "0x8000000B /* ANSI_FIXED_FONT */", + "0x8000000C /* ANSI_VAR_FONT */", + "0x8000000D /* SYSTEM_FONT */", + "0x8000000E /* DEVICE_DEFAULT_FONT */", + "0x8000000F /* DEFAULT_PALETTE */", + "0x80000010 /* SYSTEM_FIXED_FONT */", + "0x80000011 /* DEFAULT_GUI_FONT */", + "0x80000012 /* DC_BRUSH */", + "0x80000013 /* DC_PEN */" + }; + @Override public HemfRecordType getEmfRecordType() { return HemfRecordType.selectObject; @@ -184,6 +206,14 @@ public class HemfDraw { } } + @Override + public String toString() { + return "{ index: "+ + (((objectIndex & 0x80000000) != 0 && (objectIndex & 0x3FFFFFFF) <= 13 ) + ? STOCK_IDS[objectIndex & 0x3FFFFFFF] + : objectIndex)+" }"; + } + } @@ -220,7 +250,7 @@ public class HemfDraw { final int points = Math.min(count, 16384); size += LittleEndianConsts.INT_SIZE; - poly.reset(); + poly = new Path2D.Double(Path2D.WIND_EVEN_ODD, points); /* Cubic Bezier curves are defined using the endpoints and control points * specified by the points field. The first curve is drawn from the first @@ -324,6 +354,8 @@ public class HemfDraw { final int points = Math.min(count, 16384); size += LittleEndianConsts.INT_SIZE; + poly = new Path2D.Double(Path2D.WIND_EVEN_ODD, points); + Point2D pnt = new Point2D.Double(); for (int i=0; i path.moveTo(point.getX(), point.getY())); } } @@ -802,11 +831,8 @@ public class HemfDraw { } @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); + public void draw(final HemfGraphics ctx) { + ctx.draw((path) -> path.lineTo(point.getX(), point.getY())); } } @@ -829,11 +855,9 @@ public class HemfDraw { } @Override - public void draw(HemfGraphics ctx) { - final Path2D path = ctx.getProperties().getPath(); - Arc2D arc = getShape(); - path.append(arc, true); - ctx.getProperties().setLocation(endPoint); + public void draw(final HemfGraphics ctx) { + final Arc2D arc = getShape(); + ctx.draw((path) -> path.append(arc, true)); } } @@ -860,7 +884,7 @@ public class HemfDraw { size += readPoint(leis, points[i]); } - poly.reset(); + poly = new Path2D.Double(Path2D.WIND_EVEN_ODD, count); for (int i=0; i path.append(pi, true)); } - - } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java index e28a355f0c..5dca14537f 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java @@ -655,6 +655,9 @@ public class HemfFill { @Override public void draw(HemfGraphics ctx) { final HemfDrawProperties prop = ctx.getProperties(); + if (!prop.usePathBracket()) { + return; + } final Path2D path = (Path2D)prop.getPath().clone(); path.setWindingRule(ctx.getProperties().getWindingRule()); if (prop.getBrushStyle() == HwmfBrushStyle.BS_NULL) { diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java index f3424d08d0..291c83d630 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java @@ -98,7 +98,7 @@ public class HemfMisc { /** * The EMF_SAVEDC record saves the playback device context for later retrieval. */ - public static class EmfSaveDc implements HemfRecord { + public static class EmfSaveDc extends HwmfMisc.WmfSaveDc implements HemfRecord { @Override public HemfRecordType getEmfRecordType() { return HemfRecordType.saveDc; @@ -108,25 +108,13 @@ public class HemfMisc { public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { return 0; } - - @Override - public void draw(HemfGraphics ctx) { - ctx.saveProperties(); - } } /** * The EMF_RESTOREDC record restores the playback device context from a previously saved device * context. */ - public static class EmfRestoreDc implements HemfRecord { - - /** - * SavedDC (4 bytes): A 32-bit signed integer that specifies the saved state to restore relative to - * the current state. This value MUST be negative; –1 represents the state that was most - * recently saved on the stack, –2 the one before that, etc. - */ - private int nSavedDC; + public static class EmfRestoreDc extends HwmfMisc.WmfRestoreDc implements HemfRecord { @Override public HemfRecordType getEmfRecordType() { @@ -135,23 +123,19 @@ public class HemfMisc { @Override public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + // A 32-bit signed integer that specifies the saved state to restore relative to + // the current state. This value MUST be negative; –1 represents the state that was most + // recently saved on the stack, –2 the one before that, etc. nSavedDC = leis.readInt(); return LittleEndianConsts.INT_SIZE; } - - @Override - public void draw(HemfGraphics ctx) { - ctx.restoreProperties(nSavedDC); - } } /** * The META_SETBKCOLOR record sets the background color in the playback device context to a * specified color, or to the nearest physical color if the device cannot represent the specified color. */ - public static class EmfSetBkColor implements HemfRecord { - - private HwmfColorRef colorRef; + public static class EmfSetBkColor extends HwmfMisc.WmfSetBkColor implements HemfRecord { @Override public HemfRecordType getEmfRecordType() { @@ -160,14 +144,8 @@ public class HemfMisc { @Override public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { - colorRef = new HwmfColorRef(); return colorRef.init(leis); } - - @Override - public void draw(HemfGraphics ctx) { - ctx.getProperties().setBackgroundColor(colorRef); - } } @@ -287,8 +265,13 @@ public class HemfMisc { int size = colorRef.init(leis); brushHatch = HwmfHatchStyle.valueOf((int) leis.readUInt()); return size + 3 * LittleEndianConsts.INT_SIZE; - } + + @Override + public void draw(HemfGraphics ctx) { + ctx.addObjectTableEntry(this, brushIdx); + } + } /** @@ -341,6 +324,11 @@ public class HemfMisc { return size + 4 * LittleEndianConsts.INT_SIZE; } + + @Override + public void draw(HemfGraphics ctx) { + ctx.addObjectTableEntry(this, penIndex); + } } public static class EmfExtCreatePen extends EmfCreatePen { diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfPalette.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfPalette.java index b9a909991e..9811cb2c3d 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfPalette.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfPalette.java @@ -19,6 +19,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.HwmfPalette; import org.apache.poi.util.LittleEndianConsts; import org.apache.poi.util.LittleEndianInputStream; @@ -67,6 +68,11 @@ public class HemfPalette { int size = readPaletteEntries(leis, -1); return size + LittleEndianConsts.INT_SIZE + LittleEndianConsts.SHORT_SIZE; } + + @Override + public void draw(HemfGraphics ctx) { + ctx.addObjectTableEntry(this, paletteIndex); + } } /** @@ -93,6 +99,11 @@ public class HemfPalette { int size = readPaletteEntries(leis, nbrOfEntries); return size + 3*LittleEndianConsts.INT_SIZE; } + + @Override + public void draw(HemfGraphics ctx) { + ctx.addObjectTableEntry(this, paletteIndex); + } } /** @@ -118,6 +129,11 @@ public class HemfPalette { return 2*LittleEndianConsts.INT_SIZE; } + + @Override + public void draw(HemfGraphics ctx) { + ctx.addObjectTableEntry(this, paletteIndex); + } } /** diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java index 6c858ccc53..f6584443a1 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java @@ -47,7 +47,7 @@ public enum HemfRecordType { setStretchBltMode(0x00000015, HemfMisc.EmfSetStretchBltMode::new), setTextAlign(0x00000016, HemfText.EmfSetTextAlign::new), setcoloradjustment(0x00000017, UnimplementedHemfRecord::new), - setTextColor(0x00000018, HemfText.SetTextColor::new), + setTextColor(0x00000018, HemfText.EmfSetTextColor::new), setBkColor(0x00000019, HemfMisc.EmfSetBkColor::new), setOffsetClipRgn(0x0000001A, HemfWindowing.EmfSetOffsetClipRgn::new), setMoveToEx(0x0000001B, HemfDraw.EmfSetMoveToEx::new), @@ -106,8 +106,8 @@ public enum HemfRecordType { setDiBitsToDevice(0x00000050, HemfFill.EmfSetDiBitsToDevice::new), stretchdibits(0x00000051, UnimplementedHemfRecord::new), extCreateFontIndirectW(0x00000052, HemfText.ExtCreateFontIndirectW::new), - exttextouta(0x00000053, HemfText.ExtTextOutA::new), - exttextoutw(0x00000054, HemfText.ExtTextOutW::new), + exttextouta(0x00000053, HemfText.EmfExtTextOutA::new), + exttextoutw(0x00000054, HemfText.EmfExtTextOutW::new), polyBezier16(0x00000055, HemfDraw.EmfPolyBezier16::new), polygon16(0x00000056, HemfDraw.EmfPolygon16::new), polyline16(0x00000057, HemfDraw.EmfPolyline16::new), diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java index 95c110df10..2305aeced8 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java @@ -29,7 +29,7 @@ import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; -import org.apache.poi.hwmf.record.HwmfColorRef; +import org.apache.poi.hemf.draw.HemfGraphics; import org.apache.poi.hwmf.record.HwmfText; import org.apache.poi.hwmf.record.HwmfText.WmfSetTextAlign; import org.apache.poi.util.Dimension2DDouble; @@ -53,9 +53,9 @@ public class HemfText { GM_COMPATIBLE, GM_ADVANCED } - public static class ExtTextOutA implements HemfRecord { + public static class EmfExtTextOutA implements HemfRecord { - protected final Rectangle2D boundsIgnored = new Rectangle2D.Double(); + protected final Rectangle2D bounds = new Rectangle2D.Double(); protected EmfGraphicsMode graphicsMode; @@ -67,11 +67,11 @@ public class HemfText { protected final EmrTextObject textObject; - public ExtTextOutA() { + public EmfExtTextOutA() { this(false); } - protected ExtTextOutA(boolean isUnicode) { + protected EmfExtTextOutA(boolean isUnicode) { textObject = new EmrTextObject(isUnicode); } @@ -87,7 +87,7 @@ public class HemfText { } // A WMF RectL object. It is not used and MUST be ignored on receipt. - long size = readRectL(leis, boundsIgnored); + long size = readRectL(leis, bounds); // A 32-bit unsigned integer that specifies the graphics mode from the GraphicsMode enumeration graphicsMode = EmfGraphicsMode.values()[leis.readInt()-1]; @@ -104,10 +104,10 @@ public class HemfText { /** * * To be implemented! We need to get the current character set - * from the current font for {@link ExtTextOutA}, + * from the current font for {@link EmfExtTextOutA}, * which has to be tracked in the playback device. * - * For {@link ExtTextOutW}, the charset is "UTF-16LE" + * For {@link EmfExtTextOutW}, the charset is "UTF-16LE" * * @param charset the charset to be used to decode the character bytes * @return text from this text element @@ -132,11 +132,24 @@ public class HemfText { public Dimension2D getScale() { return scale; } + + @Override + public String toString() { + return + "{ bounds: { x: "+bounds.getX()+ + ", y: "+bounds.getY()+ + ", w: "+bounds.getWidth()+ + ", h: "+bounds.getHeight()+ + "}, graphicsMode: '"+graphicsMode+"'"+ + ", scale: { w: "+scale.getWidth()+", h: "+scale.getHeight()+" }"+ + ", textObject: "+textObject+ + "}"; + } } - public static class ExtTextOutW extends ExtTextOutA { + public static class EmfExtTextOutW extends EmfExtTextOutA { - public ExtTextOutW() { + public EmfExtTextOutW() { super(true); } @@ -175,10 +188,7 @@ public class HemfText { /** * The EMR_SETTEXTCOLOR record defines the current text color. */ - public static class SetTextColor implements HemfRecord { - /** A WMF ColorRef object that specifies the text color value. */ - private final HwmfColorRef colorRef = new HwmfColorRef(); - + public static class EmfSetTextColor extends HwmfText.WmfSetTextColor implements HemfRecord { @Override public HemfRecordType getEmfRecordType() { return HemfRecordType.setTextColor; @@ -284,6 +294,16 @@ public class HemfText { int size = font.init(leis, (int)(recordSize-LittleEndianConsts.INT_SIZE)); return size+LittleEndianConsts.INT_SIZE; } + + @Override + public void draw(HemfGraphics ctx) { + ctx.addObjectTableEntry(this, fontIdx); + } + + @Override + public String toString() { + return "{ index: "+fontIdx+", font: "+font+" } "; + } } public static class EmfExtTextOutOptions extends HwmfText.WmfExtTextOutOptions { diff --git a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java index de348480ad..b29c1187e9 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java @@ -55,10 +55,10 @@ public class HwmfGraphics { protected final List propStack = new LinkedList<>(); protected HwmfDrawProperties prop; protected final Graphics2D graphicsCtx; + protected final List objectTable = new ArrayList<>(); private static final Charset DEFAULT_CHARSET = LocaleUtil.CHARSET_1252; - private final List objectTable = new ArrayList<>(); - /** Bounding box from the placeable header */ + /** Bounding box from the placeable header */ private final Rectangle2D bbox; private final AffineTransform initialAT; diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfColorRef.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfColorRef.java index 5b24fbc7e9..12204d61ec 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfColorRef.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfColorRef.java @@ -69,4 +69,9 @@ public class HwmfColorRef implements Cloneable { throw new InternalError(); } } + + @Override + public String toString() { + return String.format("%#8X", colorRef.getRGB()); + } } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java index 2f052075b5..64f2dae8c9 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java @@ -23,6 +23,7 @@ import java.awt.geom.Area; import java.awt.geom.Ellipse2D; import java.awt.geom.Line2D; import java.awt.geom.Path2D; +import java.awt.geom.PathIterator; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.geom.RoundRectangle2D; @@ -57,6 +58,11 @@ public class HwmfDraw { public void draw(HwmfGraphics ctx) { ctx.getProperties().setLocation(point); } + + @Override + public String toString() { + return "{ x: "+point.getX()+", y: "+point.getY()+" }"; + } } /** @@ -84,6 +90,11 @@ public class HwmfDraw { ctx.draw(line); ctx.getProperties().setLocation(point); } + + @Override + public String toString() { + return "{ x: "+point.getX()+", y: "+point.getY()+" }"; + } } /** @@ -93,7 +104,7 @@ public class HwmfDraw { */ public static class WmfPolygon implements HwmfRecord { - protected Path2D poly = new Path2D.Double(); + protected Path2D poly; @Override public HwmfRecordType getWmfRecordType() { @@ -107,6 +118,7 @@ public class HwmfDraw { */ int numberofPoints = leis.readShort(); + poly = new Path2D.Double(Path2D.WIND_EVEN_ODD, numberofPoints); for (int i=0; i -1 && lastY != reference.getY()) { sb.append("\n"); @@ -194,7 +203,7 @@ public class HemfPictureTest { int foundExpected = 0; for (HemfRecord record : pic) { if (record.getEmfRecordType().equals(HemfRecordType.exttextoutw)) { - HemfText.ExtTextOutW extTextOutW = (HemfText.ExtTextOutW) record; + HemfText.EmfExtTextOutW extTextOutW = (HemfText.EmfExtTextOutW) record; Point2D reference = extTextOutW.getTextObject().getReference(); if (lastY > -1 && lastY != reference.getY()) { sb.append("\n"); From 312fde5bd0728039f448837bd504e388fac2ce4a Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Wed, 26 Sep 2018 22:16:33 +0000 Subject: [PATCH 07/21] #60656 - Support export file that contains emf and render it correctly git-svn-id: https://svn.apache.org/repos/asf/poi/branches/hemf@1842056 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/poi/hemf/draw/HemfGraphics.java | 2 +- .../poi/hemf/record/emf/HemfComment.java | 2 +- .../apache/poi/hemf/record/emf/HemfMisc.java | 21 ++ .../apache/poi/hemf/record/emf/HemfText.java | 185 ++++++++---------- .../apache/poi/hwmf/draw/HwmfGraphics.java | 53 +++-- .../apache/poi/hwmf/record/HwmfColorRef.java | 3 +- .../org/apache/poi/hwmf/record/HwmfMisc.java | 16 ++ .../apache/poi/hwmf/record/HwmfPenStyle.java | 11 ++ .../org/apache/poi/hwmf/record/HwmfText.java | 21 +- .../poi/hemf/usermodel/HemfPictureTest.java | 8 +- 10 files changed, 180 insertions(+), 142 deletions(-) diff --git a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java index c4783950a6..40effa0dde 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java +++ b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java @@ -104,7 +104,7 @@ public class HemfGraphics extends HwmfGraphics { prop.setLocation(path.getCurrentPoint()); if (!useBracket) { // TODO: when to use draw vs. fill? - graphicsCtx.draw(path); + super.draw(path); } } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java index fe098f2794..1752e99e1a 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java @@ -217,7 +217,7 @@ public class HemfComment { @Override public String toString() { - return "\""+new String(privateData, LocaleUtil.CHARSET_1252)+"\""; + return "\""+new String(privateData, LocaleUtil.CHARSET_1252).replaceAll("\\p{Cntrl}", ".")+"\""; } } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java index 291c83d630..d128084c6f 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java @@ -272,6 +272,15 @@ public class HemfMisc { ctx.addObjectTableEntry(this, brushIdx); } + + @Override + public String toString() { + return + "{ brushIndex: "+brushIdx+ + ", brushStyle: '"+brushStyle+"'"+ + ", colorRef: "+colorRef+ + ", brushHatch: '"+brushHatch+"' }"; + } } /** @@ -329,6 +338,11 @@ public class HemfMisc { public void draw(HemfGraphics ctx) { ctx.addObjectTableEntry(this, penIndex); } + + @Override + public String toString() { + return super.toString().replaceFirst("\\{", "{ penIndex: "+penIndex+", "); + } } public static class EmfExtCreatePen extends EmfCreatePen { @@ -421,6 +435,13 @@ public class HemfMisc { return size; } + + @Override + public String toString() { + // TODO: add style entries + bmp + return super.toString().replaceFirst("\\{", + "{ brushStyle: '"+brushStyle+"', hatchStyle: '"+hatchStyle+"', "); + } } /** diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java index 2305aeced8..d78b750aa3 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java @@ -18,18 +18,19 @@ package org.apache.poi.hemf.record.emf; import static java.nio.charset.StandardCharsets.UTF_16LE; -import static org.apache.poi.hemf.record.emf.HemfDraw.readRectL; import static org.apache.poi.hemf.record.emf.HemfDraw.readDimensionFloat; 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.Dimension2D; import java.awt.geom.Rectangle2D; import java.io.IOException; import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.List; +import org.apache.commons.codec.Charsets; import org.apache.poi.hemf.draw.HemfGraphics; +import org.apache.poi.hwmf.draw.HwmfGraphics; import org.apache.poi.hwmf.record.HwmfText; import org.apache.poi.hwmf.record.HwmfText.WmfSetTextAlign; import org.apache.poi.util.Dimension2DDouble; @@ -37,6 +38,7 @@ import org.apache.poi.util.IOUtils; import org.apache.poi.util.Internal; import org.apache.poi.util.LittleEndianConsts; import org.apache.poi.util.LittleEndianInputStream; +import org.apache.poi.util.LocaleUtil; import org.apache.poi.util.RecordFormatException; /** @@ -53,9 +55,7 @@ public class HemfText { GM_COMPATIBLE, GM_ADVANCED } - public static class EmfExtTextOutA implements HemfRecord { - - protected final Rectangle2D bounds = new Rectangle2D.Double(); + public static class EmfExtTextOutA extends HwmfText.WmfExtTextOut implements HemfRecord { protected EmfGraphicsMode graphicsMode; @@ -65,14 +65,8 @@ public class HemfText { */ protected final Dimension2D scale = new Dimension2DDouble(); - protected final EmrTextObject textObject; - public EmfExtTextOutA() { - this(false); - } - - protected EmfExtTextOutA(boolean isUnicode) { - textObject = new EmrTextObject(isUnicode); + super(new EmfExtTextOutOptions()); } @Override @@ -95,12 +89,70 @@ public class HemfText { size += readDimensionFloat(leis, scale); - // guarantee to read the rest of the EMRTextObjectRecord - size += textObject.init(leis, recordSize, (int)size); + // A WMF PointL object that specifies the coordinates of the reference point used to position the string. + // The reference point is defined by the last EMR_SETTEXTALIGN record. + // If no such record has been set, the default alignment is TA_LEFT,TA_TOP. + size += readPointL(leis, reference); + // A 32-bit unsigned integer that specifies the number of characters in the string. + stringLength = (int)leis.readUInt(); + // A 32-bit unsigned integer that specifies the offset to the output string, in bytes, + // from the start of the record in which this object is contained. + // This value MUST be 8- or 16-bit aligned, according to the character format. + int offString = (int)leis.readUInt(); + size += 2*LittleEndianConsts.INT_SIZE; + + size += options.init(leis); + // An optional WMF RectL object that defines a clipping and/or opaquing rectangle in logical units. + // This rectangle is applied to the text output performed by the containing record. + if (options.isClipped() || options.isOpaque()) { + size += readRectL(leis, bounds); + } + + // A 32-bit unsigned integer that specifies the offset to an intercharacter spacing array, in bytes, + // from the start of the record in which this object is contained. This value MUST be 32-bit aligned. + int offDx = (int)leis.readUInt(); + size += LittleEndianConsts.INT_SIZE; + + int undefinedSpace1 = (int)(offString - size - HEADER_SIZE); + assert (undefinedSpace1 >= 0); + leis.skipFully(undefinedSpace1); + size += undefinedSpace1; + + rawTextBytes = IOUtils.safelyAllocate(stringLength*(isUnicode()?2:1), MAX_RECORD_LENGTH); + leis.readFully(rawTextBytes); + size += rawTextBytes.length; + + dx.clear(); + if (offDx > 0) { + int undefinedSpace2 = (int) (offDx - size - HEADER_SIZE); + assert (undefinedSpace2 >= 0); + leis.skipFully(undefinedSpace2); + size += undefinedSpace2; + + // An array of 32-bit unsigned integers that specify the output spacing between the origins of adjacent + // character cells in logical units. The location of this field is specified by the value of offDx + // in bytes from the start of this record. If spacing is defined, this field contains the same number + // of values as characters in the output string. + // + // If the Options field of the EmrText object contains the ETO_PDY flag, then this buffer + // contains twice as many values as there are characters in the output string, one + // horizontal and one vertical offset for each, in that order. + // + // If ETO_RTLREADING is specified, characters are laid right to left instead of left to right. + // No other options affect the interpretation of this field. + while (size < recordSize) { + dx.add((int) leis.readUInt()); + size += LittleEndianConsts.INT_SIZE; + } + } return size; } + protected boolean isUnicode() { + return false; + } + /** * * To be implemented! We need to get the current character set @@ -114,15 +166,7 @@ public class HemfText { * @throws IOException */ public String getText(Charset charset) throws IOException { - return textObject.getText(charset); - } - - /** - * - * @return the x offset for the EmrTextObject - */ - public EmrTextObject getTextObject() { - return textObject; + return super.getText(charset); } public EmfGraphicsMode getGraphicsMode() { @@ -133,8 +177,20 @@ public class HemfText { return scale; } + @Override + public void draw(HwmfGraphics ctx) { + Rectangle2D bounds = new Rectangle2D.Double(reference.getX(), reference.getY(), 0, 0); + ctx.drawString(rawTextBytes, bounds, dx, isUnicode()); + } + @Override public String toString() { + String text = ""; + try { + text = getText(isUnicode() ? Charsets.UTF_16LE : LocaleUtil.CHARSET_1252); + } catch (IOException ignored) { + } + return "{ bounds: { x: "+bounds.getX()+ ", y: "+bounds.getY()+ @@ -142,17 +198,13 @@ public class HemfText { ", h: "+bounds.getHeight()+ "}, graphicsMode: '"+graphicsMode+"'"+ ", scale: { w: "+scale.getWidth()+", h: "+scale.getHeight()+" }"+ - ", textObject: "+textObject+ + ", text: '"+text.replaceAll("\\p{Cntrl}",".")+"'"+ "}"; } } public static class EmfExtTextOutW extends EmfExtTextOutA { - public EmfExtTextOutW() { - super(true); - } - @Override public HemfRecordType getEmfRecordType() { return HemfRecordType.exttextoutw; @@ -161,6 +213,10 @@ public class HemfText { public String getText() throws IOException { return getText(UTF_16LE); } + + protected boolean isUnicode() { + return true; + } } /** @@ -200,77 +256,6 @@ public class HemfText { } } - public static class EmrTextObject extends HwmfText.WmfExtTextOut { - protected final boolean isUnicode; - protected final List outputDx = new ArrayList<>(); - - public EmrTextObject(boolean isUnicode) { - super(new EmfExtTextOutOptions()); - this.isUnicode = isUnicode; - } - - @Override - public int init(LittleEndianInputStream leis, final long recordSize, final int offset) throws IOException { - // A WMF PointL object that specifies the coordinates of the reference point used to position the string. - // The reference point is defined by the last EMR_SETTEXTALIGN record. - // If no such record has been set, the default alignment is TA_LEFT,TA_TOP. - long size = readPointL(leis, reference); - // A 32-bit unsigned integer that specifies the number of characters in the string. - stringLength = (int)leis.readUInt(); - // A 32-bit unsigned integer that specifies the offset to the output string, in bytes, - // from the start of the record in which this object is contained. - // This value MUST be 8- or 16-bit aligned, according to the character format. - int offString = (int)leis.readUInt(); - size += 2*LittleEndianConsts.INT_SIZE; - - size += options.init(leis); - // An optional WMF RectL object that defines a clipping and/or opaquing rectangle in logical units. - // This rectangle is applied to the text output performed by the containing record. - if (options.isClipped() || options.isOpaque()) { - size += readRectL(leis, bounds); - } - - // A 32-bit unsigned integer that specifies the offset to an intercharacter spacing array, in bytes, - // from the start of the record in which this object is contained. This value MUST be 32-bit aligned. - int offDx = (int)leis.readUInt(); - size += LittleEndianConsts.INT_SIZE; - - int undefinedSpace1 = (int)(offString-offset-size-2*LittleEndianConsts.INT_SIZE); - assert (undefinedSpace1 >= 0); - leis.skipFully(undefinedSpace1); - size += undefinedSpace1; - - rawTextBytes = IOUtils.safelyAllocate(stringLength*(isUnicode?2:1), MAX_RECORD_LENGTH); - leis.readFully(rawTextBytes); - size += rawTextBytes.length; - - outputDx.clear(); - if (offDx > 0) { - int undefinedSpace2 = (int) (offDx - offset - size - 2 * LittleEndianConsts.INT_SIZE); - assert (undefinedSpace2 >= 0); - leis.skipFully(undefinedSpace2); - size += undefinedSpace2; - - // An array of 32-bit unsigned integers that specify the output spacing between the origins of adjacent - // character cells in logical units. The location of this field is specified by the value of offDx - // in bytes from the start of this record. If spacing is defined, this field contains the same number - // of values as characters in the output string. - // - // If the Options field of the EmrText object contains the ETO_PDY flag, then this buffer - // contains twice as many values as there are characters in the output string, one - // horizontal and one vertical offset for each, in that order. - // - // If ETO_RTLREADING is specified, characters are laid right to left instead of left to right. - // No other options affect the interpretation of this field. - while (size < recordSize) { - outputDx.add((int) leis.readUInt()); - size += LittleEndianConsts.INT_SIZE; - } - } - - return (int)size; - } - } public static class ExtCreateFontIndirectW extends HwmfText.WmfCreateFontIndirect diff --git a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java index b29c1187e9..c5873e02a6 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java @@ -37,6 +37,7 @@ import java.util.List; import java.util.ListIterator; import java.util.NoSuchElementException; +import org.apache.commons.codec.Charsets; import org.apache.poi.common.usermodel.fonts.FontInfo; import org.apache.poi.hwmf.record.HwmfBrushStyle; import org.apache.poi.hwmf.record.HwmfFont; @@ -325,7 +326,12 @@ public class HwmfGraphics { drawString(text, bounds, null); } - public void drawString(byte[] text, Rectangle2D bounds, int dx[]) { + public void drawString(byte[] text, Rectangle2D bounds, List dx) { + drawString(text, bounds, dx, false); + } + + public void drawString(byte[] text, Rectangle2D bounds, List dx, boolean isUnicode) { + HwmfFont font = getProperties().getFont(); if (font == null || text == null || text.length == 0) { return; @@ -335,14 +341,21 @@ public class HwmfGraphics { // TODO: another approx. ... double fontW = fontH/1.8; - Charset charset = (font.getCharset().getCharset() == null)? - DEFAULT_CHARSET : font.getCharset().getCharset(); + Charset charset; + if (isUnicode) { + charset = Charsets.UTF_16LE; + } else { + charset = font.getCharset().getCharset(); + if (charset == null) { + charset = DEFAULT_CHARSET; + } + } + String textString = new String(text, charset); AttributedString as = new AttributedString(textString); - if (dx == null || dx.length == 0) { + if (dx == null || dx.isEmpty()) { addAttributes(as, font); } else { - int[] dxNormed = dx; //for multi-byte encodings (e.g. Shift_JIS), the byte length //might not equal the string length(). //The x information is stored in dx[], an array parallel to the @@ -357,41 +370,41 @@ public class HwmfGraphics { // needs to be remapped as: //dxNormed[0] = 13 textString.get(0) = U+30D7 //dxNormed[1] = 14 textString.get(1) = U+30ED - if (textString.length() != text.length) { - int codePoints = textString.codePointCount(0, textString.length()); - dxNormed = new int[codePoints]; + + final List dxNormed; + if (textString.length() == text.length) { + dxNormed = new ArrayList<>(dx); + } else { + dxNormed = new ArrayList<>(dx.size()); int dxPosition = 0; + int[] chars = {0}; for (int offset = 0; offset < textString.length(); ) { - dxNormed[offset] = dx[dxPosition]; - int[] chars = new int[1]; - int cp = textString.codePointAt(offset); - chars[0] = cp; + dxNormed.add(dx.get(dxPosition)); + chars[0] = textString.codePointAt(offset); //now figure out how many bytes it takes to encode that //code point in the charset int byteLength = new String(chars, 0, chars.length).getBytes(charset).length; dxPosition += byteLength; - offset += Character.charCount(cp); + offset += Character.charCount(chars[0]); } } - for (int i = 0; i < dxNormed.length; i++) { + for (int i = 0; i < dxNormed.size(); i++) { addAttributes(as, font); // Tracking works as a prefix/advance space on characters whereas // dx[...] is the complete width of the current char // therefore we need to add the additional/suffix width to the next char - if (i < dxNormed.length - 1) { - as.addAttribute(TextAttribute.TRACKING, (dxNormed[i] - fontW) / fontH, i + 1, i + 2); - } + as.addAttribute(TextAttribute.TRACKING, (dxNormed.get(i) - fontW) / fontH, i + 1, i + 2); } } double angle = Math.toRadians(-font.getEscapement()/10.); - - + final AffineTransform at = graphicsCtx.getTransform(); try { - graphicsCtx.translate(bounds.getX(), bounds.getY()+fontH); + graphicsCtx.translate(bounds.getX(), bounds.getY()); graphicsCtx.rotate(angle); + graphicsCtx.translate(0, fontH); if (getProperties().getBkMode() == HwmfBkMode.OPAQUE) { // TODO: validate bounds graphicsCtx.setBackground(getProperties().getBackgroundColor().getColor()); diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfColorRef.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfColorRef.java index 12204d61ec..14ebc874d5 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfColorRef.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfColorRef.java @@ -19,6 +19,7 @@ package org.apache.poi.hwmf.record; import java.awt.Color; import java.io.IOException; +import java.util.Locale; import org.apache.poi.util.LittleEndianConsts; import org.apache.poi.util.LittleEndianInputStream; @@ -72,6 +73,6 @@ public class HwmfColorRef implements Cloneable { @Override public String toString() { - return String.format("%#8X", colorRef.getRGB()); + return String.format(Locale.ROOT, "%#08X", colorRef.getRGB()&0xFFFFFF); } } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java index b774549073..e66678389d 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java @@ -554,6 +554,14 @@ public class HwmfMisc { p.setPenColor(colorRef); p.setPenWidth(dimension.getWidth()); } + + @Override + public String toString() { + return + "{ penStyle: "+penStyle+ + ", dimension: { width: "+dimension.getWidth()+", height: "+dimension.getHeight()+" }"+ + ", colorRef: "+colorRef+"}"; + } } /** @@ -634,5 +642,13 @@ public class HwmfMisc { p.setBrushColor(colorRef); p.setBrushHatch(brushHatch); } + + @Override + public String toString() { + return + "{ brushStyle: '"+brushStyle+"'"+ + ", colorRef: "+colorRef+ + ", brushHatch: '"+brushHatch+"' }"; + } } } \ No newline at end of file diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPenStyle.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPenStyle.java index 0f79a03d9a..5366b3fb24 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPenStyle.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPenStyle.java @@ -195,4 +195,15 @@ public class HwmfPenStyle implements Cloneable { throw new InternalError(); } } + + @Override + public String toString() { + return + "{ lineCap: '"+getLineCap()+"'"+ + ", lineDash: '"+getLineDash()+"'"+ + ", lineJoin: '"+getLineJoin()+"'"+ + (isAlternateDash()?", alternateDash: true ":"")+ + (isGeometric()?", geometric: true ":"")+ + "}"; + } } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java index e677f9a92f..cb2cbd7560 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java @@ -28,6 +28,8 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; import org.apache.poi.hwmf.draw.HwmfDrawProperties; import org.apache.poi.hwmf.draw.HwmfGraphics; @@ -334,7 +336,7 @@ public class HwmfText { * character cell i and character cell i + 1. If this field is present, there MUST be the same * number of values as there are characters in the string. */ - private int dx[]; + protected final List dx = new ArrayList<>(); public WmfExtTextOut() { this(new WmfExtTextOutOptions()); @@ -380,9 +382,8 @@ public class HwmfText { logger.log(POILogger.WARN, "META_EXTTEXTOUT tracking info doesn't cover all characters"); } - dx = new int[stringLength]; for (int i=0; i -1 && lastY != reference.getY()) { sb.append("\n"); lastX = -1; @@ -204,7 +206,7 @@ public class HemfPictureTest { for (HemfRecord record : pic) { if (record.getEmfRecordType().equals(HemfRecordType.exttextoutw)) { HemfText.EmfExtTextOutW extTextOutW = (HemfText.EmfExtTextOutW) record; - Point2D reference = extTextOutW.getTextObject().getReference(); + Point2D reference = extTextOutW.getReference(); if (lastY > -1 && lastY != reference.getY()) { sb.append("\n"); lastX = -1; From 93b7cb0bc70ac51739b9c01172fce52965c685d0 Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Wed, 26 Sep 2018 22:51:03 +0000 Subject: [PATCH 08/21] #60656 - Support export file that contains emf and render it correctly git-svn-id: https://svn.apache.org/repos/asf/poi/branches/hemf@1842059 13f79535-47bb-0310-9956-ffa450edef68 --- .../src/org/apache/poi/hwmf/draw/HwmfGraphics.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java index c5873e02a6..23ccd1c70f 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java @@ -351,7 +351,7 @@ public class HwmfGraphics { } } - String textString = new String(text, charset); + String textString = new String(text, charset).trim(); AttributedString as = new AttributedString(textString); if (dx == null || dx.isEmpty()) { addAttributes(as, font); @@ -388,7 +388,9 @@ public class HwmfGraphics { offset += Character.charCount(chars[0]); } } - for (int i = 0; i < dxNormed.size(); i++) { + + int cps = textString.codePointCount(0, textString.length()); + for (int i = 0; i < Math.min(dxNormed.size(),cps-1); i++) { addAttributes(as, font); // Tracking works as a prefix/advance space on characters whereas // dx[...] is the complete width of the current char From 2b12e29c92bc2751b344e8f2d86347d4d55910c8 Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Sat, 6 Oct 2018 18:23:59 +0000 Subject: [PATCH 09/21] #60656 - Support export file that contains emf and render it correctly git-svn-id: https://svn.apache.org/repos/asf/poi/branches/hemf@1843025 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/poi/hemf/record/emf/HemfFill.java | 144 +++++++++++++----- .../poi/hemf/record/emf/HemfRecordType.java | 2 +- .../org/apache/poi/hwmf/record/HwmfFill.java | 7 +- 3 files changed, 112 insertions(+), 41 deletions(-) diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java index 5dca14537f..f1726b7e92 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java @@ -19,7 +19,6 @@ 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.HemfDraw.readRectL; -import static org.apache.poi.hemf.record.emf.HemfRecordIterator.HEADER_SIZE; import java.awt.geom.AffineTransform; import java.awt.geom.Area; @@ -114,7 +113,7 @@ public class HemfFill { * optionally in combination with a brush pattern, according to a specified raster operation, stretching or * compressing the output to fit the dimensions of the destination, if necessary. */ - public static class EmfStretchBlt extends HwmfFill.WmfBitBlt implements HemfRecord { + public static class EmfStretchBlt extends HwmfFill.WmfBitBlt implements HemfRecord, HemfBounded { protected final Rectangle2D bounds = new Rectangle2D.Double(); /** An XForm object that specifies a world-space to page-space transform to apply to the source bitmap. */ @@ -129,11 +128,7 @@ public class HemfFill { */ protected int usageSrc; - /** The source bitmap header. */ - protected byte[] bmiSrc; - - /** The source bitmap bits. */ - protected byte[] bitsSrc; + protected final HwmfBitmapDib bitmap = new HwmfBitmapDib(); @Override public HemfRecordType getEmfRecordType() { @@ -142,6 +137,8 @@ public class HemfFill { @Override public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + int startIdx = leis.getReadIndex(); + long size = readRectL(leis, bounds); size += readBounds2(leis, this.dstBounds); @@ -190,33 +187,103 @@ public class HemfFill { srcBounds.setRect(srcPnt.getX(), srcPnt.getY(), srcWidth, srcHeight); } - // size + type and size field - final int undefinedSpace1 = (int)(offBmiSrc - size - HEADER_SIZE); - assert(undefinedSpace1 >= 0); - leis.skipFully(undefinedSpace1); - size += undefinedSpace1; - - bmiSrc = IOUtils.safelyAllocate(cbBmiSrc, MAX_RECORD_LENGTH); - leis.readFully(bmiSrc); - size += cbBmiSrc; - - final int undefinedSpace2 = (int)(offBitsSrc - size - HEADER_SIZE); - assert(undefinedSpace2 >= 0); - leis.skipFully(undefinedSpace2); - size += undefinedSpace2; - - bitsSrc = IOUtils.safelyAllocate(cbBitsSrc, MAX_RECORD_LENGTH); - leis.readFully(bitsSrc); - size += cbBitsSrc; + size += readBitmap(leis, bitmap, startIdx, offBmiSrc, cbBmiSrc, offBitsSrc, cbBitsSrc); return size; } + @Override + public Rectangle2D getRecordBounds() { + return bounds; + } + + @Override + public Rectangle2D getShapeBounds(HemfGraphics ctx) { + return dstBounds; + } + protected boolean srcEqualsDstDimension() { return false; } } + /** + * The EMR_STRETCHDIBITS record specifies a block transfer of pixels from a source bitmap to a + * destination rectangle, optionally in combination with a brush pattern, according to a specified raster + * operation, stretching or compressing the output to fit the dimensions of the destination, if necessary. + */ + public static class EmfStretchDiBits extends HwmfFill.WmfStretchDib implements HemfRecord, HemfBounded { + protected final Rectangle2D bounds = new Rectangle2D.Double(); + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.stretchDiBits; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + final int startIdx = leis.getReadIndex(); + + long size = readRectL(leis, bounds); + + // A 32-bit signed integer that specifies the logical x-coordinate of the upper-left + // corner of the destination rectangle. + int xDest = leis.readInt(); + int yDest = leis.readInt(); + size += 2*LittleEndianConsts.INT_SIZE; + + size += readBounds2(leis, srcBounds); + + // A 32-bit unsigned integer that specifies the offset, in bytes from the start + // of this record to the source bitmap header. + int offBmiSrc = (int)leis.readUInt(); + + // A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap header. + int cbBmiSrc = (int)leis.readUInt(); + + // A 32-bit unsigned integer that specifies the offset, in bytes, from the + // start of this record to the source bitmap bits. + int offBitsSrc = (int)leis.readUInt(); + + // A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap bits. + int cbBitsSrc = (int)leis.readUInt(); + + // A 32-bit unsigned integer that specifies how to interpret values in the color table + // in the source bitmap header. This value MUST be in the DIBColors enumeration + colorUsage = ColorUsage.valueOf(leis.readInt()); + + // A 32-bit unsigned integer that specifies a raster operation code. + // These codes define how the color data of the source rectangle is to be combined with the color data + // of the destination rectangle and optionally a brush pattern, to achieve the final color. + // The value MUST be in the WMF Ternary Raster Operation enumeration + rasterOperation = HwmfTernaryRasterOp.valueOf(leis.readInt()); + + // A 32-bit signed integer that specifies the logical width of the destination rectangle. + int cxDest = leis.readInt(); + + // A 32-bit signed integer that specifies the logical height of the destination rectangle. + int cyDest = leis.readInt(); + + dstBounds.setRect(xDest, yDest, cxDest, cyDest); + + size += 8*LittleEndianConsts.INT_SIZE; + + size += readBitmap(leis, dib, startIdx, offBmiSrc, cbBmiSrc, offBitsSrc, cbBitsSrc); + + return size; + } + + @Override + public Rectangle2D getRecordBounds() { + return bounds; + } + + @Override + public Rectangle2D getShapeBounds(HemfGraphics ctx) { + return dstBounds; + } + } + /** * The EMR_BITBLT record specifies a block transfer of pixels from a source bitmap to a destination rectangle, * optionally in combination with a brush pattern, according to a specified raster operation. @@ -235,7 +302,7 @@ public class HemfFill { /** The EMR_FRAMERGN record draws a border around the specified region using the specified brush. */ - public static class EmfFrameRgn extends HwmfDraw.WmfFrameRegion implements HemfRecord { + public static class EmfFrameRgn extends HwmfDraw.WmfFrameRegion implements HemfRecord, HemfBounded { private final Rectangle2D bounds = new Rectangle2D.Double(); private final List rgnRects = new ArrayList<>(); @@ -263,18 +330,23 @@ public class HemfFill { @Override public void draw(HwmfGraphics ctx) { ctx.applyObjectTableEntry(brushIndex); + ctx.fill(getShape()); + } - Area frame = new Area(); - for (Rectangle2D rct : rgnRects) { - frame.add(new Area(rct)); - } - Rectangle2D frameBounds = frame.getBounds2D(); - AffineTransform at = new AffineTransform(); - at.translate(bounds.getX()-frameBounds.getX(), bounds.getY()-frameBounds.getY()); - at.scale(bounds.getWidth()/frameBounds.getWidth(), bounds.getHeight()/frameBounds.getHeight()); - frame.transform(at); + @Override + public Rectangle2D getRecordBounds() { + return bounds; + } - ctx.fill(frame); + @Override + public Rectangle2D getShapeBounds(HemfGraphics ctx) { + return getShape().getBounds2D(); + } + + protected Area getShape() { + final Area frame = new Area(); + rgnRects.forEach((rct) -> frame.add(new Area(rct))); + return frame; } } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java index f6584443a1..addb1d63a4 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java @@ -104,7 +104,7 @@ public enum HemfRecordType { maskblt(0x0000004E, UnimplementedHemfRecord::new), plgblt(0x0000004F, UnimplementedHemfRecord::new), setDiBitsToDevice(0x00000050, HemfFill.EmfSetDiBitsToDevice::new), - stretchdibits(0x00000051, UnimplementedHemfRecord::new), + stretchDiBits(0x00000051, HemfFill.EmfStretchDiBits::new), extCreateFontIndirectW(0x00000052, HemfText.ExtCreateFontIndirectW::new), exttextouta(0x00000053, HemfText.EmfExtTextOutA::new), exttextoutw(0x00000054, HemfText.EmfExtTextOutW::new), diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java index 26aa55696b..3878e36ae4 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java @@ -430,13 +430,13 @@ public class HwmfFill { * the playback device context, and the destination pixels are to be combined to * form the new image. */ - private HwmfTernaryRasterOp rasterOperation; + protected HwmfTernaryRasterOp rasterOperation; /** * A 16-bit unsigned integer that defines whether the Colors field of the * DIB contains explicit RGB values or indexes into a palette. */ - private ColorUsage colorUsage; + protected ColorUsage colorUsage; /** the source rectangle. */ protected final Rectangle2D srcBounds = new Rectangle2D.Double(); @@ -448,7 +448,7 @@ public class HwmfFill { * A variable-sized DeviceIndependentBitmap Object (section 2.2.2.9) that is the * source of the color data. */ - private HwmfBitmapDib dib; + protected final HwmfBitmapDib dib = new HwmfBitmapDib(); @Override public HwmfRecordType getWmfRecordType() { @@ -471,7 +471,6 @@ public class HwmfFill { size += readBounds2(leis, srcBounds); size += readBounds2(leis, dstBounds); - dib = new HwmfBitmapDib(); size += dib.init(leis, (int)(recordSize-6-size)); return size; From 616f8127cf5caf47c6167fe4afc86df83555bf67 Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Tue, 9 Oct 2018 20:47:31 +0000 Subject: [PATCH 10/21] Bug 60713 - (S)XSSFWorkbook/POIXMLDocument.write(OutputStream) closes the OutputStream regression - when reading the input from a zip stream git-svn-id: https://svn.apache.org/repos/asf/poi/branches/hemf@1843341 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/poi/openxml4j/util/ZipInputStreamZipEntrySource.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ooxml/java/org/apache/poi/openxml4j/util/ZipInputStreamZipEntrySource.java b/src/ooxml/java/org/apache/poi/openxml4j/util/ZipInputStreamZipEntrySource.java index e185239ff1..048b34e251 100644 --- a/src/ooxml/java/org/apache/poi/openxml4j/util/ZipInputStreamZipEntrySource.java +++ b/src/ooxml/java/org/apache/poi/openxml4j/util/ZipInputStreamZipEntrySource.java @@ -50,7 +50,6 @@ public class ZipInputStreamZipEntrySource implements ZipEntrySource { } zipEntries.put(zipEntry.getName(), new ZipArchiveFakeEntry(zipEntry, inp)); } - inp.close(); } @Override From f927e551cc6001f8d321770de374db9312dd6fd8 Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Tue, 9 Oct 2018 20:51:14 +0000 Subject: [PATCH 11/21] #60656 - Support export file that contains emf and render it correctly git-svn-id: https://svn.apache.org/repos/asf/poi/branches/hemf@1843342 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/poi/hemf/draw/HemfGraphics.java | 135 +++++++++++++++++- .../poi/hemf/record/emf/HemfComment.java | 2 +- .../apache/poi/hemf/record/emf/HemfDraw.java | 129 +---------------- .../apache/poi/hemf/record/emf/HemfFill.java | 56 +++++--- .../apache/poi/hemf/record/emf/HemfMisc.java | 65 ++++++++- .../poi/hemf/record/emf/HemfRecordType.java | 2 +- .../apache/poi/hemf/record/emf/HemfText.java | 25 +--- .../poi/hemf/usermodel/HemfPicture.java | 4 +- .../apache/poi/hwmf/draw/HwmfGraphics.java | 58 ++++---- .../org/apache/poi/hwmf/record/HwmfFill.java | 9 ++ .../org/apache/poi/hwmf/record/HwmfMisc.java | 6 +- .../org/apache/poi/hwmf/record/HwmfText.java | 55 ++++--- .../poi/hemf/usermodel/HemfPictureTest.java | 87 ++++++----- 13 files changed, 365 insertions(+), 268 deletions(-) diff --git a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java index 40effa0dde..2cc1f0889d 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java +++ b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java @@ -17,6 +17,10 @@ package org.apache.poi.hemf.draw; +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.Graphics2D; import java.awt.geom.AffineTransform; import java.awt.geom.Path2D; @@ -29,11 +33,20 @@ import java.util.function.Consumer; import org.apache.poi.hemf.record.emf.HemfBounded; import org.apache.poi.hemf.record.emf.HemfRecord; import org.apache.poi.hwmf.draw.HwmfGraphics; +import org.apache.poi.hwmf.record.HwmfColorRef; import org.apache.poi.hwmf.record.HwmfObjectTableEntry; +import org.apache.poi.hwmf.record.HwmfPenStyle; import org.apache.poi.util.Internal; public class HemfGraphics extends HwmfGraphics { + private static final HwmfColorRef WHITE = new HwmfColorRef(Color.WHITE); + private static final HwmfColorRef LTGRAY = new HwmfColorRef(new Color(0x00C0C0C0)); + private static final HwmfColorRef GRAY = new HwmfColorRef(new Color(0x00808080)); + private static final HwmfColorRef DKGRAY = new HwmfColorRef(new Color(0x00404040)); + private static final HwmfColorRef BLACK = new HwmfColorRef(Color.BLACK); + + private final Deque transforms = new ArrayDeque<>(); public HemfGraphics(Graphics2D graphicsCtx, Rectangle2D bbox) { @@ -52,8 +65,7 @@ public class HemfGraphics extends HwmfGraphics { @Override public void saveProperties() { - assert(prop != null); - propStack.add(prop); + propStack.add(getProperties()); prop = new HemfDrawProperties((HemfDrawProperties)prop); } @@ -138,6 +150,125 @@ public class HemfGraphics extends HwmfGraphics { } } + @Override + public void applyObjectTableEntry(int index) { + if ((index & 0x80000000) != 0) { + selectStockObject(index); + } else { + super.applyObjectTableEntry(index); + } + } + + private void selectStockObject(int objectIndex) { + final HemfDrawProperties prop = getProperties(); + switch (objectIndex) { + case 0x80000000: + // WHITE_BRUSH - A white, solid-color brush + // BrushStyle: BS_SOLID + // Color: 0x00FFFFFF + prop.setBrushColor(WHITE); + prop.setBrushStyle(BS_SOLID); + break; + case 0x80000001: + // LTGRAY_BRUSH - A light gray, solid-color brush + // BrushStyle: BS_SOLID + // Color: 0x00C0C0C0 + prop.setBrushColor(LTGRAY); + prop.setBrushStyle(BS_SOLID); + break; + case 0x80000002: + // GRAY_BRUSH - A gray, solid-color brush + // BrushStyle: BS_SOLID + // Color: 0x00808080 + prop.setBrushColor(GRAY); + prop.setBrushStyle(BS_SOLID); + break; + case 0x80000003: + // DKGRAY_BRUSH - A dark gray, solid color brush + // BrushStyle: BS_SOLID + // Color: 0x00404040 + prop.setBrushColor(DKGRAY); + prop.setBrushStyle(BS_SOLID); + break; + case 0x80000004: + // BLACK_BRUSH - A black, solid color brush + // BrushStyle: BS_SOLID + // Color: 0x00000000 + prop.setBrushColor(BLACK); + prop.setBrushStyle(BS_SOLID); + break; + case 0x80000005: + // NULL_BRUSH - A null brush + // BrushStyle: BS_NULL + prop.setBrushStyle(BS_NULL); + break; + case 0x80000006: + // WHITE_PEN - A white, solid-color pen + // PenStyle: PS_COSMETIC + PS_SOLID + // ColorRef: 0x00FFFFFF + prop.setPenStyle(HwmfPenStyle.valueOf(0)); + prop.setPenWidth(1); + prop.setPenColor(WHITE); + break; + case 0x80000007: + // BLACK_PEN - A black, solid-color pen + // PenStyle: PS_COSMETIC + PS_SOLID + // ColorRef: 0x00000000 + prop.setPenStyle(HwmfPenStyle.valueOf(0)); + prop.setPenWidth(1); + prop.setPenColor(BLACK); + break; + case 0x80000008: + // NULL_PEN - A null pen + // PenStyle: PS_NULL + prop.setPenStyle(HwmfPenStyle.valueOf(HwmfPenStyle.HwmfLineDash.NULL.wmfFlag)); + break; + case 0x8000000A: + // OEM_FIXED_FONT - A fixed-width, OEM character set + // Charset: OEM_CHARSET + // PitchAndFamily: FF_DONTCARE + FIXED_PITCH + break; + case 0x8000000B: + // ANSI_FIXED_FONT - A fixed-width font + // Charset: ANSI_CHARSET + // PitchAndFamily: FF_DONTCARE + FIXED_PITCH + break; + case 0x8000000C: + // ANSI_VAR_FONT - A variable-width font + // Charset: ANSI_CHARSET + // PitchAndFamily: FF_DONTCARE + VARIABLE_PITCH + break; + case 0x8000000D: + // SYSTEM_FONT - A font that is guaranteed to be available in the operating system + break; + case 0x8000000E: + // DEVICE_DEFAULT_FONT + // The default font that is provided by the graphics device driver for the current output device + break; + case 0x8000000F: + // DEFAULT_PALETTE + // The default palette that is defined for the current output device. + break; + case 0x80000010: + // SYSTEM_FIXED_FONT + // A fixed-width font that is guaranteed to be available in the operating system. + break; + case 0x80000011: + // DEFAULT_GUI_FONT + // The default font that is used for user interface objects such as menus and dialog boxes. + break; + case 0x80000012: + // DC_BRUSH + // The solid-color brush that is currently selected in the playback device context. + break; + case 0x80000013: + // DC_PEN + // The solid-color pen that is currently selected in the playback device context. + break; + } + } + + /** saves the current affine transform on the stack */ private void saveTransform() { diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java index 1752e99e1a..c24562a047 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java @@ -40,7 +40,7 @@ import org.apache.poi.util.RecordFormatException; */ @Internal public class HemfComment { - private static final int MAX_RECORD_LENGTH = 1_000_000; + private static final int MAX_RECORD_LENGTH = 2_000_000; public enum HemfCommentRecordType { emfGeneric(-1, EmfCommentDataGeneric::new, false), diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java index f9c6e975a2..0cffdbc4b5 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java @@ -47,12 +47,6 @@ public class HemfDraw { */ public static class EmfSelectObject extends WmfSelectObject implements HemfRecord { - private static final HwmfColorRef WHITE = new HwmfColorRef(Color.WHITE); - private static final HwmfColorRef LTGRAY = new HwmfColorRef(new Color(0x00C0C0C0)); - private static final HwmfColorRef GRAY = new HwmfColorRef(new Color(0x00808080)); - private static final HwmfColorRef DKGRAY = new HwmfColorRef(new Color(0x00404040)); - private static final HwmfColorRef BLACK = new HwmfColorRef(Color.BLACK); - private static final String[] STOCK_IDS = { "0x80000000 /* WHITE_BRUSH */", "0x80000001 /* LTGRAY_BRUSH */", @@ -88,124 +82,6 @@ public class HemfDraw { return LittleEndianConsts.INT_SIZE; } - @Override - public void draw(HemfGraphics ctx) { - if ((objectIndex & 0x80000000) != 0) { - selectStockObject(ctx); - } else { - super.draw(ctx); - } - } - - private void selectStockObject(HemfGraphics ctx) { - final HemfDrawProperties prop = ctx.getProperties(); - switch (objectIndex) { - case 0x80000000: - // WHITE_BRUSH - A white, solid-color brush - // BrushStyle: BS_SOLID - // Color: 0x00FFFFFF - prop.setBrushColor(WHITE); - prop.setBrushStyle(BS_SOLID); - break; - case 0x80000001: - // LTGRAY_BRUSH - A light gray, solid-color brush - // BrushStyle: BS_SOLID - // Color: 0x00C0C0C0 - prop.setBrushColor(LTGRAY); - prop.setBrushStyle(BS_SOLID); - break; - case 0x80000002: - // GRAY_BRUSH - A gray, solid-color brush - // BrushStyle: BS_SOLID - // Color: 0x00808080 - prop.setBrushColor(GRAY); - prop.setBrushStyle(BS_SOLID); - break; - case 0x80000003: - // DKGRAY_BRUSH - A dark gray, solid color brush - // BrushStyle: BS_SOLID - // Color: 0x00404040 - prop.setBrushColor(DKGRAY); - prop.setBrushStyle(BS_SOLID); - break; - case 0x80000004: - // BLACK_BRUSH - A black, solid color brush - // BrushStyle: BS_SOLID - // Color: 0x00000000 - prop.setBrushColor(BLACK); - prop.setBrushStyle(BS_SOLID); - break; - case 0x80000005: - // NULL_BRUSH - A null brush - // BrushStyle: BS_NULL - prop.setBrushStyle(BS_NULL); - break; - case 0x80000006: - // WHITE_PEN - A white, solid-color pen - // PenStyle: PS_COSMETIC + PS_SOLID - // ColorRef: 0x00FFFFFF - prop.setPenStyle(HwmfPenStyle.valueOf(0)); - prop.setPenWidth(1); - prop.setPenColor(WHITE); - break; - case 0x80000007: - // BLACK_PEN - A black, solid-color pen - // PenStyle: PS_COSMETIC + PS_SOLID - // ColorRef: 0x00000000 - prop.setPenStyle(HwmfPenStyle.valueOf(0)); - prop.setPenWidth(1); - prop.setPenColor(BLACK); - break; - case 0x80000008: - // NULL_PEN - A null pen - // PenStyle: PS_NULL - prop.setPenStyle(HwmfPenStyle.valueOf(HwmfPenStyle.HwmfLineDash.NULL.wmfFlag)); - break; - case 0x8000000A: - // OEM_FIXED_FONT - A fixed-width, OEM character set - // Charset: OEM_CHARSET - // PitchAndFamily: FF_DONTCARE + FIXED_PITCH - break; - case 0x8000000B: - // ANSI_FIXED_FONT - A fixed-width font - // Charset: ANSI_CHARSET - // PitchAndFamily: FF_DONTCARE + FIXED_PITCH - break; - case 0x8000000C: - // ANSI_VAR_FONT - A variable-width font - // Charset: ANSI_CHARSET - // PitchAndFamily: FF_DONTCARE + VARIABLE_PITCH - break; - case 0x8000000D: - // SYSTEM_FONT - A font that is guaranteed to be available in the operating system - break; - case 0x8000000E: - // DEVICE_DEFAULT_FONT - // The default font that is provided by the graphics device driver for the current output device - break; - case 0x8000000F: - // DEFAULT_PALETTE - // The default palette that is defined for the current output device. - break; - case 0x80000010: - // SYSTEM_FIXED_FONT - // A fixed-width font that is guaranteed to be available in the operating system. - break; - case 0x80000011: - // DEFAULT_GUI_FONT - // The default font that is used for user interface objects such as menus and dialog boxes. - break; - case 0x80000012: - // DC_BRUSH - // The solid-color brush that is currently selected in the playback device context. - break; - case 0x80000013: - // DC_PEN - // The solid-color pen that is currently selected in the playback device context. - break; - } - } - @Override public String toString() { return "{ index: "+ @@ -1166,6 +1042,11 @@ public class HemfDraw { private static void polyTo(final HemfGraphics ctx, final Path2D poly) { final PathIterator pi = poly.getPathIterator(null); + if (pi.isDone()) { + // ignore empty polys + return; + } + // ignore dummy start point (moveTo) pi.next(); assert (!pi.isDone()); diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java index f1726b7e92..ab632e28c8 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java @@ -19,6 +19,7 @@ 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.HemfDraw.readRectL; +import static org.apache.poi.hemf.record.emf.HemfRecordIterator.HEADER_SIZE; import java.awt.geom.AffineTransform; import java.awt.geom.Area; @@ -100,7 +101,7 @@ public class HemfFill { @Override public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { long size = readPointL(leis, start); - size = colorRef.init(leis); + size += colorRef.init(leis); // A 32-bit unsigned integer that specifies how to use the Color value to determine the area for // the flood fill operation. The value MUST be in the FloodFill enumeration mode = (int)leis.readUInt(); @@ -117,7 +118,7 @@ public class HemfFill { protected final Rectangle2D bounds = new Rectangle2D.Double(); /** An XForm object that specifies a world-space to page-space transform to apply to the source bitmap. */ - protected final byte[] xformSrc = new byte[24]; + protected final AffineTransform xFormSrc = new AffineTransform(); /** A WMF ColorRef object that specifies the background color of the source bitmap. */ protected final HwmfColorRef bkColorSrc = new HwmfColorRef(); @@ -155,8 +156,7 @@ public class HemfFill { final Point2D srcPnt = new Point2D.Double(); size += readPointL(leis, srcPnt); - leis.readFully(xformSrc); - size += 24; + size += readXForm(leis, xFormSrc); size += bkColorSrc.init(leis); @@ -168,6 +168,10 @@ public class HemfFill { // A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap header. final int cbBmiSrc = (int)leis.readUInt(); + size += 3*LittleEndianConsts.INT_SIZE; + if (size <= recordSize) { + return size; + } // A 32-bit unsigned integer that specifies the offset, in bytes, from the // start of this record to the source bitmap bits in the BitmapBuffer field. @@ -176,7 +180,7 @@ public class HemfFill { // A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap bits. final int cbBitsSrc = (int)leis.readUInt(); - size += 5*LittleEndianConsts.INT_SIZE; + size += 2*LittleEndianConsts.INT_SIZE; if (srcEqualsDstDimension()) { srcBounds.setRect(srcPnt.getX(), srcPnt.getY(), dstBounds.getWidth(), dstBounds.getHeight()); @@ -205,6 +209,16 @@ public class HemfFill { protected boolean srcEqualsDstDimension() { return false; } + + @Override + public String toString() { + return + "{ bounds: { x: "+bounds.getX()+", y: "+bounds.getY()+", w: "+bounds.getWidth()+", h: "+bounds.getHeight()+"}"+ + ", xFormSrc: { scaleX: "+xFormSrc.getScaleX()+", shearX: "+xFormSrc.getShearX()+", transX: "+xFormSrc.getTranslateX()+", scaleY: "+xFormSrc.getScaleY()+", shearY: "+xFormSrc.getShearY()+", transY: "+xFormSrc.getTranslateY()+" }"+ + ", bkColorSrc: "+bkColorSrc+ + ", usageSrc: "+usageSrc+", " + + super.toString().substring(1); + } } /** @@ -589,18 +603,24 @@ 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) + final int startIdx, final int offBmi, final int cbBmi, final int offBits, int cbBits) throws IOException { - final int offCurr = leis.getReadIndex()-startIdx; - final int undefinedSpace1 = offBmiSrc-offCurr; - assert(undefinedSpace1 >= 0); + if (offBmi == 0) { + return 0; + } - final int undefinedSpace2 = offBitsSrc-offCurr-cbBmiSrc-undefinedSpace1; + final int offCurr = leis.getReadIndex()-(startIdx-HEADER_SIZE); + final int undefinedSpace1 = offBmi-offCurr; + if (undefinedSpace1 < 0) { + return 0; + } + + final int undefinedSpace2 = offBits-offCurr-cbBmi-undefinedSpace1; assert(undefinedSpace2 >= 0); leis.skipFully(undefinedSpace1); - if (cbBmiSrc == 0 || cbBitsSrc == 0) { + if (cbBmi == 0 || cbBits == 0) { return undefinedSpace1; } @@ -608,18 +628,18 @@ public class HemfFill { if (undefinedSpace2 == 0) { leisDib = leis; } else { - final ByteArrayOutputStream bos = new ByteArrayOutputStream(cbBmiSrc+cbBitsSrc); - final long cbBmiSrcAct = IOUtils.copy(leis, bos, cbBmiSrc); - assert (cbBmiSrcAct == cbBmiSrc); + final ByteArrayOutputStream bos = new ByteArrayOutputStream(cbBmi+cbBits); + final long cbBmiSrcAct = IOUtils.copy(leis, bos, cbBmi); + assert (cbBmiSrcAct == cbBmi); leis.skipFully(undefinedSpace2); - final long cbBitsSrcAct = IOUtils.copy(leis, bos, cbBitsSrc); - assert (cbBitsSrcAct == cbBitsSrc); + final long cbBitsSrcAct = IOUtils.copy(leis, bos, cbBits); + assert (cbBitsSrcAct == cbBits); leisDib = new LittleEndianInputStream(new ByteArrayInputStream(bos.toByteArray())); } - final int dibSize = cbBmiSrc+cbBitsSrc; + final int dibSize = cbBmi+cbBits; final int dibSizeAct = bitmap.init(leisDib, dibSize); assert (dibSizeAct <= dibSize); - return undefinedSpace1 + cbBmiSrc + undefinedSpace2 + cbBitsSrc; + return undefinedSpace1 + cbBmi + undefinedSpace2 + cbBits; } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java index d128084c6f..dd444f7c0e 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java @@ -33,6 +33,7 @@ import org.apache.poi.hwmf.record.HwmfBinaryRasterOp; 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.HwmfFill; import org.apache.poi.hwmf.record.HwmfHatchStyle; import org.apache.poi.hwmf.record.HwmfMapMode; import org.apache.poi.hwmf.record.HwmfMisc; @@ -65,8 +66,8 @@ public class HemfMisc { int size = 2 * LittleEndianConsts.INT_SIZE; - if (offPalEntries > 0) { - int undefinedSpace1 = (int) (offPalEntries - size - HEADER_SIZE); + if (nPalEntries > 0 && offPalEntries > 0) { + int undefinedSpace1 = (int) (offPalEntries - (size + HEADER_SIZE)); assert (undefinedSpace1 >= 0); leis.skipFully(undefinedSpace1); size += undefinedSpace1; @@ -283,6 +284,59 @@ public class HemfMisc { } } + /** + * The EMR_CREATEDIBPATTERNBRUSHPT record defines a pattern brush for graphics operations. + * The pattern is specified by a DIB. + */ + public static class EmfCreateDibPatternBrushPt extends HwmfMisc.WmfDibCreatePatternBrush implements HemfRecord { + protected int brushIdx; + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.createDibPatternBrushPt; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + final int startIdx = leis.getReadIndex(); + + style = HwmfBrushStyle.BS_DIBPATTERNPT; + + // A 32-bit unsigned integer that specifies the index of the pattern brush + // object in the EMF Object Table + brushIdx = (int)leis.readUInt(); + + // A 32-bit unsigned integer that specifies how to interpret values in the color + // table in the DIB header. This value MUST be in the DIBColors enumeration + colorUsage = HwmfFill.ColorUsage.valueOf((int)leis.readUInt()); + + // A 32-bit unsigned integer that specifies the offset from the start of this + // record to the DIB header. + final int offBmi = leis.readInt(); + + // A 32-bit unsigned integer that specifies the size of the DIB header. + final int cbBmi = leis.readInt(); + + // A 32-bit unsigned integer that specifies the offset from the start of this record to the DIB bits. + final int offBits = leis.readInt(); + + // A 32-bit unsigned integer that specifies the size of the DIB bits. + final int cbBits = leis.readInt(); + + int size = 6*LittleEndianConsts.INT_SIZE; + + patternDib = new HwmfBitmapDib(); + size += readBitmap(leis, patternDib, startIdx, offBmi, cbBmi, offBits, cbBits); + return size; + } + + @Override + public void draw(HemfGraphics ctx) { + ctx.addObjectTableEntry(this, brushIdx); + } + + } + /** * The EMR_DELETEOBJECT record deletes a graphics object, which is specified by its index * in the EMF Object Table @@ -519,5 +573,12 @@ public class HemfMisc { return size + LittleEndianConsts.INT_SIZE; } + + @Override + public String toString() { + return + "{ xForm: { scaleX: "+xForm.getScaleX()+", shearX: "+xForm.getShearX()+", transX: "+xForm.getTranslateX()+", scaleY: "+xForm.getScaleY()+", shearY: "+xForm.getShearY()+", transY: "+xForm.getTranslateY()+" }"+ + ", modifyWorldTransformMode: "+modifyWorldTransformMode+" }"; + } } } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java index addb1d63a4..35d09b18af 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java @@ -117,7 +117,7 @@ public enum HemfRecordType { polyPolygon16(0x0000005B, HemfDraw.EmfPolyPolygon16::new), polyDraw16(0x0000005C, HemfDraw.EmfPolyDraw16::new), createmonobrush16(0x0000005D, UnimplementedHemfRecord::new), - createdibpatternbrushpt(0x0000005E, UnimplementedHemfRecord::new), + createDibPatternBrushPt(0x0000005E, HemfMisc.EmfCreateDibPatternBrushPt::new), extCreatePen(0x0000005F, HemfMisc.EmfExtCreatePen::new), polytextouta(0x00000060, HemfText.PolyTextOutA::new), polytextoutw(0x00000061, HemfText.PolyTextOutW::new), diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java index d78b750aa3..92528c8993 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java @@ -113,7 +113,7 @@ public class HemfText { int offDx = (int)leis.readUInt(); size += LittleEndianConsts.INT_SIZE; - int undefinedSpace1 = (int)(offString - size - HEADER_SIZE); + int undefinedSpace1 = (int)(offString - (size + HEADER_SIZE)); assert (undefinedSpace1 >= 0); leis.skipFully(undefinedSpace1); size += undefinedSpace1; @@ -124,7 +124,7 @@ public class HemfText { dx.clear(); if (offDx > 0) { - int undefinedSpace2 = (int) (offDx - size - HEADER_SIZE); + int undefinedSpace2 = (int) (offDx - (size + HEADER_SIZE)); assert (undefinedSpace2 >= 0); leis.skipFully(undefinedSpace2); size += undefinedSpace2; @@ -149,10 +149,6 @@ public class HemfText { return size; } - protected boolean isUnicode() { - return false; - } - /** * * To be implemented! We need to get the current character set @@ -185,21 +181,10 @@ public class HemfText { @Override public String toString() { - String text = ""; - try { - text = getText(isUnicode() ? Charsets.UTF_16LE : LocaleUtil.CHARSET_1252); - } catch (IOException ignored) { - } - return - "{ bounds: { x: "+bounds.getX()+ - ", y: "+bounds.getY()+ - ", w: "+bounds.getWidth()+ - ", h: "+bounds.getHeight()+ - "}, graphicsMode: '"+graphicsMode+"'"+ - ", scale: { w: "+scale.getWidth()+", h: "+scale.getHeight()+" }"+ - ", text: '"+text.replaceAll("\\p{Cntrl}",".")+"'"+ - "}"; + "{ graphicsMode: '"+graphicsMode+"'"+ + ", scale: { w: "+scale.getWidth()+", h: "+scale.getHeight()+" },"+ + super.toString().substring(1); } } diff --git a/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java b/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java index 3a68547823..77f34fd3bf 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java +++ b/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java @@ -108,11 +108,11 @@ public class HemfPicture implements Iterable { ctx.scale(graphicsBounds.getWidth()/emfBounds.getWidth(), graphicsBounds.getHeight()/emfBounds.getHeight()); ctx.translate(-emfBounds.getCenterX(), -emfBounds.getCenterY()); - - + int idx = 0; HemfGraphics g = new HemfGraphics(ctx, emfBounds); for (HemfRecord r : getRecords()) { g.draw(r); + idx++; } } finally { ctx.setTransform(at); diff --git a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java index 23ccd1c70f..7ac295e5ce 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java @@ -323,11 +323,7 @@ public class HwmfGraphics { } public void drawString(byte[] text, Rectangle2D bounds) { - drawString(text, bounds, null); - } - - public void drawString(byte[] text, Rectangle2D bounds, List dx) { - drawString(text, bounds, dx, false); + drawString(text, bounds, null, false); } public void drawString(byte[] text, Rectangle2D bounds, List dx, boolean isUnicode) { @@ -352,10 +348,12 @@ public class HwmfGraphics { } String textString = new String(text, charset).trim(); + if (textString.isEmpty()) { + return; + } AttributedString as = new AttributedString(textString); - if (dx == null || dx.isEmpty()) { - addAttributes(as, font); - } else { + addAttributes(as, font); + if (dx != null && !dx.isEmpty()) { //for multi-byte encodings (e.g. Shift_JIS), the byte length //might not equal the string length(). //The x information is stored in dx[], an array parallel to the @@ -371,31 +369,21 @@ public class HwmfGraphics { //dxNormed[0] = 13 textString.get(0) = U+30D7 //dxNormed[1] = 14 textString.get(1) = U+30ED - final List dxNormed; - if (textString.length() == text.length) { - dxNormed = new ArrayList<>(dx); - } else { - dxNormed = new ArrayList<>(dx.size()); - int dxPosition = 0; - int[] chars = {0}; - for (int offset = 0; offset < textString.length(); ) { - dxNormed.add(dx.get(dxPosition)); - chars[0] = textString.codePointAt(offset); - //now figure out how many bytes it takes to encode that - //code point in the charset - int byteLength = new String(chars, 0, chars.length).getBytes(charset).length; - dxPosition += byteLength; - offset += Character.charCount(chars[0]); + final int cps = textString.codePointCount(0, textString.length()); + final int unicodeSteps = Math.max(dx.size()/cps, 1); + int dxPosition = 0; + int beginIndex = 0; + int[] chars = {0}; + while (beginIndex < textString.length() && dxPosition < dx.size()) { + int endIndex = textString.offsetByCodePoints(beginIndex, 1); + if (beginIndex > 0) { + // Tracking works as a prefix/advance space on characters whereas + // dx[...] is the complete width of the current char + // therefore we need to add the additional/suffix width to the next char + as.addAttribute(TextAttribute.TRACKING, (float)((dx.get(dxPosition) - fontW) / fontH), beginIndex, endIndex); } - } - - int cps = textString.codePointCount(0, textString.length()); - for (int i = 0; i < Math.min(dxNormed.size(),cps-1); i++) { - addAttributes(as, font); - // Tracking works as a prefix/advance space on characters whereas - // dx[...] is the complete width of the current char - // therefore we need to add the additional/suffix width to the next char - as.addAttribute(TextAttribute.TRACKING, (dxNormed.get(i) - fontW) / fontH, i + 1, i + 2); + dxPosition += (isUnicode) ? unicodeSteps : (endIndex-beginIndex); + beginIndex = endIndex; } } @@ -413,7 +401,7 @@ public class HwmfGraphics { graphicsCtx.fill(new Rectangle2D.Double(0, 0, bounds.getWidth(), bounds.getHeight())); } graphicsCtx.setColor(getProperties().getTextColor().getColor()); - graphicsCtx.drawString(as.getIterator(), 0, 0); // (float)bounds.getX(), (float)bounds.getY()); + graphicsCtx.drawString(as.getIterator(), 0, 0); } finally { graphicsCtx.setTransform(at); } @@ -425,7 +413,9 @@ public class HwmfGraphics { as.addAttribute(TextAttribute.FAMILY, fontInfo.getTypeface()); as.addAttribute(TextAttribute.SIZE, getFontHeight(font)); - as.addAttribute(TextAttribute.STRIKETHROUGH, font.isStrikeOut()); + if (font.isStrikeOut()) { + as.addAttribute(TextAttribute.STRIKETHROUGH, TextAttribute.STRIKETHROUGH_ON); + } if (font.isUnderline()) { as.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON); } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java index 3878e36ae4..0d6f85e4d8 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java @@ -415,6 +415,15 @@ public class HwmfFill { public void draw(HwmfGraphics ctx) { } + + @Override + public String toString() { + return + "{ rasterOperation: '"+rasterOperation+"'"+ + ", srcBounds: { x: "+srcBounds.getX()+", y: "+srcBounds.getY()+", w: "+srcBounds.getWidth()+", h: "+srcBounds.getHeight()+" }"+ + ", dstBounds: { x: "+dstBounds.getX()+", y: "+dstBounds.getY()+", w: "+dstBounds.getWidth()+", h: "+dstBounds.getHeight()+" }"+ + "}"; + } } /** diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java index e66678389d..70e1d58aed 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java @@ -377,7 +377,7 @@ public class HwmfMisc { */ public static class WmfDibCreatePatternBrush implements HwmfRecord, HwmfImageRecord, HwmfObjectTableEntry { - private HwmfBrushStyle style; + protected HwmfBrushStyle style; /** * A 16-bit unsigned integer that defines whether the Colors field of a DIB @@ -388,9 +388,9 @@ public class HwmfMisc { * * If the Style field specified anything but BS_PATTERN, this field MUST be one of the ColorUsage values. */ - private ColorUsage colorUsage; + protected ColorUsage colorUsage; - private HwmfBitmapDib patternDib; + protected HwmfBitmapDib patternDib; private HwmfBitmap16 pattern16; @Override diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java index cb2cbd7560..5228d11d0b 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java @@ -31,6 +31,7 @@ import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; +import org.apache.commons.codec.Charsets; import org.apache.poi.hwmf.draw.HwmfDrawProperties; import org.apache.poi.hwmf.draw.HwmfGraphics; import org.apache.poi.hwmf.record.HwmfMisc.WmfSetMapMode; @@ -39,6 +40,7 @@ import org.apache.poi.util.BitFieldFactory; import org.apache.poi.util.IOUtils; import org.apache.poi.util.LittleEndianConsts; import org.apache.poi.util.LittleEndianInputStream; +import org.apache.poi.util.LocaleUtil; import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; import org.apache.poi.util.RecordFormatException; @@ -291,6 +293,10 @@ public class HwmfText { public boolean isClipped() { return ETO_CLIPPED.isSet(flag); } + + public boolean isYDisplaced() { + return ETO_PDY.isSet(flag); + } } /** @@ -393,37 +399,11 @@ public class HwmfText { @Override public void draw(HwmfGraphics ctx) { Rectangle2D bounds = new Rectangle2D.Double(reference.getX(), reference.getY(), 0, 0); - ctx.drawString(rawTextBytes, bounds, dx); + ctx.drawString(rawTextBytes, bounds, dx, false); } - public String getText(Charset charset) throws IOException { - StringBuilder sb = new StringBuilder(); - try (Reader r = new InputStreamReader(new ByteArrayInputStream(rawTextBytes), charset)) { - for (int i = 0; i < stringLength; i++) { - sb.appendCodePoint(readCodePoint(r)); - } - } - return sb.toString(); - } - - //TODO: move this to IOUtils? - private int readCodePoint(Reader r) throws IOException { - int c1 = r.read(); - if (c1 == -1) { - throw new EOFException("Tried to read beyond byte array"); - } - if (!Character.isHighSurrogate((char)c1)) { - return c1; - } - int c2 = r.read(); - if (c2 == -1) { - throw new EOFException("Tried to read beyond byte array"); - } - if (!Character.isLowSurrogate((char)c2)) { - throw new RecordFormatException("Expected low surrogate after high surrogate"); - } - return Character.toCodePoint((char)c1, (char)c2); + return new String(rawTextBytes, charset); } public Point2D getReference() { @@ -433,6 +413,25 @@ public class HwmfText { public Rectangle2D getBounds() { return bounds; } + + protected boolean isUnicode() { + return false; + } + + @Override + public String toString() { + String text = ""; + try { + text = getText(isUnicode() ? Charsets.UTF_16LE : LocaleUtil.CHARSET_1252); + } catch (IOException ignored) { + } + + return + "{ reference: { x: "+reference.getX()+", y: "+reference.getY()+" }"+ + ", bounds: { x: "+bounds.getX()+", y: "+bounds.getY()+", w: "+bounds.getWidth()+", h: "+bounds.getHeight()+"}"+ + ", text: '"+text.replaceAll("\\p{Cntrl}",".")+"'"+ + "}"; + } } public enum HwmfTextAlignment { diff --git a/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java b/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java index 22c29cbb6d..3b078e1213 100644 --- a/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java +++ b/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java @@ -38,6 +38,8 @@ import java.io.InputStream; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; import javax.imageio.ImageIO; @@ -64,42 +66,61 @@ public class HemfPictureTest { @Test @Ignore("Only for manual tests") public void paint() throws IOException { - File f = new File("picture_14.emf"); // sl_samples.getFile("wrench.emf"); - try (FileInputStream fis = new FileInputStream(f)) { - HemfPicture emf = new HemfPicture(fis); +// File f = new File("picture_14.emf"); // sl_samples.getFile("wrench.emf"); +// try (FileInputStream fis = new FileInputStream(f)) { - 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); - - FileWriter fw = new FileWriter("record-list.txt"); - int i=0; - for (HemfRecord r : emf.getRecords()) { - if (r.getEmfRecordType() != HemfRecordType.comment) { - fw.write(i + " " + r.getEmfRecordType() + " " + r.toString() + "\n"); + try (ZipInputStream zis = new ZipInputStream(new FileInputStream("tmp/emf.zip"))) { + for (;;) { + ZipEntry ze = zis.getNextEntry(); + if (ze == null) { + break; } - i++; + final File pngName = new File("build/tmp",ze.getName().replaceFirst( ".*/","").replace(".emf", ".png")); + if (pngName.exists()) { + continue; + } + + + // 263/263282_000.emf +// if (!ze.getName().contains("298/298837_000.emf")) continue; + HemfPicture emf = new HemfPicture(zis); + System.out.println(ze.getName()); + + 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); + + FileWriter fw = new FileWriter("record-list.txt"); + int i = 0; + for (HemfRecord r : emf.getRecords()) { + if (r.getEmfRecordType() != HemfRecordType.comment) { + fw.write(i + " " + r.getEmfRecordType() + " " + r.toString() + "\n"); + } + i++; + } + fw.close(); + + emf.draw(g, new Rectangle2D.Double(0, 0, width, height)); + + g.dispose(); + + ImageIO.write(bufImg, "PNG", pngName); + +// break; } - fw.close(); - - emf.draw(g, new Rectangle2D.Double(0,0,width,height)); - - g.dispose(); - - ImageIO.write(bufImg, "PNG", new File("bla.png")); } } From 17cc3bab08820fa178cba1c9d9166645d4b3ac5e Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Fri, 19 Oct 2018 22:53:33 +0000 Subject: [PATCH 12/21] #60656 - Support export file that contains emf and render it correctly git-svn-id: https://svn.apache.org/repos/asf/poi/branches/hemf@1844380 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/poi/hemf/draw/HemfGraphics.java | 22 ++- .../poi/hemf/record/emf/HemfComment.java | 24 ++- .../apache/poi/hemf/record/emf/HemfDraw.java | 13 +- .../apache/poi/hemf/record/emf/HemfFill.java | 30 ++-- .../poi/hemf/record/emf/HemfHeader.java | 35 ++-- .../apache/poi/hemf/record/emf/HemfMisc.java | 50 +++++- .../hemf/record/emf/HemfRecordIterator.java | 11 +- .../poi/hemf/record/emf/HemfRecordType.java | 2 +- .../apache/poi/hemf/record/emf/HemfText.java | 73 +++++--- .../poi/hemf/record/emf/HemfWindowing.java | 5 +- .../poi/hemf/usermodel/HemfPicture.java | 27 ++- .../poi/hwmf/draw/HwmfDrawProperties.java | 12 ++ .../apache/poi/hwmf/draw/HwmfGraphics.java | 61 +++++-- .../apache/poi/hwmf/record/HwmfBitmapDib.java | 78 ++++++-- .../org/apache/poi/hwmf/record/HwmfFill.java | 14 +- .../org/apache/poi/hwmf/record/HwmfMisc.java | 7 +- .../org/apache/poi/hwmf/record/HwmfText.java | 48 +++++ .../apache/poi/hwmf/record/HwmfWindowing.java | 25 ++- .../poi/hwmf/usermodel/HwmfPicture.java | 5 +- .../poi/hemf/usermodel/HemfPictureTest.java | 167 ++++++++++++------ 20 files changed, 502 insertions(+), 207 deletions(-) diff --git a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java index 2cc1f0889d..8b18be82f9 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java +++ b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java @@ -52,7 +52,7 @@ public class HemfGraphics extends HwmfGraphics { public HemfGraphics(Graphics2D graphicsCtx, Rectangle2D bbox) { super(graphicsCtx,bbox); // add dummy entry for object index 0, as emf is 1-based - addObjectTableEntry((ctx)->{}); + objectIndexes.set(0); } @Override @@ -105,6 +105,12 @@ public class HemfGraphics extends HwmfGraphics { final Path2D path; if (useBracket) { path = prop.getPath(); + if (path.getCurrentPoint() == null) { + // workaround if a path has been started and no MoveTo command + // has been specified before the first lineTo/splineTo + final Point2D loc = prop.getLocation(); + path.moveTo(loc.getX(), loc.getY()); + } } else { path = new Path2D.Double(); Point2D pnt = prop.getLocation(); @@ -135,19 +141,11 @@ public class HemfGraphics extends HwmfGraphics { */ public void addObjectTableEntry(HwmfObjectTableEntry entry, int index) { if (index < 1) { - super.addObjectTableEntry(entry); - return; + throw new IndexOutOfBoundsException("Object table entry index in EMF must be > 0 - invalid index: "+index); } - if (index > objectTable.size()) { - throw new IllegalStateException("object table hasn't grown to this index yet"); - } - - if (index == objectTable.size()) { - objectTable.add(entry); - } else { - objectTable.set(index, entry); - } + objectIndexes.set(index); + objectTable.put(index, entry); } @Override diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java index c24562a047..6b8bd13df9 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java @@ -28,6 +28,7 @@ import java.util.function.Supplier; import org.apache.poi.hemf.record.emfplus.HemfPlusRecord; import org.apache.poi.hemf.record.emfplus.HemfPlusRecordIterator; +import org.apache.poi.hwmf.usermodel.HwmfPicture; import org.apache.poi.util.IOUtils; import org.apache.poi.util.Internal; import org.apache.poi.util.LittleEndianConsts; @@ -40,7 +41,7 @@ import org.apache.poi.util.RecordFormatException; */ @Internal public class HemfComment { - private static final int MAX_RECORD_LENGTH = 2_000_000; + private static final int MAX_RECORD_LENGTH = HwmfPicture.MAX_RECORD_LENGTH; public enum HemfCommentRecordType { emfGeneric(-1, EmfCommentDataGeneric::new, false), @@ -145,7 +146,12 @@ public class HemfComment { } else { // A 32-bit unsigned integer from the RecordType enumeration that identifies this record // as a comment record. This value MUST be 0x00000046. - type = leis.readUInt(); + try { + type = leis.readUInt(); + } catch (RuntimeException e) { + // EOF + return null; + } assert(type == HemfRecordType.comment.id); // A 32-bit unsigned integer that specifies the size in bytes of this record in the // metafile. This value MUST be a multiple of 4 bytes. @@ -327,8 +333,12 @@ public class HemfComment { for (EmfCommentDataFormat fmt : formats) { int skip = fmt.offData-(leis.getReadIndex()-startIdx); leis.skipFully(skip); - fmt.rawData = new byte[fmt.sizeData]; - leis.readFully(fmt.rawData); + fmt.rawData = IOUtils.safelyAllocate(fmt.sizeData, MAX_RECORD_LENGTH); + int readBytes = leis.read(fmt.rawData); + if (readBytes < fmt.sizeData) { + // EOF + break; + } } return leis.getReadIndex()-startIdx; @@ -405,8 +415,7 @@ public class HemfComment { } @Override - public long init(final LittleEndianInputStream leis, final long dataSize) - throws IOException { + public long init(final LittleEndianInputStream leis, final long dataSize) throws IOException { final int startIdx = leis.getReadIndex(); final int commentIdentifier = (int)leis.readUInt(); assert(commentIdentifier == HemfCommentRecordType.emfPublic.id); @@ -431,7 +440,8 @@ public class HemfComment { int winMetafileSize = (int)leis.readUInt(); byte[] winMetafile = IOUtils.safelyAllocate(winMetafileSize, MAX_RECORD_LENGTH); - leis.readFully(winMetafile); + // some emf comments are truncated, so we don't use readFully here + leis.read(winMetafile); return leis.getReadIndex()-startIdx; } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java index 0cffdbc4b5..3596462364 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java @@ -934,7 +934,7 @@ public class HemfDraw { public void draw(HemfGraphics ctx) { final HemfDrawProperties prop = ctx.getProperties(); final Path2D path = prop.getPath(); - if (path != null) { + if (path != null && path.getCurrentPoint() != null) { path.closePath(); prop.setLocation(path.getCurrentPoint()); } @@ -1041,15 +1041,16 @@ public class HemfDraw { } private static void polyTo(final HemfGraphics ctx, final Path2D poly) { - final PathIterator pi = poly.getPathIterator(null); - if (pi.isDone()) { - // ignore empty polys + if (poly.getCurrentPoint() == null) { return; } - // ignore dummy start point (moveTo) + final PathIterator pi = poly.getPathIterator(null); + // ignore empty polys and dummy start point (moveTo) pi.next(); - assert (!pi.isDone()); + if (pi.isDone()) { + return; + } ctx.draw((path) -> path.append(pi, true)); } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java index ab632e28c8..2b814336f2 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java @@ -624,19 +624,19 @@ public class HemfFill { return undefinedSpace1; } - final LittleEndianInputStream leisDib; - if (undefinedSpace2 == 0) { - leisDib = leis; - } else { - final ByteArrayOutputStream bos = new ByteArrayOutputStream(cbBmi+cbBits); - final long cbBmiSrcAct = IOUtils.copy(leis, bos, cbBmi); - assert (cbBmiSrcAct == cbBmi); - leis.skipFully(undefinedSpace2); - final long cbBitsSrcAct = IOUtils.copy(leis, bos, cbBits); - assert (cbBitsSrcAct == cbBits); - leisDib = new LittleEndianInputStream(new ByteArrayInputStream(bos.toByteArray())); - } final int dibSize = cbBmi+cbBits; + if (undefinedSpace2 == 0) { + return undefinedSpace1 + bitmap.init(leis, dibSize); + } + + final ByteArrayOutputStream bos = new ByteArrayOutputStream(cbBmi+cbBits); + final long cbBmiSrcAct = IOUtils.copy(leis, bos, cbBmi); + assert (cbBmiSrcAct == cbBmi); + leis.skipFully(undefinedSpace2); + final long cbBitsSrcAct = IOUtils.copy(leis, bos, cbBits); + assert (cbBitsSrcAct == cbBits); + + final LittleEndianInputStream leisDib = new LittleEndianInputStream(new ByteArrayInputStream(bos.toByteArray())); final int dibSizeAct = bitmap.init(leisDib, dibSize); assert (dibSizeAct <= dibSize); return undefinedSpace1 + cbBmi + undefinedSpace2 + cbBits; @@ -646,8 +646,8 @@ public class HemfFill { static long readRgnData(final LittleEndianInputStream leis, final List rgnRects) { // *** RegionDataHeader *** // A 32-bit unsigned integer that specifies the size of this object in bytes. This MUST be 0x00000020. - long rgnHdrSiez = leis.readUInt(); - assert(rgnHdrSiez == 0x20); + long rgnHdrSize = leis.readUInt(); + assert(rgnHdrSize == 0x20); // A 32-bit unsigned integer that specifies the region type. This SHOULD be RDH_RECTANGLES (0x00000001) long rgnHdrType = leis.readUInt(); assert(rgnHdrType == 1); @@ -729,7 +729,7 @@ public class HemfFill { @Override public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { // A 128-bit WMF RectL object, which specifies bounding rectangle, in device units - return readRectL(leis, bounds); + return (recordSize == 0) ? 0 : readRectL(leis, bounds); } @Override diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfHeader.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfHeader.java index c1c0712c48..79e3b4943c 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfHeader.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfHeader.java @@ -17,17 +17,17 @@ package org.apache.poi.hemf.record.emf; -import static org.apache.poi.hemf.record.emf.HemfDraw.readDimensionFloat; import static org.apache.poi.hemf.record.emf.HemfDraw.readDimensionInt; 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.Dimension2D; import java.awt.geom.Rectangle2D; import java.io.IOException; +import java.nio.charset.StandardCharsets; import org.apache.poi.util.Dimension2DDouble; import org.apache.poi.util.Internal; -import org.apache.poi.util.LittleEndian; import org.apache.poi.util.LittleEndianConsts; import org.apache.poi.util.LittleEndianInputStream; @@ -46,8 +46,7 @@ public class HemfHeader implements HemfRecord { private long bytes; private long records; private int handles; - private long nDescription; - private long offDescription; + private String description; private long nPalEntries; private boolean hasExtension1; private long cbPixelFormat; @@ -79,13 +78,7 @@ public class HemfHeader implements HemfRecord { return handles; } - public long getnDescription() { - return nDescription; - } - - public long getOffDescription() { - return offDescription; - } + public String getDescription() { return description; } public long getnPalEntries() { return nPalEntries; @@ -131,8 +124,7 @@ public class HemfHeader implements HemfRecord { ", bytes=" + bytes + ", records=" + records + ", handles=" + handles + - ", nDescription=" + nDescription + - ", offDescription=" + offDescription + + ", description=" + description + ", nPalEntries=" + nPalEntries + ", hasExtension1=" + hasExtension1 + ", cbPixelFormat=" + cbPixelFormat + @@ -156,6 +148,8 @@ public class HemfHeader implements HemfRecord { throw new IOException("Not a valid EMF header. Record type:"+recordId); } + int startIdx = leis.getReadIndex(); + //bounds long size = readRectL(leis, boundsRectangle); size += readRectL(leis, frameRectangle); @@ -174,8 +168,8 @@ public class HemfHeader implements HemfRecord { //reserved leis.skipFully(LittleEndianConsts.SHORT_SIZE); - nDescription = leis.readUInt(); - offDescription = leis.readUInt(); + int nDescription = (int)leis.readUInt(); + int offDescription = (int)leis.readUInt(); nPalEntries = leis.readUInt(); size += 8*LittleEndianConsts.INT_SIZE; @@ -183,6 +177,16 @@ public class HemfHeader implements HemfRecord { size += readDimensionInt(leis, deviceDimension); size += readDimensionInt(leis, milliDimension); + if (nDescription > 0 && offDescription > 0) { + int skip = (int)(offDescription - (size + HEADER_SIZE)); + leis.mark(skip+nDescription*2); + leis.skipFully(skip); + byte[] buf = new byte[(nDescription-1)*2]; + leis.readFully(buf); + description = new String(buf, StandardCharsets.UTF_16LE).replace((char)0, ' ').trim(); + leis.reset(); + } + if (size+12 <= recordSize) { hasExtension1 = true; cbPixelFormat = leis.readUInt(); @@ -195,6 +199,7 @@ public class HemfHeader implements HemfRecord { hasExtension2 = true; size += readDimensionInt(leis, microDimension); } + return size; } } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java index dd444f7c0e..ade921d200 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java @@ -45,8 +45,6 @@ import org.apache.poi.util.LittleEndianConsts; import org.apache.poi.util.LittleEndianInputStream; public class HemfMisc { - private static final int MAX_RECORD_LENGTH = 10_000_000; - public static class EmfEof implements HemfRecord { protected final List palette = new ArrayList<>(); @@ -535,6 +533,11 @@ public class HemfMisc { public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { return readPointL(leis, origin); } + + @Override + public String toString() { + return "{ x: "+origin.getX()+", y: "+origin.getY()+" }"; + } } public static class EmfSetWorldTransform implements HemfRecord { @@ -581,4 +584,47 @@ public class HemfMisc { ", modifyWorldTransformMode: "+modifyWorldTransformMode+" }"; } } + + public static class EmfCreateMonoBrush16 extends EmfCreatePen { + protected HwmfFill.ColorUsage colorUsage; + protected final HwmfBitmapDib bitmap = new HwmfBitmapDib(); + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.createMonoBrush16; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + final int startIdx = leis.getReadIndex(); + + penIndex = (int) leis.readUInt(); + + // A 32-bit unsigned integer that specifies how to interpret values in the color + // table in the DIB header. This value MUST be in the DIBColors enumeration + colorUsage = HwmfFill.ColorUsage.valueOf((int) leis.readUInt()); + + // A 32-bit unsigned integer that specifies the offset from the start of this + // record to the DIB header, if the record contains a DIB. + int offBmi = (int) leis.readUInt(); + + // A 32-bit unsigned integer that specifies the size of the DIB header, if the + // record contains a DIB. + int cbBmi = (int) leis.readUInt(); + + // A 32-bit unsigned integer that specifies the offset from the start of this + // record to the DIB bits, if the record contains a DIB. + int offBits = (int) leis.readUInt(); + + // A 32-bit unsigned integer that specifies the size of the DIB bits, if the record + // contains a DIB. + int cbBits = (int) leis.readUInt(); + + int size = 6 * LittleEndianConsts.INT_SIZE; + + size += readBitmap(leis, bitmap, startIdx, offBmi, cbBmi, offBits, cbBits); + + return size; + } + } } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordIterator.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordIterator.java index 49452f2569..207c8ee830 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordIterator.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordIterator.java @@ -53,8 +53,15 @@ public class HemfRecordIterator implements Iterator { if (currentRecord != null && HemfRecordType.eof == currentRecord.getEmfRecordType()) { return null; } - long recordId = stream.readUInt(); - long recordSize = stream.readUInt(); + + final long recordId, recordSize; + try { + recordId = stream.readUInt(); + recordSize = stream.readUInt(); + } catch (RuntimeException e) { + // EOF + return null; + } HemfRecordType type = HemfRecordType.getById(recordId); if (type == null) { diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java index 35d09b18af..7a3bf470e0 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java @@ -116,7 +116,7 @@ public enum HemfRecordType { polyPolyline16(0x0000005A, HemfDraw.EmfPolyPolyline16::new), polyPolygon16(0x0000005B, HemfDraw.EmfPolyPolygon16::new), polyDraw16(0x0000005C, HemfDraw.EmfPolyDraw16::new), - createmonobrush16(0x0000005D, UnimplementedHemfRecord::new), + createMonoBrush16(0x0000005D, HemfMisc.EmfCreateMonoBrush16::new), createDibPatternBrushPt(0x0000005E, HemfMisc.EmfCreateDibPatternBrushPt::new), extCreatePen(0x0000005F, HemfMisc.EmfExtCreatePen::new), polytextouta(0x00000060, HemfText.PolyTextOutA::new), diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java index 92528c8993..6b2f9a6d26 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java @@ -113,36 +113,53 @@ public class HemfText { int offDx = (int)leis.readUInt(); size += LittleEndianConsts.INT_SIZE; - int undefinedSpace1 = (int)(offString - (size + HEADER_SIZE)); - assert (undefinedSpace1 >= 0); - leis.skipFully(undefinedSpace1); - size += undefinedSpace1; + // handle dx before string and other way round + for (char op : ((offDx < offString) ? "ds" : "sd").toCharArray()) { + switch (op) { + case 'd': { + dx.clear(); + int undefinedSpace2 = (int) (offDx - (size + HEADER_SIZE)); + if (offDx > 0 && undefinedSpace2 >= 0 && offDx-HEADER_SIZE < recordSize) { + leis.skipFully(undefinedSpace2); + size += undefinedSpace2; - rawTextBytes = IOUtils.safelyAllocate(stringLength*(isUnicode()?2:1), MAX_RECORD_LENGTH); - leis.readFully(rawTextBytes); - size += rawTextBytes.length; + // An array of 32-bit unsigned integers that specify the output spacing between the origins of adjacent + // character cells in logical units. The location of this field is specified by the value of offDx + // in bytes from the start of this record. If spacing is defined, this field contains the same number + // of values as characters in the output string. + // + // If the Options field of the EmrText object contains the ETO_PDY flag, then this buffer + // contains twice as many values as there are characters in the output string, one + // horizontal and one vertical offset for each, in that order. + // + // If ETO_RTLREADING is specified, characters are laid right to left instead of left to right. + // No other options affect the interpretation of this field. + final int maxSize = (int)Math.min((offDx < offString) ? (offString-HEADER_SIZE) : recordSize, recordSize); + while (size <= maxSize-LittleEndianConsts.INT_SIZE) { + dx.add((int) leis.readUInt()); + size += LittleEndianConsts.INT_SIZE; + } + } + if (dx.size() < stringLength) { + // invalid dx array + dx.clear(); + } + break; + } + default: + case 's': { + int undefinedSpace1 = (int)(offString - (size + HEADER_SIZE)); + if (offString > 0 && undefinedSpace1 >= 0 && offString-HEADER_SIZE < recordSize) { + leis.skipFully(undefinedSpace1); + size += undefinedSpace1; - dx.clear(); - if (offDx > 0) { - int undefinedSpace2 = (int) (offDx - (size + HEADER_SIZE)); - assert (undefinedSpace2 >= 0); - leis.skipFully(undefinedSpace2); - size += undefinedSpace2; - - // An array of 32-bit unsigned integers that specify the output spacing between the origins of adjacent - // character cells in logical units. The location of this field is specified by the value of offDx - // in bytes from the start of this record. If spacing is defined, this field contains the same number - // of values as characters in the output string. - // - // If the Options field of the EmrText object contains the ETO_PDY flag, then this buffer - // contains twice as many values as there are characters in the output string, one - // horizontal and one vertical offset for each, in that order. - // - // If ETO_RTLREADING is specified, characters are laid right to left instead of left to right. - // No other options affect the interpretation of this field. - while (size < recordSize) { - dx.add((int) leis.readUInt()); - size += LittleEndianConsts.INT_SIZE; + final int maxSize = (int)Math.min(recordSize-size, stringLength * (isUnicode() ? 2 : 1)); + rawTextBytes = IOUtils.safelyAllocate(maxSize, MAX_RECORD_LENGTH); + leis.readFully(rawTextBytes); + size += maxSize; + break; + } + } } } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java index cbb6ada0d0..03c033b763 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java @@ -38,9 +38,10 @@ public class HemfWindowing { @Override public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { // cx (4 bytes): A 32-bit unsigned integer that defines the x-coordinate of the point. - width = (int)leis.readUInt(); + int width = (int)leis.readUInt(); // cy (4 bytes): A 32-bit unsigned integer that defines the y-coordinate of the point. - height = (int)leis.readUInt(); + int height = (int)leis.readUInt(); + size.setSize(width, height); return 2*LittleEndianConsts.INT_SIZE; } diff --git a/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java b/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java index 77f34fd3bf..b8e863a85a 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java +++ b/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java @@ -34,6 +34,7 @@ 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.hemf.record.emf.HemfWindowing; import org.apache.poi.util.Dimension2DDouble; import org.apache.poi.util.Internal; import org.apache.poi.util.LittleEndianInputStream; @@ -47,6 +48,7 @@ public class HemfPicture implements Iterable { private final LittleEndianInputStream stream; private final List records = new ArrayList<>(); + private boolean isParsed = false; public HemfPicture(InputStream is) throws IOException { this(new LittleEndianInputStream(is)); @@ -61,7 +63,10 @@ public class HemfPicture implements Iterable { } public List getRecords() { - if (records.isEmpty()) { + if (!isParsed) { + // in case the (first) parsing throws an exception, we can provide the + // records up to that point + isParsed = true; new HemfRecordIterator(stream).forEachRemaining(records::add); } return records; @@ -89,10 +94,26 @@ public class HemfPicture implements Iterable { */ public Dimension2D getSize() { HemfHeader header = (HemfHeader)getRecords().get(0); + final double coeff = (double) Units.EMU_PER_CENTIMETER / Units.EMU_PER_POINT / 10.; Rectangle2D dim = header.getFrameRectangle(); + double width = dim.getWidth(), height = dim.getHeight(); + if (dim.isEmpty() || Math.rint(width*coeff) == 0 || Math.rint(height*coeff) == 0) { + for (HemfRecord r : getRecords()) { + if (r instanceof HemfWindowing.EmfSetWindowExtEx) { + Dimension2D d = ((HemfWindowing.EmfSetWindowExtEx)r).getSize(); + width = d.getWidth(); + height = d.getHeight(); + // keep searching - sometimes there's another record + } + } + } - double coeff = (double)Units.EMU_PER_CENTIMETER/Units.EMU_PER_POINT/10.; - return new Dimension2DDouble(dim.getWidth()*coeff, dim.getHeight()*coeff); + if (Math.rint(width*coeff) == 0 || Math.rint(height*coeff) == 0) { + width = 100; + height = 100; + } + + return new Dimension2DDouble(width*coeff, height*coeff); } public void draw(Graphics2D ctx, Rectangle2D graphicsBounds) { diff --git a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java index 705f02705d..176c8e1bce 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java @@ -37,6 +37,7 @@ import org.apache.poi.hwmf.record.HwmfMapMode; import org.apache.poi.hwmf.record.HwmfMisc.WmfSetBkMode.HwmfBkMode; import org.apache.poi.hwmf.record.HwmfPalette.PaletteEntry; import org.apache.poi.hwmf.record.HwmfPenStyle; +import org.apache.poi.hwmf.record.HwmfTernaryRasterOp; import org.apache.poi.hwmf.record.HwmfText.HwmfTextAlignment; import org.apache.poi.hwmf.record.HwmfText.HwmfTextVerticalAlignment; @@ -65,6 +66,7 @@ public class HwmfDrawProperties { private HwmfTextVerticalAlignment textVAlignLatin; private HwmfTextAlignment textAlignAsian; private HwmfTextVerticalAlignment textVAlignAsian; + private HwmfTernaryRasterOp rasterOp; public HwmfDrawProperties() { window = new Rectangle2D.Double(0, 0, 1, 1); @@ -86,6 +88,7 @@ public class HwmfDrawProperties { textVAlignLatin = HwmfTextVerticalAlignment.TOP; textAlignAsian = HwmfTextAlignment.RIGHT; textVAlignAsian = HwmfTextVerticalAlignment.TOP; + rasterOp = HwmfTernaryRasterOp.PATCOPY; } public HwmfDrawProperties(HwmfDrawProperties other) { @@ -122,6 +125,7 @@ public class HwmfDrawProperties { this.textVAlignLatin = other.textVAlignLatin; this.textAlignAsian = other.textAlignAsian; this.textVAlignAsian = other.textVAlignAsian; + this.rasterOp = other.rasterOp; } public void setViewportExt(double width, double height) { @@ -375,4 +379,12 @@ public class HwmfDrawProperties { public int getWindingRule() { return getPolyfillMode().awtFlag; } + + public HwmfTernaryRasterOp getRasterOp() { + return rasterOp; + } + + public void setRasterOp(HwmfTernaryRasterOp rasterOp) { + this.rasterOp = rasterOp; + } } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java index 7ac295e5ce..dff46ce863 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java @@ -31,11 +31,11 @@ import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.nio.charset.Charset; import java.text.AttributedString; -import java.util.ArrayList; +import java.util.BitSet; import java.util.LinkedList; import java.util.List; -import java.util.ListIterator; import java.util.NoSuchElementException; +import java.util.TreeMap; import org.apache.commons.codec.Charsets; import org.apache.poi.common.usermodel.fonts.FontInfo; @@ -56,7 +56,8 @@ public class HwmfGraphics { protected final List propStack = new LinkedList<>(); protected HwmfDrawProperties prop; protected final Graphics2D graphicsCtx; - protected final List objectTable = new ArrayList<>(); + protected final BitSet objectIndexes = new BitSet(); + protected final TreeMap objectTable = new TreeMap<>(); private static final Charset DEFAULT_CHARSET = LocaleUtil.CHARSET_1252; /** Bounding box from the placeable header */ @@ -83,7 +84,11 @@ public class HwmfGraphics { } public void draw(Shape shape) { - HwmfLineDash lineDash = getProperties().getPenStyle().getLineDash(); + HwmfPenStyle ps = getProperties().getPenStyle(); + if (ps == null) { + return; + } + HwmfLineDash lineDash = ps.getLineDash(); if (lineDash == HwmfLineDash.NULL) { // line is not drawn return; @@ -201,15 +206,9 @@ public class HwmfGraphics { * @param entry */ public void addObjectTableEntry(HwmfObjectTableEntry entry) { - ListIterator oIter = objectTable.listIterator(); - while (oIter.hasNext()) { - HwmfObjectTableEntry tableEntry = oIter.next(); - if (tableEntry == null) { - oIter.set(entry); - return; - } - } - objectTable.add(entry); + int objIdx = objectIndexes.nextClearBit(0); + objectIndexes.set(objIdx); + objectTable.put(objIdx, entry); } /** @@ -242,7 +241,13 @@ public class HwmfGraphics { * @throws IndexOutOfBoundsException if the index is out of range */ public void unsetObjectTableEntry(int index) { - objectTable.set(index, null); + if (index < 0) { + throw new IndexOutOfBoundsException("Invalid index: "+index); + } + // sometime emfs remove object table entries before they set them + // so ignore requests, if the table entry doesn't exist + objectTable.remove(index); + objectIndexes.clear(index); } /** @@ -353,6 +358,9 @@ public class HwmfGraphics { } AttributedString as = new AttributedString(textString); addAttributes(as, font); + + // disabled for the time being, as the results aren't promising + /* if (dx != null && !dx.isEmpty()) { //for multi-byte encodings (e.g. Shift_JIS), the byte length //might not equal the string length(). @@ -371,22 +379,23 @@ public class HwmfGraphics { final int cps = textString.codePointCount(0, textString.length()); final int unicodeSteps = Math.max(dx.size()/cps, 1); - int dxPosition = 0; + int dxPosition = 0, lastDxPosition = 0; int beginIndex = 0; - int[] chars = {0}; while (beginIndex < textString.length() && dxPosition < dx.size()) { int endIndex = textString.offsetByCodePoints(beginIndex, 1); if (beginIndex > 0) { // Tracking works as a prefix/advance space on characters whereas // dx[...] is the complete width of the current char // therefore we need to add the additional/suffix width to the next char - as.addAttribute(TextAttribute.TRACKING, (float)((dx.get(dxPosition) - fontW) / fontH), beginIndex, endIndex); + + as.addAttribute(TextAttribute.TRACKING, (float)((dx.get(lastDxPosition) - fontW) / fontH), beginIndex, endIndex); } + lastDxPosition = dxPosition; dxPosition += (isUnicode) ? unicodeSteps : (endIndex-beginIndex); beginIndex = endIndex; } } - + */ double angle = Math.toRadians(-font.getEscapement()/10.); @@ -439,4 +448,20 @@ public class HwmfGraphics { return fontHeight*3/4; } } + + public void drawImage(BufferedImage img, Rectangle2D srcBounds, Rectangle2D dstBounds) { + // prop.getRasterOp(); + graphicsCtx.drawImage(img, + (int)dstBounds.getX(), + (int)dstBounds.getY(), + (int)(dstBounds.getX()+dstBounds.getWidth()), + (int)(dstBounds.getY()+dstBounds.getHeight()), + (int)srcBounds.getX(), + (int)srcBounds.getY(), + (int)(srcBounds.getX()+srcBounds.getWidth()), + (int)(srcBounds.getY()+srcBounds.getHeight()), + null, // getProperties().getBackgroundColor().getColor(), + null + ); + } } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBitmapDib.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBitmapDib.java index 82f593963e..a6f378512e 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBitmapDib.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBitmapDib.java @@ -17,14 +17,21 @@ package org.apache.poi.hwmf.record; -import javax.imageio.ImageIO; +import java.awt.AlphaComposite; +import java.awt.BasicStroke; import java.awt.Color; import java.awt.Graphics2D; +import java.awt.LinearGradientPaint; +import java.awt.MultipleGradientPaint; +import java.awt.RenderingHints; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import javax.imageio.ImageIO; + +import org.apache.poi.hwmf.usermodel.HwmfPicture; import org.apache.poi.util.IOUtils; import org.apache.poi.util.LittleEndian; import org.apache.poi.util.LittleEndianConsts; @@ -38,7 +45,7 @@ import org.apache.poi.util.RecordFormatException; */ public class HwmfBitmapDib { - private static final int MAX_RECORD_LENGTH = 10000000; + private static final int MAX_RECORD_LENGTH = HwmfPicture.MAX_RECORD_LENGTH; public static enum BitCount { /** @@ -225,14 +232,23 @@ public class HwmfBitmapDib { introSize += readColors(leis); assert(introSize < 10000); - int fileSize = (headerImageSize < headerSize) ? recordSize : (int)Math.min(introSize+headerImageSize,recordSize); - leis.reset(); - imageData = IOUtils.toByteArray(leis, fileSize); - + assert( headerSize != 0x0C || ((((headerWidth * headerPlanes * headerBitCount.flag + 31) & ~31) / 8) * Math.abs(headerHeight)) == headerImageSize); - return fileSize; + if (headerImageSize < headerSize) { + imageData = IOUtils.safelyAllocate(recordSize, MAX_RECORD_LENGTH); + leis.readFully(imageData); + return recordSize; + } else { + int fileSize = (int)Math.min(introSize+headerImageSize,recordSize); + imageData = IOUtils.safelyAllocate(fileSize, MAX_RECORD_LENGTH); + leis.readFully(imageData, 0, introSize); + leis.skipFully(recordSize-fileSize); + // emfs are sometimes truncated, read as much as possible + int readBytes = leis.read(imageData, introSize, fileSize-introSize); + return introSize+(recordSize-fileSize)+readBytes; + } } protected int readHeader(LittleEndianInputStream leis) throws IOException { @@ -262,6 +278,9 @@ public class HwmfBitmapDib { headerBitCount = BitCount.valueOf(leis.readUShort()); size += 4*LittleEndianConsts.SHORT_SIZE; } else { + // fix header size, sometimes this is invalid + headerSize = 40; + // BitmapInfoHeader // A 32-bit signed integer that defines the width of the DIB, in pixels. // This value MUST be positive. @@ -306,7 +325,6 @@ public class HwmfBitmapDib { headerColorImportant = leis.readUInt(); size += 8*LittleEndianConsts.INT_SIZE+2*LittleEndianConsts.SHORT_SIZE; } - assert(size == headerSize); return size; } @@ -374,6 +392,10 @@ public class HwmfBitmapDib { return size; } + public boolean isValid() { + return (imageData != null); + } + public InputStream getBMPStream() { return new ByteArrayInputStream(getBMPData()); } @@ -407,14 +429,38 @@ public class HwmfBitmapDib { public BufferedImage getImage() { try { return ImageIO.read(getBMPStream()); - } catch (IOException e) { - logger.log(POILogger.ERROR, "invalid bitmap data - returning black opaque image"); - BufferedImage bi = new BufferedImage(headerWidth, headerHeight, BufferedImage.TYPE_INT_ARGB); - Graphics2D g = bi.createGraphics(); - g.setPaint(Color.black); - g.fillRect(0, 0, headerWidth, headerHeight); - g.dispose(); - return bi; + } catch (IOException|RuntimeException e) { + logger.log(POILogger.ERROR, "invalid bitmap data - returning placeholder image"); + return getPlaceholder(); } } + + protected BufferedImage getPlaceholder() { + BufferedImage bi = new BufferedImage(headerWidth, headerHeight, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = bi.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); + + g.setComposite(AlphaComposite.Clear); + g.fillRect(0, 0, headerWidth, headerHeight); + + final int arcs = Math.min(headerWidth, headerHeight) / 7; + + Color bg = Color.LIGHT_GRAY; + Color fg = Color.GRAY; + LinearGradientPaint lgp = new LinearGradientPaint(0f, 0f, 5, 5, + new float[] {0,.1f,.1001f}, new Color[] {fg,fg,bg}, MultipleGradientPaint.CycleMethod.REFLECT); + g.setComposite(AlphaComposite.SrcOver.derive(0.4f)); + g.setPaint(lgp); + g.fillRoundRect(0, 0, headerWidth-1, headerHeight-1, arcs, arcs); + + g.setColor(Color.DARK_GRAY); + g.setComposite(AlphaComposite.Src); + g.setStroke(new BasicStroke(2)); + g.drawRoundRect(0, 0, headerWidth-1, headerHeight-1, arcs, arcs); + g.dispose(); + return bi; + } } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java index 0d6f85e4d8..4f15272e73 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java @@ -433,7 +433,7 @@ public class HwmfFill { * The source of the color data is a DIB, and the destination of the transfer is * the current output region in the playback device context. */ - public static class WmfStretchDib implements HwmfRecord, HwmfImageRecord, HwmfObjectTableEntry { + public static class WmfStretchDib implements HwmfRecord, HwmfImageRecord { /** * A 32-bit unsigned integer that defines how the source pixels, the current brush in * the playback device context, and the destination pixels are to be combined to @@ -487,12 +487,10 @@ public class HwmfFill { @Override public void draw(HwmfGraphics ctx) { - ctx.addObjectTableEntry(this); - } - - @Override - public void applyObject(HwmfGraphics ctx) { - + if (dib.isValid()) { + ctx.getProperties().setRasterOp(rasterOperation); + ctx.drawImage(getImage(), srcBounds, dstBounds); + } } @Override @@ -720,7 +718,7 @@ public class HwmfFill { @Override public BufferedImage getImage() { - return (target == null) ? null : target.getImage(); + return (target != null && target.isValid()) ? target.getImage() : null; } } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java index 70e1d58aed..7bc46795bb 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java @@ -179,6 +179,11 @@ public class HwmfMisc { public void draw(HwmfGraphics ctx) { ctx.getProperties().setBkMode(bkMode); } + + @Override + public String toString() { + return "{ bkMode: '"+bkMode+"' }"; + } } /** @@ -436,7 +441,7 @@ public class HwmfMisc { @Override public BufferedImage getImage() { - if (patternDib != null) { + if (patternDib != null && patternDib.isValid()) { return patternDib.getImage(); } else if (pattern16 != null) { return pattern16.getImage(); diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java index 5228d11d0b..fb45170d22 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java @@ -601,6 +601,54 @@ public class HwmfText { props.setTextVAlignAsian(HwmfTextVerticalAlignment.TOP); } } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("{ align: '"); + + if (TA_CENTER.isSet(textAlignmentMode)) { + sb.append("center"); + } else if (TA_RIGHT.isSet(textAlignmentMode)) { + sb.append("right"); + } else { + sb.append("left"); + } + + sb.append("', align-asian: '"); + + if (VTA_CENTER.isSet(textAlignmentMode)) { + sb.append("center"); + } else if (VTA_LEFT.isSet(textAlignmentMode)) { + sb.append("left"); + } else { + sb.append("right"); + } + + sb.append("', valign: '"); + + if (TA_BASELINE.isSet(textAlignmentMode)) { + sb.append("baseline"); + } else if (TA_BOTTOM.isSet(textAlignmentMode)) { + sb.append("bottom"); + } else { + sb.append("top"); + } + + sb.append("', valign-asian: '"); + + if (VTA_BASELINE.isSet(textAlignmentMode)) { + sb.append("baseline"); + } else if (VTA_BOTTOM.isSet(textAlignmentMode)) { + sb.append("bottom"); + } else { + sb.append("top"); + } + + sb.append("' }"); + + return sb.toString(); + } } public static class WmfCreateFontIndirect implements HwmfRecord, HwmfObjectTableEntry { diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java index e419157050..6e17b574fd 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java @@ -21,10 +21,12 @@ import static org.apache.poi.hwmf.record.HwmfDraw.readBounds; import java.awt.Shape; import java.awt.geom.Area; +import java.awt.geom.Dimension2D; import java.awt.geom.Rectangle2D; import java.io.IOException; import org.apache.poi.hwmf.draw.HwmfGraphics; +import org.apache.poi.util.Dimension2DDouble; import org.apache.poi.util.LittleEndianConsts; import org.apache.poi.util.LittleEndianInputStream; @@ -170,11 +172,7 @@ public class HwmfWindowing { */ public static class WmfSetWindowExt implements HwmfRecord { - /** A signed integer that defines the vertical extent of the window in logical units. */ - protected int height; - - /** A signed integer that defines the horizontal extent of the window in logical units. */ - protected int width; + protected final Dimension2D size = new Dimension2DDouble(); @Override public HwmfRecordType getWmfRecordType() { @@ -183,23 +181,22 @@ public class HwmfWindowing { @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { - height = leis.readShort(); - width = leis.readShort(); + // A signed integer that defines the vertical extent of the window in logical units. + int height = leis.readShort(); + // A signed integer that defines the horizontal extent of the window in logical units. + int width = leis.readShort(); + size.setSize(width, height); return 2*LittleEndianConsts.SHORT_SIZE; } @Override public void draw(HwmfGraphics ctx) { - ctx.getProperties().setWindowExt(width, height); + ctx.getProperties().setWindowExt(size.getWidth(), size.getHeight()); ctx.updateWindowMapMode(); } - public int getHeight() { - return height; - } - - public int getWidth() { - return width; + public Dimension2D getSize() { + return size; } } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfPicture.java b/src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfPicture.java index b380d46143..bf91dc864b 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfPicture.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfPicture.java @@ -43,6 +43,9 @@ import org.apache.poi.util.RecordFormatException; import org.apache.poi.util.Units; public class HwmfPicture { + /** Max. record length - processing longer records will throw an exception */ + public static final int MAX_RECORD_LENGTH = 50_000_000; + private static final POILogger logger = POILogFactory.getLogger(HwmfPicture.class); final List records = new ArrayList<>(); @@ -156,7 +159,7 @@ public class HwmfPicture { if (wOrg == null || wExt == null) { throw new RuntimeException("invalid wmf file - window records are incomplete."); } - return new Rectangle2D.Double(wOrg.getX(), wOrg.getY(), wExt.getWidth(), wExt.getHeight()); + return new Rectangle2D.Double(wOrg.getX(), wOrg.getY(), wExt.getSize().getWidth(), wExt.getSize().getHeight()); } } diff --git a/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java b/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java index 3b078e1213..94be4712e5 100644 --- a/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java +++ b/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java @@ -22,26 +22,22 @@ 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.BufferedWriter; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; - -import javax.imageio.ImageIO; +import java.util.stream.Stream; import org.apache.poi.POIDataSamples; import org.apache.poi.hemf.record.emf.HemfComment; @@ -54,8 +50,6 @@ 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 { @@ -63,69 +57,130 @@ public class HemfPictureTest { private static final POIDataSamples ss_samples = POIDataSamples.getSpreadSheetInstance(); private static final POIDataSamples sl_samples = POIDataSamples.getSlideShowInstance(); + /* @Test - @Ignore("Only for manual tests") + @Ignore("Only for manual tests - need to add org.tukaani:xz:1.8 for this to work") public void paint() throws IOException { -// File f = new File("picture_14.emf"); // sl_samples.getFile("wrench.emf"); -// try (FileInputStream fis = new FileInputStream(f)) { + byte buf[] = new byte[50_000_000]; - try (ZipInputStream zis = new ZipInputStream(new FileInputStream("tmp/emf.zip"))) { - for (;;) { - ZipEntry ze = zis.getNextEntry(); - if (ze == null) { - break; - } - final File pngName = new File("build/tmp",ze.getName().replaceFirst( ".*/","").replace(".emf", ".png")); - if (pngName.exists()) { - continue; - } + final boolean writeLog = true; + final boolean savePng = true; + Set passed = new HashSet<>(); - // 263/263282_000.emf -// if (!ze.getName().contains("298/298837_000.emf")) continue; - HemfPicture emf = new HemfPicture(zis); - System.out.println(ze.getName()); + try (BufferedWriter sucWrite = parseEmfLog(passed, "emf-success.txt"); + BufferedWriter parseError = parseEmfLog(passed, "emf-parse.txt"); + BufferedWriter renderError = parseEmfLog(passed, "emf-render.txt"); + SevenZFile sevenZFile = new SevenZFile(new File("tmp/render_emf.7z"))) { + for (int idx=0;;idx++) { + SevenZArchiveEntry entry = sevenZFile.getNextEntry(); + if (entry == null) break; + final String etName = entry.getName(); - 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; - } + if (entry.isDirectory() || !etName.endsWith(".emf") || passed.contains(etName)) continue; - 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); + System.out.println(etName); - FileWriter fw = new FileWriter("record-list.txt"); - int i = 0; - for (HemfRecord r : emf.getRecords()) { - if (r.getEmfRecordType() != HemfRecordType.comment) { - fw.write(i + " " + r.getEmfRecordType() + " " + r.toString() + "\n"); + int size = sevenZFile.read(buf); + + HemfPicture emf = null; + try { + emf = new HemfPicture(new ByteArrayInputStream(buf, 0, size)); + + // initialize parsing + emf.getRecords(); + } catch (Exception|AssertionError e) { + if (writeLog) { + parseError.write(etName+" "+hashException(e)+"\n"); + parseError.flush(); + } + System.out.println("parse error"); + // continue with the read records up to the error anyway + if (emf.getRecords().isEmpty()) { + continue; } - i++; } - fw.close(); - emf.draw(g, new Rectangle2D.Double(0, 0, width, height)); + Graphics2D g = null; + try { + Dimension2D dim = emf.getSize(); + double width = Units.pointsToPixel(dim.getWidth()); + // keep aspect ratio for height + double height = Units.pointsToPixel(dim.getHeight()); + double max = Math.max(width, height); + if (max > 1500.) { + width *= 1500. / max; + height *= 1500. / max; + } - g.dispose(); + BufferedImage bufImg = new BufferedImage((int)Math.ceil(width), (int)Math.ceil(height), BufferedImage.TYPE_INT_ARGB); + 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); - ImageIO.write(bufImg, "PNG", pngName); + emf.draw(g, new Rectangle2D.Double(0, 0, width, height)); -// break; + final File pngName = new File("build/tmp", etName.replaceFirst(".*"+"/", "").replace(".emf", ".png")); + if (savePng) { + ImageIO.write(bufImg, "PNG", pngName); + } + } catch (Exception|AssertionError e) { + System.out.println("render error"); + if (writeLog) { + // dumpRecords(emf.getRecords()); + renderError.write(etName+" "+hashException(e)+"\n"); + renderError.flush(); + } + continue; + } finally { + if (g != null) g.dispose(); + } + + if (writeLog) { + sucWrite.write(etName + "\n"); + sucWrite.flush(); + } } } + } */ + + private static int hashException(Throwable e) { + StringBuilder sb = new StringBuilder(); + for (StackTraceElement se : e.getStackTrace()) { + sb.append(se.getClassName()+":"+se.getLineNumber()); + } + return sb.toString().hashCode(); } + private static void dumpRecords(HemfPicture emf) throws IOException { + FileWriter fw = new FileWriter("record-list.txt"); + int i = 0; + for (HemfRecord r : emf.getRecords()) { + if (r.getEmfRecordType() != HemfRecordType.comment) { + fw.write(i + " " + r.getEmfRecordType() + " " + r.toString() + "\n"); + } + i++; + } + fw.close(); + } + private static BufferedWriter parseEmfLog(Set passed, String logFile) throws IOException { + Path log = Paths.get(logFile); + StandardOpenOption soo; + if (Files.exists(log)) { + soo = StandardOpenOption.APPEND; + try (Stream stream = Files.lines(log)) { + stream.forEach((s) -> passed.add(s.split("\\s")[0])); + } + } else { + soo = StandardOpenOption.CREATE; + } + + return Files.newBufferedWriter(log, StandardCharsets.UTF_8, soo); + } @Test public void testBasicWindows() throws Exception { From 82f118a7d70ae501ef8dec3baba2aa191ebe7a45 Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Sun, 21 Oct 2018 20:49:03 +0000 Subject: [PATCH 13/21] #60656 - Support export file that contains emf and render it correctly git-svn-id: https://svn.apache.org/repos/asf/poi/branches/hemf@1844522 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/poi/hemf/draw/HemfGraphics.java | 29 ++++- .../apache/poi/hemf/record/emf/HemfDraw.java | 50 +++++++- .../apache/poi/hemf/record/emf/HemfFill.java | 75 +++++++++-- .../apache/poi/hemf/record/emf/HemfMisc.java | 118 +++++++++++++++++- .../apache/poi/hemf/record/emf/HemfText.java | 6 +- .../apache/poi/hwmf/draw/HwmfGraphics.java | 61 +++++++-- .../org/apache/poi/hwmf/record/HwmfDraw.java | 10 ++ .../org/apache/poi/hwmf/record/HwmfMisc.java | 5 + .../org/apache/poi/hwmf/record/HwmfText.java | 29 ++--- .../poi/hemf/usermodel/HemfPictureTest.java | 17 ++- 10 files changed, 350 insertions(+), 50 deletions(-) diff --git a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java index 8b18be82f9..e32c451e2a 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java +++ b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java @@ -53,6 +53,7 @@ public class HemfGraphics extends HwmfGraphics { super(graphicsCtx,bbox); // add dummy entry for object index 0, as emf is 1-based objectIndexes.set(0); + saveTransform(); } @Override @@ -82,10 +83,10 @@ public class HemfGraphics extends HwmfGraphics { 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()); +// 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()); } } } @@ -266,7 +267,27 @@ public class HemfGraphics extends HwmfGraphics { } } + /** + * @return the initial AffineTransform, when this graphics context was created + */ + public AffineTransform getInitTransform() { + return new AffineTransform(transforms.peekFirst()); + } + /** + * @return the current AffineTransform + */ + public AffineTransform getTransform() { + return new AffineTransform(graphicsCtx.getTransform()); + } + + /** + * Set the current AffineTransform + * @param tx the current AffineTransform + */ + public void setTransform(AffineTransform tx) { + graphicsCtx.setTransform(tx); + } /** saves the current affine transform on the stack */ private void saveTransform() { diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java index 3596462364..459a0b2ee3 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java @@ -860,6 +860,11 @@ public class HemfDraw { final HemfDrawProperties prop = ctx.getProperties(); prop.setPath(new Path2D.Double()); } + + @Override + public String toString() { + return "{}"; + } } /** @@ -876,6 +881,11 @@ public class HemfDraw { public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { return 0; } + + @Override + public String toString() { + return "{}"; + } } /** @@ -897,6 +907,11 @@ public class HemfDraw { final HemfDrawProperties prop = ctx.getProperties(); prop.setPath(null); } + + @Override + public String toString() { + return "{}"; + } } /** @@ -929,7 +944,6 @@ public class HemfDraw { return 0; } - @Override public void draw(HemfGraphics ctx) { final HemfDrawProperties prop = ctx.getProperties(); @@ -940,6 +954,11 @@ public class HemfDraw { } } + + @Override + public String toString() { + return "{}"; + } } /** @@ -972,12 +991,17 @@ public class HemfDraw { public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { return 0; } + + @Override + public String toString() { + return "{}"; + } } /** * The EMR_STROKEPATH record renders the specified path by using the current pen. */ - public static class EmfStrokePath implements HemfRecord { + public static class EmfStrokePath implements HemfRecord, HemfBounded { protected final Rectangle2D bounds = new Rectangle2D.Double(); @Override @@ -990,6 +1014,28 @@ public class HemfDraw { // A 128-bit WMF RectL object, which specifies bounding rectangle, in device units return readRectL(leis, bounds); } + + @Override + public void draw(HemfGraphics ctx) { + HemfDrawProperties props = ctx.getProperties(); + ctx.draw(props.getPath()); + } + + @Override + public Rectangle2D getRecordBounds() { + return bounds; + } + + @Override + public Rectangle2D getShapeBounds(HemfGraphics ctx) { + HemfDrawProperties props = ctx.getProperties(); + return props.getPath().getBounds2D(); + } + + @Override + public String toString() { + return "{ bounds: { x: "+bounds.getX()+", y: "+bounds.getY()+", w: "+bounds.getWidth()+", h: "+bounds.getHeight()+" }"; + } } static long readRectL(LittleEndianInputStream leis, Rectangle2D bounds) { diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java index 2b814336f2..90a91a0bd1 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java @@ -358,14 +358,12 @@ public class HemfFill { } protected Area getShape() { - final Area frame = new Area(); - rgnRects.forEach((rct) -> frame.add(new Area(rct))); - return frame; + return getRgnShape(rgnRects); } } /** The EMR_INVERTRGN record inverts the colors in the specified region. */ - public static class EmfInvertRgn implements HemfRecord { + public static class EmfInvertRgn implements HemfRecord, HemfBounded { protected final Rectangle2D bounds = new Rectangle2D.Double(); protected final List rgnRects = new ArrayList<>(); @@ -383,6 +381,20 @@ public class HemfFill { size += readRgnData(leis, rgnRects); return size; } + + @Override + public Rectangle2D getRecordBounds() { + return bounds; + } + + @Override + public Rectangle2D getShapeBounds(HemfGraphics ctx) { + return getShape().getBounds2D(); + } + + protected Area getShape() { + return getRgnShape(rgnRects); + } } /** @@ -397,7 +409,7 @@ public class HemfFill { } /** The EMR_FILLRGN record fills the specified region by using the specified brush. */ - public static class EmfFillRgn extends HwmfFill.WmfFillRegion implements HemfRecord { + public static class EmfFillRgn extends HwmfFill.WmfFillRegion implements HemfRecord, HemfBounded { protected final Rectangle2D bounds = new Rectangle2D.Double(); protected final List rgnRects = new ArrayList<>(); @@ -416,6 +428,20 @@ public class HemfFill { size += readRgnData(leis, rgnRects); return size; } + + @Override + public Rectangle2D getRecordBounds() { + return bounds; + } + + @Override + public Rectangle2D getShapeBounds(HemfGraphics ctx) { + return getShape().getBounds2D(); + } + + protected Area getShape() { + return getRgnShape(rgnRects); + } } public static class EmfExtSelectClipRgn implements HemfRecord { @@ -442,9 +468,13 @@ public class HemfFill { } return size; } + + protected Area getShape() { + return getRgnShape(rgnRects); + } } - public static class EmfAlphaBlend implements HemfRecord { + public static class EmfAlphaBlend implements HemfRecord, HemfBounded { /** the destination bounding rectangle in device units */ protected final Rectangle2D bounds = new Rectangle2D.Double(); /** the destination rectangle */ @@ -553,9 +583,24 @@ public class HemfFill { return size; } + + + @Override + public Rectangle2D getRecordBounds() { + return bounds; + } + + @Override + public Rectangle2D getShapeBounds(HemfGraphics ctx) { + return destRect; + } } - public static class EmfSetDiBitsToDevice implements HemfRecord { + /** + * The EMR_SETDIBITSTODEVICE record specifies a block transfer of pixels from specified scanlines of + * a source bitmap to a destination rectangle. + */ + public static class EmfSetDiBitsToDevice implements HemfRecord, HemfBounded { protected final Rectangle2D bounds = new Rectangle2D.Double(); protected final Point2D dest = new Point2D.Double(); protected final Rectangle2D src = new Rectangle2D.Double(); @@ -600,6 +645,16 @@ public class HemfFill { return size; } + + @Override + public Rectangle2D getRecordBounds() { + return bounds; + } + + @Override + public Rectangle2D getShapeBounds(HemfGraphics ctx) { + return new Rectangle2D.Double(dest.getX(), dest.getY(), src.getWidth(), src.getHeight()); + } } static long readBitmap(final LittleEndianInputStream leis, final HwmfBitmapDib bitmap, @@ -759,4 +814,10 @@ public class HemfFill { } } } + + protected static Area getRgnShape(List rgnRects) { + final Area frame = new Area(); + rgnRects.forEach((rct) -> frame.add(new Area(rct))); + return frame; + } } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java index ade921d200..e9ca805fd3 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java @@ -29,6 +29,8 @@ import java.util.ArrayList; import java.util.List; import org.apache.poi.hemf.draw.HemfGraphics; +import org.apache.poi.hwmf.draw.HwmfDrawProperties; +import org.apache.poi.hwmf.draw.HwmfGraphics; import org.apache.poi.hwmf.record.HwmfBinaryRasterOp; import org.apache.poi.hwmf.record.HwmfBitmapDib; import org.apache.poi.hwmf.record.HwmfBrushStyle; @@ -45,6 +47,44 @@ import org.apache.poi.util.LittleEndianConsts; import org.apache.poi.util.LittleEndianInputStream; public class HemfMisc { + + public enum HemfModifyWorldTransformMode { + /** + * Reset the current transform using the identity matrix. + * In this mode, the specified transform data is ignored. + */ + MWT_IDENTITY(1), + /** + * Multiply the current transform. In this mode, the specified transform data is the left multiplicand, + * and the transform that is currently defined in the playback device context is the right multiplicand. + */ + MWT_LEFTMULTIPLY(2), + /** + * Multiply the current transform. In this mode, the specified transform data is the right multiplicand, + * and the transform that is currently defined in the playback device context is the left multiplicand. + */ + MWT_RIGHTMULTIPLY(3), + /** + * Perform the function of an EMR_SETWORLDTRANSFORM record + */ + MWT_SET(4) + ; + + public final int id; + + HemfModifyWorldTransformMode(int id) { + this.id = id; + } + + public static HemfModifyWorldTransformMode valueOf(int id) { + for (HemfModifyWorldTransformMode wrt : values()) { + if (wrt.id == id) return wrt; + } + return null; + } + } + + public static class EmfEof implements HemfRecord { protected final List palette = new ArrayList<>(); @@ -518,6 +558,11 @@ public class HemfMisc { public void draw(HemfGraphics ctx) { ctx.getProperties().setPenMiterLimit(miterLimit); } + + @Override + public String toString() { + return "{ miterLimit: "+miterLimit+" }"; + } } @@ -552,11 +597,30 @@ public class HemfMisc { public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { return readXForm(leis, xForm); } + + @Override + public void draw(HemfGraphics ctx) { + AffineTransform tx = ctx.getInitTransform(); + tx.concatenate(xForm); + ctx.setTransform(tx); + } + + @Override + public String toString() { + return + "{ xForm: " + + "{ scaleX: "+xForm.getScaleX()+ + ", shearX: "+xForm.getShearX()+ + ", transX: "+xForm.getTranslateX()+ + ", scaleY: "+xForm.getScaleY()+ + ", shearY: "+xForm.getShearY()+ + ", transY: "+xForm.getTranslateY()+" } }"; + } } public static class EmfModifyWorldTransform implements HemfRecord { protected final AffineTransform xForm = new AffineTransform(); - protected int modifyWorldTransformMode; + protected HemfModifyWorldTransformMode modifyWorldTransformMode; @Override public HemfRecordType getEmfRecordType() { @@ -572,16 +636,54 @@ public class HemfMisc { // 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(); + modifyWorldTransformMode = HemfModifyWorldTransformMode.valueOf((int)leis.readUInt()); return size + LittleEndianConsts.INT_SIZE; } + @Override + public void draw(HemfGraphics ctx) { + if (modifyWorldTransformMode == null) { + return; + } + + switch (modifyWorldTransformMode) { + case MWT_IDENTITY: + ctx.setTransform(ctx.getInitTransform()); + break; + case MWT_LEFTMULTIPLY: { + AffineTransform tx = new AffineTransform(xForm); + tx.concatenate(ctx.getTransform()); + ctx.setTransform(tx); + break; + } + case MWT_RIGHTMULTIPLY: { + AffineTransform tx = new AffineTransform(xForm); + tx.preConcatenate(ctx.getTransform()); + ctx.setTransform(tx); + break; + } + default: + case MWT_SET: { + AffineTransform tx = ctx.getInitTransform(); + tx.concatenate(xForm); + ctx.setTransform(tx); + break; + } + } + } + @Override public String toString() { return - "{ xForm: { scaleX: "+xForm.getScaleX()+", shearX: "+xForm.getShearX()+", transX: "+xForm.getTranslateX()+", scaleY: "+xForm.getScaleY()+", shearY: "+xForm.getShearY()+", transY: "+xForm.getTranslateY()+" }"+ - ", modifyWorldTransformMode: "+modifyWorldTransformMode+" }"; + "{ xForm: " + + "{ scaleX: "+xForm.getScaleX()+ + ", shearX: "+xForm.getShearX()+ + ", transX: "+xForm.getTranslateX()+ + ", scaleY: "+xForm.getScaleY()+ + ", shearY: "+xForm.getShearY()+ + ", transY: "+xForm.getTranslateY()+" }"+ + ", modifyWorldTransformMode: '"+modifyWorldTransformMode+"' }"; } } @@ -626,5 +728,13 @@ public class HemfMisc { return size; } + + @Override + public void applyObject(HwmfGraphics ctx) { + super.applyObject(ctx); + HwmfDrawProperties props = ctx.getProperties(); + props.setBrushStyle(HwmfBrushStyle.BS_PATTERN); + props.setBrushBitmap(bitmap.getImage()); + } } } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java index 6b2f9a6d26..97a81d9102 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java @@ -57,6 +57,7 @@ public class HemfText { public static class EmfExtTextOutA extends HwmfText.WmfExtTextOut implements HemfRecord { + protected Rectangle2D boundsIgnored = new Rectangle2D.Double(); protected EmfGraphicsMode graphicsMode; /** @@ -81,7 +82,7 @@ public class HemfText { } // A WMF RectL object. It is not used and MUST be ignored on receipt. - long size = readRectL(leis, bounds); + long size = readRectL(leis, boundsIgnored); // A 32-bit unsigned integer that specifies the graphics mode from the GraphicsMode enumeration graphicsMode = EmfGraphicsMode.values()[leis.readInt()-1]; @@ -192,8 +193,7 @@ public class HemfText { @Override public void draw(HwmfGraphics ctx) { - Rectangle2D bounds = new Rectangle2D.Double(reference.getX(), reference.getY(), 0, 0); - ctx.drawString(rawTextBytes, bounds, dx, isUnicode()); + ctx.drawString(rawTextBytes, reference, bounds, dx, isUnicode()); } @Override diff --git a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java index dff46ce863..7a325dc0a0 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java @@ -25,8 +25,11 @@ import java.awt.Paint; import java.awt.Rectangle; import java.awt.Shape; import java.awt.TexturePaint; +import java.awt.font.FontRenderContext; import java.awt.font.TextAttribute; +import java.awt.font.TextLayout; import java.awt.geom.AffineTransform; +import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.nio.charset.Charset; @@ -47,6 +50,7 @@ import org.apache.poi.hwmf.record.HwmfMisc.WmfSetBkMode.HwmfBkMode; import org.apache.poi.hwmf.record.HwmfObjectTableEntry; import org.apache.poi.hwmf.record.HwmfPenStyle; import org.apache.poi.hwmf.record.HwmfPenStyle.HwmfLineDash; +import org.apache.poi.hwmf.record.HwmfText; import org.apache.poi.sl.draw.DrawFactory; import org.apache.poi.sl.draw.DrawFontManager; import org.apache.poi.util.LocaleUtil; @@ -327,11 +331,11 @@ public class HwmfGraphics { } } - public void drawString(byte[] text, Rectangle2D bounds) { - drawString(text, bounds, null, false); + public void drawString(byte[] text, Point2D reference, Rectangle2D clip) { + drawString(text, reference, clip, null, false); } - public void drawString(byte[] text, Rectangle2D bounds, List dx, boolean isUnicode) { + public void drawString(byte[] text, Point2D reference, Rectangle2D clip, List dx, boolean isUnicode) { HwmfFont font = getProperties().getFont(); if (font == null || text == null || text.length == 0) { @@ -396,18 +400,55 @@ public class HwmfGraphics { } } */ - + double angle = Math.toRadians(-font.getEscapement()/10.); + final HwmfText.HwmfTextAlignment align = prop.getTextAlignLatin(); + final HwmfText.HwmfTextVerticalAlignment valign = prop.getTextVAlignLatin(); + final FontRenderContext frc = graphicsCtx.getFontRenderContext(); + final TextLayout layout = new TextLayout(as.getIterator(), frc); + + final Rectangle2D pixelBounds = layout.getBounds(); + + AffineTransform tx = new AffineTransform(); + switch (align) { + default: + case LEFT: + break; + case CENTER: + tx.translate(-pixelBounds.getWidth() / 2., 0); + break; + case RIGHT: + tx.translate(-pixelBounds.getWidth(), 0); + break; + } + + // TODO: check min/max orientation + switch (valign) { + case TOP: + tx.translate(0, layout.getAscent()); + default: + case BASELINE: + break; + case BOTTOM: + tx.translate(0, pixelBounds.getHeight()); + break; + } + tx.rotate(angle); + Point2D src = new Point2D.Double(); + Point2D dst = new Point2D.Double(); + tx.transform(src, dst); + + // TODO: implement clipping on bounds final AffineTransform at = graphicsCtx.getTransform(); try { - graphicsCtx.translate(bounds.getX(), bounds.getY()); + graphicsCtx.translate(reference.getX(), reference.getY()); graphicsCtx.rotate(angle); - graphicsCtx.translate(0, fontH); - if (getProperties().getBkMode() == HwmfBkMode.OPAQUE) { + graphicsCtx.translate(dst.getX(), dst.getY()); + if (getProperties().getBkMode() == HwmfBkMode.OPAQUE && clip != null) { // TODO: validate bounds graphicsCtx.setBackground(getProperties().getBackgroundColor().getColor()); - graphicsCtx.fill(new Rectangle2D.Double(0, 0, bounds.getWidth(), bounds.getHeight())); + graphicsCtx.fill(new Rectangle2D.Double(0, 0, clip.getWidth(), clip.getHeight())); } graphicsCtx.setColor(getProperties().getTextColor().getColor()); graphicsCtx.drawString(as.getIterator(), 0, 0); @@ -415,11 +456,11 @@ public class HwmfGraphics { graphicsCtx.setTransform(at); } } - + private void addAttributes(AttributedString as, HwmfFont font) { DrawFontManager fontHandler = DrawFactory.getInstance(graphicsCtx).getFontManager(graphicsCtx); FontInfo fontInfo = fontHandler.getMappedFont(graphicsCtx, font); - + as.addAttribute(TextAttribute.FAMILY, fontInfo.getTypeface()); as.addAttribute(TextAttribute.SIZE, getFontHeight(font)); if (font.isStrikeOut()) { diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java index 64f2dae8c9..b4e7ba73ed 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java @@ -374,6 +374,16 @@ public class HwmfDraw { public void draw(HwmfGraphics ctx) { ctx.fill(bounds); } + + @Override + public String toString() { + return + "{ bounds: " + + "{ x: "+bounds.getX()+ + ", y: "+bounds.getY()+ + ", w: "+bounds.getWidth()+ + ", h: "+bounds.getHeight()+" } }"; + } } /** diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java index 7bc46795bb..7197674ffa 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java @@ -302,6 +302,11 @@ public class HwmfMisc { public void draw(HwmfGraphics ctx) { } + + @Override + public String toString() { + return "{ drawMode: '"+drawMode+"' }"; + } } /** diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java index fb45170d22..a0b240d048 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java @@ -164,17 +164,9 @@ public class HwmfText { * The string is written at the location specified by the XStart and YStart fields. */ private byte[] rawTextBytes; - /** - * A 16-bit signed integer that defines the vertical (y-axis) coordinate, in logical - * units, of the point where drawing is to start. - */ - private int yStart; - /** - * A 16-bit signed integer that defines the horizontal (x-axis) coordinate, in - * logical units, of the point where drawing is to start. - */ - private int xStart; - + + protected Point2D reference = new Point2D.Double(); + @Override public HwmfRecordType getWmfRecordType() { return HwmfRecordType.textOut; @@ -185,15 +177,19 @@ public class HwmfText { stringLength = leis.readShort(); rawTextBytes = IOUtils.safelyAllocate(stringLength+(stringLength&1), MAX_RECORD_LENGTH); leis.readFully(rawTextBytes); - yStart = leis.readShort(); - xStart = leis.readShort(); + // A 16-bit signed integer that defines the vertical (y-axis) coordinate, in logical + // units, of the point where drawing is to start. + int yStart = leis.readShort(); + // A 16-bit signed integer that defines the horizontal (x-axis) coordinate, in + // logical units, of the point where drawing is to start. + int xStart = leis.readShort(); + reference.setLocation(xStart, yStart); return 3*LittleEndianConsts.SHORT_SIZE+rawTextBytes.length; } @Override public void draw(HwmfGraphics ctx) { - Rectangle2D bounds = new Rectangle2D.Double(xStart, yStart, 0, 0); - ctx.drawString(getTextBytes(), bounds); + ctx.drawString(getTextBytes(), reference, null); } public String getText(Charset charset) { @@ -398,8 +394,7 @@ public class HwmfText { @Override public void draw(HwmfGraphics ctx) { - Rectangle2D bounds = new Rectangle2D.Double(reference.getX(), reference.getY(), 0, 0); - ctx.drawString(rawTextBytes, bounds, dx, false); + ctx.drawString(rawTextBytes, reference, bounds, dx, false); } public String getText(Charset charset) throws IOException { diff --git a/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java b/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java index 94be4712e5..cabe627f66 100644 --- a/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java +++ b/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java @@ -63,7 +63,8 @@ public class HemfPictureTest { public void paint() throws IOException { byte buf[] = new byte[50_000_000]; - final boolean writeLog = true; + final boolean writeLog = false; + final boolean dumpRecords = false; final boolean savePng = true; Set passed = new HashSet<>(); @@ -101,6 +102,10 @@ public class HemfPictureTest { } } + if (dumpRecords) { + dumpRecords(emf); + } + Graphics2D g = null; try { Dimension2D dim = emf.getSize(); @@ -112,17 +117,23 @@ public class HemfPictureTest { width *= 1500. / max; height *= 1500. / max; } + width = Math.ceil(width); + height = Math.ceil(height); - BufferedImage bufImg = new BufferedImage((int)Math.ceil(width), (int)Math.ceil(height), BufferedImage.TYPE_INT_ARGB); + BufferedImage bufImg = new BufferedImage((int)width, (int)height, BufferedImage.TYPE_INT_ARGB); 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); + g.setComposite(AlphaComposite.Clear); + g.fillRect(0, 0, (int)width, (int)height); + g.setComposite(AlphaComposite.Src); + emf.draw(g, new Rectangle2D.Double(0, 0, width, height)); - final File pngName = new File("build/tmp", etName.replaceFirst(".*"+"/", "").replace(".emf", ".png")); + final File pngName = new File("build/tmp", etName.replaceFirst(".+/", "").replace(".emf", ".png")); if (savePng) { ImageIO.write(bufImg, "PNG", pngName); } From 11e4643e5bc764feb0f92fd80b457f27ec7e5891 Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Sun, 21 Oct 2018 22:42:03 +0000 Subject: [PATCH 14/21] #60656 - Support export file that contains emf and render it correctly git-svn-id: https://svn.apache.org/repos/asf/poi/branches/hemf@1844524 13f79535-47bb-0310-9956-ffa450edef68 --- .../org/apache/poi/hemf/draw/HemfGraphics.java | 16 +++++++++------- .../org/apache/poi/hemf/record/emf/HemfDraw.java | 8 +------- .../poi/hemf/record/emf/HemfRecordType.java | 4 ++-- .../org/apache/poi/hemf/record/emf/HemfText.java | 6 ++---- 4 files changed, 14 insertions(+), 20 deletions(-) diff --git a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java index e32c451e2a..011cb56ff7 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java +++ b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java @@ -106,19 +106,21 @@ public class HemfGraphics extends HwmfGraphics { final Path2D path; if (useBracket) { path = prop.getPath(); - if (path.getCurrentPoint() == null) { - // workaround if a path has been started and no MoveTo command - // has been specified before the first lineTo/splineTo - final Point2D loc = prop.getLocation(); - path.moveTo(loc.getX(), loc.getY()); - } } else { path = new Path2D.Double(); Point2D pnt = prop.getLocation(); path.moveTo(pnt.getX(),pnt.getY()); } - pathConsumer.accept(path); + try { + pathConsumer.accept(path); + } catch (Exception e) { + // workaround if a path has been started and no MoveTo command + // has been specified before the first lineTo/splineTo + final Point2D loc = prop.getLocation(); + path.moveTo(loc.getX(), loc.getY()); + pathConsumer.accept(path); + } prop.setLocation(path.getCurrentPoint()); if (!useBracket) { diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java index 459a0b2ee3..0eb6522e44 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java @@ -17,10 +17,6 @@ package org.apache.poi.hemf.record.emf; -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; @@ -32,10 +28,8 @@ import java.io.IOException; import org.apache.poi.hemf.draw.HemfDrawProperties; import org.apache.poi.hemf.draw.HemfGraphics; -import org.apache.poi.hwmf.record.HwmfColorRef; import org.apache.poi.hwmf.record.HwmfDraw; import org.apache.poi.hwmf.record.HwmfDraw.WmfSelectObject; -import org.apache.poi.hwmf.record.HwmfPenStyle; import org.apache.poi.util.LittleEndianConsts; import org.apache.poi.util.LittleEndianInputStream; @@ -242,6 +236,7 @@ public class HemfDraw { // 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); + poly.lineTo(pnt.getX(), pnt.getY()); } } else { poly.lineTo(pnt.getX(), pnt.getY()); @@ -952,7 +947,6 @@ public class HemfDraw { path.closePath(); prop.setLocation(path.getCurrentPoint()); } - } @Override diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java index 7a3bf470e0..5b19f9d128 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java @@ -106,8 +106,8 @@ public enum HemfRecordType { setDiBitsToDevice(0x00000050, HemfFill.EmfSetDiBitsToDevice::new), stretchDiBits(0x00000051, HemfFill.EmfStretchDiBits::new), extCreateFontIndirectW(0x00000052, HemfText.ExtCreateFontIndirectW::new), - exttextouta(0x00000053, HemfText.EmfExtTextOutA::new), - exttextoutw(0x00000054, HemfText.EmfExtTextOutW::new), + extTextOutA(0x00000053, HemfText.EmfExtTextOutA::new), + extTextOutW(0x00000054, HemfText.EmfExtTextOutW::new), polyBezier16(0x00000055, HemfDraw.EmfPolyBezier16::new), polygon16(0x00000056, HemfDraw.EmfPolygon16::new), polyline16(0x00000057, HemfDraw.EmfPolyline16::new), diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java index 97a81d9102..9f36812179 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java @@ -28,7 +28,6 @@ import java.awt.geom.Rectangle2D; import java.io.IOException; import java.nio.charset.Charset; -import org.apache.commons.codec.Charsets; import org.apache.poi.hemf.draw.HemfGraphics; import org.apache.poi.hwmf.draw.HwmfGraphics; import org.apache.poi.hwmf.record.HwmfText; @@ -38,7 +37,6 @@ import org.apache.poi.util.IOUtils; import org.apache.poi.util.Internal; import org.apache.poi.util.LittleEndianConsts; import org.apache.poi.util.LittleEndianInputStream; -import org.apache.poi.util.LocaleUtil; import org.apache.poi.util.RecordFormatException; /** @@ -72,7 +70,7 @@ public class HemfText { @Override public HemfRecordType getEmfRecordType() { - return HemfRecordType.exttextouta; + return HemfRecordType.extTextOutA; } @Override @@ -209,7 +207,7 @@ public class HemfText { @Override public HemfRecordType getEmfRecordType() { - return HemfRecordType.exttextoutw; + return HemfRecordType.extTextOutW; } public String getText() throws IOException { From 29587e78dfa0592fcc93a10850aafb7a8bac43e6 Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Sat, 27 Oct 2018 00:51:19 +0000 Subject: [PATCH 15/21] #60656 - Support export file that contains emf and render it correctly git-svn-id: https://svn.apache.org/repos/asf/poi/branches/hemf@1844931 13f79535-47bb-0310-9956-ffa450edef68 --- .../poi/hemf/draw/HemfDrawProperties.java | 21 +- .../apache/poi/hemf/draw/HemfGraphics.java | 106 +++--- .../poi/hemf/record/emf/HemfBounded.java | 45 --- .../apache/poi/hemf/record/emf/HemfDraw.java | 237 ++++++++------ .../apache/poi/hemf/record/emf/HemfFill.java | 150 ++------- .../poi/hemf/record/emf/HemfRecordType.java | 4 +- .../poi/hemf/record/emf/HemfWindowing.java | 72 ++--- .../apache/poi/hwmf/draw/HwmfGraphics.java | 11 +- .../apache/poi/hwmf/record/HwmfBitmapDib.java | 27 +- .../org/apache/poi/hwmf/record/HwmfDraw.java | 177 +++++++--- .../org/apache/poi/hwmf/record/HwmfFill.java | 5 +- .../org/apache/poi/hwmf/record/HwmfMisc.java | 15 + .../org/apache/poi/hwmf/record/HwmfText.java | 6 +- .../apache/poi/hwmf/record/HwmfWindowing.java | 306 ++++++++---------- .../poi/hemf/usermodel/HemfPictureTest.java | 12 +- 15 files changed, 609 insertions(+), 585 deletions(-) delete mode 100644 src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfBounded.java diff --git a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java index b12a4caa6c..f22f5a460f 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java +++ b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java @@ -17,6 +17,7 @@ package org.apache.poi.hemf.draw; +import java.awt.Shape; import java.awt.geom.Path2D; import org.apache.poi.hwmf.draw.HwmfDrawProperties; @@ -25,6 +26,8 @@ public class HemfDrawProperties extends HwmfDrawProperties { /** Path for path bracket operations */ protected Path2D path = null; + protected Shape clip = null; + protected boolean usePathBracket = false; public HemfDrawProperties() { @@ -33,6 +36,8 @@ public class HemfDrawProperties extends HwmfDrawProperties { public HemfDrawProperties(HemfDrawProperties other) { super(other); path = (other.path != null) ? (Path2D)other.path.clone() : null; + // TODO: check how to clone + clip = other.clip; } /** @@ -55,7 +60,19 @@ public class HemfDrawProperties extends HwmfDrawProperties { * @return {@code true}, if the drawing should go to the path bracket, * if {@code false} draw directly to the graphics context */ - public boolean usePathBracket() { - return path != null; + public boolean getUsePathBracket() { + return usePathBracket; + } + + public void setUsePathBracket(boolean usePathBracket) { + this.usePathBracket = usePathBracket; + } + + public Shape getClip() { + return clip; + } + + public void setClip(Shape shape) { + clip = shape; } } diff --git a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java index 011cb56ff7..1d947f24ff 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java +++ b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java @@ -22,15 +22,15 @@ import static org.apache.poi.hwmf.record.HwmfBrushStyle.BS_SOLID; import java.awt.Color; import java.awt.Graphics2D; +import java.awt.Shape; 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.util.ArrayDeque; -import java.util.Deque; import java.util.function.Consumer; -import org.apache.poi.hemf.record.emf.HemfBounded; +import org.apache.poi.hemf.record.emf.HemfFill; import org.apache.poi.hemf.record.emf.HemfRecord; import org.apache.poi.hwmf.draw.HwmfGraphics; import org.apache.poi.hwmf.record.HwmfColorRef; @@ -47,13 +47,13 @@ public class HemfGraphics extends HwmfGraphics { private static final HwmfColorRef BLACK = new HwmfColorRef(Color.BLACK); - private final Deque transforms = new ArrayDeque<>(); + private final AffineTransform initTrans; public HemfGraphics(Graphics2D graphicsCtx, Rectangle2D bbox) { super(graphicsCtx,bbox); - // add dummy entry for object index 0, as emf is 1-based + // add dummy entry for object ind ex 0, as emf is 1-based objectIndexes.set(0); - saveTransform(); + initTrans = new AffineTransform(graphicsCtx.getTransform()); } @Override @@ -66,48 +66,34 @@ public class HemfGraphics extends HwmfGraphics { @Override public void saveProperties() { - propStack.add(getProperties()); - prop = new HemfDrawProperties((HemfDrawProperties)prop); + final HemfDrawProperties oldProp = getProperties(); + oldProp.setClip(graphicsCtx.getClip()); + propStack.add(oldProp); + prop = new HemfDrawProperties(oldProp); } @Override - public void updateWindowMapMode() { - // ignore window settings + public void restoreProperties(int index) { + super.restoreProperties(index); + HemfDrawProperties newProp = getProperties(); + graphicsCtx.setClip(newProp.getClip()); } 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(); - } } @Internal - public void draw(Consumer pathConsumer) { + public void draw(Consumer pathConsumer, FillDrawStyle fillDraw) { final HemfDrawProperties prop = getProperties(); - final boolean useBracket = prop.usePathBracket(); + final boolean useBracket = prop.getUsePathBracket(); final Path2D path; if (useBracket) { path = prop.getPath(); } else { path = new Path2D.Double(); + path.setWindingRule(prop.getWindingRule()); Point2D pnt = prop.getLocation(); path.moveTo(pnt.getX(),pnt.getY()); } @@ -124,8 +110,18 @@ public class HemfGraphics extends HwmfGraphics { prop.setLocation(path.getCurrentPoint()); if (!useBracket) { - // TODO: when to use draw vs. fill? - super.draw(path); + switch (fillDraw) { + case FILL: + super.fill(path); + break; + case DRAW: + super.draw(path); + break; + case FILL_DRAW: + super.fill(path); + super.draw(path); + break; + } } } @@ -273,7 +269,7 @@ public class HemfGraphics extends HwmfGraphics { * @return the initial AffineTransform, when this graphics context was created */ public AffineTransform getInitTransform() { - return new AffineTransform(transforms.peekFirst()); + return new AffineTransform(initTrans); } /** @@ -291,13 +287,41 @@ public class HemfGraphics extends HwmfGraphics { graphicsCtx.setTransform(tx); } - /** saves the current affine transform on the stack */ - private void saveTransform() { - transforms.push(graphicsCtx.getTransform()); - } + public void setClip(Shape clip, HemfFill.HemfRegionMode regionMode) { + Shape oldClip = graphicsCtx.getClip(); - /** restore the last saved affine transform */ - private void restoreTransform() { - graphicsCtx.setTransform(transforms.pop()); + switch (regionMode) { + case RGN_AND: + graphicsCtx.clip(clip); + break; + case RGN_OR: + if (oldClip == null) { + graphicsCtx.setClip(clip); + } else { + Area area = new Area(oldClip); + area.add(new Area(clip)); + graphicsCtx.setClip(area); + } + break; + case RGN_XOR: + if (oldClip == null) { + graphicsCtx.setClip(clip); + } else { + Area area = new Area(oldClip); + area.exclusiveOr(new Area(clip)); + graphicsCtx.setClip(area); + } + break; + case RGN_DIFF: + if (oldClip != null) { + Area area = new Area(oldClip); + area.subtract(new Area(clip)); + graphicsCtx.setClip(area); + } + break; + case RGN_COPY: + graphicsCtx.setClip(clip); + break; + } } } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfBounded.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfBounded.java deleted file mode 100644 index dee8f94ffa..0000000000 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfBounded.java +++ /dev/null @@ -1,45 +0,0 @@ -/* ==================================================================== - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -==================================================================== */ - -package org.apache.poi.hemf.record.emf; - -import java.awt.geom.Rectangle2D; - -import org.apache.poi.hemf.draw.HemfGraphics; - -/** - * In EMF, shape records bring their own bounding. - * The record bounding is in the same space as the global drawing context, - * but the specified shape points can have a different space and therefore - * need to be translated/normalized - */ -public interface HemfBounded { - /** - * Getter for the outer bounds which are given in the record - * - * @return the bounds specified in the record - */ - Rectangle2D getRecordBounds(); - - /** - * Getter for the inner bounds which are calculated by the shape points - * - * @param ctx the graphics context - * @return the bounds of the shape points - */ - Rectangle2D getShapeBounds(HemfGraphics ctx); -} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java index 0eb6522e44..103f90a052 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java @@ -17,8 +17,10 @@ package org.apache.poi.hemf.record.emf; +import static org.apache.poi.hwmf.record.HwmfDraw.boundsToString; + +import java.awt.Shape; 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; @@ -28,6 +30,7 @@ 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.FillDrawStyle; import org.apache.poi.hwmf.record.HwmfDraw; import org.apache.poi.hwmf.record.HwmfDraw.WmfSelectObject; import org.apache.poi.util.LittleEndianConsts; @@ -88,7 +91,7 @@ public class HemfDraw { /** The EMR_POLYBEZIER record specifies one or more Bezier curves. */ - public static class EmfPolyBezier extends HwmfDraw.WmfPolygon implements HemfRecord, HemfBounded { + public static class EmfPolyBezier extends HwmfDraw.WmfPolygon implements HemfRecord { private final Rectangle2D bounds = new Rectangle2D.Double(); @Override @@ -120,7 +123,7 @@ public class HemfDraw { final int points = Math.min(count, 16384); size += LittleEndianConsts.INT_SIZE; - poly = new Path2D.Double(Path2D.WIND_EVEN_ODD, points); + poly = new Path2D.Double(Path2D.WIND_EVEN_ODD, points+2); /* Cubic Bezier curves are defined using the endpoints and control points * specified by the points field. The first curve is drawn from the first @@ -135,8 +138,8 @@ public class HemfDraw { Point2D pnt[] = { new Point2D.Double(), new Point2D.Double(), new Point2D.Double() }; // points-1 because of the first point - final int pointCnt = hasStartPoint() ? points-1 : points; - for (int i=0; i+3 path.append(poly, !hasStartPoint()), getFillDrawStyle()); + } } /** @@ -203,7 +204,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, HemfBounded { + public static class EmfPolygon extends HwmfDraw.WmfPolygon implements HemfRecord { private final Rectangle2D bounds = new Rectangle2D.Double(); @Override @@ -246,25 +247,25 @@ 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 */ protected boolean hasStartPoint() { return true; } + + @Override + protected FillDrawStyle getFillDrawStyle() { + // The polygon SHOULD be outlined using the current pen and filled using the current brush and + // polygon fill mode. The polygon SHOULD be closed automatically by drawing a line from the last + // vertex to the first. + return FillDrawStyle.FILL_DRAW; + } + + @Override + public void draw(HemfGraphics ctx) { + ctx.draw(path -> path.append(poly, false), getFillDrawStyle()); + } } /** @@ -295,8 +296,9 @@ public class HemfDraw { } @Override - protected boolean isFill() { - return false; + protected FillDrawStyle getFillDrawStyle() { + // The line segments SHOULD be drawn using the current pen. + return FillDrawStyle.DRAW; } } @@ -333,14 +335,7 @@ public class HemfDraw { @Override 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; + polyTo(ctx, poly, getFillDrawStyle()); } } @@ -374,14 +369,7 @@ public class HemfDraw { @Override 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; + polyTo(ctx, poly, getFillDrawStyle()); } } @@ -406,7 +394,7 @@ public class HemfDraw { /** * The EMR_POLYPOLYGON record specifies a series of closed polygons. */ - public static class EmfPolyPolygon extends HwmfDraw.WmfPolyPolygon implements HemfRecord, HemfBounded { + public static class EmfPolyPolygon extends HwmfDraw.WmfPolyPolygon implements HemfRecord { private final Rectangle2D bounds = new Rectangle2D.Double(); @Override @@ -461,22 +449,15 @@ public class HemfDraw { return size; } - /** - * @return true, if a polyline should be closed, i.e. is a polygon - */ - protected boolean isClosed() { - return true; - } @Override - public Rectangle2D getRecordBounds() { - return bounds; - } + public void draw(HemfGraphics ctx) { + Shape shape = getShape(ctx); + if (shape == null) { + return; + } - @Override - public Rectangle2D getShapeBounds(HemfGraphics ctx) { - Area area = getShape(ctx); - return area == null ? bounds : area.getBounds2D(); + ctx.draw(path -> path.append(shape, false), getFillDrawStyle()); } } @@ -512,8 +493,8 @@ public class HemfDraw { } @Override - protected boolean isFill() { - return false; + protected FillDrawStyle getFillDrawStyle() { + return FillDrawStyle.DRAW; } } @@ -563,12 +544,12 @@ public class HemfDraw { @Override public void draw(final HemfGraphics ctx) { - ctx.draw((path) -> path.moveTo(point.getX(), point.getY())); + ctx.draw((path) -> path.moveTo(point.getX(), point.getY()), FillDrawStyle.NONE); } } /** - * The EMR_ARCTO record specifies an elliptical arc. + * The EMR_ARC record specifies an elliptical arc. * It resets the current position to the end point of the arc. */ public static class EmfArc extends HwmfDraw.WmfArc implements HemfRecord { @@ -587,8 +568,7 @@ public class HemfDraw { @Override public void draw(HemfGraphics ctx) { - super.draw(ctx); - ctx.getProperties().setLocation(endPoint); + ctx.draw(path -> path.append(getShape(), false), getFillDrawStyle()); } } @@ -610,6 +590,11 @@ public class HemfDraw { size += readPointL(leis, endPoint); return size; } + + @Override + public void draw(HemfGraphics ctx) { + ctx.draw(path -> path.append(getShape(), false), getFillDrawStyle()); + } } /** @@ -629,6 +614,11 @@ public class HemfDraw { size += readPointL(leis, endPoint); return size; } + + @Override + public void draw(HemfGraphics ctx) { + ctx.draw(path -> path.append(getShape(), false), getFillDrawStyle()); + } } /** @@ -646,6 +636,11 @@ public class HemfDraw { public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { return readRectL(leis, bounds); } + + @Override + public void draw(HemfGraphics ctx) { + ctx.draw(path -> path.append(getShape(), false), FillDrawStyle.FILL_DRAW); + } } /** @@ -662,6 +657,11 @@ public class HemfDraw { public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { return readRectL(leis, bounds); } + + @Override + public void draw(HemfGraphics ctx) { + ctx.draw(path -> path.append(bounds, false), FillDrawStyle.FILL_DRAW); + } } /** @@ -684,6 +684,11 @@ public class HemfDraw { return size + 2*LittleEndianConsts.INT_SIZE; } + + @Override + public void draw(HemfGraphics ctx) { + ctx.draw(path -> path.append(getShape(), false), FillDrawStyle.FILL_DRAW); + } } /** @@ -703,7 +708,7 @@ public class HemfDraw { @Override public void draw(final HemfGraphics ctx) { - ctx.draw((path) -> path.lineTo(point.getX(), point.getY())); + ctx.draw((path) -> path.lineTo(point.getX(), point.getY()), FillDrawStyle.DRAW); } } @@ -728,12 +733,12 @@ public class HemfDraw { @Override public void draw(final HemfGraphics ctx) { final Arc2D arc = getShape(); - ctx.draw((path) -> path.append(arc, true)); + ctx.draw((path) -> path.append(arc, true), getFillDrawStyle()); } } /** The EMR_POLYDRAW record specifies a set of line segments and Bezier curves. */ - public static class EmfPolyDraw extends HwmfDraw.WmfPolygon implements HemfRecord, HemfBounded { + public static class EmfPolyDraw extends HwmfDraw.WmfPolygon implements HemfRecord { private final Rectangle2D bounds = new Rectangle2D.Double(); @Override @@ -808,13 +813,14 @@ public class HemfDraw { } @Override - public Rectangle2D getRecordBounds() { - return bounds; + protected FillDrawStyle getFillDrawStyle() { + // Draws a set of line segments and Bezier curves. + return FillDrawStyle.DRAW; } @Override - public Rectangle2D getShapeBounds(HemfGraphics ctx) { - return poly.getBounds2D(); + public void draw(HemfGraphics ctx) { + ctx.draw(path -> path.append(poly, false), getFillDrawStyle()); } } @@ -854,6 +860,7 @@ public class HemfDraw { public void draw(HemfGraphics ctx) { final HemfDrawProperties prop = ctx.getProperties(); prop.setPath(new Path2D.Double()); + prop.setUsePathBracket(true); } @Override @@ -877,6 +884,12 @@ public class HemfDraw { return 0; } + @Override + public void draw(HemfGraphics ctx) { + final HemfDrawProperties prop = ctx.getProperties(); + prop.setUsePathBracket(false); + } + @Override public String toString() { return "{}"; @@ -901,6 +914,7 @@ public class HemfDraw { public void draw(HemfGraphics ctx) { final HemfDrawProperties prop = ctx.getProperties(); prop.setPath(null); + prop.setUsePathBracket(false); } @Override @@ -995,7 +1009,7 @@ public class HemfDraw { /** * The EMR_STROKEPATH record renders the specified path by using the current pen. */ - public static class EmfStrokePath implements HemfRecord, HemfBounded { + public static class EmfStrokePath implements HemfRecord { protected final Rectangle2D bounds = new Rectangle2D.Double(); @Override @@ -1006,29 +1020,64 @@ public class HemfDraw { @Override public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { // A 128-bit WMF RectL object, which specifies bounding rectangle, in device units - return readRectL(leis, bounds); + return (recordSize == 0) ? 0 : readRectL(leis, bounds); } @Override public void draw(HemfGraphics ctx) { HemfDrawProperties props = ctx.getProperties(); - ctx.draw(props.getPath()); - } - - @Override - public Rectangle2D getRecordBounds() { - return bounds; - } - - @Override - public Rectangle2D getShapeBounds(HemfGraphics ctx) { - HemfDrawProperties props = ctx.getProperties(); - return props.getPath().getBounds2D(); + Path2D path = props.getPath(); + path.setWindingRule(ctx.getProperties().getWindingRule()); + ctx.draw(path); } @Override public String toString() { - return "{ bounds: { x: "+bounds.getX()+", y: "+bounds.getY()+", w: "+bounds.getWidth()+", h: "+bounds.getHeight()+" }"; + return boundsToString(bounds); + } + } + + + /** + * 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 extends EmfStrokePath { + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.fillPath; + } + + @Override + public void draw(HemfGraphics ctx) { + final HemfDrawProperties prop = ctx.getProperties(); + final Path2D path = (Path2D)prop.getPath().clone(); + path.closePath(); + path.setWindingRule(ctx.getProperties().getWindingRule()); + ctx.fill(path); + } + } + + /** + * The EMR_STROKEANDFILLPATH record closes any open figures in a path, strokes the outline of the + * path by using the current pen, and fills its interior by using the current brush. + */ + public static class EmfStrokeAndFillPath extends EmfStrokePath { + protected final Rectangle2D bounds = new Rectangle2D.Double(); + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.strokeAndFillPath; + } + + @Override + public void draw(HemfGraphics ctx) { + HemfDrawProperties props = ctx.getProperties(); + Path2D path = props.getPath(); + path.closePath(); + path.setWindingRule(ctx.getProperties().getWindingRule()); + ctx.fill(path); + ctx.draw(path); } } @@ -1080,7 +1129,7 @@ public class HemfDraw { return 2*LittleEndianConsts.INT_SIZE; } - private static void polyTo(final HemfGraphics ctx, final Path2D poly) { + private static void polyTo(final HemfGraphics ctx, final Path2D poly, FillDrawStyle fillDrawStyle) { if (poly.getCurrentPoint() == null) { return; } @@ -1092,6 +1141,6 @@ public class HemfDraw { return; } - ctx.draw((path) -> path.append(pi, true)); + ctx.draw((path) -> path.append(pi, true), fillDrawStyle); } } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java index 90a91a0bd1..7b172d5506 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java @@ -51,10 +51,29 @@ public class HemfFill { private static final int MAX_RECORD_LENGTH = 10_000_000; public enum HemfRegionMode { + /** + * The new clipping region includes the intersection (overlapping areas) + * of the current clipping region and the current path (or new region). + */ RGN_AND(0x01), + /** + * The new clipping region includes the union (combined areas) + * of the current clipping region and the current path (or new region). + */ RGN_OR(0x02), + /** + * The new clipping region includes the union of the current clipping region + * and the current path (or new region) but without the overlapping areas + */ RGN_XOR(0x03), + /** + * The new clipping region includes the areas of the current clipping region + * with those of the current path (or new region) excluded. + */ RGN_DIFF(0x04), + /** + * The new clipping region is the current path (or the new region). + */ RGN_COPY(0x05); int flag; @@ -114,7 +133,7 @@ public class HemfFill { * optionally in combination with a brush pattern, according to a specified raster operation, stretching or * compressing the output to fit the dimensions of the destination, if necessary. */ - public static class EmfStretchBlt extends HwmfFill.WmfBitBlt implements HemfRecord, HemfBounded { + public static class EmfStretchBlt extends HwmfFill.WmfBitBlt implements HemfRecord { protected final Rectangle2D bounds = new Rectangle2D.Double(); /** An XForm object that specifies a world-space to page-space transform to apply to the source bitmap. */ @@ -196,16 +215,6 @@ public class HemfFill { return size; } - @Override - public Rectangle2D getRecordBounds() { - return bounds; - } - - @Override - public Rectangle2D getShapeBounds(HemfGraphics ctx) { - return dstBounds; - } - protected boolean srcEqualsDstDimension() { return false; } @@ -226,7 +235,7 @@ public class HemfFill { * destination rectangle, optionally in combination with a brush pattern, according to a specified raster * operation, stretching or compressing the output to fit the dimensions of the destination, if necessary. */ - public static class EmfStretchDiBits extends HwmfFill.WmfStretchDib implements HemfRecord, HemfBounded { + public static class EmfStretchDiBits extends HwmfFill.WmfStretchDib implements HemfRecord { protected final Rectangle2D bounds = new Rectangle2D.Double(); @Override @@ -286,16 +295,6 @@ public class HemfFill { return size; } - - @Override - public Rectangle2D getRecordBounds() { - return bounds; - } - - @Override - public Rectangle2D getShapeBounds(HemfGraphics ctx) { - return dstBounds; - } } /** @@ -316,7 +315,7 @@ public class HemfFill { /** The EMR_FRAMERGN record draws a border around the specified region using the specified brush. */ - public static class EmfFrameRgn extends HwmfDraw.WmfFrameRegion implements HemfRecord, HemfBounded { + public static class EmfFrameRgn extends HwmfDraw.WmfFrameRegion implements HemfRecord { private final Rectangle2D bounds = new Rectangle2D.Double(); private final List rgnRects = new ArrayList<>(); @@ -347,23 +346,13 @@ public class HemfFill { ctx.fill(getShape()); } - @Override - public Rectangle2D getRecordBounds() { - return bounds; - } - - @Override - public Rectangle2D getShapeBounds(HemfGraphics ctx) { - return getShape().getBounds2D(); - } - protected Area getShape() { return getRgnShape(rgnRects); } } /** The EMR_INVERTRGN record inverts the colors in the specified region. */ - public static class EmfInvertRgn implements HemfRecord, HemfBounded { + public static class EmfInvertRgn implements HemfRecord { protected final Rectangle2D bounds = new Rectangle2D.Double(); protected final List rgnRects = new ArrayList<>(); @@ -382,16 +371,6 @@ public class HemfFill { return size; } - @Override - public Rectangle2D getRecordBounds() { - return bounds; - } - - @Override - public Rectangle2D getShapeBounds(HemfGraphics ctx) { - return getShape().getBounds2D(); - } - protected Area getShape() { return getRgnShape(rgnRects); } @@ -409,7 +388,7 @@ public class HemfFill { } /** The EMR_FILLRGN record fills the specified region by using the specified brush. */ - public static class EmfFillRgn extends HwmfFill.WmfFillRegion implements HemfRecord, HemfBounded { + public static class EmfFillRgn extends HwmfFill.WmfFillRegion implements HemfRecord { protected final Rectangle2D bounds = new Rectangle2D.Double(); protected final List rgnRects = new ArrayList<>(); @@ -429,16 +408,6 @@ public class HemfFill { return size; } - @Override - public Rectangle2D getRecordBounds() { - return bounds; - } - - @Override - public Rectangle2D getShapeBounds(HemfGraphics ctx) { - return getShape().getBounds2D(); - } - protected Area getShape() { return getRgnShape(rgnRects); } @@ -474,7 +443,7 @@ public class HemfFill { } } - public static class EmfAlphaBlend implements HemfRecord, HemfBounded { + public static class EmfAlphaBlend implements HemfRecord { /** the destination bounding rectangle in device units */ protected final Rectangle2D bounds = new Rectangle2D.Double(); /** the destination rectangle */ @@ -583,24 +552,13 @@ public class HemfFill { return size; } - - - @Override - public Rectangle2D getRecordBounds() { - return bounds; - } - - @Override - public Rectangle2D getShapeBounds(HemfGraphics ctx) { - return destRect; - } } /** * The EMR_SETDIBITSTODEVICE record specifies a block transfer of pixels from specified scanlines of * a source bitmap to a destination rectangle. */ - public static class EmfSetDiBitsToDevice implements HemfRecord, HemfBounded { + public static class EmfSetDiBitsToDevice implements HemfRecord { protected final Rectangle2D bounds = new Rectangle2D.Double(); protected final Point2D dest = new Point2D.Double(); protected final Rectangle2D src = new Rectangle2D.Double(); @@ -645,16 +603,6 @@ public class HemfFill { return size; } - - @Override - public Rectangle2D getRecordBounds() { - return bounds; - } - - @Override - public Rectangle2D getShapeBounds(HemfGraphics ctx) { - return new Rectangle2D.Double(dest.getX(), dest.getY(), src.getWidth(), src.getHeight()); - } } static long readBitmap(final LittleEndianInputStream leis, final HwmfBitmapDib bitmap, @@ -769,52 +717,6 @@ public class HemfFill { return 6 * LittleEndian.INT_SIZE; } - /** - * 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, HemfBounded { - protected final Rectangle2D bounds = new Rectangle2D.Double(); - - @Override - public HemfRecordType getEmfRecordType() { - return HemfRecordType.fillPath; - } - - @Override - public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { - // A 128-bit WMF RectL object, which specifies bounding rectangle, in device units - return (recordSize == 0) ? 0 : 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(); - if (!prop.usePathBracket()) { - return; - } - 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); - } - } - } - protected static Area getRgnShape(List rgnRects) { final Area frame = new Area(); rgnRects.forEach((rct) -> frame.add(new Area(rct))); diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java index 5b19f9d128..25e7149251 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java @@ -85,8 +85,8 @@ public enum HemfRecordType { beginPath(0x0000003B, HemfDraw.EmfBeginPath::new), endPath(0x0000003C, HemfDraw.EmfEndPath::new), closeFigure(0x0000003D, HemfDraw.EmfCloseFigure::new), - fillPath(0x0000003E, HemfFill.EmfFillPath::new), - strokeandfillpath(0x0000003F, UnimplementedHemfRecord::new), + fillPath(0x0000003E, HemfDraw.EmfFillPath::new), + strokeAndFillPath(0x0000003F, HemfDraw.EmfStrokeAndFillPath::new), strokePath(0x00000040, HemfDraw.EmfStrokePath::new), flattenPath(0x00000041, HemfDraw.EmfFlattenPath::new), widenPath(0x00000042, HemfDraw.EmfWidenPath::new), diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java index 03c033b763..294bfdcb99 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java @@ -17,8 +17,13 @@ package org.apache.poi.hemf.record.emf; +import static org.apache.poi.hemf.record.emf.HemfDraw.readDimensionInt; +import static org.apache.poi.hemf.record.emf.HemfDraw.readPointL; + import java.io.IOException; +import org.apache.poi.hemf.draw.HemfDrawProperties; +import org.apache.poi.hemf.draw.HemfGraphics; import org.apache.poi.hemf.record.emf.HemfFill.HemfRegionMode; import org.apache.poi.hwmf.record.HwmfWindowing; import org.apache.poi.util.LittleEndianConsts; @@ -37,13 +42,7 @@ public class HemfWindowing { @Override public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { - // cx (4 bytes): A 32-bit unsigned integer that defines the x-coordinate of the point. - int width = (int)leis.readUInt(); - // cy (4 bytes): A 32-bit unsigned integer that defines the y-coordinate of the point. - int height = (int)leis.readUInt(); - size.setSize(width, height); - - return 2*LittleEndianConsts.INT_SIZE; + return readDimensionInt(leis, size); } } @@ -58,12 +57,7 @@ public class HemfWindowing { @Override public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { - // x (4 bytes): A 32-bit signed integer that defines the horizontal (x) coordinate of the point. - x = leis.readInt(); - // y (4 bytes): A 32-bit signed integer that defines the vertical (y) coordinate of the point. - y = leis.readInt(); - - return 2*LittleEndianConsts.INT_SIZE; + return readPointL(leis, origin); } } @@ -78,12 +72,7 @@ public class HemfWindowing { @Override public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { - // cx (4 bytes): A 32-bit unsigned integer that defines the x-coordinate of the point. - width = (int)leis.readUInt(); - // cy (4 bytes): A 32-bit unsigned integer that defines the y-coordinate of the point. - height = (int)leis.readUInt(); - - return 2*LittleEndianConsts.INT_SIZE; + return readDimensionInt(leis, extents); } } @@ -98,12 +87,7 @@ public class HemfWindowing { @Override public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { - // x (4 bytes): A 32-bit signed integer that defines the horizontal (x) coordinate of the point. - x = leis.readInt(); - // y (4 bytes): A 32-bit signed integer that defines the vertical (y) coordinate of the point. - y = leis.readInt(); - - return 2*LittleEndianConsts.INT_SIZE; + return readPointL(leis, origin); } } @@ -119,12 +103,7 @@ public class HemfWindowing { @Override public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { - // x (4 bytes): A 32-bit signed integer that defines the horizontal (x) coordinate of the point. - xOffset = leis.readInt(); - // y (4 bytes): A 32-bit signed integer that defines the vertical (y) coordinate of the point. - yOffset = leis.readInt(); - - return 2*LittleEndianConsts.INT_SIZE; + return readPointL(leis, offset); } } @@ -172,10 +151,11 @@ public class HemfWindowing { @Override public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { - xNum = leis.readInt(); - xDenom = leis.readInt(); - yNum = leis.readInt(); - yDenom = leis.readInt(); + double xNum = leis.readInt(); + double xDenom = leis.readInt(); + double yNum = leis.readInt(); + double yDenom = leis.readInt(); + scale.setSize(xNum / xDenom, yNum / yDenom); return 4*LittleEndianConsts.INT_SIZE; } } @@ -192,10 +172,13 @@ public class HemfWindowing { @Override public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { - xNum = leis.readInt(); - xDenom = leis.readInt(); - yNum = leis.readInt(); - yDenom = leis.readInt(); + double xNum = leis.readInt(); + double xDenom = leis.readInt(); + double yNum = leis.readInt(); + double yDenom = leis.readInt(); + + scale.setSize(xNum / xDenom, yNum / yDenom); + return 4*LittleEndianConsts.INT_SIZE; } } @@ -220,6 +203,17 @@ public class HemfWindowing { return LittleEndianConsts.INT_SIZE; } + + @Override + public void draw(HemfGraphics ctx) { + HemfDrawProperties props = ctx.getProperties(); + ctx.setClip(props.getPath(), regionMode); + } + + @Override + public String toString() { + return "{ regionMode: '"+regionMode+"' }"; + } } } \ No newline at end of file diff --git a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java index 7a325dc0a0..0038298946 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java @@ -57,6 +57,10 @@ import org.apache.poi.util.LocaleUtil; public class HwmfGraphics { + public enum FillDrawStyle { + NONE, FILL, DRAW, FILL_DRAW + } + protected final List propStack = new LinkedList<>(); protected HwmfDrawProperties prop; protected final Graphics2D graphicsCtx; @@ -297,6 +301,7 @@ public class HwmfGraphics { */ public void updateWindowMapMode() { Rectangle2D win = getProperties().getWindow(); + Rectangle2D view = getProperties().getViewport(); HwmfMapMode mapMode = getProperties().getMapMode(); graphicsCtx.setTransform(initialAT); @@ -304,8 +309,10 @@ public class HwmfGraphics { default: case MM_ANISOTROPIC: // scale window bounds to output bounds - graphicsCtx.scale(bbox.getWidth()/win.getWidth(), bbox.getHeight()/win.getHeight()); - graphicsCtx.translate(-win.getX(), -win.getY()); + if (view != null) { + graphicsCtx.translate(view.getX() - win.getX(), view.getY() - win.getY()); + graphicsCtx.scale(view.getWidth() / win.getWidth(), view.getHeight() / win.getHeight()); + } break; case MM_ISOTROPIC: // TODO: to be validated ... diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBitmapDib.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBitmapDib.java index a6f378512e..e6a8e302a3 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBitmapDib.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBitmapDib.java @@ -234,20 +234,33 @@ public class HwmfBitmapDib { leis.reset(); - assert( headerSize != 0x0C || ((((headerWidth * headerPlanes * headerBitCount.flag + 31) & ~31) / 8) * Math.abs(headerHeight)) == headerImageSize); + // The size and format of this data is determined by information in the DIBHeaderInfo field. If + // it is a BitmapCoreHeader, the size in bytes MUST be calculated as follows: - if (headerImageSize < headerSize) { - imageData = IOUtils.safelyAllocate(recordSize, MAX_RECORD_LENGTH); - leis.readFully(imageData); - return recordSize; - } else { - int fileSize = (int)Math.min(introSize+headerImageSize,recordSize); + int bodySize = ((((headerWidth * headerPlanes * headerBitCount.flag + 31) & ~31) / 8) * Math.abs(headerHeight)); + + // This formula SHOULD also be used to calculate the size of aData when DIBHeaderInfo is a + // BitmapInfoHeader Object, using values from that object, but only if its Compression value is + // BI_RGB, BI_BITFIELDS, or BI_CMYK. + // Otherwise, the size of aData MUST be the BitmapInfoHeader Object value ImageSize. + + assert( headerSize != 0x0C || bodySize == headerImageSize); + + if (headerSize == 0x0C || + headerCompression == Compression.BI_RGB || + headerCompression == Compression.BI_BITFIELDS || + headerCompression == Compression.BI_CMYK) { + int fileSize = (int)Math.min(introSize+bodySize,recordSize); imageData = IOUtils.safelyAllocate(fileSize, MAX_RECORD_LENGTH); leis.readFully(imageData, 0, introSize); leis.skipFully(recordSize-fileSize); // emfs are sometimes truncated, read as much as possible int readBytes = leis.read(imageData, introSize, fileSize-introSize); return introSize+(recordSize-fileSize)+readBytes; + } else { + imageData = IOUtils.safelyAllocate(recordSize, MAX_RECORD_LENGTH); + leis.readFully(imageData); + return recordSize; } } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java index b4e7ba73ed..107d440499 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java @@ -32,6 +32,8 @@ import java.util.ArrayList; import java.util.List; import org.apache.poi.hwmf.draw.HwmfGraphics; +import org.apache.poi.hwmf.draw.HwmfGraphics.FillDrawStyle; +import org.apache.poi.util.Internal; import org.apache.poi.util.LittleEndianConsts; import org.apache.poi.util.LittleEndianInputStream; @@ -61,7 +63,7 @@ public class HwmfDraw { @Override public String toString() { - return "{ x: "+point.getX()+", y: "+point.getY()+" }"; + return pointToString(point); } } @@ -93,7 +95,7 @@ public class HwmfDraw { @Override public String toString() { - return "{ x: "+point.getX()+", y: "+point.getY()+" }"; + return pointToString(point); } } @@ -139,10 +141,17 @@ public class HwmfDraw { Path2D p = (Path2D)poly.clone(); // don't close the path p.setWindingRule(ctx.getProperties().getWindingRule()); - if (isFill()) { - ctx.fill(p); - } else { - ctx.draw(p); + switch (getFillDrawStyle()) { + case FILL: + ctx.fill(p); + break; + case DRAW: + ctx.draw(p); + break; + case FILL_DRAW: + ctx.fill(p); + ctx.draw(p); + break; } } @@ -154,8 +163,8 @@ public class HwmfDraw { /** * @return true, if the shape should be filled */ - protected boolean isFill() { - return true; + protected FillDrawStyle getFillDrawStyle() { + return FillDrawStyle.FILL; } } @@ -171,8 +180,8 @@ public class HwmfDraw { } @Override - protected boolean isFill() { - return false; + protected FillDrawStyle getFillDrawStyle() { + return FillDrawStyle.DRAW; } } @@ -196,8 +205,16 @@ public class HwmfDraw { @Override public void draw(HwmfGraphics ctx) { - Shape s = new Ellipse2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight()); - ctx.fill(s); + ctx.fill(getShape()); + } + + protected Ellipse2D getShape() { + return new Ellipse2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight()); + } + + @Override + public String toString() { + return boundsToString(bounds); } } @@ -264,7 +281,7 @@ public class HwmfDraw { */ public static class WmfPolyPolygon implements HwmfRecord { - protected List polyList = new ArrayList<>(); + protected final List polyList = new ArrayList<>(); @Override public HwmfRecordType getWmfRecordType() { @@ -316,41 +333,82 @@ public class HwmfDraw { @Override public void draw(HwmfGraphics ctx) { - Area area = getShape(ctx); - if (area == null) { + Shape shape = getShape(ctx); + if (shape == null) { return; } - - if (isFill()) { - ctx.fill(area); - } else { - ctx.draw(area); + + switch (getFillDrawStyle()) { + case DRAW: + ctx.draw(shape); + break; + case FILL: + ctx.fill(shape); + break; + case FILL_DRAW: + ctx.fill(shape); + ctx.draw(shape); + break; } } - protected Area getShape(HwmfGraphics ctx) { - int windingRule = ctx.getProperties().getWindingRule(); - Area area = null; - for (Path2D poly : polyList) { - Path2D p = (Path2D)poly.clone(); - p.setWindingRule(windingRule); - Area newArea = new Area(p); - if (area == null) { - area = newArea; - } else { - area.exclusiveOr(newArea); - } - } - - return area; + protected FillDrawStyle getFillDrawStyle() { + // Each polygon SHOULD be outlined using the current pen, and filled using the current brush and + // polygon fill mode that are defined in the playback device context. The polygons defined by this + // record can overlap. + return FillDrawStyle.FILL_DRAW; } /** - * @return true, if the shape should be filled + * @return true, if a polyline should be closed, i.e. is a polygon */ - protected boolean isFill() { + protected boolean isClosed() { return true; } + + protected Shape getShape(HwmfGraphics ctx) { + int windingRule = ctx.getProperties().getWindingRule(); + + if (isClosed()) { + Area area = null; + for (Path2D poly : polyList) { + Path2D p = (Path2D)poly.clone(); + p.setWindingRule(windingRule); + Area newArea = new Area(p); + if (area == null) { + area = newArea; + } else { + area.exclusiveOr(newArea); + } + } + return area; + } else { + Path2D path = new Path2D.Double(); + path.setWindingRule(windingRule); + for (Path2D poly : polyList) { + path.append(poly, false); + } + return path; + } + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append("{ polyList: ["); + boolean isFirst = true; + for (Path2D p : polyList) { + if (!isFirst) { + sb.append(","); + } + isFirst = false; + sb.append("{ points: "); + sb.append(polyToString(p)); + sb.append(" }"); + } + sb.append(" }"); + return sb.toString(); + } } /** @@ -377,12 +435,7 @@ public class HwmfDraw { @Override public String toString() { - return - "{ bounds: " + - "{ x: "+bounds.getX()+ - ", y: "+bounds.getY()+ - ", w: "+bounds.getWidth()+ - ", h: "+bounds.getHeight()+" } }"; + return boundsToString(bounds); } } @@ -450,8 +503,11 @@ public class HwmfDraw { @Override public void draw(HwmfGraphics ctx) { - Shape s = new RoundRectangle2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), width, height); - ctx.fill(s); + ctx.fill(getShape()); + } + + protected RoundRectangle2D getShape() { + return new RoundRectangle2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), width, height); } } @@ -487,15 +543,28 @@ public class HwmfDraw { @Override public void draw(HwmfGraphics ctx) { Shape s = getShape(); + switch (getFillDrawStyle()) { + case FILL: + ctx.fill(s); + break; + case DRAW: + ctx.draw(s); + break; + case FILL_DRAW: + ctx.fill(s); + ctx.draw(s); + break; + } + } + + protected FillDrawStyle getFillDrawStyle() { switch (getWmfRecordType()) { default: case arc: - ctx.draw(s); - break; + return FillDrawStyle.DRAW; case chord: case pie: - ctx.fill(s); - break; + return FillDrawStyle.FILL_DRAW; } } @@ -665,4 +734,14 @@ public class HwmfDraw { sb.append("]"); return sb.toString(); } + + @Internal + public static String pointToString(Point2D point) { + return "{ x: "+point.getX()+", y: "+point.getY()+" }"; + } + + @Internal + public static String boundsToString(Rectangle2D bounds) { + return "{ x: "+bounds.getX()+", y: "+bounds.getY()+", w: "+bounds.getWidth()+", h: "+bounds.getHeight()+" }"; + } } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java index 4f15272e73..e335db4577 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java @@ -17,6 +17,7 @@ package org.apache.poi.hwmf.record; +import static org.apache.poi.hwmf.record.HwmfDraw.boundsToString; import static org.apache.poi.hwmf.record.HwmfDraw.readPointS; import java.awt.Shape; @@ -420,8 +421,8 @@ public class HwmfFill { public String toString() { return "{ rasterOperation: '"+rasterOperation+"'"+ - ", srcBounds: { x: "+srcBounds.getX()+", y: "+srcBounds.getY()+", w: "+srcBounds.getWidth()+", h: "+srcBounds.getHeight()+" }"+ - ", dstBounds: { x: "+dstBounds.getX()+", y: "+dstBounds.getY()+", w: "+dstBounds.getWidth()+", h: "+dstBounds.getHeight()+" }"+ + ", srcBounds: "+boundsToString(srcBounds)+ + ", dstBounds: "+boundsToString(dstBounds)+ "}"; } } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java index 7197674ffa..7303d2c6fe 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java @@ -244,6 +244,11 @@ public class HwmfMisc { ctx.getProperties().setMapMode(mapMode); ctx.updateWindowMapMode(); } + + @Override + public String toString() { + return "{ mapMode: '"+mapMode+"' }"; + } } /** @@ -275,6 +280,11 @@ public class HwmfMisc { public void draw(HwmfGraphics ctx) { } + + @Override + public String toString() { + return "{ mapperValues: "+mapperValues+" }"; + } } /** @@ -379,6 +389,11 @@ public class HwmfMisc { public void draw(HwmfGraphics ctx) { } + + @Override + public String toString() { + return "{ stretchBltMode: '"+stretchBltMode+"' }"; + } } /** diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java index a0b240d048..024e1dedf2 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java @@ -17,6 +17,8 @@ package org.apache.poi.hwmf.record; +import static org.apache.poi.hwmf.record.HwmfDraw.boundsToString; +import static org.apache.poi.hwmf.record.HwmfDraw.pointToString; import static org.apache.poi.hwmf.record.HwmfDraw.readPointS; import static org.apache.poi.hwmf.record.HwmfDraw.readRectS; @@ -422,8 +424,8 @@ public class HwmfText { } return - "{ reference: { x: "+reference.getX()+", y: "+reference.getY()+" }"+ - ", bounds: { x: "+bounds.getX()+", y: "+bounds.getY()+", w: "+bounds.getWidth()+", h: "+bounds.getHeight()+"}"+ + "{ reference: " + pointToString(reference) + + ", bounds: " + boundsToString(bounds) + ", text: '"+text.replaceAll("\\p{Cntrl}",".")+"'"+ "}"; } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java index 6e17b574fd..88915f8ad2 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java @@ -17,11 +17,15 @@ package org.apache.poi.hwmf.record; +import static org.apache.poi.hwmf.record.HwmfDraw.boundsToString; +import static org.apache.poi.hwmf.record.HwmfDraw.pointToString; import static org.apache.poi.hwmf.record.HwmfDraw.readBounds; +import static org.apache.poi.hwmf.record.HwmfDraw.readPointS; import java.awt.Shape; import java.awt.geom.Area; import java.awt.geom.Dimension2D; +import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.io.IOException; @@ -37,11 +41,7 @@ public class HwmfWindowing { */ public static class WmfSetViewportOrg implements HwmfRecord { - /** A signed integer that defines the vertical offset, in device units. */ - protected int y; - - /** A signed integer that defines the horizontal offset, in device units. */ - protected int x; + protected final Point2D origin = new Point2D.Double(); @Override public HwmfRecordType getWmfRecordType() { @@ -50,14 +50,18 @@ public class HwmfWindowing { @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { - y = leis.readShort(); - x = leis.readShort(); - return 2*LittleEndianConsts.SHORT_SIZE; + return readPointS(leis, origin); } @Override public void draw(HwmfGraphics ctx) { - ctx.getProperties().setViewportOrg(x, y); + ctx.getProperties().setViewportOrg(origin.getX(), origin.getY()); + ctx.updateWindowMapMode(); + } + + @Override + public String toString() { + return pointToString(origin); } } @@ -67,11 +71,7 @@ public class HwmfWindowing { */ public static class WmfSetViewportExt implements HwmfRecord { - /** A signed integer that defines the vertical extent of the viewport in device units. */ - protected int height; - - /** A signed integer that defines the horizontal extent of the viewport in device units. */ - protected int width; + protected final Dimension2D extents = new Dimension2DDouble(); @Override public HwmfRecordType getWmfRecordType() { @@ -80,14 +80,23 @@ public class HwmfWindowing { @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { - height = leis.readShort(); - width = leis.readShort(); + // A signed integer that defines the vertical extent of the viewport in device units. + int height = leis.readShort(); + // A signed integer that defines the horizontal extent of the viewport in device units. + int width = leis.readShort(); + extents.setSize(width, height); return 2*LittleEndianConsts.SHORT_SIZE; } @Override public void draw(HwmfGraphics ctx) { - ctx.getProperties().setViewportExt(width, height); + ctx.getProperties().setViewportExt(extents.getWidth(), extents.getHeight()); + ctx.updateWindowMapMode(); + } + + @Override + public String toString() { + return "{ width: "+extents.getWidth()+", height: "+extents.getHeight()+" }"; } } @@ -97,15 +106,7 @@ public class HwmfWindowing { */ public static class WmfOffsetViewportOrg implements HwmfRecord { - /** - * A 16-bit signed integer that defines the vertical offset, in device units. - */ - private int yOffset; - - /** - * A 16-bit signed integer that defines the horizontal offset, in device units. - */ - private int xOffset; + protected final Point2D offset = new Point2D.Double(); @Override public HwmfRecordType getWmfRecordType() { @@ -114,9 +115,7 @@ public class HwmfWindowing { @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { - yOffset = leis.readShort(); - xOffset = leis.readShort(); - return 2*LittleEndianConsts.SHORT_SIZE; + return readPointS(leis, offset); } @Override @@ -124,7 +123,12 @@ public class HwmfWindowing { Rectangle2D viewport = ctx.getProperties().getViewport(); double x = (viewport == null) ? 0 : viewport.getX(); double y = (viewport == null) ? 0 : viewport.getY(); - ctx.getProperties().setViewportOrg(x+xOffset, y+yOffset); + ctx.getProperties().setViewportOrg(x+offset.getX(), y+offset.getY()); + } + + @Override + public String toString() { + return pointToString(offset); } } @@ -133,11 +137,7 @@ public class HwmfWindowing { */ public static class WmfSetWindowOrg implements HwmfRecord { - /** A signed integer that defines the y-coordinate, in logical units. */ - protected int y; - - /** A signed integer that defines the x-coordinate, in logical units. */ - protected int x; + protected final Point2D origin = new Point2D.Double(); @Override public HwmfRecordType getWmfRecordType() { @@ -146,23 +146,26 @@ public class HwmfWindowing { @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { - y = leis.readShort(); - x = leis.readShort(); - return 2*LittleEndianConsts.SHORT_SIZE; + return readPointS(leis, origin); } @Override public void draw(HwmfGraphics ctx) { - ctx.getProperties().setWindowOrg(x, y); + ctx.getProperties().setWindowOrg(getX(), getY()); ctx.updateWindowMapMode(); } - public int getY() { - return y; + public double getY() { + return origin.getY(); } - public int getX() { - return x; + public double getX() { + return origin.getX(); + } + + @Override + public String toString() { + return pointToString(origin); } } @@ -198,6 +201,11 @@ public class HwmfWindowing { public Dimension2D getSize() { return size; } + + @Override + public String toString() { + return "{ width: "+size.getWidth()+", height: "+size.getHeight()+" }"; + } } /** @@ -206,15 +214,7 @@ public class HwmfWindowing { */ public static class WmfOffsetWindowOrg implements HwmfRecord { - /** - * A 16-bit signed integer that defines the vertical offset, in device units. - */ - private int yOffset; - - /** - * A 16-bit signed integer that defines the horizontal offset, in device units. - */ - private int xOffset; + protected final Point2D offset = new Point2D.Double(); @Override public HwmfRecordType getWmfRecordType() { @@ -223,17 +223,20 @@ public class HwmfWindowing { @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { - yOffset = leis.readShort(); - xOffset = leis.readShort(); - return 2*LittleEndianConsts.SHORT_SIZE; + return readPointS(leis, offset); } @Override public void draw(HwmfGraphics ctx) { Rectangle2D window = ctx.getProperties().getWindow(); - ctx.getProperties().setWindowOrg(window.getX()+xOffset, window.getY()+yOffset); + ctx.getProperties().setWindowOrg(window.getX()+offset.getX(), window.getY()+offset.getY()); ctx.updateWindowMapMode(); } + + @Override + public String toString() { + return pointToString(offset); + } } /** @@ -242,29 +245,7 @@ public class HwmfWindowing { */ public static class WmfScaleWindowExt implements HwmfRecord { - /** - * A signed integer that defines the amount by which to divide the - * result of multiplying the current y-extent by the value of the yNum member. - */ - protected int yDenom; - - /** - * A signed integer that defines the amount by which to multiply the - * current y-extent. - */ - protected int yNum; - - /** - * A signed integer that defines the amount by which to divide the - * result of multiplying the current x-extent by the value of the xNum member. - */ - protected int xDenom; - - /** - * A signed integer that defines the amount by which to multiply the - * current x-extent. - */ - protected int xNum; + protected final Dimension2D scale = new Dimension2DDouble(); @Override public HwmfRecordType getWmfRecordType() { @@ -273,21 +254,37 @@ public class HwmfWindowing { @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { - yDenom = leis.readShort(); - yNum = leis.readShort(); - xDenom = leis.readShort(); - xNum = leis.readShort(); + // A signed integer that defines the amount by which to divide the + // result of multiplying the current y-extent by the value of the yNum member. + double yDenom = leis.readShort(); + // A signed integer that defines the amount by which to multiply the + // current y-extent. + double yNum = leis.readShort(); + // A signed integer that defines the amount by which to divide the + // result of multiplying the current x-extent by the value of the xNum member. + double xDenom = leis.readShort(); + // A signed integer that defines the amount by which to multiply the + // current x-extent. + double xNum = leis.readShort(); + + scale.setSize(xNum / xDenom, yNum / yDenom); + return 4*LittleEndianConsts.SHORT_SIZE; } @Override public void draw(HwmfGraphics ctx) { Rectangle2D window = ctx.getProperties().getWindow(); - double width = window.getWidth() * xNum / xDenom; - double height = window.getHeight() * yNum / yDenom; + double width = window.getWidth() * scale.getWidth(); + double height = window.getHeight() * scale.getHeight(); ctx.getProperties().setWindowExt(width, height); ctx.updateWindowMapMode(); } + + @Override + public String toString() { + return "{ scaleX: "+scale.getWidth()+", scaleY: "+scale.getHeight()+" }"; + } } @@ -298,29 +295,7 @@ public class HwmfWindowing { */ public static class WmfScaleViewportExt implements HwmfRecord { - /** - * A signed integer that defines the amount by which to divide the - * result of multiplying the current y-extent by the value of the yNum member. - */ - protected int yDenom; - - /** - * A signed integer that defines the amount by which to multiply the - * current y-extent. - */ - protected int yNum; - - /** - * A signed integer that defines the amount by which to divide the - * result of multiplying the current x-extent by the value of the xNum member. - */ - protected int xDenom; - - /** - * A signed integer that defines the amount by which to multiply the - * current x-extent. - */ - protected int xNum; + protected final Dimension2D scale = new Dimension2DDouble(); @Override public HwmfRecordType getWmfRecordType() { @@ -329,10 +304,21 @@ public class HwmfWindowing { @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { - yDenom = leis.readShort(); - yNum = leis.readShort(); - xDenom = leis.readShort(); - xNum = leis.readShort(); + // A signed integer that defines the amount by which to divide the + // result of multiplying the current y-extent by the value of the yNum member. + double yDenom = leis.readShort(); + // A signed integer that defines the amount by which to multiply the + // current y-extent. + double yNum = leis.readShort(); + // A signed integer that defines the amount by which to divide the + // result of multiplying the current x-extent by the value of the xNum member. + double xDenom = leis.readShort(); + // A signed integer that defines the amount by which to multiply the + // current x-extent. + double xNum = leis.readShort(); + + scale.setSize(xNum / xDenom, yNum / yDenom); + return 4*LittleEndianConsts.SHORT_SIZE; } @@ -342,10 +328,15 @@ public class HwmfWindowing { if (viewport == null) { viewport = ctx.getProperties().getWindow(); } - double width = viewport.getWidth() * xNum / xDenom; - double height = viewport.getHeight() * yNum / yDenom; + double width = viewport.getWidth() * scale.getWidth(); + double height = viewport.getHeight() * scale.getHeight(); ctx.getProperties().setViewportExt(width, height); } + + @Override + public String toString() { + return "{ scaleX: "+scale.getWidth()+", scaleY: "+scale.getHeight()+" }"; + } } /** @@ -354,15 +345,7 @@ public class HwmfWindowing { */ public static class WmfOffsetClipRgn implements HwmfRecord, HwmfObjectTableEntry { - /** - * A signed integer that defines the number of logical units to move up or down. - */ - protected int yOffset; - - /** - * A signed integer that defines the number of logical units to move left or right. - */ - protected int xOffset; + protected final Point2D offset = new Point2D.Double(); @Override public HwmfRecordType getWmfRecordType() { @@ -371,9 +354,7 @@ public class HwmfWindowing { @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { - yOffset = leis.readShort(); - xOffset = leis.readShort(); - return 2*LittleEndianConsts.SHORT_SIZE; + return readPointS(leis, offset); } @Override @@ -384,6 +365,11 @@ public class HwmfWindowing { @Override public void applyObject(HwmfGraphics ctx) { } + + @Override + public String toString() { + return pointToString(offset); + } } /** @@ -402,20 +388,7 @@ public class HwmfWindowing { @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { - // A 16-bit signed integer that defines the y-coordinate, in logical units, of the - // lower-right corner of the rectangle. - final int bottom = leis.readShort(); - // A 16-bit signed integer that defines the x-coordinate, in logical units, of the - // lower-right corner of the rectangle. - final int right = leis.readShort(); - // A 16-bit signed integer that defines the y-coordinate, in logical units, of the - // upper-left corner of the rectangle. - final int top = leis.readShort(); - // A 16-bit signed integer that defines the x-coordinate, in logical units, of the - // upper-left corner of the rectangle. - final int left = leis.readShort(); - bounds.setRect(left, top, right-left, bottom-top); - return 4*LittleEndianConsts.SHORT_SIZE; + return readBounds(leis, bounds); } @Override @@ -426,6 +399,11 @@ public class HwmfWindowing { @Override public void applyObject(HwmfGraphics ctx) { } + + @Override + public String toString() { + return boundsToString(bounds); + } } @@ -459,12 +437,7 @@ public class HwmfWindowing { @Override public String toString() { - return - "{ x: "+bounds.getX()+ - ", y: "+bounds.getY()+ - ", w: "+bounds.getWidth()+ - ", h: "+bounds.getHeight()+ - "}"; + return boundsToString(bounds); } } @@ -572,29 +545,7 @@ public class HwmfWindowing { */ private int maxScan; - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the - * lower-right corner of the rectangle. - */ - private int bottom; - - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of the - * lower-right corner of the rectangle. - */ - private int right; - - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the - * upper-left corner of the rectangle. - */ - private int top; - - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of the - * upper-left corner of the rectangle. - */ - private int left; + private Rectangle2D bounds = new Rectangle2D.Double(); /** * An array of Scan objects that define the scanlines in the region. @@ -614,10 +565,19 @@ public class HwmfWindowing { regionSize = leis.readShort(); scanCount = leis.readShort(); maxScan = leis.readShort(); - left = leis.readShort(); - top = leis.readShort(); - right = leis.readShort(); - bottom = leis.readShort(); + // A 16-bit signed integer that defines the x-coordinate, in logical units, of the + // upper-left corner of the rectangle. + double left = leis.readShort(); + // A 16-bit signed integer that defines the y-coordinate, in logical units, of the + // upper-left corner of the rectangle. + double top = leis.readShort(); + // A 16-bit signed integer that defines the x-coordinate, in logical units, of the + // lower-right corner of the rectangle. + double right = leis.readShort(); + // A 16-bit signed integer that defines the y-coordinate, in logical units, of the + // lower-right corner of the rectangle. + double bottom = leis.readShort(); + bounds.setRect(left, top, right-left, bottom-top); int size = 9*LittleEndianConsts.SHORT_SIZE+LittleEndianConsts.INT_SIZE; diff --git a/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java b/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java index cabe627f66..8ff4742be6 100644 --- a/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java +++ b/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java @@ -63,7 +63,13 @@ public class HemfPictureTest { public void paint() throws IOException { byte buf[] = new byte[50_000_000]; - final boolean writeLog = false; + // good test samples to validate rendering: + // emfs/commoncrawl2/NB/NBWN2YH5VFCLZRFDQU7PB7IDD4UKY7DN_2.emf + // emfs/govdocs1/777/777525.ppt_0.emf + // emfs/govdocs1/844/844795.ppt_2.emf + // emfs/commoncrawl2/TO/TOYZSTNUSW5OFCFUQ6T5FBLIDLCRF3NH_0.emf + + final boolean writeLog = true; final boolean dumpRecords = false; final boolean savePng = true; @@ -257,7 +263,7 @@ public class HemfPictureTest { long fudgeFactorX = 1000; StringBuilder sb = new StringBuilder(); for (HemfRecord record : pic) { - if (record.getEmfRecordType().equals(HemfRecordType.exttextoutw)) { + if (record.getEmfRecordType().equals(HemfRecordType.extTextOutW)) { HemfText.EmfExtTextOutW extTextOutW = (HemfText.EmfExtTextOutW) record; Point2D reference = extTextOutW.getReference(); if (lastY > -1 && lastY != reference.getY()) { @@ -291,7 +297,7 @@ public class HemfPictureTest { expectedParts.add("testPDF.pdf"); int foundExpected = 0; for (HemfRecord record : pic) { - if (record.getEmfRecordType().equals(HemfRecordType.exttextoutw)) { + if (record.getEmfRecordType().equals(HemfRecordType.extTextOutW)) { HemfText.EmfExtTextOutW extTextOutW = (HemfText.EmfExtTextOutW) record; Point2D reference = extTextOutW.getReference(); if (lastY > -1 && lastY != reference.getY()) { From 0a84f6881f5a4dc1354274dc49e38d283ca764ad Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Tue, 30 Oct 2018 22:35:45 +0000 Subject: [PATCH 16/21] #60656 - Support export file that contains emf and render it correctly git-svn-id: https://svn.apache.org/repos/asf/poi/branches/hemf@1845291 13f79535-47bb-0310-9956-ffa450edef68 --- .../poi/hemf/draw/HemfDrawProperties.java | 9 - .../apache/poi/hemf/draw/HemfGraphics.java | 102 ++------ .../apache/poi/hemf/record/emf/HemfDraw.java | 6 +- .../apache/poi/hemf/record/emf/HemfFill.java | 128 +++++----- .../poi/hemf/record/emf/HemfHeader.java | 32 +-- .../apache/poi/hemf/record/emf/HemfText.java | 2 +- .../poi/hemf/record/emf/HemfWindowing.java | 10 +- .../poi/hemf/usermodel/HemfPicture.java | 2 +- .../poi/hwmf/draw/HwmfDrawProperties.java | 22 ++ .../apache/poi/hwmf/draw/HwmfGraphics.java | 218 +++++++++++++++--- .../apache/poi/hwmf/record/HwmfBitmapDib.java | 2 +- .../org/apache/poi/hwmf/record/HwmfDraw.java | 8 + .../org/apache/poi/hwmf/record/HwmfFill.java | 36 ++- .../poi/hwmf/record/HwmfRegionMode.java | 59 +++++ .../org/apache/poi/hwmf/record/HwmfText.java | 4 +- .../apache/poi/hwmf/record/HwmfWindowing.java | 1 + 16 files changed, 410 insertions(+), 231 deletions(-) create mode 100644 src/scratchpad/src/org/apache/poi/hwmf/record/HwmfRegionMode.java diff --git a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java index f22f5a460f..98b07d4c6b 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java +++ b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java @@ -26,7 +26,6 @@ public class HemfDrawProperties extends HwmfDrawProperties { /** Path for path bracket operations */ protected Path2D path = null; - protected Shape clip = null; protected boolean usePathBracket = false; @@ -67,12 +66,4 @@ public class HemfDrawProperties extends HwmfDrawProperties { public void setUsePathBracket(boolean usePathBracket) { this.usePathBracket = usePathBracket; } - - public Shape getClip() { - return clip; - } - - public void setClip(Shape shape) { - clip = shape; - } } diff --git a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java index 1d947f24ff..c7e99965f5 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java +++ b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java @@ -22,16 +22,13 @@ import static org.apache.poi.hwmf.record.HwmfBrushStyle.BS_SOLID; import java.awt.Color; import java.awt.Graphics2D; -import java.awt.Shape; -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.util.function.Consumer; -import org.apache.poi.hemf.record.emf.HemfFill; import org.apache.poi.hemf.record.emf.HemfRecord; +import org.apache.poi.hwmf.draw.HwmfDrawProperties; import org.apache.poi.hwmf.draw.HwmfGraphics; import org.apache.poi.hwmf.record.HwmfColorRef; import org.apache.poi.hwmf.record.HwmfObjectTableEntry; @@ -47,36 +44,22 @@ public class HemfGraphics extends HwmfGraphics { private static final HwmfColorRef BLACK = new HwmfColorRef(Color.BLACK); - private final AffineTransform initTrans; - public HemfGraphics(Graphics2D graphicsCtx, Rectangle2D bbox) { super(graphicsCtx,bbox); // add dummy entry for object ind ex 0, as emf is 1-based objectIndexes.set(0); - initTrans = new AffineTransform(graphicsCtx.getTransform()); } @Override public HemfDrawProperties getProperties() { - if (prop == null) { - prop = new HemfDrawProperties(); - } - return (HemfDrawProperties)prop; + return (HemfDrawProperties)super.getProperties(); } @Override - public void saveProperties() { - final HemfDrawProperties oldProp = getProperties(); - oldProp.setClip(graphicsCtx.getClip()); - propStack.add(oldProp); - prop = new HemfDrawProperties(oldProp); - } - - @Override - public void restoreProperties(int index) { - super.restoreProperties(index); - HemfDrawProperties newProp = getProperties(); - graphicsCtx.setClip(newProp.getClip()); + protected HemfDrawProperties newProperties(HwmfDrawProperties oldProps) { + return (oldProps == null) + ? new HemfDrawProperties() + : new HemfDrawProperties((HemfDrawProperties)oldProps); } public void draw(HemfRecord r) { @@ -94,8 +77,12 @@ public class HemfGraphics extends HwmfGraphics { } else { path = new Path2D.Double(); path.setWindingRule(prop.getWindingRule()); + } + + // add dummy move-to at start, to handle invalid emfs not containing that move-to + if (path.getCurrentPoint() == null) { Point2D pnt = prop.getLocation(); - path.moveTo(pnt.getX(),pnt.getY()); + path.moveTo(pnt.getX(), pnt.getY()); } try { @@ -108,7 +95,12 @@ public class HemfGraphics extends HwmfGraphics { pathConsumer.accept(path); } - prop.setLocation(path.getCurrentPoint()); + Point2D curPnt = path.getCurrentPoint(); + if (curPnt == null) { + return; + } + + prop.setLocation(curPnt); if (!useBracket) { switch (fillDraw) { case FILL: @@ -264,64 +256,4 @@ public class HemfGraphics extends HwmfGraphics { break; } } - - /** - * @return the initial AffineTransform, when this graphics context was created - */ - public AffineTransform getInitTransform() { - return new AffineTransform(initTrans); - } - - /** - * @return the current AffineTransform - */ - public AffineTransform getTransform() { - return new AffineTransform(graphicsCtx.getTransform()); - } - - /** - * Set the current AffineTransform - * @param tx the current AffineTransform - */ - public void setTransform(AffineTransform tx) { - graphicsCtx.setTransform(tx); - } - - public void setClip(Shape clip, HemfFill.HemfRegionMode regionMode) { - Shape oldClip = graphicsCtx.getClip(); - - switch (regionMode) { - case RGN_AND: - graphicsCtx.clip(clip); - break; - case RGN_OR: - if (oldClip == null) { - graphicsCtx.setClip(clip); - } else { - Area area = new Area(oldClip); - area.add(new Area(clip)); - graphicsCtx.setClip(area); - } - break; - case RGN_XOR: - if (oldClip == null) { - graphicsCtx.setClip(clip); - } else { - Area area = new Area(oldClip); - area.exclusiveOr(new Area(clip)); - graphicsCtx.setClip(area); - } - break; - case RGN_DIFF: - if (oldClip != null) { - Area area = new Area(oldClip); - area.subtract(new Area(clip)); - graphicsCtx.setClip(area); - } - break; - case RGN_COPY: - graphicsCtx.setClip(clip); - break; - } - } } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java index 103f90a052..d18f0d4119 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java @@ -1051,7 +1051,11 @@ public class HemfDraw { @Override public void draw(HemfGraphics ctx) { final HemfDrawProperties prop = ctx.getProperties(); - final Path2D path = (Path2D)prop.getPath().clone(); + final Path2D origPath = prop.getPath(); + if (origPath.getCurrentPoint() == null) { + return; + } + final Path2D path = (Path2D)origPath.clone(); path.closePath(); path.setWindingRule(ctx.getProperties().getWindingRule()); ctx.fill(path); diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java index 7b172d5506..810c9325a3 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java @@ -20,10 +20,11 @@ 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.HemfDraw.readRectL; import static org.apache.poi.hemf.record.emf.HemfRecordIterator.HEADER_SIZE; +import static org.apache.poi.hwmf.record.HwmfDraw.boundsToString; +import java.awt.Shape; 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; @@ -36,11 +37,11 @@ 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; import org.apache.poi.hwmf.record.HwmfFill.ColorUsage; +import org.apache.poi.hwmf.record.HwmfRegionMode; import org.apache.poi.hwmf.record.HwmfTernaryRasterOp; import org.apache.poi.util.IOUtils; import org.apache.poi.util.LittleEndian; @@ -50,47 +51,6 @@ import org.apache.poi.util.LittleEndianInputStream; public class HemfFill { private static final int MAX_RECORD_LENGTH = 10_000_000; - public enum HemfRegionMode { - /** - * The new clipping region includes the intersection (overlapping areas) - * of the current clipping region and the current path (or new region). - */ - RGN_AND(0x01), - /** - * The new clipping region includes the union (combined areas) - * of the current clipping region and the current path (or new region). - */ - RGN_OR(0x02), - /** - * The new clipping region includes the union of the current clipping region - * and the current path (or new region) but without the overlapping areas - */ - RGN_XOR(0x03), - /** - * The new clipping region includes the areas of the current clipping region - * with those of the current path (or new region) excluded. - */ - RGN_DIFF(0x04), - /** - * The new clipping region is the current path (or the new region). - */ - RGN_COPY(0x05); - - int flag; - HemfRegionMode(int flag) { - this.flag = flag; - } - - public static HemfRegionMode valueOf(int flag) { - for (HemfRegionMode rm : values()) { - if (rm.flag == flag) return rm; - } - return null; - } - - } - - /** * The EMR_SETPOLYFILLMODE record defines polygon fill mode. */ @@ -105,7 +65,7 @@ public class HemfFill { public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { // A 32-bit unsigned integer that specifies the polygon fill mode and // MUST be in the PolygonFillMode enumeration. - polyfillMode = HwmfPolyfillMode.valueOf((int)leis.readUInt()); + polyFillMode = HwmfPolyfillMode.valueOf((int)leis.readUInt()); return LittleEndianConsts.INT_SIZE; } } @@ -133,7 +93,7 @@ public class HemfFill { * optionally in combination with a brush pattern, according to a specified raster operation, stretching or * compressing the output to fit the dimensions of the destination, if necessary. */ - public static class EmfStretchBlt extends HwmfFill.WmfBitBlt implements HemfRecord { + public static class EmfStretchBlt extends HwmfFill.WmfStretchDib implements HemfRecord { protected final Rectangle2D bounds = new Rectangle2D.Double(); /** An XForm object that specifies a world-space to page-space transform to apply to the source bitmap. */ @@ -142,14 +102,6 @@ public class HemfFill { /** A WMF ColorRef object that specifies the background color of the source bitmap. */ protected final HwmfColorRef bkColorSrc = new HwmfColorRef(); - /** - * A 32-bit unsigned integer that specifies how to interpret values in the color table in - * the source bitmap header. This value MUST be in the DIBColors enumeration - */ - protected int usageSrc; - - protected final HwmfBitmapDib bitmap = new HwmfBitmapDib(); - @Override public HemfRecordType getEmfRecordType() { return HemfRecordType.stretchBlt; @@ -168,7 +120,7 @@ public class HemfFill { // rectangle and optionally a brush pattern, to achieve the final color. int rasterOpIndex = (int)leis.readUInt(); - rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex); + rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex >>> 16); size += LittleEndianConsts.INT_SIZE; @@ -179,7 +131,7 @@ public class HemfFill { size += bkColorSrc.init(leis); - usageSrc = (int)leis.readUInt(); + colorUsage = ColorUsage.valueOf((int)leis.readUInt()); // A 32-bit unsigned integer that specifies the offset, in bytes, from the // start of this record to the source bitmap header in the BitmapBuffer field. @@ -188,7 +140,7 @@ public class HemfFill { // A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap header. final int cbBmiSrc = (int)leis.readUInt(); size += 3*LittleEndianConsts.INT_SIZE; - if (size <= recordSize) { + if (size >= recordSize) { return size; } @@ -198,9 +150,12 @@ public class HemfFill { // A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap bits. final int cbBitsSrc = (int)leis.readUInt(); - size += 2*LittleEndianConsts.INT_SIZE; + if (size >= recordSize) { + return size; + } + if (srcEqualsDstDimension()) { srcBounds.setRect(srcPnt.getX(), srcPnt.getY(), dstBounds.getWidth(), dstBounds.getHeight()); } else { @@ -219,14 +174,20 @@ public class HemfFill { return false; } + @Override + public void draw(HemfGraphics ctx) { + HemfDrawProperties prop = ctx.getProperties(); + prop.setBackgroundColor(this.bkColorSrc); + super.draw(ctx); + } + @Override public String toString() { return - "{ bounds: { x: "+bounds.getX()+", y: "+bounds.getY()+", w: "+bounds.getWidth()+", h: "+bounds.getHeight()+"}"+ + "{ bounds: "+boundsToString(bounds)+ ", xFormSrc: { scaleX: "+xFormSrc.getScaleX()+", shearX: "+xFormSrc.getShearX()+", transX: "+xFormSrc.getTranslateX()+", scaleY: "+xFormSrc.getScaleY()+", shearY: "+xFormSrc.getShearY()+", transY: "+xFormSrc.getTranslateY()+" }"+ ", bkColorSrc: "+bkColorSrc+ - ", usageSrc: "+usageSrc+", " - + super.toString().substring(1); + ","+super.toString().substring(1); } } @@ -279,7 +240,8 @@ public class HemfFill { // These codes define how the color data of the source rectangle is to be combined with the color data // of the destination rectangle and optionally a brush pattern, to achieve the final color. // The value MUST be in the WMF Ternary Raster Operation enumeration - rasterOperation = HwmfTernaryRasterOp.valueOf(leis.readInt()); + int rasterOpIndex = (int)leis.readUInt(); + rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex >>> 16); // A 32-bit signed integer that specifies the logical width of the destination rectangle. int cxDest = leis.readInt(); @@ -291,7 +253,7 @@ public class HemfFill { size += 8*LittleEndianConsts.INT_SIZE; - size += readBitmap(leis, dib, startIdx, offBmiSrc, cbBmiSrc, offBitsSrc, cbBitsSrc); + size += readBitmap(leis, bitmap, startIdx, offBmiSrc, cbBmiSrc, offBitsSrc, cbBitsSrc); return size; } @@ -346,7 +308,7 @@ public class HemfFill { ctx.fill(getShape()); } - protected Area getShape() { + protected Shape getShape() { return getRgnShape(rgnRects); } } @@ -371,7 +333,7 @@ public class HemfFill { return size; } - protected Area getShape() { + protected Shape getShape() { return getRgnShape(rgnRects); } } @@ -408,13 +370,13 @@ public class HemfFill { return size; } - protected Area getShape() { + protected Shape getShape() { return getRgnShape(rgnRects); } } public static class EmfExtSelectClipRgn implements HemfRecord { - protected HemfRegionMode regionMode; + protected HwmfRegionMode regionMode; protected final List rgnRects = new ArrayList<>(); @Override @@ -427,20 +389,43 @@ public class HemfFill { // A 32-bit unsigned integer that specifies the size of region data in bytes long rgnDataSize = leis.readUInt(); // A 32-bit unsigned integer that specifies the way to use the region. - regionMode = HemfRegionMode.valueOf((int)leis.readUInt()); + regionMode = HwmfRegionMode.valueOf((int)leis.readUInt()); long size = 2* LittleEndianConsts.INT_SIZE; // If RegionMode is RGN_COPY, this data can be omitted and the clip region // SHOULD be set to the default (NULL) clip region. - if (regionMode != HemfRegionMode.RGN_COPY) { + if (regionMode != HwmfRegionMode.RGN_COPY) { size += readRgnData(leis, rgnRects); } return size; } - protected Area getShape() { + protected Shape getShape() { return getRgnShape(rgnRects); } + + @Override + public void draw(HemfGraphics ctx) { + HemfDrawProperties prop = ctx.getProperties(); + ctx.setClip(getShape(), regionMode, true); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("{ regionMode: '"+regionMode+"'"); + sb.append(", regions: ["); + boolean isFirst = true; + for (Rectangle2D r : rgnRects) { + if (!isFirst) { + sb.append(","); + } + isFirst = false; + sb.append(boundsToString(r)); + } + sb.append("]}"); + return sb.toString(); + } } public static class EmfAlphaBlend implements HemfRecord { @@ -717,7 +702,10 @@ public class HemfFill { return 6 * LittleEndian.INT_SIZE; } - protected static Area getRgnShape(List rgnRects) { + protected static Shape getRgnShape(List rgnRects) { + if (rgnRects.size() == 1) { + return rgnRects.get(0); + } final Area frame = new Area(); rgnRects.forEach((rct) -> frame.add(new Area(rct))); return frame; diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfHeader.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfHeader.java index 79e3b4943c..4b01a5a256 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfHeader.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfHeader.java @@ -20,6 +20,8 @@ package org.apache.poi.hemf.record.emf; import static org.apache.poi.hemf.record.emf.HemfDraw.readDimensionInt; import static org.apache.poi.hemf.record.emf.HemfDraw.readRectL; import static org.apache.poi.hemf.record.emf.HemfRecordIterator.HEADER_SIZE; +import static org.apache.poi.hwmf.record.HwmfDraw.boundsToString; +import static org.apache.poi.hwmf.record.HwmfDraw.dimToString; import java.awt.geom.Dimension2D; import java.awt.geom.Rectangle2D; @@ -119,21 +121,21 @@ public class HemfHeader implements HemfRecord { @Override public String toString() { return "HemfHeader{" + - "boundsRectangle=" + boundsRectangle + - ", frameRectangle=" + frameRectangle + - ", bytes=" + bytes + - ", records=" + records + - ", handles=" + handles + - ", description=" + description + - ", nPalEntries=" + nPalEntries + - ", hasExtension1=" + hasExtension1 + - ", cbPixelFormat=" + cbPixelFormat + - ", offPixelFormat=" + offPixelFormat + - ", bOpenGL=" + bOpenGL + - ", hasExtension2=" + hasExtension2 + - ", deviceDimension=" + deviceDimension + - ", microDimension=" + microDimension + - ", milliDimension=" + milliDimension + + "boundsRectangle: " + boundsToString(boundsRectangle) + + ", frameRectangle: " + boundsToString(frameRectangle) + + ", bytes: " + bytes + + ", records: " + records + + ", handles: " + handles + + ", description: '" + description + "'" + + ", nPalEntries: " + nPalEntries + + ", hasExtension1: " + hasExtension1 + + ", cbPixelFormat: " + cbPixelFormat + + ", offPixelFormat: " + offPixelFormat + + ", bOpenGL: " + bOpenGL + + ", hasExtension2: " + hasExtension2 + + ", deviceDimension: " + dimToString(deviceDimension) + + ", microDimension: " + dimToString(microDimension) + + ", milliDimension: " + dimToString(milliDimension) + '}'; } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java index 9f36812179..4a8508961b 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java @@ -191,7 +191,7 @@ public class HemfText { @Override public void draw(HwmfGraphics ctx) { - ctx.drawString(rawTextBytes, reference, bounds, dx, isUnicode()); + ctx.drawString(rawTextBytes, reference, bounds, options, dx, isUnicode()); } @Override diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java index 294bfdcb99..955bbd9f03 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java @@ -24,7 +24,7 @@ import java.io.IOException; import org.apache.poi.hemf.draw.HemfDrawProperties; import org.apache.poi.hemf.draw.HemfGraphics; -import org.apache.poi.hemf.record.emf.HemfFill.HemfRegionMode; +import org.apache.poi.hwmf.record.HwmfRegionMode; import org.apache.poi.hwmf.record.HwmfWindowing; import org.apache.poi.util.LittleEndianConsts; import org.apache.poi.util.LittleEndianInputStream; @@ -188,7 +188,7 @@ public class HemfWindowing { * device context, combining the new region with any existing clipping region using the specified mode. */ public static class EmfSelectClipPath implements HemfRecord { - protected HemfRegionMode regionMode; + protected HwmfRegionMode regionMode; @Override public HemfRecordType getEmfRecordType() { @@ -199,15 +199,15 @@ public class HemfWindowing { public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { // A 32-bit unsigned integer that specifies the way to use the path. // The value MUST be in the RegionMode enumeration - regionMode = HemfRegionMode.valueOf(leis.readInt()); + regionMode = HwmfRegionMode.valueOf(leis.readInt()); return LittleEndianConsts.INT_SIZE; } @Override public void draw(HemfGraphics ctx) { - HemfDrawProperties props = ctx.getProperties(); - ctx.setClip(props.getPath(), regionMode); + HemfDrawProperties prop = ctx.getProperties(); + ctx.setClip(prop.getPath(), regionMode, false); } @Override diff --git a/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java b/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java index b8e863a85a..8d49fdd0e1 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java +++ b/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java @@ -113,7 +113,7 @@ public class HemfPicture implements Iterable { height = 100; } - return new Dimension2DDouble(width*coeff, height*coeff); + return new Dimension2DDouble(Math.abs(width*coeff), Math.abs(height*coeff)); } public void draw(Graphics2D ctx, Rectangle2D graphicsBounds) { diff --git a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java index 176c8e1bce..9a58e57014 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java @@ -19,6 +19,7 @@ package org.apache.poi.hwmf.draw; import java.awt.Color; import java.awt.Shape; +import java.awt.geom.AffineTransform; import java.awt.geom.Area; import java.awt.geom.Path2D; import java.awt.geom.Point2D; @@ -67,6 +68,8 @@ public class HwmfDrawProperties { private HwmfTextAlignment textAlignAsian; private HwmfTextVerticalAlignment textVAlignAsian; private HwmfTernaryRasterOp rasterOp; + protected Shape clip; + protected final AffineTransform transform = new AffineTransform(); public HwmfDrawProperties() { window = new Rectangle2D.Double(0, 0, 1, 1); @@ -89,6 +92,7 @@ public class HwmfDrawProperties { textAlignAsian = HwmfTextAlignment.RIGHT; textVAlignAsian = HwmfTextVerticalAlignment.TOP; rasterOp = HwmfTernaryRasterOp.PATCOPY; + clip = null; } public HwmfDrawProperties(HwmfDrawProperties other) { @@ -126,6 +130,8 @@ public class HwmfDrawProperties { this.textAlignAsian = other.textAlignAsian; this.textVAlignAsian = other.textVAlignAsian; this.rasterOp = other.rasterOp; + this.transform.setTransform(other.transform); + this.clip = other.clip; } public void setViewportExt(double width, double height) { @@ -387,4 +393,20 @@ public class HwmfDrawProperties { public void setRasterOp(HwmfTernaryRasterOp rasterOp) { this.rasterOp = rasterOp; } + + public AffineTransform getTransform() { + return transform; + } + + public void setTransform(AffineTransform transform) { + this.transform.setTransform(transform); + } + + public Shape getClip() { + return clip; + } + + public void setClip(Shape clip) { + this.clip = clip; + } } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java index 0038298946..9ba07dd006 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java @@ -29,6 +29,7 @@ import java.awt.font.FontRenderContext; import java.awt.font.TextAttribute; import java.awt.font.TextLayout; import java.awt.geom.AffineTransform; +import java.awt.geom.Area; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; @@ -50,7 +51,9 @@ import org.apache.poi.hwmf.record.HwmfMisc.WmfSetBkMode.HwmfBkMode; import org.apache.poi.hwmf.record.HwmfObjectTableEntry; import org.apache.poi.hwmf.record.HwmfPenStyle; import org.apache.poi.hwmf.record.HwmfPenStyle.HwmfLineDash; +import org.apache.poi.hwmf.record.HwmfRegionMode; import org.apache.poi.hwmf.record.HwmfText; +import org.apache.poi.hwmf.record.HwmfText.WmfExtTextOutOptions; import org.apache.poi.sl.draw.DrawFactory; import org.apache.poi.sl.draw.DrawFontManager; import org.apache.poi.util.LocaleUtil; @@ -66,11 +69,12 @@ public class HwmfGraphics { protected final Graphics2D graphicsCtx; protected final BitSet objectIndexes = new BitSet(); protected final TreeMap objectTable = new TreeMap<>(); + protected final AffineTransform initialAT = new AffineTransform(); + private static final Charset DEFAULT_CHARSET = LocaleUtil.CHARSET_1252; /** Bounding box from the placeable header */ private final Rectangle2D bbox; - private final AffineTransform initialAT; /** * Initialize a graphics context for wmf rendering @@ -81,16 +85,20 @@ public class HwmfGraphics { public HwmfGraphics(Graphics2D graphicsCtx, Rectangle2D bbox) { this.graphicsCtx = graphicsCtx; this.bbox = (Rectangle2D)bbox.clone(); - this.initialAT = graphicsCtx.getTransform(); + this.initialAT.setTransform(graphicsCtx.getTransform()); } public HwmfDrawProperties getProperties() { if (prop == null) { - prop = new HwmfDrawProperties(); + prop = newProperties(null); } return prop; } + protected HwmfDrawProperties newProperties(HwmfDrawProperties oldProps) { + return (oldProps == null) ? new HwmfDrawProperties() : new HwmfDrawProperties(oldProps); + } + public void draw(Shape shape) { HwmfPenStyle ps = getProperties().getPenStyle(); if (ps == null) { @@ -119,14 +127,18 @@ public class HwmfGraphics { } public void fill(Shape shape) { - if (getProperties().getBrushStyle() != HwmfBrushStyle.BS_NULL) { -// GeneralPath gp = new GeneralPath(shape); -// gp.setWindingRule(getProperties().getPolyfillMode().awtFlag); + HwmfDrawProperties prop = getProperties(); + if (prop.getBrushStyle() != HwmfBrushStyle.BS_NULL) { + if (prop.getBkMode() == HwmfBkMode.OPAQUE) { + graphicsCtx.setPaint(prop.getBackgroundColor().getColor()); + graphicsCtx.fill(shape); + } + graphicsCtx.setPaint(getFill()); graphicsCtx.fill(shape); } - draw(shape); +// draw(shape); } protected BasicStroke getStroke() { @@ -264,8 +276,10 @@ public class HwmfGraphics { public void saveProperties() { final HwmfDrawProperties p = getProperties(); assert(p != null); + p.setTransform(graphicsCtx.getTransform()); + p.setClip(graphicsCtx.getClip()); propStack.add(p); - prop = new HwmfDrawProperties(p); + prop = newProperties(p); } /** @@ -291,7 +305,16 @@ public class HwmfGraphics { // roll to last when curIdx == 0 stackIndex = propStack.size()-1; } - prop = propStack.get(stackIndex); + + // The playback device context is restored by popping state information off a stack that was created by + // prior SAVEDC records + // ... so because being a stack, we will remove all entries having a greater index than the stackIndex + for (int i=propStack.size()-1; i>=stackIndex; i--) { + prop = propStack.remove(i); + } + + graphicsCtx.setTransform(prop.getTransform()); + graphicsCtx.setClip(prop.getClip()); } /** @@ -338,13 +361,14 @@ public class HwmfGraphics { } } - public void drawString(byte[] text, Point2D reference, Rectangle2D clip) { - drawString(text, reference, clip, null, false); + public void drawString(byte[] text, Point2D reference) { + drawString(text, reference, null, null, null, false); } - public void drawString(byte[] text, Point2D reference, Rectangle2D clip, List dx, boolean isUnicode) { + public void drawString(byte[] text, Point2D reference, Rectangle2D clip, WmfExtTextOutOptions opts, List dx, boolean isUnicode) { + final HwmfDrawProperties prop = getProperties(); - HwmfFont font = getProperties().getFont(); + HwmfFont font = prop.getFont(); if (font == null || text == null || text.length == 0) { return; } @@ -446,21 +470,31 @@ public class HwmfGraphics { Point2D dst = new Point2D.Double(); tx.transform(src, dst); - // TODO: implement clipping on bounds + final Shape clipShape = graphicsCtx.getClip(); final AffineTransform at = graphicsCtx.getTransform(); try { + if (clip != null) { + graphicsCtx.translate(-clip.getCenterX(), -clip.getCenterY()); + graphicsCtx.rotate(angle); + graphicsCtx.translate(clip.getCenterX(), clip.getCenterY()); + if (prop.getBkMode() == HwmfBkMode.OPAQUE && opts.isOpaque()) { + graphicsCtx.setPaint(prop.getBackgroundColor().getColor()); + graphicsCtx.fill(clip); + } + if (opts.isClipped()) { + graphicsCtx.setClip(clip); + } + graphicsCtx.setTransform(at); + } + graphicsCtx.translate(reference.getX(), reference.getY()); graphicsCtx.rotate(angle); graphicsCtx.translate(dst.getX(), dst.getY()); - if (getProperties().getBkMode() == HwmfBkMode.OPAQUE && clip != null) { - // TODO: validate bounds - graphicsCtx.setBackground(getProperties().getBackgroundColor().getColor()); - graphicsCtx.fill(new Rectangle2D.Double(0, 0, clip.getWidth(), clip.getHeight())); - } - graphicsCtx.setColor(getProperties().getTextColor().getColor()); + graphicsCtx.setColor(prop.getTextColor().getColor()); graphicsCtx.drawString(as.getIterator(), 0, 0); } finally { graphicsCtx.setTransform(at); + graphicsCtx.setClip(clipShape); } } @@ -498,18 +532,136 @@ public class HwmfGraphics { } public void drawImage(BufferedImage img, Rectangle2D srcBounds, Rectangle2D dstBounds) { - // prop.getRasterOp(); - graphicsCtx.drawImage(img, - (int)dstBounds.getX(), - (int)dstBounds.getY(), - (int)(dstBounds.getX()+dstBounds.getWidth()), - (int)(dstBounds.getY()+dstBounds.getHeight()), - (int)srcBounds.getX(), - (int)srcBounds.getY(), - (int)(srcBounds.getX()+srcBounds.getWidth()), - (int)(srcBounds.getY()+srcBounds.getHeight()), - null, // getProperties().getBackgroundColor().getColor(), - null - ); + HwmfDrawProperties prop = getProperties(); + + // handle raster op + // currently the raster op as described in https://docs.microsoft.com/en-us/windows/desktop/gdi/ternary-raster-operations + // are not supported, as we would need to extract the destination image area from the underlying buffered image + // and therefore would make it mandatory that the graphics context must be from a buffered image + // furthermore I doubt the purpose of bitwise image operations on non-black/white images + switch (prop.getRasterOp()) { + case D: + // keep destination, i.e. do nothing + break; + case PATCOPY: + graphicsCtx.setPaint(getFill()); + graphicsCtx.fill(dstBounds); + break; + case BLACKNESS: + graphicsCtx.setPaint(Color.BLACK); + graphicsCtx.fill(dstBounds); + break; + case WHITENESS: + graphicsCtx.setPaint(Color.WHITE); + graphicsCtx.fill(dstBounds); + break; + default: + case SRCCOPY: + final Shape clip = graphicsCtx.getClip(); + + // add clipping in case of a source subimage, i.e. a clipped source image + // some dstBounds are horizontal or vertical flipped, so we need to normalize the images + Rectangle2D normalized = new Rectangle2D.Double( + dstBounds.getWidth() >= 0 ? dstBounds.getMinX() : dstBounds.getMaxX(), + dstBounds.getHeight() >= 0 ? dstBounds.getMinY() : dstBounds.getMaxY(), + Math.abs(dstBounds.getWidth()), + Math.abs(dstBounds.getHeight())); + graphicsCtx.clip(normalized); + final AffineTransform at = graphicsCtx.getTransform(); + + final Rectangle2D imgBounds = new Rectangle2D.Double(0,0,img.getWidth(),img.getHeight()); + final boolean isImgBounds = (srcBounds.equals(new Rectangle2D.Double())); + final Rectangle2D srcBounds2 = isImgBounds ? imgBounds : srcBounds; + + // TODO: apply emf transform + graphicsCtx.translate(dstBounds.getX(), dstBounds.getY()); + graphicsCtx.scale(dstBounds.getWidth()/srcBounds2.getWidth(), dstBounds.getHeight()/srcBounds2.getHeight()); + graphicsCtx.translate(-srcBounds2.getX(), -srcBounds2.getY()); + + graphicsCtx.drawImage(img, 0, 0, prop.getBackgroundColor().getColor(), null); + + graphicsCtx.setTransform(at); + graphicsCtx.setClip(clip); + break; + } + + } + + /** + * @return the initial AffineTransform, when this graphics context was created + */ + public AffineTransform getInitTransform() { + return new AffineTransform(initialAT); + } + + /** + * @return the current AffineTransform + */ + public AffineTransform getTransform() { + return new AffineTransform(graphicsCtx.getTransform()); + } + + /** + * Set the current AffineTransform + * @param tx the current AffineTransform + */ + public void setTransform(AffineTransform tx) { + graphicsCtx.setTransform(tx); + } + + private static int clipCnt = 0; + + public void setClip(Shape clip, HwmfRegionMode regionMode, boolean useInitialAT) { + final AffineTransform at = graphicsCtx.getTransform(); + if (useInitialAT) { + graphicsCtx.setTransform(initialAT); + } + final Shape oldClip = graphicsCtx.getClip(); + final boolean isEmpty = clip.getBounds2D().isEmpty(); + switch (regionMode) { + case RGN_AND: + if (!isEmpty) { + graphicsCtx.clip(clip); + } + break; + case RGN_OR: + if (!isEmpty) { + if (oldClip == null) { + graphicsCtx.setClip(clip); + } else { + Area area = new Area(oldClip); + area.add(new Area(clip)); + graphicsCtx.setClip(area); + } + } + break; + case RGN_XOR: + if (!isEmpty) { + if (oldClip == null) { + graphicsCtx.setClip(clip); + } else { + Area area = new Area(oldClip); + area.exclusiveOr(new Area(clip)); + graphicsCtx.setClip(area); + } + } + break; + case RGN_DIFF: + if (!isEmpty) { + if (oldClip != null) { + Area area = new Area(oldClip); + area.subtract(new Area(clip)); + graphicsCtx.setClip(area); + } + } + break; + case RGN_COPY: { + graphicsCtx.setClip(isEmpty ? null : clip); + break; + } + } + if (useInitialAT) { + graphicsCtx.setTransform(at); + } } } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBitmapDib.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBitmapDib.java index e6a8e302a3..1a7e33ec11 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBitmapDib.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBitmapDib.java @@ -413,7 +413,7 @@ public class HwmfBitmapDib { return new ByteArrayInputStream(getBMPData()); } - private byte[] getBMPData() { + public byte[] getBMPData() { if (imageData == null) { throw new RecordFormatException("bitmap not initialized ... need to call init() before"); } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java index 107d440499..f3507fa103 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java @@ -20,6 +20,7 @@ package org.apache.poi.hwmf.record; import java.awt.Shape; import java.awt.geom.Arc2D; import java.awt.geom.Area; +import java.awt.geom.Dimension2D; import java.awt.geom.Ellipse2D; import java.awt.geom.Line2D; import java.awt.geom.Path2D; @@ -744,4 +745,11 @@ public class HwmfDraw { public static String boundsToString(Rectangle2D bounds) { return "{ x: "+bounds.getX()+", y: "+bounds.getY()+", w: "+bounds.getWidth()+", h: "+bounds.getHeight()+" }"; } + + @Internal + public static String dimToString(Dimension2D dim) { + return "{ w: "+dim.getWidth()+", h: "+dim.getHeight()+" }"; + } + + } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java index e335db4577..131699f6b3 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java @@ -27,6 +27,7 @@ import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.io.IOException; +import org.apache.poi.hwmf.draw.HwmfDrawProperties; import org.apache.poi.hwmf.draw.HwmfGraphics; import org.apache.poi.util.LittleEndianConsts; import org.apache.poi.util.LittleEndianInputStream; @@ -222,7 +223,7 @@ public class HwmfFill { * An unsigned integer that defines polygon fill mode. * This MUST be one of the values: ALTERNATE = 0x0001, WINDING = 0x0002 */ - protected HwmfPolyfillMode polyfillMode; + protected HwmfPolyfillMode polyFillMode; @Override public HwmfRecordType getWmfRecordType() { @@ -231,13 +232,18 @@ public class HwmfFill { @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { - polyfillMode = HwmfPolyfillMode.valueOf(leis.readUShort() & 3); + polyFillMode = HwmfPolyfillMode.valueOf(leis.readUShort() & 3); return LittleEndianConsts.SHORT_SIZE; } @Override public void draw(HwmfGraphics ctx) { - ctx.getProperties().setPolyfillMode(polyfillMode); + ctx.getProperties().setPolyfillMode(polyFillMode); + } + + @Override + public String toString() { + return "{ polyFillMode: '"+ polyFillMode +"' }"; } } @@ -458,7 +464,7 @@ public class HwmfFill { * A variable-sized DeviceIndependentBitmap Object (section 2.2.2.9) that is the * source of the color data. */ - protected final HwmfBitmapDib dib = new HwmfBitmapDib(); + protected final HwmfBitmapDib bitmap = new HwmfBitmapDib(); @Override public HwmfRecordType getWmfRecordType() { @@ -481,22 +487,36 @@ public class HwmfFill { size += readBounds2(leis, srcBounds); size += readBounds2(leis, dstBounds); - size += dib.init(leis, (int)(recordSize-6-size)); + size += bitmap.init(leis, (int)(recordSize-6-size)); return size; } @Override public void draw(HwmfGraphics ctx) { - if (dib.isValid()) { - ctx.getProperties().setRasterOp(rasterOperation); + HwmfDrawProperties prop = ctx.getProperties(); + prop.setRasterOp(rasterOperation); + if (bitmap.isValid()) { ctx.drawImage(getImage(), srcBounds, dstBounds); + } else { + BufferedImage bi = new BufferedImage((int)dstBounds.getWidth(), (int)dstBounds.getHeight(), BufferedImage.TYPE_INT_ARGB); + ctx.drawImage(bi, dstBounds, dstBounds); } } @Override public BufferedImage getImage() { - return dib.getImage(); + return bitmap.getImage(); + } + + @Override + public String toString() { + return + "{ rasterOperation: '"+rasterOperation+"'"+ + ", colorUsage: '"+colorUsage+"'"+ + ", srcBounds: "+boundsToString(srcBounds)+ + ", dstBounds: "+boundsToString(dstBounds)+ + "}"; } } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfRegionMode.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfRegionMode.java new file mode 100644 index 0000000000..3a56e52e70 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfRegionMode.java @@ -0,0 +1,59 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hwmf.record; + +import org.apache.poi.hemf.record.emf.HemfFill; + +public enum HwmfRegionMode { + /** + * The new clipping region includes the intersection (overlapping areas) + * of the current clipping region and the current path (or new region). + */ + RGN_AND(0x01), + /** + * The new clipping region includes the union (combined areas) + * of the current clipping region and the current path (or new region). + */ + RGN_OR(0x02), + /** + * The new clipping region includes the union of the current clipping region + * and the current path (or new region) but without the overlapping areas + */ + RGN_XOR(0x03), + /** + * The new clipping region includes the areas of the current clipping region + * with those of the current path (or new region) excluded. + */ + RGN_DIFF(0x04), + /** + * The new clipping region is the current path (or the new region). + */ + RGN_COPY(0x05); + + int flag; + HwmfRegionMode(int flag) { + this.flag = flag; + } + + public static HwmfRegionMode valueOf(int flag) { + for (HwmfRegionMode rm : values()) { + if (rm.flag == flag) return rm; + } + return null; + } +} diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java index 024e1dedf2..f7708ab25c 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java @@ -191,7 +191,7 @@ public class HwmfText { @Override public void draw(HwmfGraphics ctx) { - ctx.drawString(getTextBytes(), reference, null); + ctx.drawString(getTextBytes(), reference); } public String getText(Charset charset) { @@ -396,7 +396,7 @@ public class HwmfText { @Override public void draw(HwmfGraphics ctx) { - ctx.drawString(rawTextBytes, reference, bounds, dx, false); + ctx.drawString(rawTextBytes, reference, bounds, options, dx, false); } public String getText(Charset charset) throws IOException { diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java index 88915f8ad2..a3fb60bd1d 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java @@ -433,6 +433,7 @@ public class HwmfWindowing { @Override public void applyObject(HwmfGraphics ctx) { + ctx.setClip(bounds, HwmfRegionMode.RGN_AND, true); } @Override From acfed6fb29024f63abad4d8195cc1b4e9e2f3b55 Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Thu, 1 Nov 2018 16:27:59 +0000 Subject: [PATCH 17/21] #60656 - Support export file that contains emf and render it correctly git-svn-id: https://svn.apache.org/repos/asf/poi/branches/hemf@1845496 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/poi/hemf/record/emf/HemfMisc.java | 22 +- .../poi/hemf/record/emf/HemfRecordType.java | 4 +- .../apache/poi/hemf/record/emf/HemfText.java | 18 +- .../apache/poi/hwmf/draw/HwmfGraphics.java | 10 +- .../apache/poi/hwmf/record/HwmfColorRef.java | 4 + .../org/apache/poi/hwmf/record/HwmfDraw.java | 12 + .../org/apache/poi/hwmf/record/HwmfText.java | 243 ++++++++---------- 7 files changed, 155 insertions(+), 158 deletions(-) diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java index e9ca805fd3..00f77dd689 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java @@ -24,6 +24,7 @@ import static org.apache.poi.hemf.record.emf.HemfRecordIterator.HEADER_SIZE; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; +import java.awt.image.BufferedImage; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -40,6 +41,7 @@ import org.apache.poi.hwmf.record.HwmfHatchStyle; import org.apache.poi.hwmf.record.HwmfMapMode; import org.apache.poi.hwmf.record.HwmfMisc; import org.apache.poi.hwmf.record.HwmfMisc.WmfSetBkMode; +import org.apache.poi.hwmf.record.HwmfObjectTableEntry; import org.apache.poi.hwmf.record.HwmfPalette.PaletteEntry; import org.apache.poi.hwmf.record.HwmfPenStyle; import org.apache.poi.hwmf.record.HwmfPenStyle.HwmfLineDash; @@ -687,13 +689,20 @@ public class HemfMisc { } } - public static class EmfCreateMonoBrush16 extends EmfCreatePen { + public static class EmfCreateMonoBrush implements HemfRecord, HwmfObjectTableEntry { + /** + * A 32-bit unsigned integer that specifies the index of the logical palette object + * in the EMF Object Table. This index MUST be saved so that this object can be + * reused or modified. + */ + protected int penIndex; + protected HwmfFill.ColorUsage colorUsage; protected final HwmfBitmapDib bitmap = new HwmfBitmapDib(); @Override public HemfRecordType getEmfRecordType() { - return HemfRecordType.createMonoBrush16; + return HemfRecordType.createMonoBrush; } @Override @@ -729,12 +738,17 @@ public class HemfMisc { return size; } + @Override + public void draw(HemfGraphics ctx) { + ctx.addObjectTableEntry(this, penIndex); + } + @Override public void applyObject(HwmfGraphics ctx) { - super.applyObject(ctx); HwmfDrawProperties props = ctx.getProperties(); props.setBrushStyle(HwmfBrushStyle.BS_PATTERN); - props.setBrushBitmap(bitmap.getImage()); + BufferedImage bmp = bitmap.getImage(); + props.setBrushBitmap(bmp); } } } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java index 25e7149251..9d25a9c145 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java @@ -105,7 +105,7 @@ public enum HemfRecordType { plgblt(0x0000004F, UnimplementedHemfRecord::new), setDiBitsToDevice(0x00000050, HemfFill.EmfSetDiBitsToDevice::new), stretchDiBits(0x00000051, HemfFill.EmfStretchDiBits::new), - extCreateFontIndirectW(0x00000052, HemfText.ExtCreateFontIndirectW::new), + extCreateFontIndirectW(0x00000052, HemfText.EmfExtCreateFontIndirectW::new), extTextOutA(0x00000053, HemfText.EmfExtTextOutA::new), extTextOutW(0x00000054, HemfText.EmfExtTextOutW::new), polyBezier16(0x00000055, HemfDraw.EmfPolyBezier16::new), @@ -116,7 +116,7 @@ public enum HemfRecordType { polyPolyline16(0x0000005A, HemfDraw.EmfPolyPolyline16::new), polyPolygon16(0x0000005B, HemfDraw.EmfPolyPolygon16::new), polyDraw16(0x0000005C, HemfDraw.EmfPolyDraw16::new), - createMonoBrush16(0x0000005D, HemfMisc.EmfCreateMonoBrush16::new), + createMonoBrush(0x0000005D, HemfMisc.EmfCreateMonoBrush::new), createDibPatternBrushPt(0x0000005E, HemfMisc.EmfCreateDibPatternBrushPt::new), extCreatePen(0x0000005F, HemfMisc.EmfExtCreatePen::new), polytextouta(0x00000060, HemfText.PolyTextOutA::new), diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java index 4a8508961b..fb0d2a79aa 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java @@ -113,7 +113,10 @@ public class HemfText { size += LittleEndianConsts.INT_SIZE; // handle dx before string and other way round - for (char op : ((offDx < offString) ? "ds" : "sd").toCharArray()) { + final String order = (offDx < offString) ? "ds" : "sd"; + // the next byte index after the string ends + int strEnd = (int)((offDx <= HEADER_SIZE) ? recordSize : offDx-HEADER_SIZE); + for (char op : order.toCharArray()) { switch (op) { case 'd': { dx.clear(); @@ -138,6 +141,9 @@ public class HemfText { dx.add((int) leis.readUInt()); size += LittleEndianConsts.INT_SIZE; } + } else { + // if there are no dx entries, reset the string end + strEnd = (int)recordSize; } if (dx.size() < stringLength) { // invalid dx array @@ -152,7 +158,9 @@ public class HemfText { leis.skipFully(undefinedSpace1); size += undefinedSpace1; - final int maxSize = (int)Math.min(recordSize-size, stringLength * (isUnicode() ? 2 : 1)); + // read all available bytes and not just "stringLength * 1(ansi)/2(unicode)" + // in case we need to deal with surrogate pairs + final int maxSize = (int)(Math.min(recordSize, strEnd)-size); rawTextBytes = IOUtils.safelyAllocate(maxSize, MAX_RECORD_LENGTH); leis.readFully(rawTextBytes); size += maxSize; @@ -191,7 +199,7 @@ public class HemfText { @Override public void draw(HwmfGraphics ctx) { - ctx.drawString(rawTextBytes, reference, bounds, options, dx, isUnicode()); + ctx.drawString(rawTextBytes, stringLength, reference, bounds, options, dx, isUnicode()); } @Override @@ -258,11 +266,11 @@ public class HemfText { - public static class ExtCreateFontIndirectW extends HwmfText.WmfCreateFontIndirect + public static class EmfExtCreateFontIndirectW extends HwmfText.WmfCreateFontIndirect implements HemfRecord { int fontIdx; - public ExtCreateFontIndirectW() { + public EmfExtCreateFontIndirectW() { super(new HemfFont()); } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java index 9ba07dd006..4ecca1e81a 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java @@ -361,11 +361,11 @@ public class HwmfGraphics { } } - public void drawString(byte[] text, Point2D reference) { - drawString(text, reference, null, null, null, false); + public void drawString(byte[] text, int length, Point2D reference) { + drawString(text, length, reference, null, null, null, false); } - public void drawString(byte[] text, Point2D reference, Rectangle2D clip, WmfExtTextOutOptions opts, List dx, boolean isUnicode) { + public void drawString(byte[] text, int length, Point2D reference, Rectangle2D clip, WmfExtTextOutOptions opts, List dx, boolean isUnicode) { final HwmfDrawProperties prop = getProperties(); HwmfFont font = prop.getFont(); @@ -387,7 +387,7 @@ public class HwmfGraphics { } } - String textString = new String(text, charset).trim(); + String textString = new String(text, charset).substring(0,length).trim(); if (textString.isEmpty()) { return; } @@ -462,7 +462,7 @@ public class HwmfGraphics { case BASELINE: break; case BOTTOM: - tx.translate(0, pixelBounds.getHeight()); + tx.translate(0, -(pixelBounds.getHeight()-layout.getDescent())); break; } tx.rotate(angle); diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfColorRef.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfColorRef.java index 14ebc874d5..a757617752 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfColorRef.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfColorRef.java @@ -54,6 +54,10 @@ public class HwmfColorRef implements Cloneable { return colorRef; } + public void setColor(Color color) { + colorRef = color; + } + /** * Creates a new object of the same class and with the * same contents as this object. diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java index f3507fa103..f71becda8a 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java @@ -594,6 +594,18 @@ public class HwmfDraw { return new Arc2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), startAngle, arcAngle, arcClosure); } + + @Override + public String toString() { + Arc2D arc = getShape(); + return + "{ startPoint: "+pointToString(startPoint)+ + ", endPoint: "+pointToString(endPoint)+ + ", startAngle: "+arc.getAngleStart()+ + ", extentAngle: "+arc.getAngleExtent()+ + ", bounds: "+boundsToString(bounds)+ + " }"; + } } /** diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java index f7708ab25c..020e6148d4 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java @@ -24,11 +24,7 @@ import static org.apache.poi.hwmf.record.HwmfDraw.readRectS; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; -import java.io.ByteArrayInputStream; -import java.io.EOFException; import java.io.IOException; -import java.io.InputStreamReader; -import java.io.Reader; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; @@ -45,7 +41,6 @@ import org.apache.poi.util.LittleEndianInputStream; import org.apache.poi.util.LocaleUtil; import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; -import org.apache.poi.util.RecordFormatException; public class HwmfText { private static final POILogger logger = POILogFactory.getLogger(HwmfText.class); @@ -191,7 +186,7 @@ public class HwmfText { @Override public void draw(HwmfGraphics ctx) { - ctx.drawString(getTextBytes(), reference); + ctx.drawString(getTextBytes(), stringLength, reference); } public String getText(Charset charset) { @@ -396,11 +391,11 @@ public class HwmfText { @Override public void draw(HwmfGraphics ctx) { - ctx.drawString(rawTextBytes, reference, bounds, options, dx, false); + ctx.drawString(rawTextBytes, stringLength, reference, bounds, options, dx, false); } public String getText(Charset charset) throws IOException { - return new String(rawTextBytes, charset); + return new String(rawTextBytes, charset).substring(0, stringLength); } public Point2D getReference() { @@ -448,57 +443,17 @@ public class HwmfText { */ public static class WmfSetTextAlign implements HwmfRecord { - // *********************************************************************************** - // TextAlignmentMode Flags: - // *********************************************************************************** - - /** - * The drawing position in the playback device context MUST NOT be updated after each - * text output call. The reference point MUST be passed to the text output function. - */ - @SuppressWarnings("unused") - private static final BitField TA_NOUPDATECP = BitFieldFactory.getInstance(0x0000); - - /** - * The reference point MUST be on the left edge of the bounding rectangle. - */ - @SuppressWarnings("unused") - private static final BitField TA_LEFT = BitFieldFactory.getInstance(0x0000); - - /** - * The reference point MUST be on the top edge of the bounding rectangle. - */ - @SuppressWarnings("unused") - private static final BitField TA_TOP = BitFieldFactory.getInstance(0x0000); - /** * The drawing position in the playback device context MUST be updated after each text - * output call. It MUST be used as the reference point. + * output call. It MUST be used as the reference point.

    + * + * If the flag is not set, the option TA_NOUPDATECP is active, i.e. the drawing position + * in the playback device context MUST NOT be updated after each text output call. + * The reference point MUST be passed to the text output function. */ @SuppressWarnings("unused") private static final BitField TA_UPDATECP = BitFieldFactory.getInstance(0x0001); - - /** - * The reference point MUST be on the right edge of the bounding rectangle. - */ - private static final BitField TA_RIGHT = BitFieldFactory.getInstance(0x0002); - - /** - * The reference point MUST be aligned horizontally with the center of the bounding - * rectangle. - */ - private static final BitField TA_CENTER = BitFieldFactory.getInstance(0x0006); - - /** - * The reference point MUST be on the bottom edge of the bounding rectangle. - */ - private static final BitField TA_BOTTOM = BitFieldFactory.getInstance(0x0008); - - /** - * The reference point MUST be on the baseline of the text. - */ - private static final BitField TA_BASELINE = BitFieldFactory.getInstance(0x0018); - + /** * The text MUST be laid out in right-to-left reading order, instead of the default * left-to-right order. This SHOULD be applied only when the font that is defined in the @@ -506,43 +461,64 @@ public class HwmfText { */ @SuppressWarnings("unused") private static final BitField TA_RTLREADING = BitFieldFactory.getInstance(0x0100); - - // *********************************************************************************** - // VerticalTextAlignmentMode Flags (e.g. for Kanji fonts) - // *********************************************************************************** - + + + private static final BitField ALIGN_MASK = BitFieldFactory.getInstance(0x0006); + /** - * The reference point MUST be on the top edge of the bounding rectangle. + * Flag TA_LEFT (0x0000): + * The reference point MUST be on the left edge of the bounding rectangle, + * if all bits of the align mask (latin mode) are unset. + * + * Flag VTA_TOP (0x0000): + * The reference point MUST be on the top edge of the bounding rectangle, + * if all bits of the valign mask are unset. */ - @SuppressWarnings("unused") - private static final BitField VTA_TOP = BitFieldFactory.getInstance(0x0000); - + private static final int ALIGN_LEFT = 0; + /** + * Flag TA_RIGHT (0x0002): * The reference point MUST be on the right edge of the bounding rectangle. - */ - @SuppressWarnings("unused") - private static final BitField VTA_RIGHT = BitFieldFactory.getInstance(0x0000); - - /** + * + * Flag VTA_BOTTOM (0x0002): * The reference point MUST be on the bottom edge of the bounding rectangle. */ - private static final BitField VTA_BOTTOM = BitFieldFactory.getInstance(0x0002); - + private static final int ALIGN_RIGHT = 1; + /** - * The reference point MUST be aligned vertically with the center of the bounding + * Flag TA_CENTER (0x0006) / VTA_CENTER (0x0006): + * The reference point MUST be aligned horizontally with the center of the bounding * rectangle. */ - private static final BitField VTA_CENTER = BitFieldFactory.getInstance(0x0006); - + private static final int ALIGN_CENTER = 3; + + private static final BitField VALIGN_MASK = BitFieldFactory.getInstance(0x0018); + /** + * Flag TA_TOP (0x0000): + * The reference point MUST be on the top edge of the bounding rectangle, + * if all bits of the valign mask are unset. + * + * Flag VTA_RIGHT (0x0000): + * The reference point MUST be on the right edge of the bounding rectangle, + * if all bits of the align mask (asian mode) are unset. + */ + private static final int VALIGN_TOP = 0; + + /** + * Flag TA_BOTTOM (0x0008): + * The reference point MUST be on the bottom edge of the bounding rectangle. + * + * Flag VTA_LEFT (0x0008): * The reference point MUST be on the left edge of the bounding rectangle. */ - private static final BitField VTA_LEFT = BitFieldFactory.getInstance(0x0008); + private static final int VALIGN_BOTTOM = 1; /** + * Flag TA_BASELINE (0x0018) / VTA_BASELINE (0x0018): * The reference point MUST be on the baseline of the text. */ - private static final BitField VTA_BASELINE = BitFieldFactory.getInstance(0x0018); + private static final int VALIGN_BASELINE = 3; /** * A 16-bit unsigned integer that defines text alignment. @@ -566,85 +542,68 @@ public class HwmfText { @Override public void draw(HwmfGraphics ctx) { HwmfDrawProperties props = ctx.getProperties(); - if (TA_CENTER.isSet(textAlignmentMode)) { - props.setTextAlignLatin(HwmfTextAlignment.CENTER); - } else if (TA_RIGHT.isSet(textAlignmentMode)) { - props.setTextAlignLatin(HwmfTextAlignment.RIGHT); - } else { - props.setTextAlignLatin(HwmfTextAlignment.LEFT); - } - - if (VTA_CENTER.isSet(textAlignmentMode)) { - props.setTextAlignAsian(HwmfTextAlignment.CENTER); - } else if (VTA_LEFT.isSet(textAlignmentMode)) { - props.setTextAlignAsian(HwmfTextAlignment.LEFT); - } else { - props.setTextAlignAsian(HwmfTextAlignment.RIGHT); - } - - if (TA_BASELINE.isSet(textAlignmentMode)) { - props.setTextVAlignLatin(HwmfTextVerticalAlignment.BASELINE); - } else if (TA_BOTTOM.isSet(textAlignmentMode)) { - props.setTextVAlignLatin(HwmfTextVerticalAlignment.BOTTOM); - } else { - props.setTextVAlignLatin(HwmfTextVerticalAlignment.TOP); - } - - if (VTA_BASELINE.isSet(textAlignmentMode)) { - props.setTextVAlignAsian(HwmfTextVerticalAlignment.BASELINE); - } else if (VTA_BOTTOM.isSet(textAlignmentMode)) { - props.setTextVAlignAsian(HwmfTextVerticalAlignment.BOTTOM); - } else { - props.setTextVAlignAsian(HwmfTextVerticalAlignment.TOP); - } + props.setTextAlignLatin(getAlignLatin()); + props.setTextVAlignLatin(getVAlignLatin()); + props.setTextAlignAsian(getAlignAsian()); + props.setTextVAlignAsian(getVAlignAsian()); } @Override public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("{ align: '"); + return + "{ align: '"+ getAlignLatin() + "'" + + ", valign: '"+ getVAlignLatin() + "'" + + ", alignAsian: '"+ getAlignAsian() + "'" + + ", valignAsian: '"+ getVAlignAsian() + "'" + + "}"; + } - if (TA_CENTER.isSet(textAlignmentMode)) { - sb.append("center"); - } else if (TA_RIGHT.isSet(textAlignmentMode)) { - sb.append("right"); - } else { - sb.append("left"); + private HwmfTextAlignment getAlignLatin() { + switch (ALIGN_MASK.getValue(textAlignmentMode)) { + default: + case ALIGN_LEFT: + return HwmfTextAlignment.LEFT; + case ALIGN_CENTER: + return HwmfTextAlignment.CENTER; + case ALIGN_RIGHT: + return HwmfTextAlignment.RIGHT; } + } - sb.append("', align-asian: '"); - - if (VTA_CENTER.isSet(textAlignmentMode)) { - sb.append("center"); - } else if (VTA_LEFT.isSet(textAlignmentMode)) { - sb.append("left"); - } else { - sb.append("right"); + private HwmfTextVerticalAlignment getVAlignLatin() { + switch (VALIGN_MASK.getValue(textAlignmentMode)) { + default: + case VALIGN_TOP: + return HwmfTextVerticalAlignment.TOP; + case VALIGN_BASELINE: + return HwmfTextVerticalAlignment.BASELINE; + case VALIGN_BOTTOM: + return HwmfTextVerticalAlignment.BOTTOM; } + } - sb.append("', valign: '"); - - if (TA_BASELINE.isSet(textAlignmentMode)) { - sb.append("baseline"); - } else if (TA_BOTTOM.isSet(textAlignmentMode)) { - sb.append("bottom"); - } else { - sb.append("top"); + private HwmfTextAlignment getAlignAsian() { + switch (getVAlignLatin()) { + default: + case TOP: + return HwmfTextAlignment.RIGHT; + case BASELINE: + return HwmfTextAlignment.CENTER; + case BOTTOM: + return HwmfTextAlignment.LEFT; } + } - sb.append("', valign-asian: '"); - - if (VTA_BASELINE.isSet(textAlignmentMode)) { - sb.append("baseline"); - } else if (VTA_BOTTOM.isSet(textAlignmentMode)) { - sb.append("bottom"); - } else { - sb.append("top"); + private HwmfTextVerticalAlignment getVAlignAsian() { + switch (getAlignLatin()) { + default: + case LEFT: + return HwmfTextVerticalAlignment.TOP; + case CENTER: + return HwmfTextVerticalAlignment.BASELINE; + case RIGHT: + return HwmfTextVerticalAlignment.BOTTOM; } - - sb.append("' }"); - - return sb.toString(); } } From 8b3974f9452388acf0fa65ec3bad316ec955883c Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Fri, 2 Nov 2018 18:01:50 +0000 Subject: [PATCH 18/21] #60656 - Support export file that contains emf and render it correctly git-svn-id: https://svn.apache.org/repos/asf/poi/branches/hemf@1845612 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/poi/hemf/record/emf/HemfDraw.java | 8 +-- .../apache/poi/hemf/record/emf/HemfFill.java | 12 +++++ .../apache/poi/hemf/record/emf/HemfFont.java | 37 ++++++++++++-- .../poi/hemf/record/emf/HemfHeader.java | 2 +- .../apache/poi/hemf/record/emf/HemfMisc.java | 12 +++++ .../apache/poi/hemf/record/emf/HemfText.java | 6 ++- .../poi/hemf/record/emf/HemfWindowing.java | 3 +- .../poi/hwmf/draw/HwmfDrawProperties.java | 2 + .../apache/poi/hwmf/draw/HwmfGraphics.java | 11 ++-- .../apache/poi/hwmf/record/HwmfBitmapDib.java | 50 ++++++++++++++++--- .../org/apache/poi/hwmf/record/HwmfDraw.java | 10 ++++ .../org/apache/poi/hwmf/record/HwmfFill.java | 2 +- .../org/apache/poi/hwmf/record/HwmfFont.java | 17 ++++++- .../org/apache/poi/hwmf/record/HwmfMisc.java | 3 ++ .../org/apache/poi/hwmf/record/HwmfText.java | 2 +- .../apache/poi/hwmf/record/HwmfWindowing.java | 2 + 16 files changed, 158 insertions(+), 21 deletions(-) diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java index d18f0d4119..367d64b084 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java @@ -18,6 +18,7 @@ package org.apache.poi.hemf.record.emf; import static org.apache.poi.hwmf.record.HwmfDraw.boundsToString; +import static org.apache.poi.hwmf.record.HwmfDraw.normalizeBounds; import java.awt.Shape; import java.awt.geom.Arc2D; @@ -660,7 +661,7 @@ public class HemfDraw { @Override public void draw(HemfGraphics ctx) { - ctx.draw(path -> path.append(bounds, false), FillDrawStyle.FILL_DRAW); + ctx.draw(path -> path.append(normalizeBounds(bounds), false), FillDrawStyle.FILL_DRAW); } } @@ -1127,8 +1128,9 @@ public class HemfDraw { } static long readDimensionInt(LittleEndianInputStream leis, Dimension2D dimension) { - final double width = leis.readUInt(); - final double height = leis.readUInt(); + // although the spec says "use unsigned ints", there are examples out there using signed ints + final double width = leis.readInt(); + final double height = leis.readInt(); dimension.setSize(width, height); return 2*LittleEndianConsts.INT_SIZE; } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java index 810c9325a3..5cd4879b15 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java @@ -21,6 +21,7 @@ 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 static org.apache.poi.hwmf.record.HwmfDraw.boundsToString; +import static org.apache.poi.hwmf.record.HwmfDraw.pointToString; import java.awt.Shape; import java.awt.geom.AffineTransform; @@ -588,6 +589,17 @@ public class HemfFill { return size; } + + @Override + public String toString() { + return + "{ bounds: " + boundsToString(bounds) + + ", dest: " + pointToString(dest) + + ", src: " + boundsToString(src) + + ", usageSrc: '" + usageSrc + "'" + + ", bitmap: " + bitmap + + "}"; + } } static long readBitmap(final LittleEndianInputStream leis, final HwmfBitmapDib bitmap, diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFont.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFont.java index 8690855f16..ea3eb7754e 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFont.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFont.java @@ -36,6 +36,11 @@ public class HemfFont extends HwmfFont { protected static class LogFontExDv implements LogFontDetails { protected int[] designVector; + + @Override + public String toString() { + return "{ designVectorLen: " + (designVector == null ? 0 : designVector.length) + " }"; + } } protected static class LogFontPanose implements LogFontDetails { @@ -195,6 +200,25 @@ public class HemfFont extends HwmfFont { protected Letterform letterform; protected MidLine midLine; protected XHeight xHeight; + + @Override + public String toString() { + return + "{ styleSize: " + styleSize + + ", vendorId: " + vendorId + + ", culture: " + culture + + ", familyType: '" + familyType + "'" + + ", serifStyle: '" + serifStyle + "'" + + ", weight: '" + weight + "'" + + ", proportion: '" + proportion + "'" + + ", contrast: '" + contrast + "'" + + ", strokeVariation: '" + strokeVariation + "'" + + ", armStyle: '" + armStyle + "'" + + ", letterform: '" + letterform + "'" + + ", midLine: '" + midLine + "'" + + ", xHeight: '" + xHeight + "'" + + "}"; + } } protected String fullname; @@ -435,12 +459,19 @@ public class HemfFont extends HwmfFont { size += (2+numAxes)*LittleEndianConsts.INT_SIZE; } - - - return size; } + @Override + public String toString() { + return + "{ fullname: '" + (fullname == null ? "" : fullname) + "'" + + ", style: '" + (style == null ? "" : style) + "'" + + ", script: '" + (script == null ? "" : script) + "'" + + ", details: " + details + + "," + super.toString().substring(1); + } + @Override protected int readString(LittleEndianInputStream leis, StringBuilder sb, int limit) throws IOException { sb.setLength(0); diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfHeader.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfHeader.java index 4b01a5a256..6c1cf5cadd 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfHeader.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfHeader.java @@ -126,7 +126,7 @@ public class HemfHeader implements HemfRecord { ", bytes: " + bytes + ", records: " + records + ", handles: " + handles + - ", description: '" + description + "'" + + ", description: '" + (description == null ? "" : description) + "'" + ", nPalEntries: " + nPalEntries + ", hasExtension1: " + hasExtension1 + ", cbPixelFormat: " + cbPixelFormat + diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java index 00f77dd689..206b7d3162 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java @@ -745,10 +745,22 @@ public class HemfMisc { @Override public void applyObject(HwmfGraphics ctx) { + if (!bitmap.isValid()) { + return; + } HwmfDrawProperties props = ctx.getProperties(); props.setBrushStyle(HwmfBrushStyle.BS_PATTERN); BufferedImage bmp = bitmap.getImage(); props.setBrushBitmap(bmp); } + + @Override + public String toString() { + return + "{ penIndex: " + penIndex + + ", colorUsage: " + colorUsage + + ", bitmap: " + bitmap + + "}"; + } } } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java index fb0d2a79aa..f62155d182 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java @@ -199,7 +199,11 @@ public class HemfText { @Override public void draw(HwmfGraphics ctx) { - ctx.drawString(rawTextBytes, stringLength, reference, bounds, options, dx, isUnicode()); + // A 32-bit floating-point value that specifies the scale factor to apply along + // the axis to convert from page space units to .01mm units. + // This SHOULD be used only if the graphics mode specified by iGraphicsMode is GM_COMPATIBLE. + Dimension2D scl = graphicsMode == EmfGraphicsMode.GM_COMPATIBLE ? scale : null; + ctx.drawString(rawTextBytes, stringLength, reference, scl, bounds, options, dx, isUnicode()); } @Override diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java index 955bbd9f03..a312dc116e 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java @@ -19,6 +19,7 @@ package org.apache.poi.hemf.record.emf; import static org.apache.poi.hemf.record.emf.HemfDraw.readDimensionInt; import static org.apache.poi.hemf.record.emf.HemfDraw.readPointL; +import static org.apache.poi.hwmf.record.HwmfDraw.normalizeBounds; import java.io.IOException; @@ -135,7 +136,7 @@ public class HemfWindowing { @Override public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { - return HemfDraw.readRectL(leis, bounds); + return HemfDraw.readRectL(leis, normalizeBounds(bounds)); } } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java index 9a58e57014..8b8358c30f 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java @@ -93,6 +93,8 @@ public class HwmfDrawProperties { textVAlignAsian = HwmfTextVerticalAlignment.TOP; rasterOp = HwmfTernaryRasterOp.PATCOPY; clip = null; + font = new HwmfFont(); + font.initDefaults(); } public HwmfDrawProperties(HwmfDrawProperties other) { diff --git a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java index 4ecca1e81a..dadb091699 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java @@ -30,6 +30,7 @@ import java.awt.font.TextAttribute; import java.awt.font.TextLayout; import java.awt.geom.AffineTransform; import java.awt.geom.Area; +import java.awt.geom.Dimension2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; @@ -333,8 +334,9 @@ public class HwmfGraphics { case MM_ANISOTROPIC: // scale window bounds to output bounds if (view != null) { - graphicsCtx.translate(view.getX() - win.getX(), view.getY() - win.getY()); + graphicsCtx.translate(view.getCenterX(), view.getCenterY()); graphicsCtx.scale(view.getWidth() / win.getWidth(), view.getHeight() / win.getHeight()); + graphicsCtx.translate(-win.getCenterX(), -win.getCenterY()); } break; case MM_ISOTROPIC: @@ -362,10 +364,10 @@ public class HwmfGraphics { } public void drawString(byte[] text, int length, Point2D reference) { - drawString(text, length, reference, null, null, null, false); + drawString(text, length, reference, null, null, null, null, false); } - public void drawString(byte[] text, int length, Point2D reference, Rectangle2D clip, WmfExtTextOutOptions opts, List dx, boolean isUnicode) { + public void drawString(byte[] text, int length, Point2D reference, Dimension2D scale, Rectangle2D clip, WmfExtTextOutOptions opts, List dx, boolean isUnicode) { final HwmfDrawProperties prop = getProperties(); HwmfFont font = prop.getFont(); @@ -489,6 +491,9 @@ public class HwmfGraphics { graphicsCtx.translate(reference.getX(), reference.getY()); graphicsCtx.rotate(angle); + if (scale != null) { + graphicsCtx.scale(scale.getWidth() < 0 ? -1 : 1, scale.getHeight() < 0 ? -1 : 1); + } graphicsCtx.translate(dst.getX(), dst.getY()); graphicsCtx.setColor(prop.getTextColor().getColor()); graphicsCtx.drawString(as.getIterator(), 0, 0); diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBitmapDib.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBitmapDib.java index 1a7e33ec11..a800bb0e05 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBitmapDib.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBitmapDib.java @@ -45,9 +45,11 @@ import org.apache.poi.util.RecordFormatException; */ public class HwmfBitmapDib { + private static final POILogger logger = POILogFactory.getLogger(HwmfBitmapDib.class); + private static final int BMP_HEADER_SIZE = 14; private static final int MAX_RECORD_LENGTH = HwmfPicture.MAX_RECORD_LENGTH; - public static enum BitCount { + public enum BitCount { /** * The image SHOULD be in either JPEG or PNG format. <6> Neither of these formats includes * a color table, so this value specifies that no color table is present. See [JFIF] and [RFC2083] @@ -129,7 +131,7 @@ public class HwmfBitmapDib { } } - public static enum Compression { + public enum Compression { /** * The bitmap is in uncompressed red green blue (RGB) format that is not compressed * and does not use color masks. @@ -198,9 +200,7 @@ public class HwmfBitmapDib { } } - private final static POILogger logger = POILogFactory.getLogger(HwmfBitmapDib.class); - private static final int BMP_HEADER_SIZE = 14; - + private int headerSize; private int headerWidth; private int headerHeight; @@ -406,7 +406,27 @@ public class HwmfBitmapDib { } public boolean isValid() { - return (imageData != null); + // the recordsize ended before the image data + if (imageData == null) { + return false; + } + + // ignore all black mono-brushes + if (this.headerBitCount == BitCount.BI_BITCOUNT_1) { + if (colorTable == null) { + return false; + } + + for (Color c : colorTable) { + if (!Color.BLACK.equals(c)) { + return true; + } + } + + return false; + } + + return true; } public InputStream getBMPStream() { @@ -448,6 +468,24 @@ public class HwmfBitmapDib { } } + @Override + public String toString() { + return + "{ headerSize: " + headerSize + + ", width: " + headerWidth + + ", height: " + headerHeight + + ", planes: " + headerPlanes + + ", bitCount: '" + headerBitCount + "'" + + ", compression: '" + headerCompression + "'" + + ", imageSize: " + headerImageSize + + ", xPelsPerMeter: " + headerXPelsPerMeter + + ", yPelsPerMeter: " + headerYPelsPerMeter + + ", colorUsed: " + headerColorUsed + + ", colorImportant: " + headerColorImportant + + ", imageSize: " + (imageData == null ? 0 : imageData.length) + + "}"; + } + protected BufferedImage getPlaceholder() { BufferedImage bi = new BufferedImage(headerWidth, headerHeight, BufferedImage.TYPE_INT_ARGB); Graphics2D g = bi.createGraphics(); diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java index f71becda8a..17cd3c83d8 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java @@ -763,5 +763,15 @@ public class HwmfDraw { return "{ w: "+dim.getWidth()+", h: "+dim.getHeight()+" }"; } + @Internal + public static Rectangle2D normalizeBounds(Rectangle2D bounds) { + return (bounds.getWidth() >= 0 && bounds.getHeight() >= 0) ? bounds + : new Rectangle2D.Double( + bounds.getWidth() >= 0 ? bounds.getMinX() : bounds.getMaxX(), + bounds.getHeight() >= 0 ? bounds.getMinY() : bounds.getMaxY(), + Math.abs(bounds.getWidth()), + Math.abs(bounds.getHeight()) + ); + } } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java index 131699f6b3..e353750bd9 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java @@ -498,7 +498,7 @@ public class HwmfFill { prop.setRasterOp(rasterOperation); if (bitmap.isValid()) { ctx.drawImage(getImage(), srcBounds, dstBounds); - } else { + } else if (!dstBounds.isEmpty()) { BufferedImage bi = new BufferedImage((int)dstBounds.getWidth(), (int)dstBounds.getHeight(), BufferedImage.TYPE_INT_ARGB); ctx.drawImage(bi, dstBounds, dstBounds); } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFont.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFont.java index 8cb8486771..4413983ef3 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFont.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFont.java @@ -369,6 +369,21 @@ public class HwmfFont implements FontInfo { return 5*LittleEndianConsts.SHORT_SIZE+8*LittleEndianConsts.BYTE_SIZE+readBytes; } + public void initDefaults() { + height = -12; + width = 0; + escapement = 0; + weight = 400; + italic = false; + underline = false; + strikeOut = false; + charSet = FontCharset.ANSI; + outPrecision = WmfOutPrecision.OUT_DEFAULT_PRECIS; + quality = WmfFontQuality.ANTIALIASED_QUALITY; + pitchAndFamily = FontFamily.FF_DONTCARE.getFlag() | (FontPitch.DEFAULT.getNativeId() << 6); + facename = "SansSerif"; + } + public int getHeight() { return height; } @@ -479,7 +494,7 @@ public class HwmfFont implements FontInfo { ", charset: '"+charSet+"'"+ ", outPrecision: '"+outPrecision+"'"+ ", clipPrecision: '"+clipPrecision+"'"+ - ", qualtiy: '"+quality+"'"+ + ", quality: '"+quality+"'"+ ", pitch: '"+getPitch()+"'"+ ", family: '"+getFamily()+"'"+ ", facename: '"+facename+"'"+ diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java index 7303d2c6fe..eef15e30d7 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java @@ -454,6 +454,9 @@ public class HwmfMisc { @Override public void applyObject(HwmfGraphics ctx) { + if (patternDib != null && !patternDib.isValid()) { + return; + } HwmfDrawProperties prop = ctx.getProperties(); prop.setBrushStyle(style); prop.setBrushBitmap(getImage()); diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java index 020e6148d4..391215d657 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java @@ -391,7 +391,7 @@ public class HwmfText { @Override public void draw(HwmfGraphics ctx) { - ctx.drawString(rawTextBytes, stringLength, reference, bounds, options, dx, false); + ctx.drawString(rawTextBytes, stringLength, reference, null, bounds, options, dx, false); } public String getText(Charset charset) throws IOException { diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java index a3fb60bd1d..34c948df02 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java @@ -18,6 +18,7 @@ package org.apache.poi.hwmf.record; import static org.apache.poi.hwmf.record.HwmfDraw.boundsToString; +import static org.apache.poi.hwmf.record.HwmfDraw.normalizeBounds; import static org.apache.poi.hwmf.record.HwmfDraw.pointToString; import static org.apache.poi.hwmf.record.HwmfDraw.readBounds; import static org.apache.poi.hwmf.record.HwmfDraw.readPointS; @@ -398,6 +399,7 @@ public class HwmfWindowing { @Override public void applyObject(HwmfGraphics ctx) { + ctx.setClip(normalizeBounds(bounds), HwmfRegionMode.RGN_DIFF, false); } @Override From 940daf0d923f84a5ea8cc02397060c84d4f90ae7 Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Mon, 12 Nov 2018 23:21:18 +0000 Subject: [PATCH 19/21] #60656 - Support export file that contains emf and render it correctly git-svn-id: https://svn.apache.org/repos/asf/poi/branches/hemf@1846472 13f79535-47bb-0310-9956-ffa450edef68 --- .../poi/sl/draw/DrawFontManagerDefault.java | 51 ++++++---- .../apache/poi/hemf/record/emf/HemfDraw.java | 29 +++--- .../apache/poi/hemf/record/emf/HemfFill.java | 36 +++---- .../apache/poi/hemf/record/emf/HemfFont.java | 9 +- .../apache/poi/hemf/record/emf/HemfMisc.java | 77 ++++++++++----- .../poi/hemf/record/emf/HemfPenStyle.java | 45 +++++++++ .../poi/hemf/record/emf/HemfRecord.java | 4 + .../hemf/record/emf/HemfRecordIterator.java | 4 +- .../apache/poi/hemf/record/emf/HemfText.java | 4 +- .../apache/poi/hwmf/draw/HwmfGraphics.java | 41 ++++++-- .../org/apache/poi/hwmf/record/HwmfFill.java | 4 +- .../apache/poi/hwmf/record/HwmfPenStyle.java | 13 ++- .../apache/poi/hwmf/record/HwmfWindowing.java | 94 +++++++++++++------ 13 files changed, 286 insertions(+), 125 deletions(-) create mode 100644 src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfPenStyle.java diff --git a/src/java/org/apache/poi/sl/draw/DrawFontManagerDefault.java b/src/java/org/apache/poi/sl/draw/DrawFontManagerDefault.java index c439fc926f..a428943699 100644 --- a/src/java/org/apache/poi/sl/draw/DrawFontManagerDefault.java +++ b/src/java/org/apache/poi/sl/draw/DrawFontManagerDefault.java @@ -22,6 +22,8 @@ package org.apache.poi.sl.draw; import java.awt.Font; import java.awt.Graphics2D; import java.util.Map; +import java.util.Set; +import java.util.TreeSet; import org.apache.poi.common.usermodel.fonts.FontInfo; import org.apache.poi.sl.draw.Drawable.DrawableHint; @@ -33,6 +35,13 @@ import org.apache.poi.sl.draw.Drawable.DrawableHint; */ public class DrawFontManagerDefault implements DrawFontManager { + protected final Set knownSymbolFonts = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); + + public DrawFontManagerDefault() { + knownSymbolFonts.add("Wingdings"); + knownSymbolFonts.add("Symbol"); + } + @Override public FontInfo getMappedFont(Graphics2D graphics, FontInfo fontInfo) { return getFontWithFallback(graphics, Drawable.FONT_MAP, fontInfo); @@ -49,25 +58,35 @@ public class DrawFontManagerDefault implements DrawFontManager { public String mapFontCharset(Graphics2D graphics, FontInfo fontInfo, String text) { // TODO: find a real charset mapping solution instead of hard coding for Wingdings - String attStr = text; - if (fontInfo != null && "Wingdings".equalsIgnoreCase(fontInfo.getTypeface())) { - // wingdings doesn't contain high-surrogates, so chars are ok - boolean changed = false; - char chrs[] = attStr.toCharArray(); - for (int i=0; i = : + // mapping = + // m00 (scaleX) = eM11 (Horizontal scaling component) - // m11 (scaleY) = eM22 (Vertical scaling component) + double m00 = leis.readFloat(); + // m01 (shearX) = eM12 (Horizontal proportionality constant) + double m01 = leis.readFloat(); + // m10 (shearY) = eM21 (Vertical proportionality constant) + double m10 = leis.readFloat(); + + // m11 (scaleY) = eM22 (Vertical scaling component) + double m11 = leis.readFloat(); + // m02 (translateX) = eDx (The horizontal translation component, in logical units.) + double m02 = leis.readFloat(); + // m12 (translateY) = eDy (The vertical translation component, in logical units.) + double m12 = leis.readFloat(); - // A 32-bit floating-point value of the transform matrix. - double eM11 = leis.readFloat(); - - // A 32-bit floating-point value of the transform matrix. - double eM12 = leis.readFloat(); - - // A 32-bit floating-point value of the transform matrix. - double eM21 = leis.readFloat(); - - // A 32-bit floating-point value of the transform matrix. - double eM22 = leis.readFloat(); - - // A 32-bit floating-point value that contains a horizontal translation component, in logical units. - double eDx = leis.readFloat(); - - // A 32-bit floating-point value that contains a vertical translation component, in logical units. - double eDy = leis.readFloat(); - - xform.setTransform(eM11, eM21, eM12, eM22, eDx, eDy); + xform.setTransform(m00, m10, m01, m11, m02, m12); return 6 * LittleEndian.INT_SIZE; } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFont.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFont.java index ea3eb7754e..a7eb4c8b1c 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFont.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFont.java @@ -273,15 +273,15 @@ public class HemfFont extends HwmfFont { // An 8-bit unsigned integer that specifies an italic font if set to 0x01; // otherwise, it MUST be set to 0x00. - italic = (leis.readUByte() == 0x01); + italic = (leis.readUByte() != 0x00); // An 8-bit unsigned integer that specifies an underlined font if set to 0x01; // otherwise, it MUST be set to 0x00. - underline = (leis.readUByte() == 0x01); + underline = (leis.readUByte() != 0x00); // An 8-bit unsigned integer that specifies a strikeout font if set to 0x01; // otherwise, it MUST be set to 0x00. - strikeOut = (leis.readUByte() == 0x01); + strikeOut = (leis.readUByte() != 0x00); // An 8-bit unsigned integer that specifies the set of character glyphs. // It MUST be a value in the WMF CharacterSet enumeration. @@ -441,7 +441,8 @@ public class HemfFont extends HwmfFont { // A 32-bit unsigned integer that MUST be set to the value 0x08007664. int signature = leis.readInt(); - assert (signature == 0x08007664); + // some non-conformant applications don't write the magic code in + // assert (signature == 0x08007664); // A 32-bit unsigned integer that specifies the number of elements in the // Values array. It MUST be in the range 0 to 16, inclusive. diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java index 206b7d3162..55d8c4cc23 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java @@ -27,7 +27,9 @@ import java.awt.geom.Point2D; import java.awt.image.BufferedImage; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.function.Function; import org.apache.poi.hemf.draw.HemfGraphics; import org.apache.poi.hwmf.draw.HwmfDrawProperties; @@ -443,8 +445,6 @@ public class HemfMisc { protected HwmfBrushStyle brushStyle; protected HwmfHatchStyle hatchStyle; - protected int[] styleEntry; - protected final HwmfBitmapDib bitmap = new HwmfBitmapDib(); @@ -477,7 +477,8 @@ public class HemfMisc { // A 32-bit unsigned integer that specifies the PenStyle. // The value MUST be defined from the PenStyle enumeration table - penStyle = HwmfPenStyle.valueOf((int) leis.readUInt()); + final HemfPenStyle emfPS = HemfPenStyle.valueOf((int) leis.readUInt()); + penStyle = emfPS; // A 32-bit unsigned integer that specifies the width of the line drawn by the pen. // If the pen type in the PenStyle field is PS_GEOMETRIC, this value is the width in logical @@ -517,10 +518,14 @@ public class HemfMisc { // If the pen type in the PenStyle field is PS_GEOMETRIC, the lengths are specified in logical // units; otherwise, the lengths are specified in device units. - styleEntry = new int[numStyleEntries]; + float[] dashPattern = new float[numStyleEntries]; for (int i = 0; i < numStyleEntries; i++) { - styleEntry[i] = (int) leis.readUInt(); + dashPattern[i] = (int) leis.readUInt(); + } + + if (penStyle.getLineDash() == HwmfLineDash.USERSTYLE) { + emfPS.setLineDashes(dashPattern); } size += numStyleEntries * LittleEndianConsts.INT_SIZE; @@ -533,8 +538,11 @@ public class HemfMisc { @Override public String toString() { // TODO: add style entries + bmp - return super.toString().replaceFirst("\\{", - "{ brushStyle: '"+brushStyle+"', hatchStyle: '"+hatchStyle+"', "); + return + "{ brushStyle: '"+brushStyle+"'"+ + ", hatchStyle: '"+hatchStyle+"'"+ + ", dashPattern: "+ Arrays.toString(penStyle.getLineDashes())+ + ", "+super.toString().substring(1); } } @@ -602,7 +610,8 @@ public class HemfMisc { @Override public void draw(HemfGraphics ctx) { - AffineTransform tx = ctx.getInitTransform(); + ctx.updateWindowMapMode(); + AffineTransform tx = ctx.getTransform(); tx.concatenate(xForm); ctx.setTransform(tx); } @@ -649,30 +658,46 @@ public class HemfMisc { return; } + final AffineTransform tx; switch (modifyWorldTransformMode) { + case MWT_LEFTMULTIPLY: + tx = ctx.getTransform(); + tx.concatenate(adaptXForm(tx)); + break; + case MWT_RIGHTMULTIPLY: + tx = ctx.getTransform(); + tx.preConcatenate(adaptXForm(tx)); + break; case MWT_IDENTITY: - ctx.setTransform(ctx.getInitTransform()); + ctx.updateWindowMapMode(); + tx = ctx.getTransform(); break; - case MWT_LEFTMULTIPLY: { - AffineTransform tx = new AffineTransform(xForm); - tx.concatenate(ctx.getTransform()); - ctx.setTransform(tx); - break; - } - case MWT_RIGHTMULTIPLY: { - AffineTransform tx = new AffineTransform(xForm); - tx.preConcatenate(ctx.getTransform()); - ctx.setTransform(tx); - break; - } default: - case MWT_SET: { - AffineTransform tx = ctx.getInitTransform(); - tx.concatenate(xForm); - ctx.setTransform(tx); + case MWT_SET: + ctx.updateWindowMapMode(); + tx = ctx.getTransform(); + tx.concatenate(adaptXForm(tx)); break; - } } + ctx.setTransform(tx); + } + + /** + * adapt xform depending on the base transformation (... experimental ...) + */ + private AffineTransform adaptXForm(AffineTransform other) { + // normalize signed zero + Function nn = (d) -> (d == 0. ? 0. : d); + double yDiff = Math.signum(nn.apply(xForm.getTranslateY())) == Math.signum(nn.apply(other.getTranslateY())) ? 1. : -1.; + double xDiff = Math.signum(nn.apply(xForm.getTranslateX())) == Math.signum(nn.apply(other.getTranslateX())) ? 1. : -1.; + return new AffineTransform( + xForm.getScaleX() == 0 ? 1. : xForm.getScaleX(), + yDiff * xForm.getShearY(), + xDiff * xForm.getShearX(), + xForm.getScaleY() == 0. ? 1. : xForm.getScaleY(), + xForm.getTranslateX(), + xForm.getTranslateY() + ); } @Override diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfPenStyle.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfPenStyle.java new file mode 100644 index 0000000000..74e9459923 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfPenStyle.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 org.apache.poi.hwmf.record.HwmfPenStyle; + +public class HemfPenStyle extends HwmfPenStyle { + + private float[] dashPattern; + + public static HemfPenStyle valueOf(int flag) { + HemfPenStyle ps = new HemfPenStyle(); + ps.flag = flag; + return ps; + } + + @Override + public float[] getLineDashes() { + return (getLineDash() == HwmfLineDash.USERSTYLE) ? dashPattern : super.getLineDashes(); + } + + public void setLineDashes(float[] dashPattern) { + this.dashPattern = (dashPattern == null) ? null : dashPattern.clone(); + } + + @Override + public HemfPenStyle clone() { + return (HemfPenStyle)super.clone(); + } +} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecord.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecord.java index 4f11906bbd..4fb2cc22d4 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecord.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecord.java @@ -43,6 +43,10 @@ public interface HemfRecord { */ long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException; + /** + * Draws the record, the default redirects to the parent WMF record drawing + * @param ctx the drawing context + */ default void draw(HemfGraphics ctx) { if (this instanceof HwmfRecord) { ((HwmfRecord) this).draw(ctx); diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordIterator.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordIterator.java index 207c8ee830..dfa68670e1 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordIterator.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordIterator.java @@ -54,6 +54,8 @@ public class HemfRecordIterator implements Iterator { return null; } + final int readIndex = stream.getReadIndex(); + final long recordId, recordSize; try { recordId = stream.readUInt(); @@ -65,7 +67,7 @@ public class HemfRecordIterator implements Iterator { HemfRecordType type = HemfRecordType.getById(recordId); if (type == null) { - throw new RecordFormatException("Undefined record of type:"+recordId); + throw new RecordFormatException("Undefined record of type: "+recordId+" at "+Integer.toHexString(readIndex)); } final HemfRecord record = type.constructor.get(); diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java index f62155d182..7e785315c0 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java @@ -141,14 +141,12 @@ public class HemfText { dx.add((int) leis.readUInt()); size += LittleEndianConsts.INT_SIZE; } - } else { - // if there are no dx entries, reset the string end - strEnd = (int)recordSize; } if (dx.size() < stringLength) { // invalid dx array dx.clear(); } + strEnd = (int)recordSize; break; } default: diff --git a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java index dadb091699..dd189a0f55 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java @@ -31,6 +31,7 @@ import java.awt.font.TextLayout; import java.awt.geom.AffineTransform; import java.awt.geom.Area; import java.awt.geom.Dimension2D; +import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; @@ -43,6 +44,7 @@ import java.util.NoSuchElementException; import java.util.TreeMap; import org.apache.commons.codec.Charsets; +import org.apache.poi.common.usermodel.fonts.FontCharset; import org.apache.poi.common.usermodel.fonts.FontInfo; import org.apache.poi.hwmf.record.HwmfBrushStyle; import org.apache.poi.hwmf.record.HwmfFont; @@ -57,6 +59,7 @@ import org.apache.poi.hwmf.record.HwmfText; import org.apache.poi.hwmf.record.HwmfText.WmfExtTextOutOptions; import org.apache.poi.sl.draw.DrawFactory; import org.apache.poi.sl.draw.DrawFontManager; +import org.apache.poi.sl.draw.DrawFontManagerDefault; import org.apache.poi.util.LocaleUtil; public class HwmfGraphics { @@ -152,7 +155,7 @@ public class HwmfGraphics { int cap = ps.getLineCap().awtFlag; int join = ps.getLineJoin().awtFlag; float miterLimit = (float)getProperties().getPenMiterLimit(); - float dashes[] = ps.getLineDash().dashes; + float dashes[] = ps.getLineDashes(); boolean dashAlt = ps.isAlternateDash(); // This value is not an integer index into the dash pattern array. // Instead, it is a floating-point value that specifies a linear distance. @@ -370,6 +373,17 @@ public class HwmfGraphics { public void drawString(byte[] text, int length, Point2D reference, Dimension2D scale, Rectangle2D clip, WmfExtTextOutOptions opts, List dx, boolean isUnicode) { final HwmfDrawProperties prop = getProperties(); + final AffineTransform at = graphicsCtx.getTransform(); + if (at.getScaleX() == 0. || at.getScaleY() == 0.) { + return; + } + + try { + at.createInverse(); + } catch (NoninvertibleTransformException e) { + return; + } + HwmfFont font = prop.getFont(); if (font == null || text == null || text.length == 0) { return; @@ -390,11 +404,19 @@ public class HwmfGraphics { } String textString = new String(text, charset).substring(0,length).trim(); + if (textString.isEmpty()) { return; } + + DrawFontManager fontHandler = DrawFactory.getInstance(graphicsCtx).getFontManager(graphicsCtx); + FontInfo fontInfo = fontHandler.getMappedFont(graphicsCtx, font); + if (fontInfo.getCharset() == FontCharset.SYMBOL) { + textString = DrawFontManagerDefault.mapSymbolChars(textString); + } + AttributedString as = new AttributedString(textString); - addAttributes(as, font); + addAttributes(as, font, fontInfo.getTypeface()); // disabled for the time being, as the results aren't promising /* @@ -473,7 +495,6 @@ public class HwmfGraphics { tx.transform(src, dst); final Shape clipShape = graphicsCtx.getClip(); - final AffineTransform at = graphicsCtx.getTransform(); try { if (clip != null) { graphicsCtx.translate(-clip.getCenterX(), -clip.getCenterY()); @@ -503,11 +524,8 @@ public class HwmfGraphics { } } - private void addAttributes(AttributedString as, HwmfFont font) { - DrawFontManager fontHandler = DrawFactory.getInstance(graphicsCtx).getFontManager(graphicsCtx); - FontInfo fontInfo = fontHandler.getMappedFont(graphicsCtx, font); - - as.addAttribute(TextAttribute.FAMILY, fontInfo.getTypeface()); + private void addAttributes(AttributedString as, HwmfFont font, String typeface) { + as.addAttribute(TextAttribute.FAMILY, typeface); as.addAttribute(TextAttribute.SIZE, getFontHeight(font)); if (font.isStrikeOut()) { as.addAttribute(TextAttribute.STRIKETHROUGH, TextAttribute.STRIKETHROUGH_ON); @@ -669,4 +687,11 @@ public class HwmfGraphics { graphicsCtx.setTransform(at); } } + + /** + * @return the bounding box + */ + public Rectangle2D getBbox() { + return (Rectangle2D)bbox.clone(); + } } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java index e353750bd9..3b420e401d 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java @@ -499,8 +499,8 @@ public class HwmfFill { if (bitmap.isValid()) { ctx.drawImage(getImage(), srcBounds, dstBounds); } else if (!dstBounds.isEmpty()) { - BufferedImage bi = new BufferedImage((int)dstBounds.getWidth(), (int)dstBounds.getHeight(), BufferedImage.TYPE_INT_ARGB); - ctx.drawImage(bi, dstBounds, dstBounds); + BufferedImage bi = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB); + ctx.drawImage(bi, new Rectangle2D.Double(0,0,100,100), dstBounds); } } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPenStyle.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPenStyle.java index 5366b3fb24..776d48a3e7 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPenStyle.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPenStyle.java @@ -142,7 +142,7 @@ public class HwmfPenStyle implements Cloneable { private static final BitField SUBSECTION_JOIN = BitFieldFactory.getInstance(0x03000); private static final BitField SUBSECTION_GEOMETRIC = BitFieldFactory.getInstance(0x10000); - private int flag; + protected int flag; public static HwmfPenStyle valueOf(int flag) { HwmfPenStyle ps = new HwmfPenStyle(); @@ -161,7 +161,16 @@ public class HwmfPenStyle implements Cloneable { public HwmfLineDash getLineDash() { return HwmfLineDash.valueOf(SUBSECTION_DASH.getValue(flag)); } - + + /** + * Convienence method which should be used instead of accessing {@link HwmfLineDash#dashes} + * directly, so an subclass can provide user-style dashes + * + * @return the dash pattern + */ + public float[] getLineDashes() { + return getLineDash().dashes; + } /** * The pen sets every other pixel (this style is applicable only for cosmetic pens). diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java index 34c948df02..bbf5b9eb15 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java @@ -18,6 +18,7 @@ package org.apache.poi.hwmf.record; import static org.apache.poi.hwmf.record.HwmfDraw.boundsToString; +import static org.apache.poi.hwmf.record.HwmfDraw.dimToString; import static org.apache.poi.hwmf.record.HwmfDraw.normalizeBounds; import static org.apache.poi.hwmf.record.HwmfDraw.pointToString; import static org.apache.poi.hwmf.record.HwmfDraw.readBounds; @@ -30,6 +31,7 @@ import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.io.IOException; +import org.apache.poi.hwmf.draw.HwmfDrawProperties; import org.apache.poi.hwmf.draw.HwmfGraphics; import org.apache.poi.util.Dimension2DDouble; import org.apache.poi.util.LittleEndianConsts; @@ -56,8 +58,14 @@ public class HwmfWindowing { @Override public void draw(HwmfGraphics ctx) { - ctx.getProperties().setViewportOrg(origin.getX(), origin.getY()); - ctx.updateWindowMapMode(); + final HwmfDrawProperties prop = ctx.getProperties(); + Rectangle2D old = prop.getViewport(); + double oldX = (old == null ? 0 : old.getX()); + double oldY = (old == null ? 0 : old.getY()); + if (oldX != origin.getX() || oldY != origin.getY()) { + prop.setViewportOrg(origin.getX(), origin.getY()); + ctx.updateWindowMapMode(); + } } @Override @@ -91,13 +99,19 @@ public class HwmfWindowing { @Override public void draw(HwmfGraphics ctx) { - ctx.getProperties().setViewportExt(extents.getWidth(), extents.getHeight()); - ctx.updateWindowMapMode(); + final HwmfDrawProperties prop = ctx.getProperties(); + Rectangle2D old = prop.getViewport(); + double oldW = (old == null ? 0 : old.getWidth()); + double oldH = (old == null ? 0 : old.getHeight()); + if (oldW != extents.getWidth() || oldH != extents.getHeight()) { + prop.setViewportExt(extents.getWidth(), extents.getHeight()); + ctx.updateWindowMapMode(); + } } @Override public String toString() { - return "{ width: "+extents.getWidth()+", height: "+extents.getHeight()+" }"; + return dimToString(extents); } } @@ -121,10 +135,14 @@ public class HwmfWindowing { @Override public void draw(HwmfGraphics ctx) { - Rectangle2D viewport = ctx.getProperties().getViewport(); - double x = (viewport == null) ? 0 : viewport.getX(); - double y = (viewport == null) ? 0 : viewport.getY(); - ctx.getProperties().setViewportOrg(x+offset.getX(), y+offset.getY()); + final HwmfDrawProperties prop = ctx.getProperties(); + Rectangle2D viewport = prop.getViewport(); + if (offset.getX() != 0 || offset.getY() != 0) { + double x = (viewport == null) ? 0 : viewport.getX(); + double y = (viewport == null) ? 0 : viewport.getY(); + prop.setViewportOrg(x + offset.getX(), y + offset.getY()); + ctx.updateWindowMapMode(); + } } @Override @@ -152,8 +170,14 @@ public class HwmfWindowing { @Override public void draw(HwmfGraphics ctx) { - ctx.getProperties().setWindowOrg(getX(), getY()); - ctx.updateWindowMapMode(); + final HwmfDrawProperties prop = ctx.getProperties(); + final Rectangle2D old = prop.getWindow(); + double oldX = (old == null ? 0 : old.getX()); + double oldY = (old == null ? 0 : old.getY()); + if (oldX != getX() || oldY != getY()) { + prop.setWindowOrg(getX(), getY()); + ctx.updateWindowMapMode(); + } } public double getY() { @@ -195,8 +219,14 @@ public class HwmfWindowing { @Override public void draw(HwmfGraphics ctx) { - ctx.getProperties().setWindowExt(size.getWidth(), size.getHeight()); - ctx.updateWindowMapMode(); + final HwmfDrawProperties prop = ctx.getProperties(); + Rectangle2D old = prop.getWindow(); + double oldW = (old == null ? 0 : old.getWidth()); + double oldH = (old == null ? 0 : old.getHeight()); + if (oldW != size.getWidth() || oldH != size.getHeight()) { + prop.setWindowExt(size.getWidth(), size.getHeight()); + ctx.updateWindowMapMode(); + } } public Dimension2D getSize() { @@ -205,7 +235,7 @@ public class HwmfWindowing { @Override public String toString() { - return "{ width: "+size.getWidth()+", height: "+size.getHeight()+" }"; + return dimToString(size); } } @@ -229,9 +259,12 @@ public class HwmfWindowing { @Override public void draw(HwmfGraphics ctx) { - Rectangle2D window = ctx.getProperties().getWindow(); - ctx.getProperties().setWindowOrg(window.getX()+offset.getX(), window.getY()+offset.getY()); - ctx.updateWindowMapMode(); + final HwmfDrawProperties prop = ctx.getProperties(); + Rectangle2D old = prop.getWindow(); + if (offset.getX() != 0 || offset.getY() != 0) { + prop.setWindowOrg(old.getX() + offset.getX(), old.getY() + offset.getY()); + ctx.updateWindowMapMode(); + } } @Override @@ -275,11 +308,14 @@ public class HwmfWindowing { @Override public void draw(HwmfGraphics ctx) { - Rectangle2D window = ctx.getProperties().getWindow(); - double width = window.getWidth() * scale.getWidth(); - double height = window.getHeight() * scale.getHeight(); - ctx.getProperties().setWindowExt(width, height); - ctx.updateWindowMapMode(); + final HwmfDrawProperties prop = ctx.getProperties(); + Rectangle2D old = prop.getWindow(); + if (scale.getWidth() != 1.0 || scale.getHeight() != 1.0) { + double width = old.getWidth() * scale.getWidth(); + double height = old.getHeight() * scale.getHeight(); + ctx.getProperties().setWindowExt(width, height); + ctx.updateWindowMapMode(); + } } @Override @@ -325,13 +361,15 @@ public class HwmfWindowing { @Override public void draw(HwmfGraphics ctx) { - Rectangle2D viewport = ctx.getProperties().getViewport(); - if (viewport == null) { - viewport = ctx.getProperties().getWindow(); + final HwmfDrawProperties prop = ctx.getProperties(); + final Rectangle2D old = prop.getViewport() == null ? prop.getWindow() : prop.getViewport(); + + if (scale.getWidth() != 1.0 || scale.getHeight() != 1.0) { + double width = old.getWidth() * scale.getWidth(); + double height = old.getHeight() * scale.getHeight(); + prop.setViewportExt(width, height); + ctx.updateWindowMapMode(); } - double width = viewport.getWidth() * scale.getWidth(); - double height = viewport.getHeight() * scale.getHeight(); - ctx.getProperties().setViewportExt(width, height); } @Override From 11cbe34ee56eafb96761f7d48365682b0b0857ad Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Fri, 23 Nov 2018 02:08:28 +0000 Subject: [PATCH 20/21] #60656 - Support export file that contains emf and render it correctly git-svn-id: https://svn.apache.org/repos/asf/poi/branches/hemf@1847209 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/poi/hemf/record/emf/HemfMisc.java | 41 ++++++++++++++++++- .../poi/hemf/record/emf/HemfRecord.java | 6 +++ .../poi/hemf/usermodel/HemfPicture.java | 28 ++++++++++--- .../apache/poi/hwmf/draw/HwmfGraphics.java | 10 ----- 4 files changed, 68 insertions(+), 17 deletions(-) diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java index 55d8c4cc23..5e04e932d6 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java @@ -23,7 +23,9 @@ 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.NoninvertibleTransformException; import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.io.IOException; import java.util.ArrayList; @@ -31,6 +33,7 @@ import java.util.Arrays; import java.util.List; import java.util.function.Function; +import org.apache.poi.hemf.draw.HemfDrawProperties; import org.apache.poi.hemf.draw.HemfGraphics; import org.apache.poi.hwmf.draw.HwmfDrawProperties; import org.apache.poi.hwmf.draw.HwmfGraphics; @@ -632,6 +635,7 @@ public class HemfMisc { public static class EmfModifyWorldTransform implements HemfRecord { protected final AffineTransform xForm = new AffineTransform(); protected HemfModifyWorldTransformMode modifyWorldTransformMode; + protected HemfHeader header; @Override public HemfRecordType getEmfRecordType() { @@ -652,17 +656,50 @@ public class HemfMisc { return size + LittleEndianConsts.INT_SIZE; } + @Override + public void setHeader(HemfHeader header) { + this.header = header; + } + @Override public void draw(HemfGraphics ctx) { if (modifyWorldTransformMode == null) { return; } + final HemfDrawProperties prop = ctx.getProperties(); + final AffineTransform tx; switch (modifyWorldTransformMode) { case MWT_LEFTMULTIPLY: + + AffineTransform wsTrans; + final Rectangle2D win = prop.getWindow(); + boolean noSetWindowExYet = win.getWidth() == 1 && win.getHeight() == 1; + if (noSetWindowExYet) { + // TODO: understand world-space transformation [MSDN-WRLDPGSPC] + // experimental and horrible solved, because the world-space transformation behind it + // is not understood :( + // only found one example which had landscape bounds and transform of 90 degress + + try { + wsTrans = xForm.createInverse(); + } catch (NoninvertibleTransformException e) { + wsTrans = new AffineTransform(); + } + + Rectangle2D emfBounds = header.getBoundsRectangle(); + + if (xForm.getShearX() == -1.0 && xForm.getShearY() == 1.0) { + // rotate 90 deg + wsTrans.translate(-emfBounds.getHeight(), emfBounds.getHeight()); + } + } else { + wsTrans = adaptXForm(ctx.getTransform()); + } + tx = ctx.getTransform(); - tx.concatenate(adaptXForm(tx)); + tx.concatenate(wsTrans); break; case MWT_RIGHTMULTIPLY: tx = ctx.getTransform(); @@ -690,7 +727,7 @@ public class HemfMisc { Function nn = (d) -> (d == 0. ? 0. : d); double yDiff = Math.signum(nn.apply(xForm.getTranslateY())) == Math.signum(nn.apply(other.getTranslateY())) ? 1. : -1.; double xDiff = Math.signum(nn.apply(xForm.getTranslateX())) == Math.signum(nn.apply(other.getTranslateX())) ? 1. : -1.; - return new AffineTransform( + return new AffineTransform( xForm.getScaleX() == 0 ? 1. : xForm.getScaleX(), yDiff * xForm.getShearY(), xDiff * xForm.getShearX(), diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecord.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecord.java index 4fb2cc22d4..41e9f30442 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecord.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecord.java @@ -52,4 +52,10 @@ public interface HemfRecord { ((HwmfRecord) this).draw(ctx); } } + + /** + * Sets the header reference, in case the record needs to refer to it + * @param header the emf header + */ + default void setHeader(HemfHeader header) {} } diff --git a/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java b/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java index 8d49fdd0e1..6d53ae23ea 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java +++ b/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java @@ -67,7 +67,14 @@ public class HemfPicture implements Iterable { // in case the (first) parsing throws an exception, we can provide the // records up to that point isParsed = true; - new HemfRecordIterator(stream).forEachRemaining(records::add); + HemfHeader[] header = new HemfHeader[1]; + new HemfRecordIterator(stream).forEachRemaining(r -> { + if (r instanceof HemfHeader) { + header[0] = (HemfHeader) r; + } + r.setHeader(header[0]); + records.add(r); + }); } return records; } @@ -116,23 +123,34 @@ public class HemfPicture implements Iterable { return new Dimension2DDouble(Math.abs(width*coeff), Math.abs(height*coeff)); } + private static double minX(Rectangle2D bounds) { + return Math.min(bounds.getMinX(), bounds.getMaxX()); + } + + private static double minY(Rectangle2D bounds) { + return Math.min(bounds.getMinY(), bounds.getMaxY()); + } + 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.translate(minX(graphicsBounds), minY(graphicsBounds)); ctx.scale(graphicsBounds.getWidth()/emfBounds.getWidth(), graphicsBounds.getHeight()/emfBounds.getHeight()); - ctx.translate(-emfBounds.getCenterX(), -emfBounds.getCenterY()); + ctx.translate(-minX(emfBounds), -minY(emfBounds)); int idx = 0; HemfGraphics g = new HemfGraphics(ctx, emfBounds); for (HemfRecord r : getRecords()) { - g.draw(r); + try { + g.draw(r); + } catch (RuntimeException ignored) { + + } idx++; } } finally { diff --git a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java index dd189a0f55..2f9811fc63 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java @@ -374,9 +374,6 @@ public class HwmfGraphics { final HwmfDrawProperties prop = getProperties(); final AffineTransform at = graphicsCtx.getTransform(); - if (at.getScaleX() == 0. || at.getScaleY() == 0.) { - return; - } try { at.createInverse(); @@ -687,11 +684,4 @@ public class HwmfGraphics { graphicsCtx.setTransform(at); } } - - /** - * @return the bounding box - */ - public Rectangle2D getBbox() { - return (Rectangle2D)bbox.clone(); - } } From fce876b4eff2f6629656648bf0e955b39ab79683 Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Fri, 14 Dec 2018 00:44:40 +0000 Subject: [PATCH 21/21] merge trunk git-svn-id: https://svn.apache.org/repos/asf/poi/branches/hemf@1848906 13f79535-47bb-0310-9956-ffa450edef68 --- build.xml | 387 +++++++---------- doap_POI.rdf | 7 + .../poi/xslf/usermodel/ChartFromScratch.java | 145 +++++++ .../usermodel/examples/ChartFromScratch.java | 140 +++++++ .../org/apache/poi/TestAllFiles.java | 1 + .../apache/poi/stress/XSSFFileHandler.java | 9 +- .../org/apache/poi/ddf/EscherProperties.java | 30 +- .../org/apache/poi/hpsf/VariantSupport.java | 2 +- .../poi/hpsf/wellknown/PropertyIDMap.java | 7 +- .../poi/hssf/model/InternalWorkbook.java | 2 + .../poi/hssf/record/RecordInputStream.java | 19 +- .../org/apache/poi/hssf/record/SSTRecord.java | 8 +- .../poi/hssf/record/SharedFormulaRecord.java | 6 +- .../hssf/record/SharedValueRecordBase.java | 12 +- .../crypt/ChunkedCipherOutputStream.java | 26 +- .../crypt/cryptoapi/CryptoAPIEncryptor.java | 8 +- .../poi/poifs/filesystem/FileMagic.java | 24 +- .../apache/poi/sl/draw/DrawBackground.java | 6 +- .../apache/poi/sl/draw/DrawFreeformShape.java | 36 +- .../org/apache/poi/sl/draw/DrawPaint.java | 125 +++--- .../apache/poi/sl/draw/DrawSimpleShape.java | 14 +- .../apache/poi/sl/draw/DrawTableShape.java | 6 +- .../apache/poi/sl/draw/PathGradientPaint.java | 47 ++- .../org/apache/poi/sl/draw/geom/Context.java | 22 +- .../poi/sl/draw/geom/PresetGeometries.java | 45 +- .../poi/sl/usermodel/FreeformShape.java | 15 +- .../poi/ss/formula/SheetNameFormatter.java | 11 +- .../SheetRangeAndWorkbookIndexFormatter.java | 73 ++++ .../ss/formula/functions/MatrixFunction.java | 2 +- .../apache/poi/ss/formula/ptg/Area3DPxg.java | 13 +- .../apache/poi/ss/formula/ptg/Ref3DPxg.java | 16 +- .../poi/util/RecordFormatException.java | 4 +- src/java/org/apache/poi/util/StringUtil.java | 2 +- src/java/org/apache/poi/util/Units.java | 2 +- .../apache/poi/ooxml/POIXMLDocumentPart.java | 2 +- .../extractor/CommandLineTextExtractor.java | 58 ++- .../poi/ooxml/util/POIXMLConstants.java | 1 + .../poi/openxml4j/opc/internal/ZipHelper.java | 2 - .../util/ZipInputStreamZipEntrySource.java | 2 +- .../crypt/dsig/SignatureOutputStream.java | 8 +- .../poi/xddf/usermodel/chart/XDDFChart.java | 46 +- .../poi/xslf/usermodel/XMLSlideShow.java | 15 +- .../apache/poi/xslf/usermodel/XSLFChart.java | 57 +++ .../poi/xslf/usermodel/XSLFDrawing.java | 13 + .../poi/xslf/usermodel/XSLFFreeformShape.java | 33 +- .../apache/poi/xslf/usermodel/XSLFSheet.java | 31 +- .../poi/xslf/usermodel/XSLFSimpleShape.java | 1 - .../poi/xssf/streaming/SXSSFWorkbook.java | 24 +- .../apache/poi/xwpf/usermodel/XWPFChart.java | 4 +- .../poi/xwpf/usermodel/XWPFDocument.java | 3 +- .../poi/ooxml/TestPOIXMLProperties.java | 5 +- .../poi/poifs/crypt/TestSignatureInfo.java | 3 + .../usermodel/TestNecessaryOOXMLClasses.java | 6 + .../extractor/TestXDGFVisioExtractor.java | 2 +- .../poi/xslf/usermodel/TestPPTX2PNG.java | 4 +- .../poi/xslf/usermodel/TestXSLFSlide.java | 31 +- .../apache/poi/xssf/XSSFTestDataSamples.java | 51 +-- .../xwpf/extractor/TestXWPFWordExtractor.java | 8 + .../poi/hslf/record/CurrentUserAtom.java | 4 +- .../poi/hslf/usermodel/HSLFAutoShape.java | 395 +++++++++++++++++- .../apache/poi/hslf/usermodel/HSLFFill.java | 49 ++- .../poi/hslf/usermodel/HSLFFreeformShape.java | 264 +----------- .../apache/poi/hslf/usermodel/HSLFShape.java | 23 +- .../poi/hslf/usermodel/HSLFSimpleShape.java | 6 +- .../poi/hslf/usermodel/HSLFSlideShow.java | 1 + .../poi/hslf/usermodel/HSLFSlideShowImpl.java | 19 +- .../poi/hslf/model/AllHSLFModelTests.java | 1 - .../apache/poi/hslf/model/TestFreeform.java | 2 +- .../hslf/usermodel/AllHSLFUserModelTests.java | 1 + .../{model => usermodel}/TestBackground.java | 2 +- .../poi/poifs/filesystem/TestFileMagic.java | 81 ++++ .../poifs/filesystem/TestPOIFSFileSystem.java | 15 + ...eetRangeAndWorkbookIndexFormatterTest.java | 66 +++ .../TestExcelStyleDateFormatter.java | 4 +- test-data/document/60316.docx | Bin 0 -> 48142 bytes test-data/document/60316b.dotx | Bin 0 -> 14726 bytes test-data/slideshow/customGeo.ppt | Bin 0 -> 3333632 bytes test-data/slideshow/customGeo.pptx | Bin 0 -> 1046067 bytes test-data/slideshow/keyframes.pptx | Bin 0 -> 389330 bytes test-data/slideshow/pp40only.ppt | Bin 0 -> 263681 bytes 80 files changed, 1741 insertions(+), 874 deletions(-) create mode 100644 src/examples/src/org/apache/poi/xslf/usermodel/ChartFromScratch.java create mode 100644 src/examples/src/org/apache/poi/xwpf/usermodel/examples/ChartFromScratch.java create mode 100644 src/java/org/apache/poi/ss/formula/SheetRangeAndWorkbookIndexFormatter.java rename src/scratchpad/testcases/org/apache/poi/hslf/{model => usermodel}/TestBackground.java (99%) create mode 100644 src/testcases/org/apache/poi/poifs/filesystem/TestFileMagic.java create mode 100644 src/testcases/org/apache/poi/ss/formula/SheetRangeAndWorkbookIndexFormatterTest.java create mode 100644 test-data/document/60316.docx create mode 100644 test-data/document/60316b.dotx create mode 100644 test-data/slideshow/customGeo.ppt create mode 100644 test-data/slideshow/customGeo.pptx create mode 100644 test-data/slideshow/keyframes.pptx create mode 100644 test-data/slideshow/pp40only.ppt diff --git a/build.xml b/build.xml index 207acc4c68..ce3c83c860 100644 --- a/build.xml +++ b/build.xml @@ -60,6 +60,10 @@ under the License. + + + + - - - - - - - - - - @@ -1180,6 +1173,59 @@ under the License. + + + + + + + + + + + sun/java2d/pipe/AAShapePipe.renderTiles(Lsun/java2d/SunGraphics2D;Ljava/awt/Shape;Lsun/java2d/pipe/AATileGenerator;[I)V + sun/java2d/pipe/AlphaPaintPipe.renderPathTile(Ljava/lang/Object;[BIIIIII)V + java/awt/TexturePaintContext.getRaster(IIII)Ljava/awt/image/Raster; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1219,39 +1265,23 @@ under the License. - + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + - + @@ -1306,6 +1336,7 @@ under the License. + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + @@ -1500,45 +1514,23 @@ under the License. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + @@ -1559,72 +1551,35 @@ under the License. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1650,34 +1605,16 @@ under the License. unless="integration.test.notRequired" xmlns:jacoco="antlib:org.jacoco.ant"> - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + @@ -1739,33 +1676,16 @@ under the License. - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + @@ -1791,6 +1711,9 @@ under the License. + + + @@ -1828,7 +1751,7 @@ under the License. - + POI API Documentation]]> @@ -2113,6 +2036,7 @@ under the License. sonar/*/src/**, compile-lib/**, ooxml-lib/**, + ooxml-testlib/**, scripts/**, TEST*, *.ipr, @@ -2303,7 +2227,10 @@ under the License. - + + @@ -2453,7 +2380,7 @@ under the License. - + diff --git a/doap_POI.rdf b/doap_POI.rdf index f1d21a8cee..88ba48f04d 100644 --- a/doap_POI.rdf +++ b/doap_POI.rdf @@ -35,6 +35,13 @@ Java + + + Apache POI 4.0.1 + 2018-12-03 + 4.0.1 + + Apache POI 4.0.0 diff --git a/src/examples/src/org/apache/poi/xslf/usermodel/ChartFromScratch.java b/src/examples/src/org/apache/poi/xslf/usermodel/ChartFromScratch.java new file mode 100644 index 0000000000..12f96fa6fd --- /dev/null +++ b/src/examples/src/org/apache/poi/xslf/usermodel/ChartFromScratch.java @@ -0,0 +1,145 @@ +/* + * ==================================================================== + * 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.xslf.usermodel; + +import java.awt.geom.Rectangle2D; +import java.io.BufferedReader; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; + +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.xddf.usermodel.chart.AxisCrosses; +import org.apache.poi.xddf.usermodel.chart.AxisPosition; +import org.apache.poi.xddf.usermodel.chart.BarDirection; +import org.apache.poi.xddf.usermodel.chart.ChartTypes; +import org.apache.poi.xddf.usermodel.chart.LegendPosition; +import org.apache.poi.xddf.usermodel.chart.XDDFBarChartData; +import org.apache.poi.xddf.usermodel.chart.XDDFChart; +import org.apache.poi.xddf.usermodel.chart.XDDFChartAxis; +import org.apache.poi.xddf.usermodel.chart.XDDFChartLegend; +import org.apache.poi.xddf.usermodel.chart.XDDFDataSource; +import org.apache.poi.xddf.usermodel.chart.XDDFDataSourcesFactory; +import org.apache.poi.xddf.usermodel.chart.XDDFNumericalDataSource; +import org.apache.poi.xddf.usermodel.chart.XDDFValueAxis; + +/** + * Build a chart without reading template file + */ +public class ChartFromScratch { + private static void usage(){ + System.out.println("Usage: BarChartExample "); + System.out.println(" bar-chart-data.txt the model to set. First line is chart title, " + + "then go pairs {axis-label value}"); + } + + public static void main(String[] args) throws Exception { + if(args.length < 1) { + usage(); + return; + } + + try (BufferedReader modelReader = new BufferedReader(new FileReader(args[0]))) { + + String chartTitle = modelReader.readLine(); // first line is chart title + String[] series = modelReader.readLine().split(","); + + // Category Axis Data + List listLanguages = new ArrayList<>(10); + + // Values + List listCountries = new ArrayList<>(10); + List listSpeakers = new ArrayList<>(10); + + // set model + String ln; + while((ln = modelReader.readLine()) != null) { + String[] vals = ln.split(","); + listCountries.add(Double.valueOf(vals[0])); + listSpeakers.add(Double.valueOf(vals[1])); + listLanguages.add(vals[2]); + } + + String[] categories = listLanguages.toArray(new String[listLanguages.size()]); + Double[] values1 = listCountries.toArray(new Double[listCountries.size()]); + Double[] values2 = listSpeakers.toArray(new Double[listSpeakers.size()]); + + try { + + XMLSlideShow ppt = new XMLSlideShow(); + XSLFSlide slide = ppt.createSlide(); + XSLFChart chart = ppt.createChart(); + Rectangle2D rect2D = new java.awt.Rectangle(XDDFChart.DEFAULT_X, XDDFChart.DEFAULT_Y, + XDDFChart.DEFAULT_WIDTH, XDDFChart.DEFAULT_HEIGHT); + slide.addChart(chart, rect2D); + setBarData(chart, chartTitle, series, categories, values1, values2); + // save the result + try (OutputStream out = new FileOutputStream("bar-chart-demo-output.pptx")) { + ppt.write(out); + } + } + catch(Exception e) + { + e.printStackTrace(); + } + } + System.out.println("Done"); + } + + private static void setBarData(XSLFChart chart, String chartTitle, String[] series, String[] categories, Double[] values1, Double[] values2) { + // Use a category axis for the bottom axis. + XDDFChartAxis bottomAxis = chart.createCategoryAxis(AxisPosition.BOTTOM); + bottomAxis.setTitle(series[2]); + XDDFValueAxis leftAxis = chart.createValueAxis(AxisPosition.LEFT); + leftAxis.setTitle(series[0]+","+series[1]); + leftAxis.setCrosses(AxisCrosses.AUTO_ZERO); + + final int numOfPoints = categories.length; + final String categoryDataRange = chart.formatRange(new CellRangeAddress(1, numOfPoints, 0, 0)); + final String valuesDataRange = chart.formatRange(new CellRangeAddress(1, numOfPoints, 1, 1)); + final String valuesDataRange2 = chart.formatRange(new CellRangeAddress(1, numOfPoints, 2, 2)); + final XDDFDataSource categoriesData = XDDFDataSourcesFactory.fromArray(categories, categoryDataRange, 0); + final XDDFNumericalDataSource valuesData = XDDFDataSourcesFactory.fromArray(values1, valuesDataRange, 1); + values1[6] = 16.0; // if you ever want to change the underlying data + final XDDFNumericalDataSource valuesData2 = XDDFDataSourcesFactory.fromArray(values2, valuesDataRange2, 2); + + + XDDFBarChartData bar = (XDDFBarChartData) chart.createData(ChartTypes.BAR, bottomAxis, leftAxis); + XDDFBarChartData.Series series1 = (XDDFBarChartData.Series) bar.addSeries(categoriesData, valuesData); + series1.setTitle(series[0], chart.setSheetTitle(series[0], 1)); + + XDDFBarChartData.Series series2 = (XDDFBarChartData.Series) bar.addSeries(categoriesData, valuesData2); + series2.setTitle(series[1], chart.setSheetTitle(series[1], 2)); + + bar.setVaryColors(true); + bar.setBarDirection(BarDirection.COL); + chart.plot(bar); + + XDDFChartLegend legend = chart.getOrAddLegend(); + legend.setPosition(LegendPosition.LEFT); + legend.setOverlay(false); + + chart.setTitleText(chartTitle); + chart.setTitleOverlay(false); + } +} + diff --git a/src/examples/src/org/apache/poi/xwpf/usermodel/examples/ChartFromScratch.java b/src/examples/src/org/apache/poi/xwpf/usermodel/examples/ChartFromScratch.java new file mode 100644 index 0000000000..4a1a78150f --- /dev/null +++ b/src/examples/src/org/apache/poi/xwpf/usermodel/examples/ChartFromScratch.java @@ -0,0 +1,140 @@ +/* + * ==================================================================== + * 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.xwpf.usermodel.examples; + +import java.io.BufferedReader; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; + +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.xddf.usermodel.chart.AxisCrosses; +import org.apache.poi.xddf.usermodel.chart.AxisPosition; +import org.apache.poi.xddf.usermodel.chart.BarDirection; +import org.apache.poi.xddf.usermodel.chart.ChartTypes; +import org.apache.poi.xddf.usermodel.chart.LegendPosition; +import org.apache.poi.xddf.usermodel.chart.XDDFBarChartData; +import org.apache.poi.xddf.usermodel.chart.XDDFChart; +import org.apache.poi.xddf.usermodel.chart.XDDFChartAxis; +import org.apache.poi.xddf.usermodel.chart.XDDFChartLegend; +import org.apache.poi.xddf.usermodel.chart.XDDFDataSource; +import org.apache.poi.xddf.usermodel.chart.XDDFDataSourcesFactory; +import org.apache.poi.xddf.usermodel.chart.XDDFNumericalDataSource; +import org.apache.poi.xddf.usermodel.chart.XDDFValueAxis; +import org.apache.poi.xwpf.usermodel.XWPFChart; +import org.apache.poi.xwpf.usermodel.XWPFDocument; + +/** + * Build a chart without reading template file + */ +public class ChartFromScratch { + private static void usage(){ + System.out.println("Usage: BarChartExample "); + System.out.println(" bar-chart-data.txt the model to set. First line is chart title, " + + "then go pairs {axis-label value}"); + } + + public static void main(String[] args) throws Exception { + if(args.length < 1) { + usage(); + return; + } + + try (BufferedReader modelReader = new BufferedReader(new FileReader(args[0]))) { + + String chartTitle = modelReader.readLine(); // first line is chart title + String[] series = modelReader.readLine().split(","); + + // Category Axis Data + List listLanguages = new ArrayList<>(10); + + // Values + List listCountries = new ArrayList<>(10); + List listSpeakers = new ArrayList<>(10); + + // set model + String ln; + while((ln = modelReader.readLine()) != null) { + String[] vals = ln.split(","); + listCountries.add(Double.valueOf(vals[0])); + listSpeakers.add(Double.valueOf(vals[1])); + listLanguages.add(vals[2]); + } + + String[] categories = listLanguages.toArray(new String[listLanguages.size()]); + Double[] values1 = listCountries.toArray(new Double[listCountries.size()]); + Double[] values2 = listSpeakers.toArray(new Double[listSpeakers.size()]); + + try (XWPFDocument doc = new XWPFDocument()) { + XWPFChart chart = doc.createChart(XDDFChart.DEFAULT_WIDTH, XDDFChart.DEFAULT_HEIGHT); + setBarData(chart, chartTitle, series, categories, values1, values2); + // save the result + try (OutputStream out = new FileOutputStream("bar-chart-demo-output.docx")) { + doc.write(out); + } + } + catch(Exception e) + { + e.printStackTrace(); + } + } + System.out.println("Done"); + } + + private static void setBarData(XWPFChart chart, String chartTitle, String[] series, String[] categories, Double[] values1, Double[] values2) { + // Use a category axis for the bottom axis. + XDDFChartAxis bottomAxis = chart.createCategoryAxis(AxisPosition.BOTTOM); + bottomAxis.setTitle(series[2]); + XDDFValueAxis leftAxis = chart.createValueAxis(AxisPosition.LEFT); + leftAxis.setTitle(series[0]+","+series[1]); + leftAxis.setCrosses(AxisCrosses.AUTO_ZERO); + + final int numOfPoints = categories.length; + final String categoryDataRange = chart.formatRange(new CellRangeAddress(1, numOfPoints, 0, 0)); + final String valuesDataRange = chart.formatRange(new CellRangeAddress(1, numOfPoints, 1, 1)); + final String valuesDataRange2 = chart.formatRange(new CellRangeAddress(1, numOfPoints, 2, 2)); + final XDDFDataSource categoriesData = XDDFDataSourcesFactory.fromArray(categories, categoryDataRange, 0); + final XDDFNumericalDataSource valuesData = XDDFDataSourcesFactory.fromArray(values1, valuesDataRange, 1); + values1[6] = 16.0; // if you ever want to change the underlying data + final XDDFNumericalDataSource valuesData2 = XDDFDataSourcesFactory.fromArray(values2, valuesDataRange2, 2); + + + XDDFBarChartData bar = (XDDFBarChartData) chart.createData(ChartTypes.BAR, bottomAxis, leftAxis); + XDDFBarChartData.Series series1 = (XDDFBarChartData.Series) bar.addSeries(categoriesData, valuesData); + series1.setTitle(series[0], chart.setSheetTitle(series[0], 1)); + + XDDFBarChartData.Series series2 = (XDDFBarChartData.Series) bar.addSeries(categoriesData, valuesData2); + series2.setTitle(series[1], chart.setSheetTitle(series[1], 2)); + + bar.setVaryColors(true); + bar.setBarDirection(BarDirection.COL); + chart.plot(bar); + + XDDFChartLegend legend = chart.getOrAddLegend(); + legend.setPosition(LegendPosition.LEFT); + legend.setOverlay(false); + + chart.setTitleText(chartTitle); + chart.setTitleOverlay(false); + } +} + diff --git a/src/integrationtest/org/apache/poi/TestAllFiles.java b/src/integrationtest/org/apache/poi/TestAllFiles.java index 61c47b9e88..40f19f9c85 100644 --- a/src/integrationtest/org/apache/poi/TestAllFiles.java +++ b/src/integrationtest/org/apache/poi/TestAllFiles.java @@ -289,6 +289,7 @@ public class TestAllFiles { "document/Bug50955.doc", "document/57843.doc", "slideshow/PPT95.ppt", + "slideshow/pp40only.ppt", "slideshow/Divino_Revelado.pptx", "openxml4j/OPCCompliance_CoreProperties_DCTermsNamespaceLimitedUseFAIL.docx", "openxml4j/OPCCompliance_CoreProperties_DoNotUseCompatibilityMarkupFAIL.docx", diff --git a/src/integrationtest/org/apache/poi/stress/XSSFFileHandler.java b/src/integrationtest/org/apache/poi/stress/XSSFFileHandler.java index 5c47d9af7e..48a30a2569 100644 --- a/src/integrationtest/org/apache/poi/stress/XSSFFileHandler.java +++ b/src/integrationtest/org/apache/poi/stress/XSSFFileHandler.java @@ -35,7 +35,6 @@ import java.util.Iterator; import java.util.Locale; import java.util.Set; -import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; import org.apache.poi.EncryptedDocumentException; @@ -148,7 +147,7 @@ public class XSSFFileHandler extends SpreadsheetHandler { } private void exportToXML(XSSFWorkbook wb) throws SAXException, - ParserConfigurationException, TransformerException { + TransformerException { for (XSSFMap map : wb.getCustomXMLMappings()) { XSSFExportToXml exporter = new XSSFExportToXml(map); @@ -165,7 +164,6 @@ public class XSSFFileHandler extends SpreadsheetHandler { // zip-bomb EXPECTED_ADDITIONAL_FAILURES.add("spreadsheet/54764.xlsx"); EXPECTED_ADDITIONAL_FAILURES.add("spreadsheet/54764-2.xlsx"); - EXPECTED_ADDITIONAL_FAILURES.add("spreadsheet/54764.xlsx"); EXPECTED_ADDITIONAL_FAILURES.add("spreadsheet/poc-xmlbomb.xlsx"); EXPECTED_ADDITIONAL_FAILURES.add("spreadsheet/poc-xmlbomb-empty.xlsx"); // strict OOXML @@ -185,18 +183,19 @@ public class XSSFFileHandler extends SpreadsheetHandler { public void handleAdditional(File file) throws Exception { // redirect stdout as the examples often write lots of text PrintStream oldOut = System.out; + String testFile = file.getParentFile().getName() + "/" + file.getName(); try { System.setOut(new NullPrintStream()); FromHowTo.main(new String[]{file.getAbsolutePath()}); XLSX2CSV.main(new String[]{file.getAbsolutePath()}); assertFalse("Expected Extraction to fail for file " + file + " and handler " + this + ", but did not fail!", - EXPECTED_ADDITIONAL_FAILURES.contains(file.getParentFile().getName() + "/" + file.getName())); + EXPECTED_ADDITIONAL_FAILURES.contains(testFile)); } catch (OLE2NotOfficeXmlFileException e) { // we have some files that are not actually OOXML and thus cannot be tested here } catch (IllegalArgumentException | InvalidFormatException | POIXMLException | IOException e) { - if(!EXPECTED_ADDITIONAL_FAILURES.contains(file.getParentFile().getName() + "/" + file.getName())) { + if(!EXPECTED_ADDITIONAL_FAILURES.contains(testFile)) { throw e; } } finally { diff --git a/src/java/org/apache/poi/ddf/EscherProperties.java b/src/java/org/apache/poi/ddf/EscherProperties.java index acff6241c2..482bce95c5 100644 --- a/src/java/org/apache/poi/ddf/EscherProperties.java +++ b/src/java/org/apache/poi/ddf/EscherProperties.java @@ -26,6 +26,7 @@ import java.util.Map; * * @author Glen Stampoultzis (glens at apache.org) */ +@SuppressWarnings("WeakerAccess") public final class EscherProperties { // Property constants @@ -117,6 +118,15 @@ public final class EscherProperties { public static final short GEOMETRY__ADJUST8VALUE = 334; public static final short GEOMETRY__ADJUST9VALUE = 335; public static final short GEOMETRY__ADJUST10VALUE = 336; + public static final short GEOMETRY__PCONNECTIONSITES = 337; + public static final short GEOMETRY__PCONNECTIONSITESDIR = 338; + public static final short GEOMETRY__XLIMO = 339; + public static final short GEOMETRY__YLIMO = 340; + public static final short GEOMETRY__PADJUSTHANDLES = 341; + public static final short GEOMETRY__PGUIDES = 342; + public static final short GEOMETRY__PINSCRIBE = 343; + public static final short GEOMETRY__CXK = 344; + public static final short GEOMETRY__PFRAGMENTS = 345; public static final short GEOMETRY__SHADOWok = 378; public static final short GEOMETRY__3DOK = 379; public static final short GEOMETRY__LINEOK = 380; @@ -333,6 +343,9 @@ public final class EscherProperties { private static final Map properties = initProps(); + private EscherProperties() { + } + private static Map initProps() { Map m = new HashMap<>(); addProp(m, TRANSFORM__ROTATION, "transform.rotation"); @@ -423,6 +436,15 @@ public final class EscherProperties { addProp(m, GEOMETRY__ADJUST8VALUE, "geometry.adjust8value"); addProp(m, GEOMETRY__ADJUST9VALUE, "geometry.adjust9value"); addProp(m, GEOMETRY__ADJUST10VALUE, "geometry.adjust10value"); + addProp(m, GEOMETRY__PCONNECTIONSITES, "geometry.pConnectionSites"); + addProp(m, GEOMETRY__PCONNECTIONSITESDIR, "geometry.pConnectionSitesDir"); + addProp(m, GEOMETRY__XLIMO, "geometry.xLimo"); + addProp(m, GEOMETRY__YLIMO, "geometry.yLimo"); + addProp(m, GEOMETRY__PADJUSTHANDLES, "geometry.pAdjustHandles"); + addProp(m, GEOMETRY__PGUIDES, "geometry.pGuides"); + addProp(m, GEOMETRY__PINSCRIBE, "geometry.pInscribe"); + addProp(m, GEOMETRY__CXK, "geometry.cxk"); + addProp(m, GEOMETRY__PFRAGMENTS, "geometry.pFragments"); addProp(m, GEOMETRY__SHADOWok, "geometry.shadowOK"); addProp(m, GEOMETRY__3DOK, "geometry.3dok"); addProp(m, GEOMETRY__LINEOK, "geometry.lineok"); @@ -641,20 +663,20 @@ public final class EscherProperties { } private static void addProp(Map m, int s, String propName) { - m.put(Short.valueOf((short) s), new EscherPropertyMetaData(propName)); + m.put((short) s, new EscherPropertyMetaData(propName)); } private static void addProp(Map m, int s, String propName, byte type) { - m.put(Short.valueOf((short) s), new EscherPropertyMetaData(propName, type)); + m.put((short) s, new EscherPropertyMetaData(propName, type)); } public static String getPropertyName(short propertyId) { - EscherPropertyMetaData o = properties.get(Short.valueOf(propertyId)); + EscherPropertyMetaData o = properties.get(propertyId); return o == null ? "unknown" : o.getDescription(); } public static byte getPropertyType(short propertyId) { - EscherPropertyMetaData escherPropertyMetaData = properties.get(Short.valueOf(propertyId)); + EscherPropertyMetaData escherPropertyMetaData = properties.get(propertyId); return escherPropertyMetaData == null ? 0 : escherPropertyMetaData.getType(); } } diff --git a/src/java/org/apache/poi/hpsf/VariantSupport.java b/src/java/org/apache/poi/hpsf/VariantSupport.java index e60679027d..0e8dc08c6e 100644 --- a/src/java/org/apache/poi/hpsf/VariantSupport.java +++ b/src/java/org/apache/poi/hpsf/VariantSupport.java @@ -36,7 +36,7 @@ import org.apache.poi.util.POILogger; * Supports reading and writing of variant data.

    * * FIXME (3): Reading and writing should be made more - * uniform than it is now. The following items should be resolved:

    + * uniform than it is now. The following items should be resolved: * *