From e8d75b424882e644510d6e7615837ce3ba200b65 Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Thu, 1 Nov 2018 16:27:59 +0000 Subject: [PATCH] #60656 - Support export file that contains emf and render it correctly git-svn-id: https://svn.apache.org/repos/asf/poi/branches/hemf@1845496 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/poi/hemf/record/emf/HemfMisc.java | 22 +- .../poi/hemf/record/emf/HemfRecordType.java | 4 +- .../apache/poi/hemf/record/emf/HemfText.java | 18 +- .../apache/poi/hwmf/draw/HwmfGraphics.java | 10 +- .../apache/poi/hwmf/record/HwmfColorRef.java | 4 + .../org/apache/poi/hwmf/record/HwmfDraw.java | 12 + .../org/apache/poi/hwmf/record/HwmfText.java | 245 ++++++++---------- 7 files changed, 156 insertions(+), 159 deletions(-) diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java index e9ca805fd3..00f77dd689 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java @@ -24,6 +24,7 @@ import static org.apache.poi.hemf.record.emf.HemfRecordIterator.HEADER_SIZE; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; +import java.awt.image.BufferedImage; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -40,6 +41,7 @@ import org.apache.poi.hwmf.record.HwmfHatchStyle; import org.apache.poi.hwmf.record.HwmfMapMode; import org.apache.poi.hwmf.record.HwmfMisc; import org.apache.poi.hwmf.record.HwmfMisc.WmfSetBkMode; +import org.apache.poi.hwmf.record.HwmfObjectTableEntry; import org.apache.poi.hwmf.record.HwmfPalette.PaletteEntry; import org.apache.poi.hwmf.record.HwmfPenStyle; import org.apache.poi.hwmf.record.HwmfPenStyle.HwmfLineDash; @@ -687,13 +689,20 @@ public class HemfMisc { } } - public static class EmfCreateMonoBrush16 extends EmfCreatePen { + public static class EmfCreateMonoBrush implements HemfRecord, HwmfObjectTableEntry { + /** + * A 32-bit unsigned integer that specifies the index of the logical palette object + * in the EMF Object Table. This index MUST be saved so that this object can be + * reused or modified. + */ + protected int penIndex; + protected HwmfFill.ColorUsage colorUsage; protected final HwmfBitmapDib bitmap = new HwmfBitmapDib(); @Override public HemfRecordType getEmfRecordType() { - return HemfRecordType.createMonoBrush16; + return HemfRecordType.createMonoBrush; } @Override @@ -729,12 +738,17 @@ public class HemfMisc { return size; } + @Override + public void draw(HemfGraphics ctx) { + ctx.addObjectTableEntry(this, penIndex); + } + @Override public void applyObject(HwmfGraphics ctx) { - super.applyObject(ctx); HwmfDrawProperties props = ctx.getProperties(); props.setBrushStyle(HwmfBrushStyle.BS_PATTERN); - props.setBrushBitmap(bitmap.getImage()); + BufferedImage bmp = bitmap.getImage(); + props.setBrushBitmap(bmp); } } } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java index 25e7149251..9d25a9c145 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java @@ -105,7 +105,7 @@ public enum HemfRecordType { plgblt(0x0000004F, UnimplementedHemfRecord::new), setDiBitsToDevice(0x00000050, HemfFill.EmfSetDiBitsToDevice::new), stretchDiBits(0x00000051, HemfFill.EmfStretchDiBits::new), - extCreateFontIndirectW(0x00000052, HemfText.ExtCreateFontIndirectW::new), + extCreateFontIndirectW(0x00000052, HemfText.EmfExtCreateFontIndirectW::new), extTextOutA(0x00000053, HemfText.EmfExtTextOutA::new), extTextOutW(0x00000054, HemfText.EmfExtTextOutW::new), polyBezier16(0x00000055, HemfDraw.EmfPolyBezier16::new), @@ -116,7 +116,7 @@ public enum HemfRecordType { polyPolyline16(0x0000005A, HemfDraw.EmfPolyPolyline16::new), polyPolygon16(0x0000005B, HemfDraw.EmfPolyPolygon16::new), polyDraw16(0x0000005C, HemfDraw.EmfPolyDraw16::new), - createMonoBrush16(0x0000005D, HemfMisc.EmfCreateMonoBrush16::new), + createMonoBrush(0x0000005D, HemfMisc.EmfCreateMonoBrush::new), createDibPatternBrushPt(0x0000005E, HemfMisc.EmfCreateDibPatternBrushPt::new), extCreatePen(0x0000005F, HemfMisc.EmfExtCreatePen::new), polytextouta(0x00000060, HemfText.PolyTextOutA::new), diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java index 4a8508961b..fb0d2a79aa 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java @@ -113,7 +113,10 @@ public class HemfText { size += LittleEndianConsts.INT_SIZE; // handle dx before string and other way round - for (char op : ((offDx < offString) ? "ds" : "sd").toCharArray()) { + final String order = (offDx < offString) ? "ds" : "sd"; + // the next byte index after the string ends + int strEnd = (int)((offDx <= HEADER_SIZE) ? recordSize : offDx-HEADER_SIZE); + for (char op : order.toCharArray()) { switch (op) { case 'd': { dx.clear(); @@ -138,6 +141,9 @@ public class HemfText { dx.add((int) leis.readUInt()); size += LittleEndianConsts.INT_SIZE; } + } else { + // if there are no dx entries, reset the string end + strEnd = (int)recordSize; } if (dx.size() < stringLength) { // invalid dx array @@ -152,7 +158,9 @@ public class HemfText { leis.skipFully(undefinedSpace1); size += undefinedSpace1; - final int maxSize = (int)Math.min(recordSize-size, stringLength * (isUnicode() ? 2 : 1)); + // read all available bytes and not just "stringLength * 1(ansi)/2(unicode)" + // in case we need to deal with surrogate pairs + final int maxSize = (int)(Math.min(recordSize, strEnd)-size); rawTextBytes = IOUtils.safelyAllocate(maxSize, MAX_RECORD_LENGTH); leis.readFully(rawTextBytes); size += maxSize; @@ -191,7 +199,7 @@ public class HemfText { @Override public void draw(HwmfGraphics ctx) { - ctx.drawString(rawTextBytes, reference, bounds, options, dx, isUnicode()); + ctx.drawString(rawTextBytes, stringLength, reference, bounds, options, dx, isUnicode()); } @Override @@ -258,11 +266,11 @@ public class HemfText { - public static class ExtCreateFontIndirectW extends HwmfText.WmfCreateFontIndirect + public static class EmfExtCreateFontIndirectW extends HwmfText.WmfCreateFontIndirect implements HemfRecord { int fontIdx; - public ExtCreateFontIndirectW() { + public EmfExtCreateFontIndirectW() { super(new HemfFont()); } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java index 9ba07dd006..4ecca1e81a 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java @@ -361,11 +361,11 @@ public class HwmfGraphics { } } - public void drawString(byte[] text, Point2D reference) { - drawString(text, reference, null, null, null, false); + public void drawString(byte[] text, int length, Point2D reference) { + drawString(text, length, reference, null, null, null, false); } - public void drawString(byte[] text, Point2D reference, Rectangle2D clip, WmfExtTextOutOptions opts, List dx, boolean isUnicode) { + public void drawString(byte[] text, int length, Point2D reference, Rectangle2D clip, WmfExtTextOutOptions opts, List dx, boolean isUnicode) { final HwmfDrawProperties prop = getProperties(); HwmfFont font = prop.getFont(); @@ -387,7 +387,7 @@ public class HwmfGraphics { } } - String textString = new String(text, charset).trim(); + String textString = new String(text, charset).substring(0,length).trim(); if (textString.isEmpty()) { return; } @@ -462,7 +462,7 @@ public class HwmfGraphics { case BASELINE: break; case BOTTOM: - tx.translate(0, pixelBounds.getHeight()); + tx.translate(0, -(pixelBounds.getHeight()-layout.getDescent())); break; } tx.rotate(angle); diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfColorRef.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfColorRef.java index 14ebc874d5..a757617752 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfColorRef.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfColorRef.java @@ -54,6 +54,10 @@ public class HwmfColorRef implements Cloneable { return colorRef; } + public void setColor(Color color) { + colorRef = color; + } + /** * Creates a new object of the same class and with the * same contents as this object. diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java index f3507fa103..f71becda8a 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java @@ -594,6 +594,18 @@ public class HwmfDraw { return new Arc2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), startAngle, arcAngle, arcClosure); } + + @Override + public String toString() { + Arc2D arc = getShape(); + return + "{ startPoint: "+pointToString(startPoint)+ + ", endPoint: "+pointToString(endPoint)+ + ", startAngle: "+arc.getAngleStart()+ + ", extentAngle: "+arc.getAngleExtent()+ + ", bounds: "+boundsToString(bounds)+ + " }"; + } } /** diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java index f7708ab25c..020e6148d4 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java @@ -24,11 +24,7 @@ import static org.apache.poi.hwmf.record.HwmfDraw.readRectS; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; -import java.io.ByteArrayInputStream; -import java.io.EOFException; import java.io.IOException; -import java.io.InputStreamReader; -import java.io.Reader; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; @@ -45,7 +41,6 @@ import org.apache.poi.util.LittleEndianInputStream; import org.apache.poi.util.LocaleUtil; import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; -import org.apache.poi.util.RecordFormatException; public class HwmfText { private static final POILogger logger = POILogFactory.getLogger(HwmfText.class); @@ -191,7 +186,7 @@ public class HwmfText { @Override public void draw(HwmfGraphics ctx) { - ctx.drawString(getTextBytes(), reference); + ctx.drawString(getTextBytes(), stringLength, reference); } public String getText(Charset charset) { @@ -396,11 +391,11 @@ public class HwmfText { @Override public void draw(HwmfGraphics ctx) { - ctx.drawString(rawTextBytes, reference, bounds, options, dx, false); + ctx.drawString(rawTextBytes, stringLength, reference, bounds, options, dx, false); } public String getText(Charset charset) throws IOException { - return new String(rawTextBytes, charset); + return new String(rawTextBytes, charset).substring(0, stringLength); } public Point2D getReference() { @@ -448,57 +443,17 @@ public class HwmfText { */ public static class WmfSetTextAlign implements HwmfRecord { - // *********************************************************************************** - // TextAlignmentMode Flags: - // *********************************************************************************** - - /** - * The drawing position in the playback device context MUST NOT be updated after each - * text output call. The reference point MUST be passed to the text output function. - */ - @SuppressWarnings("unused") - private static final BitField TA_NOUPDATECP = BitFieldFactory.getInstance(0x0000); - - /** - * The reference point MUST be on the left edge of the bounding rectangle. - */ - @SuppressWarnings("unused") - private static final BitField TA_LEFT = BitFieldFactory.getInstance(0x0000); - - /** - * The reference point MUST be on the top edge of the bounding rectangle. - */ - @SuppressWarnings("unused") - private static final BitField TA_TOP = BitFieldFactory.getInstance(0x0000); - /** * The drawing position in the playback device context MUST be updated after each text - * output call. It MUST be used as the reference point. + * output call. It MUST be used as the reference point.

