#60656 - Support export file that contains emf and render it correctly

git-svn-id: https://svn.apache.org/repos/asf/poi/branches/hemf@1843342 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Andreas Beeker 2018-10-09 20:51:14 +00:00
parent 616f8127cf
commit f927e551cc
13 changed files with 365 additions and 268 deletions

View File

@ -17,6 +17,10 @@
package org.apache.poi.hemf.draw;
import static org.apache.poi.hwmf.record.HwmfBrushStyle.BS_NULL;
import static org.apache.poi.hwmf.record.HwmfBrushStyle.BS_SOLID;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
@ -29,11 +33,20 @@ import java.util.function.Consumer;
import org.apache.poi.hemf.record.emf.HemfBounded;
import org.apache.poi.hemf.record.emf.HemfRecord;
import org.apache.poi.hwmf.draw.HwmfGraphics;
import org.apache.poi.hwmf.record.HwmfColorRef;
import org.apache.poi.hwmf.record.HwmfObjectTableEntry;
import org.apache.poi.hwmf.record.HwmfPenStyle;
import org.apache.poi.util.Internal;
public class HemfGraphics extends HwmfGraphics {
private static final HwmfColorRef WHITE = new HwmfColorRef(Color.WHITE);
private static final HwmfColorRef LTGRAY = new HwmfColorRef(new Color(0x00C0C0C0));
private static final HwmfColorRef GRAY = new HwmfColorRef(new Color(0x00808080));
private static final HwmfColorRef DKGRAY = new HwmfColorRef(new Color(0x00404040));
private static final HwmfColorRef BLACK = new HwmfColorRef(Color.BLACK);
private final Deque<AffineTransform> transforms = new ArrayDeque<>();
public HemfGraphics(Graphics2D graphicsCtx, Rectangle2D bbox) {
@ -52,8 +65,7 @@ public class HemfGraphics extends HwmfGraphics {
@Override
public void saveProperties() {
assert(prop != null);
propStack.add(prop);
propStack.add(getProperties());
prop = new HemfDrawProperties((HemfDrawProperties)prop);
}
@ -138,6 +150,125 @@ public class HemfGraphics extends HwmfGraphics {
}
}
@Override
public void applyObjectTableEntry(int index) {
if ((index & 0x80000000) != 0) {
selectStockObject(index);
} else {
super.applyObjectTableEntry(index);
}
}
private void selectStockObject(int objectIndex) {
final HemfDrawProperties prop = getProperties();
switch (objectIndex) {
case 0x80000000:
// WHITE_BRUSH - A white, solid-color brush
// BrushStyle: BS_SOLID
// Color: 0x00FFFFFF
prop.setBrushColor(WHITE);
prop.setBrushStyle(BS_SOLID);
break;
case 0x80000001:
// LTGRAY_BRUSH - A light gray, solid-color brush
// BrushStyle: BS_SOLID
// Color: 0x00C0C0C0
prop.setBrushColor(LTGRAY);
prop.setBrushStyle(BS_SOLID);
break;
case 0x80000002:
// GRAY_BRUSH - A gray, solid-color brush
// BrushStyle: BS_SOLID
// Color: 0x00808080
prop.setBrushColor(GRAY);
prop.setBrushStyle(BS_SOLID);
break;
case 0x80000003:
// DKGRAY_BRUSH - A dark gray, solid color brush
// BrushStyle: BS_SOLID
// Color: 0x00404040
prop.setBrushColor(DKGRAY);
prop.setBrushStyle(BS_SOLID);
break;
case 0x80000004:
// BLACK_BRUSH - A black, solid color brush
// BrushStyle: BS_SOLID
// Color: 0x00000000
prop.setBrushColor(BLACK);
prop.setBrushStyle(BS_SOLID);
break;
case 0x80000005:
// NULL_BRUSH - A null brush
// BrushStyle: BS_NULL
prop.setBrushStyle(BS_NULL);
break;
case 0x80000006:
// WHITE_PEN - A white, solid-color pen
// PenStyle: PS_COSMETIC + PS_SOLID
// ColorRef: 0x00FFFFFF
prop.setPenStyle(HwmfPenStyle.valueOf(0));
prop.setPenWidth(1);
prop.setPenColor(WHITE);
break;
case 0x80000007:
// BLACK_PEN - A black, solid-color pen
// PenStyle: PS_COSMETIC + PS_SOLID
// ColorRef: 0x00000000
prop.setPenStyle(HwmfPenStyle.valueOf(0));
prop.setPenWidth(1);
prop.setPenColor(BLACK);
break;
case 0x80000008:
// NULL_PEN - A null pen
// PenStyle: PS_NULL
prop.setPenStyle(HwmfPenStyle.valueOf(HwmfPenStyle.HwmfLineDash.NULL.wmfFlag));
break;
case 0x8000000A:
// OEM_FIXED_FONT - A fixed-width, OEM character set
// Charset: OEM_CHARSET
// PitchAndFamily: FF_DONTCARE + FIXED_PITCH
break;
case 0x8000000B:
// ANSI_FIXED_FONT - A fixed-width font
// Charset: ANSI_CHARSET
// PitchAndFamily: FF_DONTCARE + FIXED_PITCH
break;
case 0x8000000C:
// ANSI_VAR_FONT - A variable-width font
// Charset: ANSI_CHARSET
// PitchAndFamily: FF_DONTCARE + VARIABLE_PITCH
break;
case 0x8000000D:
// SYSTEM_FONT - A font that is guaranteed to be available in the operating system
break;
case 0x8000000E:
// DEVICE_DEFAULT_FONT
// The default font that is provided by the graphics device driver for the current output device
break;
case 0x8000000F:
// DEFAULT_PALETTE
// The default palette that is defined for the current output device.
break;
case 0x80000010:
// SYSTEM_FIXED_FONT
// A fixed-width font that is guaranteed to be available in the operating system.
break;
case 0x80000011:
// DEFAULT_GUI_FONT
// The default font that is used for user interface objects such as menus and dialog boxes.
break;
case 0x80000012:
// DC_BRUSH
// The solid-color brush that is currently selected in the playback device context.
break;
case 0x80000013:
// DC_PEN
// The solid-color pen that is currently selected in the playback device context.
break;
}
}
/** saves the current affine transform on the stack */
private void saveTransform() {

View File

@ -40,7 +40,7 @@ import org.apache.poi.util.RecordFormatException;
*/
@Internal
public class HemfComment {
private static final int MAX_RECORD_LENGTH = 1_000_000;
private static final int MAX_RECORD_LENGTH = 2_000_000;
public enum HemfCommentRecordType {
emfGeneric(-1, EmfCommentDataGeneric::new, false),

View File

@ -47,12 +47,6 @@ public class HemfDraw {
*/
public static class EmfSelectObject extends WmfSelectObject implements HemfRecord {
private static final HwmfColorRef WHITE = new HwmfColorRef(Color.WHITE);
private static final HwmfColorRef LTGRAY = new HwmfColorRef(new Color(0x00C0C0C0));
private static final HwmfColorRef GRAY = new HwmfColorRef(new Color(0x00808080));
private static final HwmfColorRef DKGRAY = new HwmfColorRef(new Color(0x00404040));
private static final HwmfColorRef BLACK = new HwmfColorRef(Color.BLACK);
private static final String[] STOCK_IDS = {
"0x80000000 /* WHITE_BRUSH */",
"0x80000001 /* LTGRAY_BRUSH */",
@ -88,124 +82,6 @@ public class HemfDraw {
return LittleEndianConsts.INT_SIZE;
}
@Override
public void draw(HemfGraphics ctx) {
if ((objectIndex & 0x80000000) != 0) {
selectStockObject(ctx);
} else {
super.draw(ctx);
}
}
private void selectStockObject(HemfGraphics ctx) {
final HemfDrawProperties prop = ctx.getProperties();
switch (objectIndex) {
case 0x80000000:
// WHITE_BRUSH - A white, solid-color brush
// BrushStyle: BS_SOLID
// Color: 0x00FFFFFF
prop.setBrushColor(WHITE);
prop.setBrushStyle(BS_SOLID);
break;
case 0x80000001:
// LTGRAY_BRUSH - A light gray, solid-color brush
// BrushStyle: BS_SOLID
// Color: 0x00C0C0C0
prop.setBrushColor(LTGRAY);
prop.setBrushStyle(BS_SOLID);
break;
case 0x80000002:
// GRAY_BRUSH - A gray, solid-color brush
// BrushStyle: BS_SOLID
// Color: 0x00808080
prop.setBrushColor(GRAY);
prop.setBrushStyle(BS_SOLID);
break;
case 0x80000003:
// DKGRAY_BRUSH - A dark gray, solid color brush
// BrushStyle: BS_SOLID
// Color: 0x00404040
prop.setBrushColor(DKGRAY);
prop.setBrushStyle(BS_SOLID);
break;
case 0x80000004:
// BLACK_BRUSH - A black, solid color brush
// BrushStyle: BS_SOLID
// Color: 0x00000000
prop.setBrushColor(BLACK);
prop.setBrushStyle(BS_SOLID);
break;
case 0x80000005:
// NULL_BRUSH - A null brush
// BrushStyle: BS_NULL
prop.setBrushStyle(BS_NULL);
break;
case 0x80000006:
// WHITE_PEN - A white, solid-color pen
// PenStyle: PS_COSMETIC + PS_SOLID
// ColorRef: 0x00FFFFFF
prop.setPenStyle(HwmfPenStyle.valueOf(0));
prop.setPenWidth(1);
prop.setPenColor(WHITE);
break;
case 0x80000007:
// BLACK_PEN - A black, solid-color pen
// PenStyle: PS_COSMETIC + PS_SOLID
// ColorRef: 0x00000000
prop.setPenStyle(HwmfPenStyle.valueOf(0));
prop.setPenWidth(1);
prop.setPenColor(BLACK);
break;
case 0x80000008:
// NULL_PEN - A null pen
// PenStyle: PS_NULL
prop.setPenStyle(HwmfPenStyle.valueOf(HwmfPenStyle.HwmfLineDash.NULL.wmfFlag));
break;
case 0x8000000A:
// OEM_FIXED_FONT - A fixed-width, OEM character set
// Charset: OEM_CHARSET
// PitchAndFamily: FF_DONTCARE + FIXED_PITCH
break;
case 0x8000000B:
// ANSI_FIXED_FONT - A fixed-width font
// Charset: ANSI_CHARSET
// PitchAndFamily: FF_DONTCARE + FIXED_PITCH
break;
case 0x8000000C:
// ANSI_VAR_FONT - A variable-width font
// Charset: ANSI_CHARSET
// PitchAndFamily: FF_DONTCARE + VARIABLE_PITCH
break;
case 0x8000000D:
// SYSTEM_FONT - A font that is guaranteed to be available in the operating system
break;
case 0x8000000E:
// DEVICE_DEFAULT_FONT
// The default font that is provided by the graphics device driver for the current output device
break;
case 0x8000000F:
// DEFAULT_PALETTE
// The default palette that is defined for the current output device.
break;
case 0x80000010:
// SYSTEM_FIXED_FONT
// A fixed-width font that is guaranteed to be available in the operating system.
break;
case 0x80000011:
// DEFAULT_GUI_FONT
// The default font that is used for user interface objects such as menus and dialog boxes.
break;
case 0x80000012:
// DC_BRUSH
// The solid-color brush that is currently selected in the playback device context.
break;
case 0x80000013:
// DC_PEN
// The solid-color pen that is currently selected in the playback device context.
break;
}
}
@Override
public String toString() {
return "{ index: "+
@ -1166,6 +1042,11 @@ public class HemfDraw {
private static void polyTo(final HemfGraphics ctx, final Path2D poly) {
final PathIterator pi = poly.getPathIterator(null);
if (pi.isDone()) {
// ignore empty polys
return;
}
// ignore dummy start point (moveTo)
pi.next();
assert (!pi.isDone());

View File

@ -19,6 +19,7 @@ package org.apache.poi.hemf.record.emf;
import static org.apache.poi.hemf.record.emf.HemfDraw.readPointL;
import static org.apache.poi.hemf.record.emf.HemfDraw.readRectL;
import static org.apache.poi.hemf.record.emf.HemfRecordIterator.HEADER_SIZE;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
@ -100,7 +101,7 @@ public class HemfFill {
@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
long size = readPointL(leis, start);
size = colorRef.init(leis);
size += colorRef.init(leis);
// A 32-bit unsigned integer that specifies how to use the Color value to determine the area for
// the flood fill operation. The value MUST be in the FloodFill enumeration
mode = (int)leis.readUInt();
@ -117,7 +118,7 @@ public class HemfFill {
protected final Rectangle2D bounds = new Rectangle2D.Double();
/** An XForm object that specifies a world-space to page-space transform to apply to the source bitmap. */
protected final byte[] xformSrc = new byte[24];
protected final AffineTransform xFormSrc = new AffineTransform();
/** A WMF ColorRef object that specifies the background color of the source bitmap. */
protected final HwmfColorRef bkColorSrc = new HwmfColorRef();
@ -155,8 +156,7 @@ public class HemfFill {
final Point2D srcPnt = new Point2D.Double();
size += readPointL(leis, srcPnt);
leis.readFully(xformSrc);
size += 24;
size += readXForm(leis, xFormSrc);
size += bkColorSrc.init(leis);
@ -168,6 +168,10 @@ public class HemfFill {
// A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap header.
final int cbBmiSrc = (int)leis.readUInt();
size += 3*LittleEndianConsts.INT_SIZE;
if (size <= recordSize) {
return size;
}
// A 32-bit unsigned integer that specifies the offset, in bytes, from the
// start of this record to the source bitmap bits in the BitmapBuffer field.
@ -176,7 +180,7 @@ public class HemfFill {
// A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap bits.
final int cbBitsSrc = (int)leis.readUInt();
size += 5*LittleEndianConsts.INT_SIZE;
size += 2*LittleEndianConsts.INT_SIZE;
if (srcEqualsDstDimension()) {
srcBounds.setRect(srcPnt.getX(), srcPnt.getY(), dstBounds.getWidth(), dstBounds.getHeight());
@ -205,6 +209,16 @@ public class HemfFill {
protected boolean srcEqualsDstDimension() {
return false;
}
@Override
public String toString() {
return
"{ bounds: { x: "+bounds.getX()+", y: "+bounds.getY()+", w: "+bounds.getWidth()+", h: "+bounds.getHeight()+"}"+
", xFormSrc: { scaleX: "+xFormSrc.getScaleX()+", shearX: "+xFormSrc.getShearX()+", transX: "+xFormSrc.getTranslateX()+", scaleY: "+xFormSrc.getScaleY()+", shearY: "+xFormSrc.getShearY()+", transY: "+xFormSrc.getTranslateY()+" }"+
", bkColorSrc: "+bkColorSrc+
", usageSrc: "+usageSrc+", "
+ super.toString().substring(1);
}
}
/**
@ -589,18 +603,24 @@ public class HemfFill {
}
static long readBitmap(final LittleEndianInputStream leis, final HwmfBitmapDib bitmap,
final int startIdx, final int offBmiSrc, final int cbBmiSrc, final int offBitsSrc, int cbBitsSrc)
final int startIdx, final int offBmi, final int cbBmi, final int offBits, int cbBits)
throws IOException {
final int offCurr = leis.getReadIndex()-startIdx;
final int undefinedSpace1 = offBmiSrc-offCurr;
assert(undefinedSpace1 >= 0);
if (offBmi == 0) {
return 0;
}
final int undefinedSpace2 = offBitsSrc-offCurr-cbBmiSrc-undefinedSpace1;
final int offCurr = leis.getReadIndex()-(startIdx-HEADER_SIZE);
final int undefinedSpace1 = offBmi-offCurr;
if (undefinedSpace1 < 0) {
return 0;
}
final int undefinedSpace2 = offBits-offCurr-cbBmi-undefinedSpace1;
assert(undefinedSpace2 >= 0);
leis.skipFully(undefinedSpace1);
if (cbBmiSrc == 0 || cbBitsSrc == 0) {
if (cbBmi == 0 || cbBits == 0) {
return undefinedSpace1;
}
@ -608,18 +628,18 @@ public class HemfFill {
if (undefinedSpace2 == 0) {
leisDib = leis;
} else {
final ByteArrayOutputStream bos = new ByteArrayOutputStream(cbBmiSrc+cbBitsSrc);
final long cbBmiSrcAct = IOUtils.copy(leis, bos, cbBmiSrc);
assert (cbBmiSrcAct == cbBmiSrc);
final ByteArrayOutputStream bos = new ByteArrayOutputStream(cbBmi+cbBits);
final long cbBmiSrcAct = IOUtils.copy(leis, bos, cbBmi);
assert (cbBmiSrcAct == cbBmi);
leis.skipFully(undefinedSpace2);
final long cbBitsSrcAct = IOUtils.copy(leis, bos, cbBitsSrc);
assert (cbBitsSrcAct == cbBitsSrc);
final long cbBitsSrcAct = IOUtils.copy(leis, bos, cbBits);
assert (cbBitsSrcAct == cbBits);
leisDib = new LittleEndianInputStream(new ByteArrayInputStream(bos.toByteArray()));
}
final int dibSize = cbBmiSrc+cbBitsSrc;
final int dibSize = cbBmi+cbBits;
final int dibSizeAct = bitmap.init(leisDib, dibSize);
assert (dibSizeAct <= dibSize);
return undefinedSpace1 + cbBmiSrc + undefinedSpace2 + cbBitsSrc;
return undefinedSpace1 + cbBmi + undefinedSpace2 + cbBits;
}

View File

@ -33,6 +33,7 @@ import org.apache.poi.hwmf.record.HwmfBinaryRasterOp;
import org.apache.poi.hwmf.record.HwmfBitmapDib;
import org.apache.poi.hwmf.record.HwmfBrushStyle;
import org.apache.poi.hwmf.record.HwmfColorRef;
import org.apache.poi.hwmf.record.HwmfFill;
import org.apache.poi.hwmf.record.HwmfHatchStyle;
import org.apache.poi.hwmf.record.HwmfMapMode;
import org.apache.poi.hwmf.record.HwmfMisc;
@ -65,8 +66,8 @@ public class HemfMisc {
int size = 2 * LittleEndianConsts.INT_SIZE;
if (offPalEntries > 0) {
int undefinedSpace1 = (int) (offPalEntries - size - HEADER_SIZE);
if (nPalEntries > 0 && offPalEntries > 0) {
int undefinedSpace1 = (int) (offPalEntries - (size + HEADER_SIZE));
assert (undefinedSpace1 >= 0);
leis.skipFully(undefinedSpace1);
size += undefinedSpace1;
@ -283,6 +284,59 @@ public class HemfMisc {
}
}
/**
* The EMR_CREATEDIBPATTERNBRUSHPT record defines a pattern brush for graphics operations.
* The pattern is specified by a DIB.
*/
public static class EmfCreateDibPatternBrushPt extends HwmfMisc.WmfDibCreatePatternBrush implements HemfRecord {
protected int brushIdx;
@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.createDibPatternBrushPt;
}
@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
final int startIdx = leis.getReadIndex();
style = HwmfBrushStyle.BS_DIBPATTERNPT;
// A 32-bit unsigned integer that specifies the index of the pattern brush
// object in the EMF Object Table
brushIdx = (int)leis.readUInt();
// A 32-bit unsigned integer that specifies how to interpret values in the color
// table in the DIB header. This value MUST be in the DIBColors enumeration
colorUsage = HwmfFill.ColorUsage.valueOf((int)leis.readUInt());
// A 32-bit unsigned integer that specifies the offset from the start of this
// record to the DIB header.
final int offBmi = leis.readInt();
// A 32-bit unsigned integer that specifies the size of the DIB header.
final int cbBmi = leis.readInt();
// A 32-bit unsigned integer that specifies the offset from the start of this record to the DIB bits.
final int offBits = leis.readInt();
// A 32-bit unsigned integer that specifies the size of the DIB bits.
final int cbBits = leis.readInt();
int size = 6*LittleEndianConsts.INT_SIZE;
patternDib = new HwmfBitmapDib();
size += readBitmap(leis, patternDib, startIdx, offBmi, cbBmi, offBits, cbBits);
return size;
}
@Override
public void draw(HemfGraphics ctx) {
ctx.addObjectTableEntry(this, brushIdx);
}
}
/**
* The EMR_DELETEOBJECT record deletes a graphics object, which is specified by its index
* in the EMF Object Table
@ -519,5 +573,12 @@ public class HemfMisc {
return size + LittleEndianConsts.INT_SIZE;
}
@Override
public String toString() {
return
"{ xForm: { scaleX: "+xForm.getScaleX()+", shearX: "+xForm.getShearX()+", transX: "+xForm.getTranslateX()+", scaleY: "+xForm.getScaleY()+", shearY: "+xForm.getShearY()+", transY: "+xForm.getTranslateY()+" }"+
", modifyWorldTransformMode: "+modifyWorldTransformMode+" }";
}
}
}

View File

@ -117,7 +117,7 @@ public enum HemfRecordType {
polyPolygon16(0x0000005B, HemfDraw.EmfPolyPolygon16::new),
polyDraw16(0x0000005C, HemfDraw.EmfPolyDraw16::new),
createmonobrush16(0x0000005D, UnimplementedHemfRecord::new),
createdibpatternbrushpt(0x0000005E, UnimplementedHemfRecord::new),
createDibPatternBrushPt(0x0000005E, HemfMisc.EmfCreateDibPatternBrushPt::new),
extCreatePen(0x0000005F, HemfMisc.EmfExtCreatePen::new),
polytextouta(0x00000060, HemfText.PolyTextOutA::new),
polytextoutw(0x00000061, HemfText.PolyTextOutW::new),

View File

@ -113,7 +113,7 @@ public class HemfText {
int offDx = (int)leis.readUInt();
size += LittleEndianConsts.INT_SIZE;
int undefinedSpace1 = (int)(offString - size - HEADER_SIZE);
int undefinedSpace1 = (int)(offString - (size + HEADER_SIZE));
assert (undefinedSpace1 >= 0);
leis.skipFully(undefinedSpace1);
size += undefinedSpace1;
@ -124,7 +124,7 @@ public class HemfText {
dx.clear();
if (offDx > 0) {
int undefinedSpace2 = (int) (offDx - size - HEADER_SIZE);
int undefinedSpace2 = (int) (offDx - (size + HEADER_SIZE));
assert (undefinedSpace2 >= 0);
leis.skipFully(undefinedSpace2);
size += undefinedSpace2;
@ -149,10 +149,6 @@ public class HemfText {
return size;
}
protected boolean isUnicode() {
return false;
}
/**
*
* To be implemented! We need to get the current character set
@ -185,21 +181,10 @@ public class HemfText {
@Override
public String toString() {
String text = "";
try {
text = getText(isUnicode() ? Charsets.UTF_16LE : LocaleUtil.CHARSET_1252);
} catch (IOException ignored) {
}
return
"{ bounds: { x: "+bounds.getX()+
", y: "+bounds.getY()+
", w: "+bounds.getWidth()+
", h: "+bounds.getHeight()+
"}, graphicsMode: '"+graphicsMode+"'"+
", scale: { w: "+scale.getWidth()+", h: "+scale.getHeight()+" }"+
", text: '"+text.replaceAll("\\p{Cntrl}",".")+"'"+
"}";
"{ graphicsMode: '"+graphicsMode+"'"+
", scale: { w: "+scale.getWidth()+", h: "+scale.getHeight()+" },"+
super.toString().substring(1);
}
}

View File

@ -108,11 +108,11 @@ public class HemfPicture implements Iterable<HemfRecord> {
ctx.scale(graphicsBounds.getWidth()/emfBounds.getWidth(), graphicsBounds.getHeight()/emfBounds.getHeight());
ctx.translate(-emfBounds.getCenterX(), -emfBounds.getCenterY());
int idx = 0;
HemfGraphics g = new HemfGraphics(ctx, emfBounds);
for (HemfRecord r : getRecords()) {
g.draw(r);
idx++;
}
} finally {
ctx.setTransform(at);

View File

@ -323,11 +323,7 @@ public class HwmfGraphics {
}
public void drawString(byte[] text, Rectangle2D bounds) {
drawString(text, bounds, null);
}
public void drawString(byte[] text, Rectangle2D bounds, List<Integer> dx) {
drawString(text, bounds, dx, false);
drawString(text, bounds, null, false);
}
public void drawString(byte[] text, Rectangle2D bounds, List<Integer> dx, boolean isUnicode) {
@ -352,10 +348,12 @@ public class HwmfGraphics {
}
String textString = new String(text, charset).trim();
if (textString.isEmpty()) {
return;
}
AttributedString as = new AttributedString(textString);
if (dx == null || dx.isEmpty()) {
addAttributes(as, font);
} else {
addAttributes(as, font);
if (dx != null && !dx.isEmpty()) {
//for multi-byte encodings (e.g. Shift_JIS), the byte length
//might not equal the string length().
//The x information is stored in dx[], an array parallel to the
@ -371,31 +369,21 @@ public class HwmfGraphics {
//dxNormed[0] = 13 textString.get(0) = U+30D7
//dxNormed[1] = 14 textString.get(1) = U+30ED
final List<Integer> dxNormed;
if (textString.length() == text.length) {
dxNormed = new ArrayList<>(dx);
} else {
dxNormed = new ArrayList<>(dx.size());
int dxPosition = 0;
int[] chars = {0};
for (int offset = 0; offset < textString.length(); ) {
dxNormed.add(dx.get(dxPosition));
chars[0] = textString.codePointAt(offset);
//now figure out how many bytes it takes to encode that
//code point in the charset
int byteLength = new String(chars, 0, chars.length).getBytes(charset).length;
dxPosition += byteLength;
offset += Character.charCount(chars[0]);
final int cps = textString.codePointCount(0, textString.length());
final int unicodeSteps = Math.max(dx.size()/cps, 1);
int dxPosition = 0;
int beginIndex = 0;
int[] chars = {0};
while (beginIndex < textString.length() && dxPosition < dx.size()) {
int endIndex = textString.offsetByCodePoints(beginIndex, 1);
if (beginIndex > 0) {
// Tracking works as a prefix/advance space on characters whereas
// dx[...] is the complete width of the current char
// therefore we need to add the additional/suffix width to the next char
as.addAttribute(TextAttribute.TRACKING, (float)((dx.get(dxPosition) - fontW) / fontH), beginIndex, endIndex);
}
}
int cps = textString.codePointCount(0, textString.length());
for (int i = 0; i < Math.min(dxNormed.size(),cps-1); i++) {
addAttributes(as, font);
// Tracking works as a prefix/advance space on characters whereas
// dx[...] is the complete width of the current char
// therefore we need to add the additional/suffix width to the next char
as.addAttribute(TextAttribute.TRACKING, (dxNormed.get(i) - fontW) / fontH, i + 1, i + 2);
dxPosition += (isUnicode) ? unicodeSteps : (endIndex-beginIndex);
beginIndex = endIndex;
}
}
@ -413,7 +401,7 @@ public class HwmfGraphics {
graphicsCtx.fill(new Rectangle2D.Double(0, 0, bounds.getWidth(), bounds.getHeight()));
}
graphicsCtx.setColor(getProperties().getTextColor().getColor());
graphicsCtx.drawString(as.getIterator(), 0, 0); // (float)bounds.getX(), (float)bounds.getY());
graphicsCtx.drawString(as.getIterator(), 0, 0);
} finally {
graphicsCtx.setTransform(at);
}
@ -425,7 +413,9 @@ public class HwmfGraphics {
as.addAttribute(TextAttribute.FAMILY, fontInfo.getTypeface());
as.addAttribute(TextAttribute.SIZE, getFontHeight(font));
as.addAttribute(TextAttribute.STRIKETHROUGH, font.isStrikeOut());
if (font.isStrikeOut()) {
as.addAttribute(TextAttribute.STRIKETHROUGH, TextAttribute.STRIKETHROUGH_ON);
}
if (font.isUnderline()) {
as.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
}

View File

@ -415,6 +415,15 @@ public class HwmfFill {
public void draw(HwmfGraphics ctx) {
}
@Override
public String toString() {
return
"{ rasterOperation: '"+rasterOperation+"'"+
", srcBounds: { x: "+srcBounds.getX()+", y: "+srcBounds.getY()+", w: "+srcBounds.getWidth()+", h: "+srcBounds.getHeight()+" }"+
", dstBounds: { x: "+dstBounds.getX()+", y: "+dstBounds.getY()+", w: "+dstBounds.getWidth()+", h: "+dstBounds.getHeight()+" }"+
"}";
}
}
/**

View File

@ -377,7 +377,7 @@ public class HwmfMisc {
*/
public static class WmfDibCreatePatternBrush implements HwmfRecord, HwmfImageRecord, HwmfObjectTableEntry {
private HwmfBrushStyle style;
protected HwmfBrushStyle style;
/**
* A 16-bit unsigned integer that defines whether the Colors field of a DIB
@ -388,9 +388,9 @@ public class HwmfMisc {
*
* If the Style field specified anything but BS_PATTERN, this field MUST be one of the ColorUsage values.
*/
private ColorUsage colorUsage;
protected ColorUsage colorUsage;
private HwmfBitmapDib patternDib;
protected HwmfBitmapDib patternDib;
private HwmfBitmap16 pattern16;
@Override

View File

@ -31,6 +31,7 @@ import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.codec.Charsets;
import org.apache.poi.hwmf.draw.HwmfDrawProperties;
import org.apache.poi.hwmf.draw.HwmfGraphics;
import org.apache.poi.hwmf.record.HwmfMisc.WmfSetMapMode;
@ -39,6 +40,7 @@ import org.apache.poi.util.BitFieldFactory;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.LittleEndianInputStream;
import org.apache.poi.util.LocaleUtil;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
import org.apache.poi.util.RecordFormatException;
@ -291,6 +293,10 @@ public class HwmfText {
public boolean isClipped() {
return ETO_CLIPPED.isSet(flag);
}
public boolean isYDisplaced() {
return ETO_PDY.isSet(flag);
}
}
/**
@ -393,37 +399,11 @@ public class HwmfText {
@Override
public void draw(HwmfGraphics ctx) {
Rectangle2D bounds = new Rectangle2D.Double(reference.getX(), reference.getY(), 0, 0);
ctx.drawString(rawTextBytes, bounds, dx);
ctx.drawString(rawTextBytes, bounds, dx, false);
}
public String getText(Charset charset) throws IOException {
StringBuilder sb = new StringBuilder();
try (Reader r = new InputStreamReader(new ByteArrayInputStream(rawTextBytes), charset)) {
for (int i = 0; i < stringLength; i++) {
sb.appendCodePoint(readCodePoint(r));
}
}
return sb.toString();
}
//TODO: move this to IOUtils?
private int readCodePoint(Reader r) throws IOException {
int c1 = r.read();
if (c1 == -1) {
throw new EOFException("Tried to read beyond byte array");
}
if (!Character.isHighSurrogate((char)c1)) {
return c1;
}
int c2 = r.read();
if (c2 == -1) {
throw new EOFException("Tried to read beyond byte array");
}
if (!Character.isLowSurrogate((char)c2)) {
throw new RecordFormatException("Expected low surrogate after high surrogate");
}
return Character.toCodePoint((char)c1, (char)c2);
return new String(rawTextBytes, charset);
}
public Point2D getReference() {
@ -433,6 +413,25 @@ public class HwmfText {
public Rectangle2D getBounds() {
return bounds;
}
protected boolean isUnicode() {
return false;
}
@Override
public String toString() {
String text = "";
try {
text = getText(isUnicode() ? Charsets.UTF_16LE : LocaleUtil.CHARSET_1252);
} catch (IOException ignored) {
}
return
"{ reference: { x: "+reference.getX()+", y: "+reference.getY()+" }"+
", bounds: { x: "+bounds.getX()+", y: "+bounds.getY()+", w: "+bounds.getWidth()+", h: "+bounds.getHeight()+"}"+
", text: '"+text.replaceAll("\\p{Cntrl}",".")+"'"+
"}";
}
}
public enum HwmfTextAlignment {

View File

@ -38,6 +38,8 @@ import java.io.InputStream;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import javax.imageio.ImageIO;
@ -64,42 +66,61 @@ public class HemfPictureTest {
@Test
@Ignore("Only for manual tests")
public void paint() throws IOException {
File f = new File("picture_14.emf"); // sl_samples.getFile("wrench.emf");
try (FileInputStream fis = new FileInputStream(f)) {
HemfPicture emf = new HemfPicture(fis);
// File f = new File("picture_14.emf"); // sl_samples.getFile("wrench.emf");
// try (FileInputStream fis = new FileInputStream(f)) {
Dimension2D dim = emf.getSize();
int width = Units.pointsToPixel(dim.getWidth());
// keep aspect ratio for height
int height = Units.pointsToPixel(dim.getHeight());
double max = Math.max(width, height);
if (max > 1500) {
width *= 1500 / max;
height *= 1500 / max;
}
BufferedImage bufImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = bufImg.createGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
FileWriter fw = new FileWriter("record-list.txt");
int i=0;
for (HemfRecord r : emf.getRecords()) {
if (r.getEmfRecordType() != HemfRecordType.comment) {
fw.write(i + " " + r.getEmfRecordType() + " " + r.toString() + "\n");
try (ZipInputStream zis = new ZipInputStream(new FileInputStream("tmp/emf.zip"))) {
for (;;) {
ZipEntry ze = zis.getNextEntry();
if (ze == null) {
break;
}
i++;
final File pngName = new File("build/tmp",ze.getName().replaceFirst( ".*/","").replace(".emf", ".png"));
if (pngName.exists()) {
continue;
}
// 263/263282_000.emf
// if (!ze.getName().contains("298/298837_000.emf")) continue;
HemfPicture emf = new HemfPicture(zis);
System.out.println(ze.getName());
Dimension2D dim = emf.getSize();
int width = Units.pointsToPixel(dim.getWidth());
// keep aspect ratio for height
int height = Units.pointsToPixel(dim.getHeight());
double max = Math.max(width, height);
if (max > 1500) {
width *= 1500 / max;
height *= 1500 / max;
}
BufferedImage bufImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = bufImg.createGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
FileWriter fw = new FileWriter("record-list.txt");
int i = 0;
for (HemfRecord r : emf.getRecords()) {
if (r.getEmfRecordType() != HemfRecordType.comment) {
fw.write(i + " " + r.getEmfRecordType() + " " + r.toString() + "\n");
}
i++;
}
fw.close();
emf.draw(g, new Rectangle2D.Double(0, 0, width, height));
g.dispose();
ImageIO.write(bufImg, "PNG", pngName);
// break;
}
fw.close();
emf.draw(g, new Rectangle2D.Double(0,0,width,height));
g.dispose();
ImageIO.write(bufImg, "PNG", new File("bla.png"));
}
}