git-svn-id: https://svn.apache.org/repos/asf/poi/branches/hemf@1842056 13f79535-47bb-0310-9956-ffa450edef68pull/131/head
@@ -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); | |||
} | |||
} |
@@ -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}", ".")+"\""; | |||
} | |||
} | |||
@@ -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+"', "); | |||
} | |||
} | |||
/** |
@@ -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<Integer> 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 |
@@ -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<Integer> dx) { | |||
drawString(text, bounds, dx, false); | |||
} | |||
public void drawString(byte[] text, Rectangle2D bounds, List<Integer> 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<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[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()); |
@@ -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); | |||
} | |||
} |
@@ -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+"' }"; | |||
} | |||
} | |||
} |
@@ -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 ":"")+ | |||
"}"; | |||
} | |||
} |
@@ -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<Integer> 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<dxLen; i++) { | |||
dx[i] = leis.readShort(); | |||
dx.add((int)leis.readShort()); | |||
size += LittleEndianConsts.SHORT_SIZE; | |||
} | |||
@@ -392,7 +393,7 @@ public class HwmfText { | |||
@Override | |||
public void draw(HwmfGraphics ctx) { | |||
Rectangle2D bounds = new Rectangle2D.Double(reference.getX(), reference.getY(), 0, 0); | |||
ctx.drawString(getTextBytes(), bounds, dx); | |||
ctx.drawString(rawTextBytes, bounds, dx); | |||
} | |||
@@ -432,18 +433,6 @@ public class HwmfText { | |||
public Rectangle2D getBounds() { | |||
return bounds; | |||
} | |||
/** | |||
* | |||
* @return a copy of a trimmed byte array of rawTextBytes bytes. | |||
* This includes only the bytes from 0..stringLength. | |||
* This does not include the extra optional padding on the byte array. | |||
*/ | |||
private byte[] getTextBytes() { | |||
byte[] ret = IOUtils.safelyAllocate(stringLength, MAX_RECORD_LENGTH); | |||
System.arraycopy(rawTextBytes, 0, ret, 0, stringLength); | |||
return ret; | |||
} | |||
} | |||
public enum HwmfTextAlignment { |
@@ -88,7 +88,9 @@ public class HemfPictureTest { | |||
FileWriter fw = new FileWriter("record-list.txt"); | |||
int i=0; | |||
for (HemfRecord r : emf.getRecords()) { | |||
fw.write(i + " "+r.getEmfRecordType()+" "+r.toString()+"\n"); | |||
if (r.getEmfRecordType() != HemfRecordType.comment) { | |||
fw.write(i + " " + r.getEmfRecordType() + " " + r.toString() + "\n"); | |||
} | |||
i++; | |||
} | |||
fw.close(); | |||
@@ -170,7 +172,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; | |||
@@ -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; |