+ * + * If the flag is not set, the option TA_NOUPDATECP is active, i.e. the drawing position + * in the playback device context MUST NOT be updated after each text output call. + * The reference point MUST be passed to the text output function. */ @SuppressWarnings("unused") private static final BitField TA_UPDATECP = BitFieldFactory.getInstance(0x0001); - - /** - * The reference point MUST be on the right edge of the bounding rectangle. - */ - private static final BitField TA_RIGHT = BitFieldFactory.getInstance(0x0002); - - /** - * The reference point MUST be aligned horizontally with the center of the bounding - * rectangle. - */ - private static final BitField TA_CENTER = BitFieldFactory.getInstance(0x0006); - - /** - * The reference point MUST be on the bottom edge of the bounding rectangle. - */ - private static final BitField TA_BOTTOM = BitFieldFactory.getInstance(0x0008); - - /** - * The reference point MUST be on the baseline of the text. - */ - private static final BitField TA_BASELINE = BitFieldFactory.getInstance(0x0018); - + /** * The text MUST be laid out in right-to-left reading order, instead of the default * left-to-right order. This SHOULD be applied only when the font that is defined in the @@ -506,43 +461,64 @@ public class HwmfText { */ @SuppressWarnings("unused") private static final BitField TA_RTLREADING = BitFieldFactory.getInstance(0x0100); - - // *********************************************************************************** - // VerticalTextAlignmentMode Flags (e.g. for Kanji fonts) - // *********************************************************************************** - + + + private static final BitField ALIGN_MASK = BitFieldFactory.getInstance(0x0006); + /** - * The reference point MUST be on the top edge of the bounding rectangle. + * Flag TA_LEFT (0x0000): + * The reference point MUST be on the left edge of the bounding rectangle, + * if all bits of the align mask (latin mode) are unset. + * + * Flag VTA_TOP (0x0000): + * The reference point MUST be on the top edge of the bounding rectangle, + * if all bits of the valign mask are unset. */ - @SuppressWarnings("unused") - private static final BitField VTA_TOP = BitFieldFactory.getInstance(0x0000); - + private static final int ALIGN_LEFT = 0; + /** + * Flag TA_RIGHT (0x0002): * The reference point MUST be on the right edge of the bounding rectangle. - */ - @SuppressWarnings("unused") - private static final BitField VTA_RIGHT = BitFieldFactory.getInstance(0x0000); - - /** + * + * Flag VTA_BOTTOM (0x0002): * The reference point MUST be on the bottom edge of the bounding rectangle. */ - private static final BitField VTA_BOTTOM = BitFieldFactory.getInstance(0x0002); - + private static final int ALIGN_RIGHT = 1; + /** - * The reference point MUST be aligned vertically with the center of the bounding + * Flag TA_CENTER (0x0006) / VTA_CENTER (0x0006): + * The reference point MUST be aligned horizontally with the center of the bounding * rectangle. */ - private static final BitField VTA_CENTER = BitFieldFactory.getInstance(0x0006); - + private static final int ALIGN_CENTER = 3; + + private static final BitField VALIGN_MASK = BitFieldFactory.getInstance(0x0018); + + /** + * Flag TA_TOP (0x0000): + * The reference point MUST be on the top edge of the bounding rectangle, + * if all bits of the valign mask are unset. + * + * Flag VTA_RIGHT (0x0000): + * The reference point MUST be on the right edge of the bounding rectangle, + * if all bits of the align mask (asian mode) are unset. + */ + private static final int VALIGN_TOP = 0; + /** + * Flag TA_BOTTOM (0x0008): + * The reference point MUST be on the bottom edge of the bounding rectangle. + * + * Flag VTA_LEFT (0x0008): * The reference point MUST be on the left edge of the bounding rectangle. */ - private static final BitField VTA_LEFT = BitFieldFactory.getInstance(0x0008); + private static final int VALIGN_BOTTOM = 1; /** + * Flag TA_BASELINE (0x0018) / VTA_BASELINE (0x0018): * The reference point MUST be on the baseline of the text. */ - private static final BitField VTA_BASELINE = BitFieldFactory.getInstance(0x0018); + private static final int VALIGN_BASELINE = 3; /** * A 16-bit unsigned integer that defines text alignment. @@ -566,85 +542,68 @@ public class HwmfText { @Override public void draw(HwmfGraphics ctx) { HwmfDrawProperties props = ctx.getProperties(); - if (TA_CENTER.isSet(textAlignmentMode)) { - props.setTextAlignLatin(HwmfTextAlignment.CENTER); - } else if (TA_RIGHT.isSet(textAlignmentMode)) { - props.setTextAlignLatin(HwmfTextAlignment.RIGHT); - } else { - props.setTextAlignLatin(HwmfTextAlignment.LEFT); - } - - if (VTA_CENTER.isSet(textAlignmentMode)) { - props.setTextAlignAsian(HwmfTextAlignment.CENTER); - } else if (VTA_LEFT.isSet(textAlignmentMode)) { - props.setTextAlignAsian(HwmfTextAlignment.LEFT); - } else { - props.setTextAlignAsian(HwmfTextAlignment.RIGHT); - } - - if (TA_BASELINE.isSet(textAlignmentMode)) { - props.setTextVAlignLatin(HwmfTextVerticalAlignment.BASELINE); - } else if (TA_BOTTOM.isSet(textAlignmentMode)) { - props.setTextVAlignLatin(HwmfTextVerticalAlignment.BOTTOM); - } else { - props.setTextVAlignLatin(HwmfTextVerticalAlignment.TOP); - } - - if (VTA_BASELINE.isSet(textAlignmentMode)) { - props.setTextVAlignAsian(HwmfTextVerticalAlignment.BASELINE); - } else if (VTA_BOTTOM.isSet(textAlignmentMode)) { - props.setTextVAlignAsian(HwmfTextVerticalAlignment.BOTTOM); - } else { - props.setTextVAlignAsian(HwmfTextVerticalAlignment.TOP); - } + props.setTextAlignLatin(getAlignLatin()); + props.setTextVAlignLatin(getVAlignLatin()); + props.setTextAlignAsian(getAlignAsian()); + props.setTextVAlignAsian(getVAlignAsian()); } @Override public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("{ align: '"); - - 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: '"); + return + "{ align: '"+ getAlignLatin() + "'" + + ", valign: '"+ getVAlignLatin() + "'" + + ", alignAsian: '"+ getAlignAsian() + "'" + + ", valignAsian: '"+ getVAlignAsian() + "'" + + "}"; + } - if (VTA_CENTER.isSet(textAlignmentMode)) { - sb.append("center"); - } else if (VTA_LEFT.isSet(textAlignmentMode)) { - sb.append("left"); - } else { - sb.append("right"); + 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("', valign: '"); - - if (TA_BASELINE.isSet(textAlignmentMode)) { - sb.append("baseline"); - } else if (TA_BOTTOM.isSet(textAlignmentMode)) { - sb.append("bottom"); - } else { - sb.append("top"); + 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-asian: '"); - - if (VTA_BASELINE.isSet(textAlignmentMode)) { - sb.append("baseline"); - } else if (VTA_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("' }"); - - return sb.toString(); + private HwmfTextVerticalAlignment getVAlignAsian() { + switch (getAlignLatin()) { + default: + case LEFT: + return HwmfTextVerticalAlignment.TOP; + case CENTER: + return HwmfTextVerticalAlignment.BASELINE; + case RIGHT: + return HwmfTextVerticalAlignment.BOTTOM; + } } } -- 2.39.5