mirror of https://github.com/apache/poi.git
#60656 - Support export file that contains emf and render it correctly
git-svn-id: https://svn.apache.org/repos/asf/poi/branches/hemf@1844380 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
6bc60e7881
commit
17cc3bab08
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -624,19 +624,19 @@ public class HemfFill {
|
|||
return undefinedSpace1;
|
||||
}
|
||||
|
||||
final LittleEndianInputStream leisDib;
|
||||
final int dibSize = cbBmi+cbBits;
|
||||
if (undefinedSpace2 == 0) {
|
||||
leisDib = leis;
|
||||
} else {
|
||||
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);
|
||||
leisDib = new LittleEndianInputStream(new ByteArrayInputStream(bos.toByteArray()));
|
||||
}
|
||||
final int dibSize = cbBmi+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<Rectangle2D> 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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<PaletteEntry> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,8 +53,15 @@ public class HemfRecordIterator implements Iterator<HemfRecord> {
|
|||
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) {
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -113,19 +113,13 @@ 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;
|
||||
|
||||
rawTextBytes = IOUtils.safelyAllocate(stringLength*(isUnicode()?2:1), MAX_RECORD_LENGTH);
|
||||
leis.readFully(rawTextBytes);
|
||||
size += rawTextBytes.length;
|
||||
|
||||
// handle dx before string and other way round
|
||||
for (char op : ((offDx < offString) ? "ds" : "sd").toCharArray()) {
|
||||
switch (op) {
|
||||
case 'd': {
|
||||
dx.clear();
|
||||
if (offDx > 0) {
|
||||
int undefinedSpace2 = (int) (offDx - (size + HEADER_SIZE));
|
||||
assert (undefinedSpace2 >= 0);
|
||||
if (offDx > 0 && undefinedSpace2 >= 0 && offDx-HEADER_SIZE < recordSize) {
|
||||
leis.skipFully(undefinedSpace2);
|
||||
size += undefinedSpace2;
|
||||
|
||||
|
@ -140,11 +134,34 @@ public class HemfText {
|
|||
//
|
||||
// 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) {
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<HemfRecord> {
|
|||
|
||||
private final LittleEndianInputStream stream;
|
||||
private final List<HemfRecord> 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<HemfRecord> {
|
|||
}
|
||||
|
||||
public List<HemfRecord> 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<HemfRecord> {
|
|||
*/
|
||||
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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<HwmfDrawProperties> propStack = new LinkedList<>();
|
||||
protected HwmfDrawProperties prop;
|
||||
protected final Graphics2D graphicsCtx;
|
||||
protected final List<HwmfObjectTableEntry> objectTable = new ArrayList<>();
|
||||
protected final BitSet objectIndexes = new BitSet();
|
||||
protected final TreeMap<Integer,HwmfObjectTableEntry> 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<HwmfObjectTableEntry> 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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
} 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.setPaint(Color.black);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
if (dib.isValid()) {
|
||||
ctx.getProperties().setRasterOp(rasterOperation);
|
||||
ctx.drawImage(getImage(), srcBounds, dstBounds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyObject(HwmfGraphics ctx) {
|
||||
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<HwmfRecord> 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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,46 +57,104 @@ 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 boolean writeLog = true;
|
||||
final boolean savePng = true;
|
||||
|
||||
Set<String> passed = new HashSet<>();
|
||||
|
||||
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();
|
||||
|
||||
if (entry.isDirectory() || !etName.endsWith(".emf") || passed.contains(etName)) continue;
|
||||
|
||||
System.out.println(etName);
|
||||
|
||||
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();
|
||||
}
|
||||
final File pngName = new File("build/tmp",ze.getName().replaceFirst( ".*/","").replace(".emf", ".png"));
|
||||
if (pngName.exists()) {
|
||||
System.out.println("parse error");
|
||||
// continue with the read records up to the error anyway
|
||||
if (emf.getRecords().isEmpty()) {
|
||||
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();
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
emf.draw(g, new Rectangle2D.Double(0, 0, width, height));
|
||||
|
||||
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()) {
|
||||
|
@ -112,20 +164,23 @@ public class HemfPictureTest {
|
|||
i++;
|
||||
}
|
||||
fw.close();
|
||||
|
||||
emf.draw(g, new Rectangle2D.Double(0, 0, width, height));
|
||||
|
||||
g.dispose();
|
||||
|
||||
ImageIO.write(bufImg, "PNG", pngName);
|
||||
|
||||
// break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static BufferedWriter parseEmfLog(Set<String> passed, String logFile) throws IOException {
|
||||
Path log = Paths.get(logFile);
|
||||
|
||||
StandardOpenOption soo;
|
||||
if (Files.exists(log)) {
|
||||
soo = StandardOpenOption.APPEND;
|
||||
try (Stream<String> 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 {
|
||||
|
|
Loading…
Reference in New Issue