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

git-svn-id: https://svn.apache.org/repos/asf/poi/branches/hemf@1845496 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Andreas Beeker 2018-11-01 16:27:59 +00:00
parent 0a84f6881f
commit acfed6fb29
7 changed files with 155 additions and 158 deletions

View File

@ -24,6 +24,7 @@ import static org.apache.poi.hemf.record.emf.HemfRecordIterator.HEADER_SIZE;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@ -40,6 +41,7 @@ import org.apache.poi.hwmf.record.HwmfHatchStyle;
import org.apache.poi.hwmf.record.HwmfMapMode;
import org.apache.poi.hwmf.record.HwmfMisc;
import org.apache.poi.hwmf.record.HwmfMisc.WmfSetBkMode;
import org.apache.poi.hwmf.record.HwmfObjectTableEntry;
import org.apache.poi.hwmf.record.HwmfPalette.PaletteEntry;
import org.apache.poi.hwmf.record.HwmfPenStyle;
import org.apache.poi.hwmf.record.HwmfPenStyle.HwmfLineDash;
@ -687,13 +689,20 @@ public class HemfMisc {
}
}
public static class EmfCreateMonoBrush16 extends EmfCreatePen {
public static class EmfCreateMonoBrush implements HemfRecord, HwmfObjectTableEntry {
/**
* A 32-bit unsigned integer that specifies the index of the logical palette object
* in the EMF Object Table. This index MUST be saved so that this object can be
* reused or modified.
*/
protected int penIndex;
protected HwmfFill.ColorUsage colorUsage;
protected final HwmfBitmapDib bitmap = new HwmfBitmapDib();
@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.createMonoBrush16;
return HemfRecordType.createMonoBrush;
}
@Override
@ -729,12 +738,17 @@ public class HemfMisc {
return size;
}
@Override
public void draw(HemfGraphics ctx) {
ctx.addObjectTableEntry(this, penIndex);
}
@Override
public void applyObject(HwmfGraphics ctx) {
super.applyObject(ctx);
HwmfDrawProperties props = ctx.getProperties();
props.setBrushStyle(HwmfBrushStyle.BS_PATTERN);
props.setBrushBitmap(bitmap.getImage());
BufferedImage bmp = bitmap.getImage();
props.setBrushBitmap(bmp);
}
}
}

View File

