git-svn-id: https://svn.apache.org/repos/asf/poi/branches/hemf@1841897 13f79535-47bb-0310-9956-ffa450edef68pull/131/head
@@ -33,7 +33,10 @@ import org.apache.poi.util.POILogger; | |||
public class DrawPictureShape extends DrawSimpleShape { | |||
private static final POILogger LOG = POILogFactory.getLogger(DrawPictureShape.class); | |||
private static final ServiceLoader<ImageRenderer> rendererLoader = ServiceLoader.load(ImageRenderer.class); | |||
private static final String[] KNOWN_RENDERER = { | |||
"org.apache.poi.hwmf.draw.HwmfImageRenderer", | |||
"org.apache.poi.hemf.draw.HemfImageRenderer" | |||
}; | |||
public DrawPictureShape(PictureShape<?,?> shape) { | |||
super(shape); | |||
@@ -62,24 +65,44 @@ public class DrawPictureShape extends DrawSimpleShape { | |||
* @param graphics the graphics context | |||
* @return the image renderer | |||
*/ | |||
@SuppressWarnings("WeakerAccess") | |||
@SuppressWarnings({"WeakerAccess", "unchecked"}) | |||
public static ImageRenderer getImageRenderer(Graphics2D graphics, String contentType) { | |||
ImageRenderer renderer = (ImageRenderer)graphics.getRenderingHint(Drawable.IMAGE_RENDERER); | |||
if (renderer != null) { | |||
final ImageRenderer renderer = (ImageRenderer)graphics.getRenderingHint(Drawable.IMAGE_RENDERER); | |||
if (renderer != null && renderer.canRender(contentType)) { | |||
return renderer; | |||
} | |||
for (ImageRenderer ir : rendererLoader) { | |||
// first try with our default image renderer | |||
final BitmapImageRenderer bir = new BitmapImageRenderer(); | |||
if (bir.canRender(contentType)) { | |||
return bir; | |||
} | |||
// then iterate through the scratchpad renderers | |||
// | |||
// this could be nicely implemented via a j.u.ServiceLoader, but OSGi makes things complicated ... | |||
// https://blog.osgi.org/2013/02/javautilserviceloader-in-osgi.html | |||
// ... therefore falling back to classloading attempts | |||
ClassLoader cl = ImageRenderer.class.getClassLoader(); | |||
for (String kr : KNOWN_RENDERER) { | |||
final ImageRenderer ir; | |||
try { | |||
ir = ((Class<? extends ImageRenderer>)cl.loadClass(kr)).newInstance(); | |||
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { | |||
// scratchpad was not on the path, ignore and continue | |||
LOG.log(POILogger.INFO, "Known image renderer '"+kr+" not found/loaded - include poi-scratchpad jar!", e); | |||
continue; | |||
} | |||
if (ir.canRender(contentType)) { | |||
return ir; | |||
} | |||
} | |||
LOG.log(POILogger.ERROR, "No suiteable image renderer found for content-type '"+ | |||
LOG.log(POILogger.WARN, "No suiteable image renderer found for content-type '"+ | |||
contentType+"' - include poi-scratchpad jar!"); | |||
// falling back to BitmapImageRenderer although this doesn't make much sense ... | |||
return new BitmapImageRenderer(); | |||
// falling back to BitmapImageRenderer, at least it gracefully handles invalid images | |||
return bir; | |||
} | |||
@Override |
@@ -1 +0,0 @@ | |||
org.apache.poi.sl.draw.BitmapImageRenderer |
@@ -1,2 +0,0 @@ | |||
org.apache.poi.hwmf.draw.HwmfImageRenderer | |||
org.apache.poi.hemf.draw.HemfImageRenderer |
@@ -24,16 +24,15 @@ import org.apache.poi.hwmf.draw.HwmfDrawProperties; | |||
public class HemfDrawProperties extends HwmfDrawProperties { | |||
/** Path for path bracket operations */ | |||
protected final Path2D path; | |||
protected Path2D path = null; | |||
public HemfDrawProperties() { | |||
path = new Path2D.Double(); | |||
} | |||
public HemfDrawProperties(HemfDrawProperties other) { | |||
super(other); | |||
path = (Path2D)other.path.clone(); | |||
path = (other.path != null) ? (Path2D)other.path.clone() : null; | |||
} | |||
/** | |||
@@ -42,4 +41,21 @@ public class HemfDrawProperties extends HwmfDrawProperties { | |||
public Path2D getPath() { | |||
return path; | |||
} | |||
/** | |||
* Un-/Sets the bracket path | |||
* @param path the bracket path | |||
*/ | |||
public void setPath(Path2D path) { | |||
this.path = path; | |||
} | |||
/** | |||
* Use path (bracket) or graphics context for drawing operations | |||
* @return {@code true}, if the drawing should go to the path bracket, | |||
* if {@code false} draw directly to the graphics context | |||
*/ | |||
public boolean usePathBracket() { | |||
return path != null; | |||
} | |||
} |
@@ -19,13 +19,18 @@ package org.apache.poi.hemf.draw; | |||
import java.awt.Graphics2D; | |||
import java.awt.geom.AffineTransform; | |||
import java.awt.geom.Path2D; | |||
import java.awt.geom.Point2D; | |||
import java.awt.geom.Rectangle2D; | |||
import java.util.ArrayDeque; | |||
import java.util.Deque; | |||
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.HwmfObjectTableEntry; | |||
import org.apache.poi.util.Internal; | |||
public class HemfGraphics extends HwmfGraphics { | |||
@@ -80,6 +85,59 @@ public class HemfGraphics extends HwmfGraphics { | |||
} | |||
} | |||
@Internal | |||
public void draw(Consumer<Path2D> pathConsumer) { | |||
final HemfDrawProperties prop = getProperties(); | |||
final boolean useBracket = prop.usePathBracket(); | |||
final Path2D path; | |||
if (useBracket) { | |||
path = prop.getPath(); | |||
} else { | |||
path = new Path2D.Double(); | |||
Point2D pnt = prop.getLocation(); | |||
path.moveTo(pnt.getX(),pnt.getY()); | |||
} | |||
pathConsumer.accept(path); | |||
prop.setLocation(path.getCurrentPoint()); | |||
if (!useBracket) { | |||
// TODO: when to use draw vs. fill? | |||
graphicsCtx.draw(path); | |||
} | |||
} | |||
/** | |||
* Adds or sets an record of type {@link HwmfObjectTableEntry} to the object table. | |||
* If the {@code index} is less than 1, the method acts the same as | |||
* {@link HwmfGraphics#addObjectTableEntry(HwmfObjectTableEntry)}, otherwise the | |||
* index is used to access the object table. | |||
* As the table is filled successively, the index must be between 1 and size+1 | |||
* | |||
* @param entry the record to be stored | |||
* @param index the index to be overwritten, regardless if its content was unset before | |||
* | |||
* @see HwmfGraphics#addObjectTableEntry(HwmfObjectTableEntry) | |||
*/ | |||
public void addObjectTableEntry(HwmfObjectTableEntry entry, int index) { | |||
if (index < 1) { | |||
super.addObjectTableEntry(entry); | |||
return; | |||
} | |||
if (index > objectTable.size()) { | |||
throw new IllegalStateException("object table hasn't grown to this index yet"); | |||
} | |||
if (index == objectTable.size()) { | |||
objectTable.add(entry); | |||
} else { | |||
objectTable.set(index, entry); | |||
} | |||
} | |||
/** saves the current affine transform on the stack */ | |||
private void saveTransform() { |
@@ -32,6 +32,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; | |||
/** | |||
@@ -97,6 +98,11 @@ public class HemfComment { | |||
public EmfCommentData getCommentData() { | |||
return data; | |||
} | |||
@Override | |||
public String toString() { | |||
return "{ data: "+data+" }"; | |||
} | |||
} | |||
public static class EmfCommentDataIterator implements Iterator<EmfCommentData> { | |||
@@ -208,6 +214,11 @@ public class HemfComment { | |||
leis.readFully(privateData); | |||
return privateData.length; | |||
} | |||
@Override | |||
public String toString() { | |||
return "\""+new String(privateData, LocaleUtil.CHARSET_1252)+"\""; | |||
} | |||
} | |||
/** The EMR_COMMENT_EMFPLUS record contains embedded EMF+ records. */ |
@@ -53,6 +53,28 @@ public class HemfDraw { | |||
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 */", | |||
"0x80000002 /* GRAY_BRUSH */", | |||
"0x80000003 /* DKGRAY_BRUSH */", | |||
"0x80000004 /* BLACK_BRUSH */", | |||
"0x80000005 /* NULL_BRUSH */", | |||
"0x80000006 /* WHITE_PEN */", | |||
"0x80000007 /* BLACK_PEN */", | |||
"0x80000008 /* NULL_PEN */", | |||
"0x8000000A /* OEM_FIXED_FONT */", | |||
"0x8000000B /* ANSI_FIXED_FONT */", | |||
"0x8000000C /* ANSI_VAR_FONT */", | |||
"0x8000000D /* SYSTEM_FONT */", | |||
"0x8000000E /* DEVICE_DEFAULT_FONT */", | |||
"0x8000000F /* DEFAULT_PALETTE */", | |||
"0x80000010 /* SYSTEM_FIXED_FONT */", | |||
"0x80000011 /* DEFAULT_GUI_FONT */", | |||
"0x80000012 /* DC_BRUSH */", | |||
"0x80000013 /* DC_PEN */" | |||
}; | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.selectObject; | |||
@@ -184,6 +206,14 @@ public class HemfDraw { | |||
} | |||
} | |||
@Override | |||
public String toString() { | |||
return "{ index: "+ | |||
(((objectIndex & 0x80000000) != 0 && (objectIndex & 0x3FFFFFFF) <= 13 ) | |||
? STOCK_IDS[objectIndex & 0x3FFFFFFF] | |||
: objectIndex)+" }"; | |||
} | |||
} | |||
@@ -220,7 +250,7 @@ public class HemfDraw { | |||
final int points = Math.min(count, 16384); | |||
size += LittleEndianConsts.INT_SIZE; | |||
poly.reset(); | |||
poly = new Path2D.Double(Path2D.WIND_EVEN_ODD, points); | |||
/* Cubic Bezier curves are defined using the endpoints and control points | |||
* specified by the points field. The first curve is drawn from the first | |||
@@ -324,6 +354,8 @@ public class HemfDraw { | |||
final int points = Math.min(count, 16384); | |||
size += LittleEndianConsts.INT_SIZE; | |||
poly = new Path2D.Double(Path2D.WIND_EVEN_ODD, points); | |||
Point2D pnt = new Point2D.Double(); | |||
for (int i=0; i<points; i++) { | |||
size += readPoint(leis, pnt); | |||
@@ -541,7 +573,7 @@ public class HemfDraw { | |||
* An array of WMF PointL objects that specifies the points for all polygons in logical units. | |||
* The number of points is specified by the Count field value. | |||
*/ | |||
Path2D poly = new Path2D.Double(); | |||
Path2D poly = new Path2D.Double(Path2D.WIND_EVEN_ODD, (int)nPoints); | |||
for (int i=0; i<nPoints; i++) { | |||
size += readPoint(leis, pnt); | |||
if (i == 0) { | |||
@@ -659,11 +691,8 @@ public class HemfDraw { | |||
} | |||
@Override | |||
public void draw(HemfGraphics ctx) { | |||
HemfDrawProperties prop = ctx.getProperties(); | |||
final Path2D path = prop.getPath(); | |||
path.moveTo(point.getX(), point.getY()); | |||
prop.setLocation(point); | |||
public void draw(final HemfGraphics ctx) { | |||
ctx.draw((path) -> path.moveTo(point.getX(), point.getY())); | |||
} | |||
} | |||
@@ -802,11 +831,8 @@ public class HemfDraw { | |||
} | |||
@Override | |||
public void draw(HemfGraphics ctx) { | |||
final HemfDrawProperties prop = ctx.getProperties(); | |||
final Path2D path = prop.getPath(); | |||
path.lineTo(point.getX(), point.getY()); | |||
prop.setLocation(point); | |||
public void draw(final HemfGraphics ctx) { | |||
ctx.draw((path) -> path.lineTo(point.getX(), point.getY())); | |||
} | |||
} | |||
@@ -829,11 +855,9 @@ public class HemfDraw { | |||
} | |||
@Override | |||
public void draw(HemfGraphics ctx) { | |||
final Path2D path = ctx.getProperties().getPath(); | |||
Arc2D arc = getShape(); | |||
path.append(arc, true); | |||
ctx.getProperties().setLocation(endPoint); | |||
public void draw(final HemfGraphics ctx) { | |||
final Arc2D arc = getShape(); | |||
ctx.draw((path) -> path.append(arc, true)); | |||
} | |||
} | |||
@@ -860,7 +884,7 @@ public class HemfDraw { | |||
size += readPoint(leis, points[i]); | |||
} | |||
poly.reset(); | |||
poly = new Path2D.Double(Path2D.WIND_EVEN_ODD, count); | |||
for (int i=0; i<count; i++) { | |||
int mode = leis.readUByte(); | |||
@@ -958,8 +982,7 @@ public class HemfDraw { | |||
@Override | |||
public void draw(HemfGraphics ctx) { | |||
final HemfDrawProperties prop = ctx.getProperties(); | |||
final Path2D path = prop.getPath(); | |||
path.reset(); | |||
prop.setPath(new Path2D.Double()); | |||
} | |||
} | |||
@@ -996,8 +1019,7 @@ public class HemfDraw { | |||
@Override | |||
public void draw(HemfGraphics ctx) { | |||
final HemfDrawProperties prop = ctx.getProperties(); | |||
final Path2D path = prop.getPath(); | |||
path.reset(); | |||
prop.setPath(null); | |||
} | |||
} | |||
@@ -1036,8 +1058,10 @@ public class HemfDraw { | |||
public void draw(HemfGraphics ctx) { | |||
final HemfDrawProperties prop = ctx.getProperties(); | |||
final Path2D path = prop.getPath(); | |||
path.closePath(); | |||
prop.setLocation(path.getCurrentPoint()); | |||
if (path != null) { | |||
path.closePath(); | |||
prop.setLocation(path.getCurrentPoint()); | |||
} | |||
} | |||
} | |||
@@ -1140,16 +1164,12 @@ public class HemfDraw { | |||
return 2*LittleEndianConsts.INT_SIZE; | |||
} | |||
private static void polyTo(HemfGraphics ctx, Path2D poly) { | |||
final HemfDrawProperties prop = ctx.getProperties(); | |||
final Path2D path = prop.getPath(); | |||
PathIterator pi = poly.getPathIterator(null); | |||
private static void polyTo(final HemfGraphics ctx, final Path2D poly) { | |||
final PathIterator pi = poly.getPathIterator(null); | |||
// ignore dummy start point (moveTo) | |||
pi.next(); | |||
assert(!pi.isDone()); | |||
path.append(pi, true); | |||
prop.setLocation(path.getCurrentPoint()); | |||
} | |||
assert (!pi.isDone()); | |||
ctx.draw((path) -> path.append(pi, true)); | |||
} | |||
} |
@@ -655,6 +655,9 @@ public class HemfFill { | |||
@Override | |||
public void draw(HemfGraphics ctx) { | |||
final HemfDrawProperties prop = ctx.getProperties(); | |||
if (!prop.usePathBracket()) { | |||
return; | |||
} | |||
final Path2D path = (Path2D)prop.getPath().clone(); | |||
path.setWindingRule(ctx.getProperties().getWindingRule()); | |||
if (prop.getBrushStyle() == HwmfBrushStyle.BS_NULL) { |
@@ -98,7 +98,7 @@ public class HemfMisc { | |||
/** | |||
* The EMF_SAVEDC record saves the playback device context for later retrieval. | |||
*/ | |||
public static class EmfSaveDc implements HemfRecord { | |||
public static class EmfSaveDc extends HwmfMisc.WmfSaveDc implements HemfRecord { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.saveDc; | |||
@@ -108,25 +108,13 @@ public class HemfMisc { | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
return 0; | |||
} | |||
@Override | |||
public void draw(HemfGraphics ctx) { | |||
ctx.saveProperties(); | |||
} | |||
} | |||
/** | |||
* The EMF_RESTOREDC record restores the playback device context from a previously saved device | |||
* context. | |||
*/ | |||
public static class EmfRestoreDc implements HemfRecord { | |||
/** | |||
* SavedDC (4 bytes): A 32-bit signed integer that specifies the saved state to restore relative to | |||
* the current state. This value MUST be negative; –1 represents the state that was most | |||
* recently saved on the stack, –2 the one before that, etc. | |||
*/ | |||
private int nSavedDC; | |||
public static class EmfRestoreDc extends HwmfMisc.WmfRestoreDc implements HemfRecord { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
@@ -135,23 +123,19 @@ public class HemfMisc { | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
// A 32-bit signed integer that specifies the saved state to restore relative to | |||
// the current state. This value MUST be negative; –1 represents the state that was most | |||
// recently saved on the stack, –2 the one before that, etc. | |||
nSavedDC = leis.readInt(); | |||
return LittleEndianConsts.INT_SIZE; | |||
} | |||
@Override | |||
public void draw(HemfGraphics ctx) { | |||
ctx.restoreProperties(nSavedDC); | |||
} | |||
} | |||
/** | |||
* The META_SETBKCOLOR record sets the background color in the playback device context to a | |||
* specified color, or to the nearest physical color if the device cannot represent the specified color. | |||
*/ | |||
public static class EmfSetBkColor implements HemfRecord { | |||
private HwmfColorRef colorRef; | |||
public static class EmfSetBkColor extends HwmfMisc.WmfSetBkColor implements HemfRecord { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
@@ -160,14 +144,8 @@ public class HemfMisc { | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
colorRef = new HwmfColorRef(); | |||
return colorRef.init(leis); | |||
} | |||
@Override | |||
public void draw(HemfGraphics ctx) { | |||
ctx.getProperties().setBackgroundColor(colorRef); | |||
} | |||
} | |||
@@ -287,8 +265,13 @@ public class HemfMisc { | |||
int size = colorRef.init(leis); | |||
brushHatch = HwmfHatchStyle.valueOf((int) leis.readUInt()); | |||
return size + 3 * LittleEndianConsts.INT_SIZE; | |||
} | |||
@Override | |||
public void draw(HemfGraphics ctx) { | |||
ctx.addObjectTableEntry(this, brushIdx); | |||
} | |||
} | |||
/** | |||
@@ -341,6 +324,11 @@ public class HemfMisc { | |||
return size + 4 * LittleEndianConsts.INT_SIZE; | |||
} | |||
@Override | |||
public void draw(HemfGraphics ctx) { | |||
ctx.addObjectTableEntry(this, penIndex); | |||
} | |||
} | |||
public static class EmfExtCreatePen extends EmfCreatePen { |
@@ -19,6 +19,7 @@ package org.apache.poi.hemf.record.emf; | |||
import java.io.IOException; | |||
import org.apache.poi.hemf.draw.HemfGraphics; | |||
import org.apache.poi.hwmf.record.HwmfPalette; | |||
import org.apache.poi.util.LittleEndianConsts; | |||
import org.apache.poi.util.LittleEndianInputStream; | |||
@@ -67,6 +68,11 @@ public class HemfPalette { | |||
int size = readPaletteEntries(leis, -1); | |||
return size + LittleEndianConsts.INT_SIZE + LittleEndianConsts.SHORT_SIZE; | |||
} | |||
@Override | |||
public void draw(HemfGraphics ctx) { | |||
ctx.addObjectTableEntry(this, paletteIndex); | |||
} | |||
} | |||
/** | |||
@@ -93,6 +99,11 @@ public class HemfPalette { | |||
int size = readPaletteEntries(leis, nbrOfEntries); | |||
return size + 3*LittleEndianConsts.INT_SIZE; | |||
} | |||
@Override | |||
public void draw(HemfGraphics ctx) { | |||
ctx.addObjectTableEntry(this, paletteIndex); | |||
} | |||
} | |||
/** | |||
@@ -118,6 +129,11 @@ public class HemfPalette { | |||
return 2*LittleEndianConsts.INT_SIZE; | |||
} | |||
@Override | |||
public void draw(HemfGraphics ctx) { | |||
ctx.addObjectTableEntry(this, paletteIndex); | |||
} | |||
} | |||
/** |
@@ -47,7 +47,7 @@ public enum HemfRecordType { | |||
setStretchBltMode(0x00000015, HemfMisc.EmfSetStretchBltMode::new), | |||
setTextAlign(0x00000016, HemfText.EmfSetTextAlign::new), | |||
setcoloradjustment(0x00000017, UnimplementedHemfRecord::new), | |||
setTextColor(0x00000018, HemfText.SetTextColor::new), | |||
setTextColor(0x00000018, HemfText.EmfSetTextColor::new), | |||
setBkColor(0x00000019, HemfMisc.EmfSetBkColor::new), | |||
setOffsetClipRgn(0x0000001A, HemfWindowing.EmfSetOffsetClipRgn::new), | |||
setMoveToEx(0x0000001B, HemfDraw.EmfSetMoveToEx::new), | |||
@@ -106,8 +106,8 @@ public enum HemfRecordType { | |||
setDiBitsToDevice(0x00000050, HemfFill.EmfSetDiBitsToDevice::new), | |||
stretchdibits(0x00000051, UnimplementedHemfRecord::new), | |||
extCreateFontIndirectW(0x00000052, HemfText.ExtCreateFontIndirectW::new), | |||
exttextouta(0x00000053, HemfText.ExtTextOutA::new), | |||
exttextoutw(0x00000054, HemfText.ExtTextOutW::new), | |||
exttextouta(0x00000053, HemfText.EmfExtTextOutA::new), | |||
exttextoutw(0x00000054, HemfText.EmfExtTextOutW::new), | |||
polyBezier16(0x00000055, HemfDraw.EmfPolyBezier16::new), | |||
polygon16(0x00000056, HemfDraw.EmfPolygon16::new), | |||
polyline16(0x00000057, HemfDraw.EmfPolyline16::new), |
@@ -29,7 +29,7 @@ import java.nio.charset.Charset; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import org.apache.poi.hwmf.record.HwmfColorRef; | |||
import org.apache.poi.hemf.draw.HemfGraphics; | |||
import org.apache.poi.hwmf.record.HwmfText; | |||
import org.apache.poi.hwmf.record.HwmfText.WmfSetTextAlign; | |||
import org.apache.poi.util.Dimension2DDouble; | |||
@@ -53,9 +53,9 @@ public class HemfText { | |||
GM_COMPATIBLE, GM_ADVANCED | |||
} | |||
public static class ExtTextOutA implements HemfRecord { | |||
public static class EmfExtTextOutA implements HemfRecord { | |||
protected final Rectangle2D boundsIgnored = new Rectangle2D.Double(); | |||
protected final Rectangle2D bounds = new Rectangle2D.Double(); | |||
protected EmfGraphicsMode graphicsMode; | |||
@@ -67,11 +67,11 @@ public class HemfText { | |||
protected final EmrTextObject textObject; | |||
public ExtTextOutA() { | |||
public EmfExtTextOutA() { | |||
this(false); | |||
} | |||
protected ExtTextOutA(boolean isUnicode) { | |||
protected EmfExtTextOutA(boolean isUnicode) { | |||
textObject = new EmrTextObject(isUnicode); | |||
} | |||
@@ -87,7 +87,7 @@ public class HemfText { | |||
} | |||
// A WMF RectL object. It is not used and MUST be ignored on receipt. | |||
long size = readRectL(leis, boundsIgnored); | |||
long size = readRectL(leis, bounds); | |||
// A 32-bit unsigned integer that specifies the graphics mode from the GraphicsMode enumeration | |||
graphicsMode = EmfGraphicsMode.values()[leis.readInt()-1]; | |||
@@ -104,10 +104,10 @@ public class HemfText { | |||
/** | |||
* | |||
* To be implemented! We need to get the current character set | |||
* from the current font for {@link ExtTextOutA}, | |||
* from the current font for {@link EmfExtTextOutA}, | |||
* which has to be tracked in the playback device. | |||
* | |||
* For {@link ExtTextOutW}, the charset is "UTF-16LE" | |||
* For {@link EmfExtTextOutW}, the charset is "UTF-16LE" | |||
* | |||
* @param charset the charset to be used to decode the character bytes | |||
* @return text from this text element | |||
@@ -132,11 +132,24 @@ public class HemfText { | |||
public Dimension2D getScale() { | |||
return scale; | |||
} | |||
@Override | |||
public String toString() { | |||
return | |||
"{ bounds: { x: "+bounds.getX()+ | |||
", y: "+bounds.getY()+ | |||
", w: "+bounds.getWidth()+ | |||
", h: "+bounds.getHeight()+ | |||
"}, graphicsMode: '"+graphicsMode+"'"+ | |||
", scale: { w: "+scale.getWidth()+", h: "+scale.getHeight()+" }"+ | |||
", textObject: "+textObject+ | |||
"}"; | |||
} | |||
} | |||
public static class ExtTextOutW extends ExtTextOutA { | |||
public static class EmfExtTextOutW extends EmfExtTextOutA { | |||
public ExtTextOutW() { | |||
public EmfExtTextOutW() { | |||
super(true); | |||
} | |||
@@ -175,10 +188,7 @@ public class HemfText { | |||
/** | |||
* The EMR_SETTEXTCOLOR record defines the current text color. | |||
*/ | |||
public static class SetTextColor implements HemfRecord { | |||
/** A WMF ColorRef object that specifies the text color value. */ | |||
private final HwmfColorRef colorRef = new HwmfColorRef(); | |||
public static class EmfSetTextColor extends HwmfText.WmfSetTextColor implements HemfRecord { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.setTextColor; | |||
@@ -284,6 +294,16 @@ public class HemfText { | |||
int size = font.init(leis, (int)(recordSize-LittleEndianConsts.INT_SIZE)); | |||
return size+LittleEndianConsts.INT_SIZE; | |||
} | |||
@Override | |||
public void draw(HemfGraphics ctx) { | |||
ctx.addObjectTableEntry(this, fontIdx); | |||
} | |||
@Override | |||
public String toString() { | |||
return "{ index: "+fontIdx+", font: "+font+" } "; | |||
} | |||
} | |||
public static class EmfExtTextOutOptions extends HwmfText.WmfExtTextOutOptions { |
@@ -55,10 +55,10 @@ public class HwmfGraphics { | |||
protected final List<HwmfDrawProperties> propStack = new LinkedList<>(); | |||
protected HwmfDrawProperties prop; | |||
protected final Graphics2D graphicsCtx; | |||
protected final List<HwmfObjectTableEntry> objectTable = new ArrayList<>(); | |||
private static final Charset DEFAULT_CHARSET = LocaleUtil.CHARSET_1252; | |||
private final List<HwmfObjectTableEntry> objectTable = new ArrayList<>(); | |||
/** Bounding box from the placeable header */ | |||
/** Bounding box from the placeable header */ | |||
private final Rectangle2D bbox; | |||
private final AffineTransform initialAT; | |||
@@ -69,4 +69,9 @@ public class HwmfColorRef implements Cloneable { | |||
throw new InternalError(); | |||
} | |||
} | |||
@Override | |||
public String toString() { | |||
return String.format("%#8X", colorRef.getRGB()); | |||
} | |||
} |
@@ -23,6 +23,7 @@ import java.awt.geom.Area; | |||
import java.awt.geom.Ellipse2D; | |||
import java.awt.geom.Line2D; | |||
import java.awt.geom.Path2D; | |||
import java.awt.geom.PathIterator; | |||
import java.awt.geom.Point2D; | |||
import java.awt.geom.Rectangle2D; | |||
import java.awt.geom.RoundRectangle2D; | |||
@@ -57,6 +58,11 @@ public class HwmfDraw { | |||
public void draw(HwmfGraphics ctx) { | |||
ctx.getProperties().setLocation(point); | |||
} | |||
@Override | |||
public String toString() { | |||
return "{ x: "+point.getX()+", y: "+point.getY()+" }"; | |||
} | |||
} | |||
/** | |||
@@ -84,6 +90,11 @@ public class HwmfDraw { | |||
ctx.draw(line); | |||
ctx.getProperties().setLocation(point); | |||
} | |||
@Override | |||
public String toString() { | |||
return "{ x: "+point.getX()+", y: "+point.getY()+" }"; | |||
} | |||
} | |||
/** | |||
@@ -93,7 +104,7 @@ public class HwmfDraw { | |||
*/ | |||
public static class WmfPolygon implements HwmfRecord { | |||
protected Path2D poly = new Path2D.Double(); | |||
protected Path2D poly; | |||
@Override | |||
public HwmfRecordType getWmfRecordType() { | |||
@@ -107,6 +118,7 @@ public class HwmfDraw { | |||
*/ | |||
int numberofPoints = leis.readShort(); | |||
poly = new Path2D.Double(Path2D.WIND_EVEN_ODD, numberofPoints); | |||
for (int i=0; i<numberofPoints; i++) { | |||
// A 16-bit signed integer that defines the horizontal (x) coordinate of the point. | |||
int x = leis.readShort(); | |||
@@ -134,6 +146,11 @@ public class HwmfDraw { | |||
} | |||
} | |||
@Override | |||
public String toString() { | |||
return "{ poly: "+polyToString(poly)+" }"; | |||
} | |||
/** | |||
* @return true, if the shape should be filled | |||
*/ | |||
@@ -279,7 +296,7 @@ public class HwmfDraw { | |||
* An array of 16-bit signed integers that define the coordinates of the polygons. | |||
* (Note: MS-WMF wrongly says unsigned integers ...) | |||
*/ | |||
Path2D poly = new Path2D.Double(); | |||
Path2D poly = new Path2D.Double(Path2D.WIND_EVEN_ODD, nPoints); | |||
for (int i=0; i<nPoints; i++) { | |||
int x = leis.readShort(); | |||
int y = leis.readShort(); | |||
@@ -556,6 +573,11 @@ public class HwmfDraw { | |||
public void draw(HwmfGraphics ctx) { | |||
ctx.applyObjectTableEntry(objectIndex); | |||
} | |||
@Override | |||
public String toString() { | |||
return "{ index: "+objectIndex +" }"; | |||
} | |||
} | |||
static int readBounds(LittleEndianInputStream leis, Rectangle2D bounds) { | |||
@@ -603,4 +625,34 @@ public class HwmfDraw { | |||
point.setLocation(x, y); | |||
return 2*LittleEndianConsts.SHORT_SIZE; | |||
} | |||
static String polyToString(Path2D poly) { | |||
StringBuilder sb = new StringBuilder(); | |||
sb.append("["); | |||
final PathIterator iter = poly.getPathIterator(null); | |||
double[] pnts = new double[6]; | |||
while (!iter.isDone()) { | |||
int segType = iter.currentSegment(pnts); | |||
switch (segType) { | |||
case PathIterator.SEG_MOVETO: | |||
sb.append("{ type: 'move', x: "+pnts[0]+", y: "+pnts[1]+" }, "); | |||
break; | |||
case PathIterator.SEG_LINETO: | |||
sb.append("{ type: 'lineto', x: "+pnts[0]+", y: "+pnts[1]+" }, "); | |||
break; | |||
case PathIterator.SEG_QUADTO: | |||
sb.append("{ type: 'quad', x1: "+pnts[0]+", y1: "+pnts[1]+", x2: "+pnts[2]+", y2: "+pnts[3]+" }, "); | |||
break; | |||
case PathIterator.SEG_CUBICTO: | |||
sb.append("{ type: 'cubic', x1: "+pnts[0]+", y1: "+pnts[1]+", x2: "+pnts[2]+", y2: "+pnts[3]+", x3: "+pnts[4]+", y3: "+pnts[5]+" }, "); | |||
break; | |||
case PathIterator.SEG_CLOSE: | |||
sb.append("{ type: 'close' }, "); | |||
break; | |||
} | |||
iter.next(); | |||
} | |||
sb.append("]"); | |||
return sb.toString(); | |||
} | |||
} |
@@ -146,6 +146,20 @@ public class HwmfFont implements FontInfo { | |||
flag = leis.readUByte(); | |||
return LittleEndianConsts.BYTE_SIZE; | |||
} | |||
@Override | |||
public String toString() { | |||
return | |||
(((flag&0x3) == 0 ? "default " : " ")+ | |||
(CLIP_CHARACTER_PRECIS.isSet(flag) ? "char " : " ")+ | |||
(CLIP_STROKE_PRECIS.isSet(flag) ? "stroke " : " ")+ | |||
(CLIP_LH_ANGLES.isSet(flag) ? "angles " : " ")+ | |||
(CLIP_TT_ALWAYS.isSet(flag) ? "tt_always " : " ")+ | |||
(CLIP_DFA_DISABLE.isSet(flag) ? "dfa " : " ")+ | |||
(CLIP_EMBEDDED.isSet(flag) ? "embedded " : " ") | |||
).trim() | |||
; | |||
} | |||
} | |||
/** | |||
@@ -453,6 +467,25 @@ public class HwmfFont implements FontInfo { | |||
throw new UnsupportedOperationException("setCharset not supported by HwmfFont."); | |||
} | |||
@Override | |||
public String toString() { | |||
return "{ height: "+height+ | |||
", width: "+width+ | |||
", escapment: "+escapement+ | |||
", weight: "+weight+ | |||
", italic: "+italic+ | |||
", underline: "+underline+ | |||
", strikeOut: "+strikeOut+ | |||
", charset: '"+charSet+"'"+ | |||
", outPrecision: '"+outPrecision+"'"+ | |||
", clipPrecision: '"+clipPrecision+"'"+ | |||
", qualtiy: '"+quality+"'"+ | |||
", pitch: '"+getPitch()+"'"+ | |||
", family: '"+getFamily()+"'"+ | |||
", facename: '"+facename+"'"+ | |||
"}"; | |||
} | |||
protected int readString(LittleEndianInputStream leis, StringBuilder sb, int limit) throws IOException { | |||
byte buf[] = new byte[limit], b, readBytes = 0; | |||
do { |
@@ -49,6 +49,11 @@ public class HwmfMisc { | |||
public void draw(HwmfGraphics ctx) { | |||
ctx.saveProperties(); | |||
} | |||
@Override | |||
public String toString() { | |||
return "{}"; | |||
} | |||
} | |||
/** | |||
@@ -80,7 +85,7 @@ public class HwmfMisc { | |||
* member is positive, nSavedDC represents a specific instance of the state to be restored. If | |||
* this member is negative, nSavedDC represents an instance relative to the current state. | |||
*/ | |||
private int nSavedDC; | |||
protected int nSavedDC; | |||
@Override | |||
public HwmfRecordType getWmfRecordType() { | |||
@@ -97,6 +102,11 @@ public class HwmfMisc { | |||
public void draw(HwmfGraphics ctx) { | |||
ctx.restoreProperties(nSavedDC); | |||
} | |||
@Override | |||
public String toString() { | |||
return "{ nSavedDC: "+nSavedDC+" }"; | |||
} | |||
} | |||
/** | |||
@@ -105,7 +115,7 @@ public class HwmfMisc { | |||
*/ | |||
public static class WmfSetBkColor implements HwmfRecord { | |||
private HwmfColorRef colorRef; | |||
protected final HwmfColorRef colorRef = new HwmfColorRef(); | |||
@Override | |||
public HwmfRecordType getWmfRecordType() { | |||
@@ -114,7 +124,6 @@ public class HwmfMisc { | |||
@Override | |||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |||
colorRef = new HwmfColorRef(); | |||
return colorRef.init(leis); | |||
} | |||
@@ -122,6 +131,11 @@ public class HwmfMisc { | |||
public void draw(HwmfGraphics ctx) { | |||
ctx.getProperties().setBackgroundColor(colorRef); | |||
} | |||
@Override | |||
public String toString() { | |||
return "{ colorRef: "+colorRef+" }"; | |||
} | |||
} | |||
/** | |||
@@ -465,6 +479,11 @@ public class HwmfMisc { | |||
ctx.unsetObjectTableEntry(objectIndex); | |||
} | |||
@Override | |||
public String toString() { | |||
return "{ index: "+objectIndex+" }"; | |||
} | |||
} | |||
public static class WmfCreatePatternBrush implements HwmfRecord, HwmfObjectTableEntry { |
@@ -82,7 +82,7 @@ public class HwmfText { | |||
*/ | |||
public static class WmfSetTextColor implements HwmfRecord { | |||
private HwmfColorRef colorRef; | |||
protected final HwmfColorRef colorRef = new HwmfColorRef(); | |||
@Override | |||
public HwmfRecordType getWmfRecordType() { | |||
@@ -91,7 +91,6 @@ public class HwmfText { | |||
@Override | |||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |||
colorRef = new HwmfColorRef(); | |||
return colorRef.init(leis); | |||
} | |||
@@ -99,6 +98,11 @@ public class HwmfText { | |||
public void draw(HwmfGraphics ctx) { | |||
ctx.getProperties().setTextColor(colorRef); | |||
} | |||
@Override | |||
public String toString() { | |||
return "{ colorRef: "+colorRef+" }"; | |||
} | |||
} | |||
/** | |||
@@ -645,5 +649,10 @@ public class HwmfText { | |||
public HwmfFont getFont() { | |||
return font; | |||
} | |||
@Override | |||
public String toString() { | |||
return "{ font: "+font+" } "; | |||
} | |||
} | |||
} |
@@ -17,6 +17,8 @@ | |||
package org.apache.poi.hwmf.record; | |||
import static org.apache.poi.hwmf.record.HwmfDraw.readBounds; | |||
import java.awt.Shape; | |||
import java.awt.geom.Area; | |||
import java.awt.geom.Rectangle2D; | |||
@@ -446,20 +448,7 @@ public class HwmfWindowing { | |||
@Override | |||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |||
// A 16-bit signed integer that defines the y-coordinate, in logical units, of the | |||
// lower-right corner of the rectangle. | |||
final int bottom = leis.readShort(); | |||
// A 16-bit signed integer that defines the x-coordinate, in logical units, of the | |||
// lower-right corner of the rectangle. | |||
final int right = leis.readShort(); | |||
// A 16-bit signed integer that defines the y-coordinate, in logical units, of the | |||
// upper-left corner of the rectangle. | |||
final int top = leis.readShort(); | |||
// A 16-bit signed integer that defines the x-coordinate, in logical units, of the | |||
// upper-left corner of the rectangle. | |||
final int left = leis.readShort(); | |||
bounds.setRect(left, top, right-left, bottom-top); | |||
return 4*LittleEndianConsts.SHORT_SIZE; | |||
return readBounds(leis, bounds); | |||
} | |||
@Override | |||
@@ -470,6 +459,16 @@ public class HwmfWindowing { | |||
@Override | |||
public void applyObject(HwmfGraphics ctx) { | |||
} | |||
@Override | |||
public String toString() { | |||
return | |||
"{ x: "+bounds.getX()+ | |||
", y: "+bounds.getY()+ | |||
", w: "+bounds.getWidth()+ | |||
", h: "+bounds.getHeight()+ | |||
"}"; | |||
} | |||
} | |||
/** |
@@ -32,6 +32,7 @@ import java.io.ByteArrayInputStream; | |||
import java.io.ByteArrayOutputStream; | |||
import java.io.File; | |||
import java.io.FileInputStream; | |||
import java.io.FileWriter; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.util.HashSet; | |||
@@ -63,7 +64,7 @@ public class HemfPictureTest { | |||
@Test | |||
@Ignore("Only for manual tests") | |||
public void paint() throws IOException { | |||
File f = sl_samples.getFile("wrench.emf"); | |||
File f = new File("picture_14.emf"); // sl_samples.getFile("wrench.emf"); | |||
try (FileInputStream fis = new FileInputStream(f)) { | |||
HemfPicture emf = new HemfPicture(fis); | |||
@@ -84,6 +85,14 @@ public class HemfPictureTest { | |||
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()) { | |||
fw.write(i + " "+r.getEmfRecordType()+" "+r.toString()+"\n"); | |||
i++; | |||
} | |||
fw.close(); | |||
emf.draw(g, new Rectangle2D.Double(0,0,width,height)); | |||
g.dispose(); | |||
@@ -160,7 +169,7 @@ public class HemfPictureTest { | |||
StringBuilder sb = new StringBuilder(); | |||
for (HemfRecord record : pic) { | |||
if (record.getEmfRecordType().equals(HemfRecordType.exttextoutw)) { | |||
HemfText.ExtTextOutW extTextOutW = (HemfText.ExtTextOutW) record; | |||
HemfText.EmfExtTextOutW extTextOutW = (HemfText.EmfExtTextOutW) record; | |||
Point2D reference = extTextOutW.getTextObject().getReference(); | |||
if (lastY > -1 && lastY != reference.getY()) { | |||
sb.append("\n"); | |||
@@ -194,7 +203,7 @@ public class HemfPictureTest { | |||
int foundExpected = 0; | |||
for (HemfRecord record : pic) { | |||
if (record.getEmfRecordType().equals(HemfRecordType.exttextoutw)) { | |||
HemfText.ExtTextOutW extTextOutW = (HemfText.ExtTextOutW) record; | |||
HemfText.EmfExtTextOutW extTextOutW = (HemfText.EmfExtTextOutW) record; | |||
Point2D reference = extTextOutW.getTextObject().getReference(); | |||
if (lastY > -1 && lastY != reference.getY()) { | |||
sb.append("\n"); |