diff --git a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java index c4783950a6..40effa0dde 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java +++ b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java @@ -104,7 +104,7 @@ public class HemfGraphics extends HwmfGraphics { prop.setLocation(path.getCurrentPoint()); if (!useBracket) { // TODO: when to use draw vs. fill? - graphicsCtx.draw(path); + super.draw(path); } } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java index fe098f2794..1752e99e1a 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java @@ -217,7 +217,7 @@ public class HemfComment { @Override public String toString() { - return "\""+new String(privateData, LocaleUtil.CHARSET_1252)+"\""; + return "\""+new String(privateData, LocaleUtil.CHARSET_1252).replaceAll("\\p{Cntrl}", ".")+"\""; } } 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 291c83d630..d128084c6f 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 @@ -272,6 +272,15 @@ public class HemfMisc { ctx.addObjectTableEntry(this, brushIdx); } + + @Override + public String toString() { + return + "{ brushIndex: "+brushIdx+ + ", brushStyle: '"+brushStyle+"'"+ + ", colorRef: "+colorRef+ + ", brushHatch: '"+brushHatch+"' }"; + } } /** @@ -329,6 +338,11 @@ public class HemfMisc { public void draw(HemfGraphics ctx) { ctx.addObjectTableEntry(this, penIndex); } + + @Override + public String toString() { + return super.toString().replaceFirst("\\{", "{ penIndex: "+penIndex+", "); + } } public static class EmfExtCreatePen extends EmfCreatePen { @@ -421,6 +435,13 @@ public class HemfMisc { return size; } + + @Override + public String toString() { + // TODO: add style entries + bmp + return super.toString().replaceFirst("\\{", + "{ brushStyle: '"+brushStyle+"', hatchStyle: '"+hatchStyle+"', "); + } } /** 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 2305aeced8..d78b750aa3 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 @@ -18,18 +18,19 @@ package org.apache.poi.hemf.record.emf; import static java.nio.charset.StandardCharsets.UTF_16LE; -import static org.apache.poi.hemf.record.emf.HemfDraw.readRectL; import static org.apache.poi.hemf.record.emf.HemfDraw.readDimensionFloat; 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.Dimension2D; import java.awt.geom.Rectangle2D; import java.io.IOException; import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.List; +import org.apache.commons.codec.Charsets; import org.apache.poi.hemf.draw.HemfGraphics; +import org.apache.poi.hwmf.draw.HwmfGraphics; import org.apache.poi.hwmf.record.HwmfText; import org.apache.poi.hwmf.record.HwmfText.WmfSetTextAlign; import org.apache.poi.util.Dimension2DDouble; @@ -37,6 +38,7 @@ import org.apache.poi.util.IOUtils; import org.apache.poi.util.Internal; import org.apache.poi.util.LittleEndianConsts; import org.apache.poi.util.LittleEndianInputStream; +import org.apache.poi.util.LocaleUtil; import org.apache.poi.util.RecordFormatException; /** @@ -53,9 +55,7 @@ public class HemfText { GM_COMPATIBLE, GM_ADVANCED } - public static class EmfExtTextOutA implements HemfRecord { - - protected final Rectangle2D bounds = new Rectangle2D.Double(); + public static class EmfExtTextOutA extends HwmfText.WmfExtTextOut implements HemfRecord { protected EmfGraphicsMode graphicsMode; @@ -65,14 +65,8 @@ public class HemfText { */ protected final Dimension2D scale = new Dimension2DDouble(); - protected final EmrTextObject textObject; - public EmfExtTextOutA() { - this(false); - } - - protected EmfExtTextOutA(boolean isUnicode) { - textObject = new EmrTextObject(isUnicode); + super(new EmfExtTextOutOptions()); } @Override @@ -95,12 +89,70 @@ public class HemfText { size += readDimensionFloat(leis, scale); - // guarantee to read the rest of the EMRTextObjectRecord - size += textObject.init(leis, recordSize, (int)size); + // A WMF PointL object that specifies the coordinates of the reference point used to position the string. + // The reference point is defined by the last EMR_SETTEXTALIGN record. + // If no such record has been set, the default alignment is TA_LEFT,TA_TOP. + size += readPointL(leis, reference); + // A 32-bit unsigned integer that specifies the number of characters in the string. + stringLength = (int)leis.readUInt(); + // A 32-bit unsigned integer that specifies the offset to the output string, in bytes, + // from the start of the record in which this object is contained. + // This value MUST be 8- or 16-bit aligned, according to the character format. + int offString = (int)leis.readUInt(); + size += 2*LittleEndianConsts.INT_SIZE; + + size += options.init(leis); + // An optional WMF RectL object that defines a clipping and/or opaquing rectangle in logical units. + // This rectangle is applied to the text output performed by the containing record. + if (options.isClipped() || options.isOpaque()) { + size += readRectL(leis, bounds); + } + + // A 32-bit unsigned integer that specifies the offset to an intercharacter spacing array, in bytes, + // from the start of the record in which this object is contained. This value MUST be 32-bit aligned. + 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; + } + } return size; } + protected boolean isUnicode() { + return false; + } + /** * * To be implemented! We need to get the current character set @@ -114,15 +166,7 @@ public class HemfText { * @throws IOException */ public String getText(Charset charset) throws IOException { - return textObject.getText(charset); - } - - /** - * - * @return the x offset for the EmrTextObject - */ - public EmrTextObject getTextObject() { - return textObject; + return super.getText(charset); } public EmfGraphicsMode getGraphicsMode() { @@ -133,8 +177,20 @@ public class HemfText { return scale; } + @Override + public void draw(HwmfGraphics ctx) { + Rectangle2D bounds = new Rectangle2D.Double(reference.getX(), reference.getY(), 0, 0); + ctx.drawString(rawTextBytes, bounds, dx, isUnicode()); + } + @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()+ @@ -142,17 +198,13 @@ public class HemfText { ", h: "+bounds.getHeight()+ "}, graphicsMode: '"+graphicsMode+"'"+ ", scale: { w: "+scale.getWidth()+", h: "+scale.getHeight()+" }"+ - ", textObject: "+textObject+ + ", text: '"+text.replaceAll("\\p{Cntrl}",".")+"'"+ "}"; } } public static class EmfExtTextOutW extends EmfExtTextOutA { - public EmfExtTextOutW() { - super(true); - } - @Override public HemfRecordType getEmfRecordType() { return HemfRecordType.exttextoutw; @@ -161,6 +213,10 @@ public class HemfText { public String getText() throws IOException { return getText(UTF_16LE); } + + protected boolean isUnicode() { + return true; + } } /** @@ -200,77 +256,6 @@ public class HemfText { } } - public static class EmrTextObject extends HwmfText.WmfExtTextOut { - protected final boolean isUnicode; - protected final List outputDx = new ArrayList<>(); - - public EmrTextObject(boolean isUnicode) { - super(new EmfExtTextOutOptions()); - this.isUnicode = isUnicode; - } - - @Override - public int init(LittleEndianInputStream leis, final long recordSize, final int offset) throws IOException { - // A WMF PointL object that specifies the coordinates of the reference point used to position the string. - // The reference point is defined by the last EMR_SETTEXTALIGN record. - // If no such record has been set, the default alignment is TA_LEFT,TA_TOP. - long size = readPointL(leis, reference); - // A 32-bit unsigned integer that specifies the number of characters in the string. - stringLength = (int)leis.readUInt(); - // A 32-bit unsigned integer that specifies the offset to the output string, in bytes, - // from the start of the record in which this object is contained. - // This value MUST be 8- or 16-bit aligned, according to the character format. - int offString = (int)leis.readUInt(); - size += 2*LittleEndianConsts.INT_SIZE; - - size += options.init(leis); - // An optional WMF RectL object that defines a clipping and/or opaquing rectangle in logical units. - // This rectangle is applied to the text output performed by the containing record. - if (options.isClipped() || options.isOpaque()) { - size += readRectL(leis, bounds); - } - - // A 32-bit unsigned integer that specifies the offset to an intercharacter spacing array, in bytes, - // from the start of the record in which this object is contained. This value MUST be 32-bit aligned. - int offDx = (int)leis.readUInt(); - size += LittleEndianConsts.INT_SIZE; - - int undefinedSpace1 = (int)(offString-offset-size-2*LittleEndianConsts.INT_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; - - outputDx.clear(); - if (offDx > 0) { - int undefinedSpace2 = (int) (offDx - offset - size - 2 * LittleEndianConsts.INT_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) { - outputDx.add((int) leis.readUInt()); - size += LittleEndianConsts.INT_SIZE; - } - } - - return (int)size; - } - } public static class ExtCreateFontIndirectW extends HwmfText.WmfCreateFontIndirect 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 b29c1187e9..c5873e02a6 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java @@ -37,6 +37,7 @@ import java.util.List; import java.util.ListIterator; import java.util.NoSuchElementException; +import org.apache.commons.codec.Charsets; import org.apache.poi.common.usermodel.fonts.FontInfo; import org.apache.poi.hwmf.record.HwmfBrushStyle; import org.apache.poi.hwmf.record.HwmfFont; @@ -325,7 +326,12 @@ public class HwmfGraphics { drawString(text, bounds, null); } - public void drawString(byte[] text, Rectangle2D bounds, int dx[]) { + public void drawString(byte[] text, Rectangle2D bounds, List dx) { + drawString(text, bounds, dx, false); + } + + public void drawString(byte[] text, Rectangle2D bounds, List dx, boolean isUnicode) { + HwmfFont font = getProperties().getFont(); if (font == null || text == null || text.length == 0) { return; @@ -335,14 +341,21 @@ public class HwmfGraphics { // TODO: another approx. ... double fontW = fontH/1.8; - Charset charset = (font.getCharset().getCharset() == null)? - DEFAULT_CHARSET : font.getCharset().getCharset(); + Charset charset; + if (isUnicode) { + charset = Charsets.UTF_16LE; + } else { + charset = font.getCharset().getCharset(); + if (charset == null) { + charset = DEFAULT_CHARSET; + } + } + String textString = new String(text, charset); AttributedString as = new AttributedString(textString); - if (dx == null || dx.length == 0) { + if (dx == null || dx.isEmpty()) { addAttributes(as, font); } else { - int[] dxNormed = dx; //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 @@ -357,41 +370,41 @@ public class HwmfGraphics { // needs to be remapped as: //dxNormed[0] = 13 textString.get(0) = U+30D7 //dxNormed[1] = 14 textString.get(1) = U+30ED - if (textString.length() != text.length) { - int codePoints = textString.codePointCount(0, textString.length()); - dxNormed = new int[codePoints]; + + final List 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[offset] = dx[dxPosition]; - int[] chars = new int[1]; - int cp = textString.codePointAt(offset); - chars[0] = cp; + 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(cp); + offset += Character.charCount(chars[0]); } } - for (int i = 0; i < dxNormed.length; i++) { + for (int i = 0; i < dxNormed.size(); 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 - if (i < dxNormed.length - 1) { - as.addAttribute(TextAttribute.TRACKING, (dxNormed[i] - fontW) / fontH, i + 1, i + 2); - } + as.addAttribute(TextAttribute.TRACKING, (dxNormed.get(i) - fontW) / fontH, i + 1, i + 2); } } double angle = Math.toRadians(-font.getEscapement()/10.); - - + final AffineTransform at = graphicsCtx.getTransform(); try { - graphicsCtx.translate(bounds.getX(), bounds.getY()+fontH); + graphicsCtx.translate(bounds.getX(), bounds.getY()); graphicsCtx.rotate(angle); + graphicsCtx.translate(0, fontH); if (getProperties().getBkMode() == HwmfBkMode.OPAQUE) { // TODO: validate bounds graphicsCtx.setBackground(getProperties().getBackgroundColor().getColor()); 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 12204d61ec..14ebc874d5 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfColorRef.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfColorRef.java @@ -19,6 +19,7 @@ package org.apache.poi.hwmf.record; import java.awt.Color; import java.io.IOException; +import java.util.Locale; import org.apache.poi.util.LittleEndianConsts; import org.apache.poi.util.LittleEndianInputStream; @@ -72,6 +73,6 @@ public class HwmfColorRef implements Cloneable { @Override public String toString() { - return String.format("%#8X", colorRef.getRGB()); + return String.format(Locale.ROOT, "%#08X", colorRef.getRGB()&0xFFFFFF); } } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java index b774549073..e66678389d 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java @@ -554,6 +554,14 @@ public class HwmfMisc { p.setPenColor(colorRef); p.setPenWidth(dimension.getWidth()); } + + @Override + public String toString() { + return + "{ penStyle: "+penStyle+ + ", dimension: { width: "+dimension.getWidth()+", height: "+dimension.getHeight()+" }"+ + ", colorRef: "+colorRef+"}"; + } } /** @@ -634,5 +642,13 @@ public class HwmfMisc { p.setBrushColor(colorRef); p.setBrushHatch(brushHatch); } + + @Override + public String toString() { + return + "{ brushStyle: '"+brushStyle+"'"+ + ", colorRef: "+colorRef+ + ", brushHatch: '"+brushHatch+"' }"; + } } } \ No newline at end of file diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPenStyle.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPenStyle.java index 0f79a03d9a..5366b3fb24 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPenStyle.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPenStyle.java @@ -195,4 +195,15 @@ public class HwmfPenStyle implements Cloneable { throw new InternalError(); } } + + @Override + public String toString() { + return + "{ lineCap: '"+getLineCap()+"'"+ + ", lineDash: '"+getLineDash()+"'"+ + ", lineJoin: '"+getLineJoin()+"'"+ + (isAlternateDash()?", alternateDash: true ":"")+ + (isGeometric()?", geometric: true ":"")+ + "}"; + } } 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 e677f9a92f..cb2cbd7560 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java @@ -28,6 +28,8 @@ 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; import org.apache.poi.hwmf.draw.HwmfDrawProperties; import org.apache.poi.hwmf.draw.HwmfGraphics; @@ -334,7 +336,7 @@ public class HwmfText { * character cell i and character cell i + 1. If this field is present, there MUST be the same * number of values as there are characters in the string. */ - private int dx[]; + protected final List dx = new ArrayList<>(); public WmfExtTextOut() { this(new WmfExtTextOutOptions()); @@ -380,9 +382,8 @@ public class HwmfText { logger.log(POILogger.WARN, "META_EXTTEXTOUT tracking info doesn't cover all characters"); } - dx = new int[stringLength]; for (int i=0; i -1 && lastY != reference.getY()) { sb.append("\n"); lastX = -1; @@ -204,7 +206,7 @@ public class HemfPictureTest { for (HemfRecord record : pic) { if (record.getEmfRecordType().equals(HemfRecordType.exttextoutw)) { HemfText.EmfExtTextOutW extTextOutW = (HemfText.EmfExtTextOutW) record; - Point2D reference = extTextOutW.getTextObject().getReference(); + Point2D reference = extTextOutW.getReference(); if (lastY > -1 && lastY != reference.getY()) { sb.append("\n"); lastX = -1;