git-svn-id: https://svn.apache.org/repos/asf/poi/branches/hemf@1844380 13f79535-47bb-0310-9956-ffa450edef68tags/REL_4_1_0
@@ -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; | |||
} | |||
if (index > objectTable.size()) { | |||
throw new IllegalStateException("object table hasn't grown to this index yet"); | |||
throw new IndexOutOfBoundsException("Object table entry index in EMF must be > 0 - invalid index: "+index); | |||
} | |||
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. | |||
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; | |||
} |
@@ -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 { | |||
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())); | |||
return undefinedSpace1 + bitmap.init(leis, dibSize); | |||
} | |||
final int dibSize = cbBmi+cbBits; | |||
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<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,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; | |||
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; | |||
// 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; | |||
// 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; | |||
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; | |||
} | |||
} | |||
} | |||
} | |||
@@ -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 | |||
} | |||
} | |||
} | |||
if (Math.rint(width*coeff) == 0 || Math.rint(height*coeff) == 0) { | |||
width = 100; | |||
height = 100; | |||
} | |||
double coeff = (double)Units.EMU_PER_CENTIMETER/Units.EMU_PER_POINT/10.; | |||
return new Dimension2DDouble(dim.getWidth()*coeff, dim.getHeight()*coeff); | |||
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"); | |||
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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} | |||
@@ -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,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)) { | |||
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; | |||
} | |||
byte buf[] = new byte[50_000_000]; | |||
final boolean writeLog = true; | |||
final boolean savePng = true; | |||
// 263/263282_000.emf | |||
// if (!ze.getName().contains("298/298837_000.emf")) continue; | |||
HemfPicture emf = new HemfPicture(zis); | |||
System.out.println(ze.getName()); | |||
Set<String> passed = new HashSet<>(); | |||
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; | |||
} | |||
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); | |||
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"); | |||
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; | |||
} | |||
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)); | |||
g.dispose(); | |||
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(); | |||
} | |||
ImageIO.write(bufImg, "PNG", pngName); | |||
if (writeLog) { | |||
sucWrite.write(etName + "\n"); | |||
sucWrite.flush(); | |||
} | |||
} | |||
} | |||
} */ | |||
// break; | |||
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<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 { |