@ -105,7 +105,7 @@ public enum HemfRecordType {
plgblt(0x0000004F, UnimplementedHemfRecord::new),
setDiBitsToDevice(0x00000050, HemfFill.EmfSetDiBitsToDevice::new),
stretchDiBits(0x00000051, HemfFill.EmfStretchDiBits::new),
extCreateFontIndirectW(0x00000052, HemfText.ExtCreateFontIndirectW::new),
extCreateFontIndirectW(0x00000052, HemfText.EmfExtCreateFontIndirectW::new),
extTextOutA(0x00000053, HemfText.EmfExtTextOutA::new),
extTextOutW(0x00000054, HemfText.EmfExtTextOutW::new),
polyBezier16(0x00000055, HemfDraw.EmfPolyBezier16::new),
@ -116,7 +116,7 @@ public enum HemfRecordType {
polyPolyline16(0x0000005A, HemfDraw.EmfPolyPolyline16::new),
polyPolygon16(0x0000005B, HemfDraw.EmfPolyPolygon16::new),
polyDraw16(0x0000005C, HemfDraw.EmfPolyDraw16::new),
createMonoBrush16(0x0000005D, HemfMisc.EmfCreateMonoBrush16::new),
createMonoBrush(0x0000005D, HemfMisc.EmfCreateMonoBrush::new),
createDibPatternBrushPt(0x0000005E, HemfMisc.EmfCreateDibPatternBrushPt::new),
extCreatePen(0x0000005F, HemfMisc.EmfExtCreatePen::new),
polytextouta(0x00000060, HemfText.PolyTextOutA::new),

View File

@ -113,7 +113,10 @@ public class HemfText {
size += LittleEndianConsts.INT_SIZE;
// handle dx before string and other way round
for (char op : ((offDx < offString) ? "ds" : "sd").toCharArray()) {
final String order = (offDx < offString) ? "ds" : "sd";
// the next byte index after the string ends
int strEnd = (int)((offDx <= HEADER_SIZE) ? recordSize : offDx-HEADER_SIZE);
for (char op : order.toCharArray()) {
switch (op) {
case 'd': {
dx.clear();
@ -138,6 +141,9 @@ public class HemfText {
dx.add((int) leis.readUInt());
size += LittleEndianConsts.INT_SIZE;
}
} else {
// if there are no dx entries, reset the string end
strEnd = (int)recordSize;
}
if (dx.size() < stringLength) {
// invalid dx array
@ -152,7 +158,9 @@ public class HemfText {
leis.skipFully(undefinedSpace1);
size += undefinedSpace1;
final int maxSize = (int)Math.min(recordSize-size, stringLength * (isUnicode() ? 2 : 1));
// read all available bytes and not just "stringLength * 1(ansi)/2(unicode)"
// in case we need to deal with surrogate pairs
final int maxSize = (int)(Math.min(recordSize, strEnd)-size);
rawTextBytes = IOUtils.safelyAllocate(maxSize, MAX_RECORD_LENGTH);
leis.readFully(rawTextBytes);
size += maxSize;
@ -191,7 +199,7 @@ public class HemfText {
@Override
public void draw(HwmfGraphics ctx) {
ctx.drawString(rawTextBytes, reference, bounds, options, dx, isUnicode());
ctx.drawString(rawTextBytes, stringLength, reference, bounds, options, dx, isUnicode());
}
@Override
@ -258,11 +266,11 @@ public class HemfText {
public static class ExtCreateFontIndirectW extends HwmfText.WmfCreateFontIndirect
public static class EmfExtCreateFontIndirectW extends HwmfText.WmfCreateFontIndirect
implements HemfRecord {
int fontIdx;
public ExtCreateFontIndirectW() {
public EmfExtCreateFontIndirectW() {
super(new HemfFont());
}

View File

@ -361,11 +361,11 @@ public class HwmfGraphics {
}
}
public void drawString(byte[] text, Point2D reference) {
drawString(text, reference, null, null, null, false);
public void drawString(byte[] text, int length, Point2D reference) {
drawString(text, length, reference, null, null, null, false);
}
public void drawString(byte[] text, Point2D reference, Rectangle2D clip, WmfExtTextOutOptions opts, List<Integer> dx, boolean isUnicode) {
public void drawString(byte[] text, int length, Point2D reference, Rectangle2D clip, WmfExtTextOutOptions opts, List<Integer> dx, boolean isUnicode) {
final HwmfDrawProperties prop = getProperties();
HwmfFont font = prop.getFont();
@ -387,7 +387,7 @@ public class HwmfGraphics {
}
}
String textString = new String(text, charset).trim();
String textString = new String(text, charset).substring(0,length).trim();
if (textString.isEmpty()) {
return;
}
@ -462,7 +462,7 @@ public class HwmfGraphics {
case BASELINE:
break;
case BOTTOM:
tx.translate(0, pixelBounds.getHeight());
tx.translate(0, -(pixelBounds.getHeight()-layout.getDescent()));
break;
}
tx.rotate(angle);

View File

@ -54,6 +54,10 @@ public class HwmfColorRef implements Cloneable {
return colorRef;
}
public void setColor(Color color) {
colorRef = color;
}
/**
* Creates a new object of the same class and with the
* same contents as this object.

View File

@ -594,6 +594,18 @@ public class HwmfDraw {
return new Arc2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), startAngle, arcAngle, arcClosure);
}
@Override
public String toString() {
Arc2D arc = getShape();
return
"{ startPoint: "+pointToString(startPoint)+
", endPoint: "+pointToString(endPoint)+
", startAngle: "+arc.getAngleStart()+
", extentAngle: "+arc.getAngleExtent()+
", bounds: "+boundsToString(bounds)+
" }";
}
}
/**

View File

@ -24,11 +24,7 @@ import static org.apache.poi.hwmf.record.HwmfDraw.readRectS;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.ByteArrayInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
@ -45,7 +41,6 @@ import org.apache.poi.util.LittleEndianInputStream;
import org.apache.poi.util.LocaleUtil;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
import org.apache.poi.util.RecordFormatException;
public class HwmfText {
private static final POILogger logger = POILogFactory.getLogger(HwmfText.class);
@ -191,7 +186,7 @@ public class HwmfText {
@Override
public void draw(HwmfGraphics ctx) {
ctx.drawString(getTextBytes(), reference);
ctx.drawString(getTextBytes(), stringLength, reference);
}
public String getText(Charset charset) {
@ -396,11 +391,11 @@ public class HwmfText {
@Override
public void draw(HwmfGraphics ctx) {
ctx.drawString(rawTextBytes, reference, bounds, options, dx, false);
ctx.drawString(rawTextBytes, stringLength, reference, bounds, options, dx, false);
}
public String getText(Charset charset) throws IOException {
return new String(rawTextBytes, charset);
return new String(rawTextBytes, charset).substring(0, stringLength);
}
public Point2D getReference() {
@ -448,57 +443,17 @@ public class HwmfText {
*/
public static class WmfSetTextAlign implements HwmfRecord {
// ***********************************************************************************
// TextAlignmentMode Flags:
// ***********************************************************************************
/**
* The drawing position in the playback device context MUST NOT be updated after each
* text output call. The reference point MUST be passed to the text output function.
*/
@SuppressWarnings("unused")
private static final BitField TA_NOUPDATECP = BitFieldFactory.getInstance(0x0000);
/**
* The reference point MUST be on the left edge of the bounding rectangle.
*/
@SuppressWarnings("unused")
private static final BitField TA_LEFT = BitFieldFactory.getInstance(0x0000);
/**
* The reference point MUST be on the top edge of the bounding rectangle.
*/
@SuppressWarnings("unused")
private static final BitField TA_TOP = BitFieldFactory.getInstance(0x0000);
/**
* The drawing position in the playback device context MUST be updated after each text
* output call. It MUST be used as the reference point.
* output call. It MUST be used as the reference point.<p>
*
* If the flag is not set, the option TA_NOUPDATECP is active, i.e. the drawing position
* in the playback device context MUST NOT be updated after each text output call.
* The reference point MUST be passed to the text output function.
*/
@SuppressWarnings("unused")
private static final BitField TA_UPDATECP = BitFieldFactory.getInstance(0x0001);
/**
* The reference point MUST be on the right edge of the bounding rectangle.
*/
private static final BitField TA_RIGHT = BitFieldFactory.getInstance(0x0002);
/**
* The reference point MUST be aligned horizontally with the center of the bounding
* rectangle.
*/
private static final BitField TA_CENTER = BitFieldFactory.getInstance(0x0006);
/**
* The reference point MUST be on the bottom edge of the bounding rectangle.
*/
private static final BitField TA_BOTTOM = BitFieldFactory.getInstance(0x0008);
/**
* The reference point MUST be on the baseline of the text.
*/
private static final BitField TA_BASELINE = BitFieldFactory.getInstance(0x0018);
/**
* The text MUST be laid out in right-to-left reading order, instead of the default
* left-to-right order. This SHOULD be applied only when the font that is defined in the
@ -506,43 +461,64 @@ public class HwmfText {
*/
@SuppressWarnings("unused")
private static final BitField TA_RTLREADING = BitFieldFactory.getInstance(0x0100);
// ***********************************************************************************
// VerticalTextAlignmentMode Flags (e.g. for Kanji fonts)
// ***********************************************************************************
private static final BitField ALIGN_MASK = BitFieldFactory.getInstance(0x0006);
/**
* The reference point MUST be on the top edge of the bounding rectangle.
* Flag TA_LEFT (0x0000):
* The reference point MUST be on the left edge of the bounding rectangle,
* if all bits of the align mask (latin mode) are unset.
*
* Flag VTA_TOP (0x0000):
* The reference point MUST be on the top edge of the bounding rectangle,
* if all bits of the valign mask are unset.
*/
@SuppressWarnings("unused")
private static final BitField VTA_TOP = BitFieldFactory.getInstance(0x0000);
private static final int ALIGN_LEFT = 0;
/**
* Flag TA_RIGHT (0x0002):
* The reference point MUST be on the right edge of the bounding rectangle.
*/
@SuppressWarnings("unused")
private static final BitField VTA_RIGHT = BitFieldFactory.getInstance(0x0000);
/**
*
* Flag VTA_BOTTOM (0x0002):
* The reference point MUST be on the bottom edge of the bounding rectangle.
*/
private static final BitField VTA_BOTTOM = BitFieldFactory.getInstance(0x0002);
private static final int ALIGN_RIGHT = 1;
/**
* The reference point MUST be aligned vertically with the center of the bounding
* Flag TA_CENTER (0x0006) / VTA_CENTER (0x0006):
* The reference point MUST be aligned horizontally with the center of the bounding
* rectangle.
*/
private static final BitField VTA_CENTER = BitFieldFactory.getInstance(0x0006);
private static final int ALIGN_CENTER = 3;
private static final BitField VALIGN_MASK = BitFieldFactory.getInstance(0x0018);
/**
* Flag TA_TOP (0x0000):
* The reference point MUST be on the top edge of the bounding rectangle,
* if all bits of the valign mask are unset.
*
* Flag VTA_RIGHT (0x0000):
* The reference point MUST be on the right edge of the bounding rectangle,
* if all bits of the align mask (asian mode) are unset.
*/
private static final int VALIGN_TOP = 0;
/**
* Flag TA_BOTTOM (0x0008):
* The reference point MUST be on the bottom edge of the bounding rectangle.
*
* Flag VTA_LEFT (0x0008):
* The reference point MUST be on the left edge of the bounding rectangle.
*/
private static final BitField VTA_LEFT = BitFieldFactory.getInstance(0x0008);
private static final int VALIGN_BOTTOM = 1;
/**
* Flag TA_BASELINE (0x0018) / VTA_BASELINE (0x0018):
* The reference point MUST be on the baseline of the text.
*/
private static final BitField VTA_BASELINE = BitFieldFactory.getInstance(0x0018);
private static final int VALIGN_BASELINE = 3;
/**
* A 16-bit unsigned integer that defines text alignment.
@ -566,85 +542,68 @@ public class HwmfText {
@Override
public void draw(HwmfGraphics ctx) {
HwmfDrawProperties props = ctx.getProperties();
if (TA_CENTER.isSet(textAlignmentMode)) {
props.setTextAlignLatin(HwmfTextAlignment.CENTER);
} else if (TA_RIGHT.isSet(textAlignmentMode)) {
props.setTextAlignLatin(HwmfTextAlignment.RIGHT);
} else {
props.setTextAlignLatin(HwmfTextAlignment.LEFT);
}
if (VTA_CENTER.isSet(textAlignmentMode)) {
props.setTextAlignAsian(HwmfTextAlignment.CENTER);
} else if (VTA_LEFT.isSet(textAlignmentMode)) {
props.setTextAlignAsian(HwmfTextAlignment.LEFT);
} else {
props.setTextAlignAsian(HwmfTextAlignment.RIGHT);
}
if (TA_BASELINE.isSet(textAlignmentMode)) {
props.setTextVAlignLatin(HwmfTextVerticalAlignment.BASELINE);
} else if (TA_BOTTOM.isSet(textAlignmentMode)) {
props.setTextVAlignLatin(HwmfTextVerticalAlignment.BOTTOM);
} else {
props.setTextVAlignLatin(HwmfTextVerticalAlignment.TOP);
}
if (VTA_BASELINE.isSet(textAlignmentMode)) {
props.setTextVAlignAsian(HwmfTextVerticalAlignment.BASELINE);
} else if (VTA_BOTTOM.isSet(textAlignmentMode)) {
props.setTextVAlignAsian(HwmfTextVerticalAlignment.BOTTOM);
} else {
props.setTextVAlignAsian(HwmfTextVerticalAlignment.TOP);
}
props.setTextAlignLatin(getAlignLatin());
props.setTextVAlignLatin(getVAlignLatin());
props.setTextAlignAsian(getAlignAsian());
props.setTextVAlignAsian(getVAlignAsian());
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("{ align: '");
return
"{ align: '"+ getAlignLatin() + "'" +
", valign: '"+ getVAlignLatin() + "'" +
", alignAsian: '"+ getAlignAsian() + "'" +
", valignAsian: '"+ getVAlignAsian() + "'" +
"}";
}
if (TA_CENTER.isSet(textAlignmentMode)) {
sb.append("center");
} else if (TA_RIGHT.isSet(textAlignmentMode)) {
sb.append("right");
} else {
sb.append("left");
private HwmfTextAlignment getAlignLatin() {
switch (ALIGN_MASK.getValue(textAlignmentMode)) {
default:
case ALIGN_LEFT:
return HwmfTextAlignment.LEFT;
case ALIGN_CENTER:
return HwmfTextAlignment.CENTER;
case ALIGN_RIGHT:
return HwmfTextAlignment.RIGHT;
}
}
sb.append("', align-asian: '");
if (VTA_CENTER.isSet(textAlignmentMode)) {
sb.append("center");
} else if (VTA_LEFT.isSet(textAlignmentMode)) {
sb.append("left");
} else {
sb.append("right");
private HwmfTextVerticalAlignment getVAlignLatin() {
switch (VALIGN_MASK.getValue(textAlignmentMode)) {
default:
case VALIGN_TOP:
return HwmfTextVerticalAlignment.TOP;
case VALIGN_BASELINE:
return HwmfTextVerticalAlignment.BASELINE;
case VALIGN_BOTTOM:
return HwmfTextVerticalAlignment.BOTTOM;
}
}
sb.append("', valign: '");
if (TA_BASELINE.isSet(textAlignmentMode)) {
sb.append("baseline");
} else if (TA_BOTTOM.isSet(textAlignmentMode)) {
sb.append("bottom");
} else {
sb.append("top");
private HwmfTextAlignment getAlignAsian() {
switch (getVAlignLatin()) {
default:
case TOP:
return HwmfTextAlignment.RIGHT;
case BASELINE:
return HwmfTextAlignment.CENTER;
case BOTTOM:
return HwmfTextAlignment.LEFT;
}
}
sb.append("', valign-asian: '");
if (VTA_BASELINE.isSet(textAlignmentMode)) {
sb.append("baseline");
} else if (VTA_BOTTOM.isSet(textAlignmentMode)) {
sb.append("bottom");
} else {
sb.append("top");
private HwmfTextVerticalAlignment getVAlignAsian() {
switch (getAlignLatin()) {
default:
case LEFT:
return HwmfTextVerticalAlignment.TOP;
case CENTER:
return HwmfTextVerticalAlignment.BASELINE;
case RIGHT:
return HwmfTextVerticalAlignment.BOTTOM;
}
sb.append("' }");
return sb.toString();
}
}