mirror of
https://github.com/apache/poi.git
synced 2025-03-06 08:59:07 +00:00
Bug 60656 - EMF image support in slideshows
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1860732 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
8808bf7b72
commit
27c20b0e9a
@ -17,9 +17,10 @@
|
|||||||
|
|
||||||
package org.apache.poi.hemf.draw;
|
package org.apache.poi.hemf.draw;
|
||||||
|
|
||||||
import java.awt.Shape;
|
|
||||||
import java.awt.geom.Path2D;
|
import java.awt.geom.Path2D;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
|
||||||
|
import org.apache.poi.hemf.record.emfplus.HemfPlusBrush.EmfPlusHatchStyle;
|
||||||
import org.apache.poi.hwmf.draw.HwmfDrawProperties;
|
import org.apache.poi.hwmf.draw.HwmfDrawProperties;
|
||||||
|
|
||||||
public class HemfDrawProperties extends HwmfDrawProperties {
|
public class HemfDrawProperties extends HwmfDrawProperties {
|
||||||
@ -27,6 +28,8 @@ public class HemfDrawProperties extends HwmfDrawProperties {
|
|||||||
/** Path for path bracket operations */
|
/** Path for path bracket operations */
|
||||||
protected Path2D path = null;
|
protected Path2D path = null;
|
||||||
protected boolean usePathBracket = false;
|
protected boolean usePathBracket = false;
|
||||||
|
private EmfPlusHatchStyle emfPlusBrushHatch;
|
||||||
|
private BufferedImage emfPlusImage;
|
||||||
|
|
||||||
|
|
||||||
public HemfDrawProperties() {
|
public HemfDrawProperties() {
|
||||||
@ -35,8 +38,11 @@ public class HemfDrawProperties extends HwmfDrawProperties {
|
|||||||
public HemfDrawProperties(HemfDrawProperties other) {
|
public HemfDrawProperties(HemfDrawProperties other) {
|
||||||
super(other);
|
super(other);
|
||||||
path = (other.path != null) ? (Path2D)other.path.clone() : null;
|
path = (other.path != null) ? (Path2D)other.path.clone() : null;
|
||||||
|
usePathBracket = other.usePathBracket;
|
||||||
|
emfPlusBrushHatch = other.emfPlusBrushHatch;
|
||||||
// TODO: check how to clone
|
// TODO: check how to clone
|
||||||
clip = other.clip;
|
clip = other.clip;
|
||||||
|
emfPlusImage = other.emfPlusImage;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -66,4 +72,20 @@ public class HemfDrawProperties extends HwmfDrawProperties {
|
|||||||
public void setUsePathBracket(boolean usePathBracket) {
|
public void setUsePathBracket(boolean usePathBracket) {
|
||||||
this.usePathBracket = usePathBracket;
|
this.usePathBracket = usePathBracket;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public EmfPlusHatchStyle getEmfPlusBrushHatch() {
|
||||||
|
return emfPlusBrushHatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmfPlusBrushHatch(EmfPlusHatchStyle emfPlusBrushHatch) {
|
||||||
|
this.emfPlusBrushHatch = emfPlusBrushHatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BufferedImage getEmfPlusImage() {
|
||||||
|
return emfPlusImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmfPlusImage(BufferedImage emfPlusImage) {
|
||||||
|
this.emfPlusImage = emfPlusImage;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,12 +22,15 @@ import static org.apache.poi.hwmf.record.HwmfBrushStyle.BS_SOLID;
|
|||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.awt.Graphics2D;
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.Paint;
|
||||||
import java.awt.geom.Path2D;
|
import java.awt.geom.Path2D;
|
||||||
import java.awt.geom.Point2D;
|
import java.awt.geom.Point2D;
|
||||||
import java.awt.geom.Rectangle2D;
|
import java.awt.geom.Rectangle2D;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import org.apache.poi.hemf.record.emf.HemfComment.EmfComment;
|
||||||
import org.apache.poi.hemf.record.emf.HemfRecord;
|
import org.apache.poi.hemf.record.emf.HemfRecord;
|
||||||
|
import org.apache.poi.hemf.record.emfplus.HemfPlusRecord;
|
||||||
import org.apache.poi.hwmf.draw.HwmfDrawProperties;
|
import org.apache.poi.hwmf.draw.HwmfDrawProperties;
|
||||||
import org.apache.poi.hwmf.draw.HwmfGraphics;
|
import org.apache.poi.hwmf.draw.HwmfGraphics;
|
||||||
import org.apache.poi.hwmf.record.HwmfColorRef;
|
import org.apache.poi.hwmf.record.HwmfColorRef;
|
||||||
@ -37,12 +40,20 @@ import org.apache.poi.util.Internal;
|
|||||||
|
|
||||||
public class HemfGraphics extends HwmfGraphics {
|
public class HemfGraphics extends HwmfGraphics {
|
||||||
|
|
||||||
|
public enum EmfRenderState {
|
||||||
|
INITIAL,
|
||||||
|
EMF_ONLY,
|
||||||
|
EMFPLUS_ONLY,
|
||||||
|
EMF_DCONTEXT
|
||||||
|
}
|
||||||
|
|
||||||
private static final HwmfColorRef WHITE = new HwmfColorRef(Color.WHITE);
|
private static final HwmfColorRef WHITE = new HwmfColorRef(Color.WHITE);
|
||||||
private static final HwmfColorRef LTGRAY = new HwmfColorRef(new Color(0x00C0C0C0));
|
private static final HwmfColorRef LTGRAY = new HwmfColorRef(new Color(0x00C0C0C0));
|
||||||
private static final HwmfColorRef GRAY = new HwmfColorRef(new Color(0x00808080));
|
private static final HwmfColorRef GRAY = new HwmfColorRef(new Color(0x00808080));
|
||||||
private static final HwmfColorRef DKGRAY = new HwmfColorRef(new Color(0x00404040));
|
private static final HwmfColorRef DKGRAY = new HwmfColorRef(new Color(0x00404040));
|
||||||
private static final HwmfColorRef BLACK = new HwmfColorRef(Color.BLACK);
|
private static final HwmfColorRef BLACK = new HwmfColorRef(Color.BLACK);
|
||||||
|
|
||||||
|
private EmfRenderState renderState = EmfRenderState.INITIAL;
|
||||||
|
|
||||||
public HemfGraphics(Graphics2D graphicsCtx, Rectangle2D bbox) {
|
public HemfGraphics(Graphics2D graphicsCtx, Rectangle2D bbox) {
|
||||||
super(graphicsCtx,bbox);
|
super(graphicsCtx,bbox);
|
||||||
@ -62,8 +73,49 @@ public class HemfGraphics extends HwmfGraphics {
|
|||||||
: new HemfDrawProperties((HemfDrawProperties)oldProps);
|
: new HemfDrawProperties((HemfDrawProperties)oldProps);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public EmfRenderState getRenderState() {
|
||||||
|
return renderState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRenderState(EmfRenderState renderState) {
|
||||||
|
this.renderState = renderState;
|
||||||
|
}
|
||||||
|
|
||||||
public void draw(HemfRecord r) {
|
public void draw(HemfRecord r) {
|
||||||
r.draw(this);
|
switch (renderState) {
|
||||||
|
case EMF_DCONTEXT:
|
||||||
|
// keep the dcontext state, if the next record is an EMF+ record
|
||||||
|
// only reset it, when we are processing EMF records again
|
||||||
|
if (!(r instanceof EmfComment)) {
|
||||||
|
renderState = EmfRenderState.INITIAL;
|
||||||
|
}
|
||||||
|
r.draw(this);
|
||||||
|
break;
|
||||||
|
case INITIAL:
|
||||||
|
r.draw(this);
|
||||||
|
break;
|
||||||
|
case EMF_ONLY:
|
||||||
|
case EMFPLUS_ONLY:
|
||||||
|
if ((r instanceof EmfComment) == (renderState == EmfRenderState.EMFPLUS_ONLY)) {
|
||||||
|
r.draw(this);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void draw(HemfPlusRecord r) {
|
||||||
|
switch (renderState) {
|
||||||
|
case EMFPLUS_ONLY:
|
||||||
|
case EMF_DCONTEXT:
|
||||||
|
case INITIAL:
|
||||||
|
r.draw(this);
|
||||||
|
break;
|
||||||
|
case EMF_ONLY:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Internal
|
@Internal
|
||||||
@ -131,14 +183,36 @@ public class HemfGraphics extends HwmfGraphics {
|
|||||||
* @see HwmfGraphics#addObjectTableEntry(HwmfObjectTableEntry)
|
* @see HwmfGraphics#addObjectTableEntry(HwmfObjectTableEntry)
|
||||||
*/
|
*/
|
||||||
public void addObjectTableEntry(HwmfObjectTableEntry entry, int index) {
|
public void addObjectTableEntry(HwmfObjectTableEntry entry, int index) {
|
||||||
if (index < 1) {
|
checkTableEntryIndex(index);
|
||||||
throw new IndexOutOfBoundsException("Object table entry index in EMF must be > 0 - invalid index: "+index);
|
|
||||||
}
|
|
||||||
|
|
||||||
objectIndexes.set(index);
|
objectIndexes.set(index);
|
||||||
objectTable.put(index, entry);
|
objectTable.put(index, entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a record which was registered earliser
|
||||||
|
* @param index the record index
|
||||||
|
* @return the record or {@code null} if it doesn't exist
|
||||||
|
*/
|
||||||
|
public HwmfObjectTableEntry getObjectTableEntry(int index) {
|
||||||
|
checkTableEntryIndex(index);
|
||||||
|
return objectTable.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkTableEntryIndex(int index) {
|
||||||
|
if (renderState != EmfRenderState.EMFPLUS_ONLY) {
|
||||||
|
// in EMF the index must > 0
|
||||||
|
if (index < 1) {
|
||||||
|
throw new IndexOutOfBoundsException("Object table entry index in EMF must be > 0 - invalid index: "+index);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// in EMF+ the index must be between 0 and 63
|
||||||
|
if (index < 0 || index > 63) {
|
||||||
|
throw new IndexOutOfBoundsException("Object table entry index in EMF+ must be [0..63] - invalid index: "+index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void applyObjectTableEntry(int index) {
|
public void applyObjectTableEntry(int index) {
|
||||||
if ((index & 0x80000000) != 0) {
|
if ((index & 0x80000000) != 0) {
|
||||||
@ -256,4 +330,10 @@ public class HemfGraphics extends HwmfGraphics {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Paint getHatchedFill() {
|
||||||
|
// TODO: use EmfPlusHatchBrushData
|
||||||
|
return super.getHatchedFill();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ import java.util.Iterator;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.apache.poi.hemf.draw.HemfGraphics;
|
||||||
import org.apache.poi.hemf.record.emfplus.HemfPlusRecord;
|
import org.apache.poi.hemf.record.emfplus.HemfPlusRecord;
|
||||||
import org.apache.poi.hemf.record.emfplus.HemfPlusRecordIterator;
|
import org.apache.poi.hemf.record.emfplus.HemfPlusRecordIterator;
|
||||||
import org.apache.poi.hwmf.usermodel.HwmfPicture;
|
import org.apache.poi.hwmf.usermodel.HwmfPicture;
|
||||||
@ -102,6 +103,17 @@ public class HemfComment {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(HemfGraphics ctx) {
|
||||||
|
if (data instanceof EmfCommentDataPlus) {
|
||||||
|
if (ctx.getRenderState() == HemfGraphics.EmfRenderState.INITIAL) {
|
||||||
|
ctx.setRenderState(HemfGraphics.EmfRenderState.EMFPLUS_ONLY);
|
||||||
|
}
|
||||||
|
|
||||||
|
((EmfCommentDataPlus)data).draw(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "{ data: "+data+" }";
|
return "{ data: "+data+" }";
|
||||||
@ -255,6 +267,10 @@ public class HemfComment {
|
|||||||
public List<HemfPlusRecord> getRecords() {
|
public List<HemfPlusRecord> getRecords() {
|
||||||
return Collections.unmodifiableList(records);
|
return Collections.unmodifiableList(records);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void draw(HemfGraphics ctx) {
|
||||||
|
records.forEach(ctx::draw);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class EmfCommentDataBeginGroup implements EmfCommentData {
|
public static class EmfCommentDataBeginGroup implements EmfCommentData {
|
||||||
|
@ -21,6 +21,7 @@ import static org.apache.poi.hwmf.record.HwmfDraw.boundsToString;
|
|||||||
import static org.apache.poi.hwmf.record.HwmfDraw.normalizeBounds;
|
import static org.apache.poi.hwmf.record.HwmfDraw.normalizeBounds;
|
||||||
|
|
||||||
import java.awt.Shape;
|
import java.awt.Shape;
|
||||||
|
import java.awt.geom.AffineTransform;
|
||||||
import java.awt.geom.Arc2D;
|
import java.awt.geom.Arc2D;
|
||||||
import java.awt.geom.Dimension2D;
|
import java.awt.geom.Dimension2D;
|
||||||
import java.awt.geom.Path2D;
|
import java.awt.geom.Path2D;
|
||||||
@ -34,6 +35,7 @@ import org.apache.poi.hemf.draw.HemfGraphics;
|
|||||||
import org.apache.poi.hwmf.draw.HwmfGraphics.FillDrawStyle;
|
import org.apache.poi.hwmf.draw.HwmfGraphics.FillDrawStyle;
|
||||||
import org.apache.poi.hwmf.record.HwmfDraw;
|
import org.apache.poi.hwmf.record.HwmfDraw;
|
||||||
import org.apache.poi.hwmf.record.HwmfDraw.WmfSelectObject;
|
import org.apache.poi.hwmf.record.HwmfDraw.WmfSelectObject;
|
||||||
|
import org.apache.poi.util.Internal;
|
||||||
import org.apache.poi.util.LittleEndianConsts;
|
import org.apache.poi.util.LittleEndianConsts;
|
||||||
import org.apache.poi.util.LittleEndianInputStream;
|
import org.apache.poi.util.LittleEndianInputStream;
|
||||||
|
|
||||||
@ -1153,4 +1155,16 @@ public class HemfDraw {
|
|||||||
|
|
||||||
ctx.draw((path) -> path.append(pi, true), fillDrawStyle);
|
ctx.draw((path) -> path.append(pi, true), fillDrawStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Internal
|
||||||
|
public static String xformToString(AffineTransform xForm) {
|
||||||
|
return (xForm == null) ? "null" :
|
||||||
|
"{ scaleX: "+xForm.getScaleX()+
|
||||||
|
", shearX: "+xForm.getShearX()+
|
||||||
|
", transX: "+xForm.getTranslateX()+
|
||||||
|
", scaleY: "+xForm.getScaleY()+
|
||||||
|
", shearY: "+xForm.getShearY()+
|
||||||
|
", transY: "+xForm.getTranslateY()+" }";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ package org.apache.poi.hemf.record.emf;
|
|||||||
|
|
||||||
import static org.apache.poi.hemf.record.emf.HemfDraw.readPointL;
|
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.HemfDraw.readRectL;
|
||||||
|
import static org.apache.poi.hemf.record.emf.HemfDraw.xformToString;
|
||||||
import static org.apache.poi.hemf.record.emf.HemfRecordIterator.HEADER_SIZE;
|
import static org.apache.poi.hemf.record.emf.HemfRecordIterator.HEADER_SIZE;
|
||||||
import static org.apache.poi.hwmf.record.HwmfDraw.boundsToString;
|
import static org.apache.poi.hwmf.record.HwmfDraw.boundsToString;
|
||||||
import static org.apache.poi.hwmf.record.HwmfDraw.pointToString;
|
import static org.apache.poi.hwmf.record.HwmfDraw.pointToString;
|
||||||
@ -186,7 +187,7 @@ public class HemfFill {
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
return
|
return
|
||||||
"{ bounds: "+boundsToString(bounds)+
|
"{ bounds: "+boundsToString(bounds)+
|
||||||
", xFormSrc: { scaleX: "+xFormSrc.getScaleX()+", shearX: "+xFormSrc.getShearX()+", transX: "+xFormSrc.getTranslateX()+", scaleY: "+xFormSrc.getScaleY()+", shearY: "+xFormSrc.getShearY()+", transY: "+xFormSrc.getTranslateY()+" }"+
|
", xFormSrc: " + xformToString(xFormSrc) +
|
||||||
", bkColorSrc: "+bkColorSrc+
|
", bkColorSrc: "+bkColorSrc+
|
||||||
","+super.toString().substring(1);
|
","+super.toString().substring(1);
|
||||||
}
|
}
|
||||||
@ -705,6 +706,10 @@ public class HemfFill {
|
|||||||
|
|
||||||
xform.setTransform(m00, m10, m01, m11, m02, m12);
|
xform.setTransform(m00, m10, m01, m11, m02, m12);
|
||||||
|
|
||||||
|
if (xform.isIdentity()) {
|
||||||
|
xform.setToIdentity();
|
||||||
|
}
|
||||||
|
|
||||||
return 6 * LittleEndian.INT_SIZE;
|
return 6 * LittleEndian.INT_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
package org.apache.poi.hemf.record.emf;
|
package org.apache.poi.hemf.record.emf;
|
||||||
|
|
||||||
import static org.apache.poi.hemf.record.emf.HemfDraw.readPointL;
|
import static org.apache.poi.hemf.record.emf.HemfDraw.readPointL;
|
||||||
|
import static org.apache.poi.hemf.record.emf.HemfDraw.xformToString;
|
||||||
import static org.apache.poi.hemf.record.emf.HemfFill.readBitmap;
|
import static org.apache.poi.hemf.record.emf.HemfFill.readBitmap;
|
||||||
import static org.apache.poi.hemf.record.emf.HemfFill.readXForm;
|
import static org.apache.poi.hemf.record.emf.HemfFill.readXForm;
|
||||||
import static org.apache.poi.hemf.record.emf.HemfRecordIterator.HEADER_SIZE;
|
import static org.apache.poi.hemf.record.emf.HemfRecordIterator.HEADER_SIZE;
|
||||||
@ -621,14 +622,7 @@ public class HemfMisc {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return
|
return "{ xForm: " + xformToString(xForm)+" }";
|
||||||
"{ xForm: " +
|
|
||||||
"{ scaleX: "+xForm.getScaleX()+
|
|
||||||
", shearX: "+xForm.getShearX()+
|
|
||||||
", transX: "+xForm.getTranslateX()+
|
|
||||||
", scaleY: "+xForm.getScaleY()+
|
|
||||||
", shearY: "+xForm.getShearY()+
|
|
||||||
", transY: "+xForm.getTranslateY()+" } }";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -695,7 +689,7 @@ public class HemfMisc {
|
|||||||
wsTrans.translate(-emfBounds.getHeight(), emfBounds.getHeight());
|
wsTrans.translate(-emfBounds.getHeight(), emfBounds.getHeight());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
wsTrans = adaptXForm(ctx.getTransform());
|
wsTrans = adaptXForm(xForm, ctx.getTransform());
|
||||||
}
|
}
|
||||||
|
|
||||||
tx = ctx.getTransform();
|
tx = ctx.getTransform();
|
||||||
@ -703,7 +697,7 @@ public class HemfMisc {
|
|||||||
break;
|
break;
|
||||||
case MWT_RIGHTMULTIPLY:
|
case MWT_RIGHTMULTIPLY:
|
||||||
tx = ctx.getTransform();
|
tx = ctx.getTransform();
|
||||||
tx.preConcatenate(adaptXForm(tx));
|
tx.preConcatenate(adaptXForm(xForm, tx));
|
||||||
break;
|
break;
|
||||||
case MWT_IDENTITY:
|
case MWT_IDENTITY:
|
||||||
ctx.updateWindowMapMode();
|
ctx.updateWindowMapMode();
|
||||||
@ -713,40 +707,16 @@ public class HemfMisc {
|
|||||||
case MWT_SET:
|
case MWT_SET:
|
||||||
ctx.updateWindowMapMode();
|
ctx.updateWindowMapMode();
|
||||||
tx = ctx.getTransform();
|
tx = ctx.getTransform();
|
||||||
tx.concatenate(adaptXForm(tx));
|
tx.concatenate(adaptXForm(xForm, tx));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
ctx.setTransform(tx);
|
ctx.setTransform(tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* adapt xform depending on the base transformation (... experimental ...)
|
|
||||||
*/
|
|
||||||
private AffineTransform adaptXForm(AffineTransform other) {
|
|
||||||
// normalize signed zero
|
|
||||||
Function<Double,Double> nn = (d) -> (d == 0. ? 0. : d);
|
|
||||||
double yDiff = Math.signum(nn.apply(xForm.getTranslateY())) == Math.signum(nn.apply(other.getTranslateY())) ? 1. : -1.;
|
|
||||||
double xDiff = Math.signum(nn.apply(xForm.getTranslateX())) == Math.signum(nn.apply(other.getTranslateX())) ? 1. : -1.;
|
|
||||||
return new AffineTransform(
|
|
||||||
xForm.getScaleX() == 0 ? 1. : xForm.getScaleX(),
|
|
||||||
yDiff * xForm.getShearY(),
|
|
||||||
xDiff * xForm.getShearX(),
|
|
||||||
xForm.getScaleY() == 0. ? 1. : xForm.getScaleY(),
|
|
||||||
xForm.getTranslateX(),
|
|
||||||
xForm.getTranslateY()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return
|
return
|
||||||
"{ xForm: " +
|
"{ xForm: " + xformToString(xForm) +
|
||||||
"{ scaleX: "+xForm.getScaleX()+
|
|
||||||
", shearX: "+xForm.getShearX()+
|
|
||||||
", transX: "+xForm.getTranslateX()+
|
|
||||||
", scaleY: "+xForm.getScaleY()+
|
|
||||||
", shearY: "+xForm.getShearY()+
|
|
||||||
", transY: "+xForm.getTranslateY()+" }"+
|
|
||||||
", modifyWorldTransformMode: '"+modifyWorldTransformMode+"' }";
|
", modifyWorldTransformMode: '"+modifyWorldTransformMode+"' }";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -825,4 +795,23 @@ public class HemfMisc {
|
|||||||
"}";
|
"}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* adapt xform depending on the base transformation (... experimental ...)
|
||||||
|
*/
|
||||||
|
public static AffineTransform adaptXForm(AffineTransform xForm, AffineTransform other) {
|
||||||
|
// normalize signed zero
|
||||||
|
Function<Double,Double> nn = (d) -> (d == 0. ? 0. : d);
|
||||||
|
double yDiff = Math.signum(nn.apply(xForm.getTranslateY())) == Math.signum(nn.apply(other.getTranslateY())) ? 1. : -1.;
|
||||||
|
double xDiff = Math.signum(nn.apply(xForm.getTranslateX())) == Math.signum(nn.apply(other.getTranslateX())) ? 1. : -1.;
|
||||||
|
return new AffineTransform(
|
||||||
|
xForm.getScaleX() == 0 ? 1. : xForm.getScaleX(),
|
||||||
|
yDiff * xForm.getShearY(),
|
||||||
|
xDiff * xForm.getShearX(),
|
||||||
|
xForm.getScaleY() == 0. ? 1. : xForm.getScaleY(),
|
||||||
|
xForm.getTranslateX(),
|
||||||
|
xForm.getTranslateY()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,27 +17,40 @@
|
|||||||
|
|
||||||
package org.apache.poi.hemf.record.emfplus;
|
package org.apache.poi.hemf.record.emfplus;
|
||||||
|
|
||||||
|
import static java.util.stream.Collectors.joining;
|
||||||
|
import static org.apache.poi.hemf.record.emf.HemfDraw.xformToString;
|
||||||
import static org.apache.poi.hemf.record.emf.HemfFill.readXForm;
|
import static org.apache.poi.hemf.record.emf.HemfFill.readXForm;
|
||||||
import static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readARGB;
|
import static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readARGB;
|
||||||
import static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readPointF;
|
import static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readPointF;
|
||||||
import static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readRectF;
|
import static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readRectF;
|
||||||
|
import static org.apache.poi.hwmf.record.HwmfDraw.boundsToString;
|
||||||
|
import static org.apache.poi.hwmf.record.HwmfDraw.pointToString;
|
||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.awt.geom.AffineTransform;
|
import java.awt.geom.AffineTransform;
|
||||||
import java.awt.geom.Point2D;
|
import java.awt.geom.Point2D;
|
||||||
import java.awt.geom.Rectangle2D;
|
import java.awt.geom.Rectangle2D;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.apache.poi.hemf.draw.HemfDrawProperties;
|
||||||
|
import org.apache.poi.hemf.draw.HemfGraphics;
|
||||||
import org.apache.poi.hemf.record.emfplus.HemfPlusHeader.EmfPlusGraphicsVersion;
|
import org.apache.poi.hemf.record.emfplus.HemfPlusHeader.EmfPlusGraphicsVersion;
|
||||||
import org.apache.poi.hemf.record.emfplus.HemfPlusImage.EmfPlusImage;
|
import org.apache.poi.hemf.record.emfplus.HemfPlusImage.EmfPlusImage;
|
||||||
import org.apache.poi.hemf.record.emfplus.HemfPlusImage.EmfPlusWrapMode;
|
import org.apache.poi.hemf.record.emfplus.HemfPlusImage.EmfPlusWrapMode;
|
||||||
import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectData;
|
import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectData;
|
||||||
import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectType;
|
import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectType;
|
||||||
import org.apache.poi.hemf.record.emfplus.HemfPlusPath.EmfPlusPath;
|
import org.apache.poi.hemf.record.emfplus.HemfPlusPath.EmfPlusPath;
|
||||||
|
import org.apache.poi.hwmf.record.HwmfBrushStyle;
|
||||||
|
import org.apache.poi.hwmf.record.HwmfColorRef;
|
||||||
|
import org.apache.poi.hwmf.record.HwmfDraw;
|
||||||
import org.apache.poi.util.BitField;
|
import org.apache.poi.util.BitField;
|
||||||
import org.apache.poi.util.BitFieldFactory;
|
import org.apache.poi.util.BitFieldFactory;
|
||||||
|
import org.apache.poi.util.Internal;
|
||||||
import org.apache.poi.util.LittleEndianConsts;
|
import org.apache.poi.util.LittleEndianConsts;
|
||||||
import org.apache.poi.util.LittleEndianInputStream;
|
import org.apache.poi.util.LittleEndianInputStream;
|
||||||
|
|
||||||
@ -68,58 +81,149 @@ public class HemfPlusBrush {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public enum EmfPlusHatchStyle {
|
public enum EmfPlusHatchStyle {
|
||||||
|
/** Specifies equally spaced horizontal lines. */
|
||||||
HORIZONTAL(0X00000000),
|
HORIZONTAL(0X00000000),
|
||||||
|
/** Specifies equally spaced vertical lines. */
|
||||||
VERTICAL(0X00000001),
|
VERTICAL(0X00000001),
|
||||||
|
/** Specifies lines on a diagonal from upper left to lower right. */
|
||||||
FORWARD_DIAGONAL(0X00000002),
|
FORWARD_DIAGONAL(0X00000002),
|
||||||
|
/** Specifies lines on a diagonal from upper right to lower left. */
|
||||||
BACKWARD_DIAGONAL(0X00000003),
|
BACKWARD_DIAGONAL(0X00000003),
|
||||||
|
/** Specifies crossing horizontal and vertical lines. */
|
||||||
LARGE_GRID(0X00000004),
|
LARGE_GRID(0X00000004),
|
||||||
|
/** Specifies crossing forward diagonal and backward diagonal lines with anti-aliasing. */
|
||||||
DIAGONAL_CROSS(0X00000005),
|
DIAGONAL_CROSS(0X00000005),
|
||||||
|
/** Specifies a 5-percent hatch, which is the ratio of foreground color to background color equal to 5:100. */
|
||||||
PERCENT_05(0X00000006),
|
PERCENT_05(0X00000006),
|
||||||
|
/** Specifies a 10-percent hatch, which is the ratio of foreground color to background color equal to 10:100. */
|
||||||
PERCENT_10(0X00000007),
|
PERCENT_10(0X00000007),
|
||||||
|
/** Specifies a 20-percent hatch, which is the ratio of foreground color to background color equal to 20:100. */
|
||||||
PERCENT_20(0X00000008),
|
PERCENT_20(0X00000008),
|
||||||
|
/** Specifies a 25-percent hatch, which is the ratio of foreground color to background color equal to 25:100. */
|
||||||
PERCENT_25(0X00000009),
|
PERCENT_25(0X00000009),
|
||||||
|
/** Specifies a 30-percent hatch, which is the ratio of foreground color to background color equal to 30:100. */
|
||||||
PERCENT_30(0X0000000A),
|
PERCENT_30(0X0000000A),
|
||||||
|
/** Specifies a 40-percent hatch, which is the ratio of foreground color to background color equal to 40:100. */
|
||||||
PERCENT_40(0X0000000B),
|
PERCENT_40(0X0000000B),
|
||||||
|
/** Specifies a 50-percent hatch, which is the ratio of foreground color to background color equal to 50:100. */
|
||||||
PERCENT_50(0X0000000C),
|
PERCENT_50(0X0000000C),
|
||||||
|
/** Specifies a 60-percent hatch, which is the ratio of foreground color to background color equal to 60:100. */
|
||||||
PERCENT_60(0X0000000D),
|
PERCENT_60(0X0000000D),
|
||||||
|
/** Specifies a 70-percent hatch, which is the ratio of foreground color to background color equal to 70:100. */
|
||||||
PERCENT_70(0X0000000E),
|
PERCENT_70(0X0000000E),
|
||||||
|
/** Specifies a 75-percent hatch, which is the ratio of foreground color to background color equal to 75:100. */
|
||||||
PERCENT_75(0X0000000F),
|
PERCENT_75(0X0000000F),
|
||||||
|
/** Specifies an 80-percent hatch, which is the ratio of foreground color to background color equal to 80:100. */
|
||||||
PERCENT_80(0X00000010),
|
PERCENT_80(0X00000010),
|
||||||
|
/** Specifies a 90-percent hatch, which is the ratio of foreground color to background color equal to 90:100. */
|
||||||
PERCENT_90(0X00000011),
|
PERCENT_90(0X00000011),
|
||||||
|
/**
|
||||||
|
* Specifies diagonal lines that slant to the right from top to bottom points with no anti-aliasing.
|
||||||
|
* They are spaced 50 percent further apart than lines in the FORWARD_DIAGONAL pattern
|
||||||
|
*/
|
||||||
LIGHT_DOWNWARD_DIAGONAL(0X00000012),
|
LIGHT_DOWNWARD_DIAGONAL(0X00000012),
|
||||||
|
/**
|
||||||
|
* Specifies diagonal lines that slant to the left from top to bottom points with no anti-aliasing.
|
||||||
|
* They are spaced 50 percent further apart than lines in the BACKWARD_DIAGONAL pattern.
|
||||||
|
*/
|
||||||
LIGHT_UPWARD_DIAGONAL(0X00000013),
|
LIGHT_UPWARD_DIAGONAL(0X00000013),
|
||||||
|
/**
|
||||||
|
* Specifies diagonal lines that slant to the right from top to bottom points with no anti-aliasing.
|
||||||
|
* They are spaced 50 percent closer and are twice the width of lines in the FORWARD_DIAGONAL pattern.
|
||||||
|
*/
|
||||||
DARK_DOWNWARD_DIAGONAL(0X00000014),
|
DARK_DOWNWARD_DIAGONAL(0X00000014),
|
||||||
|
/**
|
||||||
|
* Specifies diagonal lines that slant to the left from top to bottom points with no anti-aliasing.
|
||||||
|
* They are spaced 50 percent closer and are twice the width of lines in the BACKWARD_DIAGONAL pattern.
|
||||||
|
*/
|
||||||
DARK_UPWARD_DIAGONAL(0X00000015),
|
DARK_UPWARD_DIAGONAL(0X00000015),
|
||||||
|
/**
|
||||||
|
* Specifies diagonal lines that slant to the right from top to bottom points with no anti-aliasing.
|
||||||
|
* They have the same spacing between lines in WIDE_DOWNWARD_DIAGONAL pattern and FORWARD_DIAGONAL pattern,
|
||||||
|
* but WIDE_DOWNWARD_DIAGONAL has the triple line width of FORWARD_DIAGONAL.
|
||||||
|
*/
|
||||||
WIDE_DOWNWARD_DIAGONAL(0X00000016),
|
WIDE_DOWNWARD_DIAGONAL(0X00000016),
|
||||||
|
/**
|
||||||
|
* Specifies diagonal lines that slant to the left from top to bottom points with no anti-aliasing.
|
||||||
|
* They have the same spacing between lines in WIDE_UPWARD_DIAGONAL pattern and BACKWARD_DIAGONAL pattern,
|
||||||
|
* but WIDE_UPWARD_DIAGONAL has the triple line width of WIDE_UPWARD_DIAGONAL.
|
||||||
|
*/
|
||||||
WIDE_UPWARD_DIAGONAL(0X00000017),
|
WIDE_UPWARD_DIAGONAL(0X00000017),
|
||||||
|
/** Specifies vertical lines that are spaced 50 percent closer together than lines in the VERTICAL pattern. */
|
||||||
LIGHT_VERTICAL(0X00000018),
|
LIGHT_VERTICAL(0X00000018),
|
||||||
|
/** Specifies horizontal lines that are spaced 50 percent closer than lines in the HORIZONTAL pattern. */
|
||||||
LIGHT_HORIZONTAL(0X00000019),
|
LIGHT_HORIZONTAL(0X00000019),
|
||||||
|
/**
|
||||||
|
* Specifies vertical lines that are spaced 75 percent closer than lines in the VERTICAL pattern;
|
||||||
|
* or 25 percent closer than lines in the LIGHT_VERTICAL pattern.
|
||||||
|
*/
|
||||||
NARROW_VERTICAL(0X0000001A),
|
NARROW_VERTICAL(0X0000001A),
|
||||||
|
/**
|
||||||
|
* Specifies horizontal lines that are spaced 75 percent closer than lines in the HORIZONTAL pattern;
|
||||||
|
* or 25 percent closer than lines in the LIGHT_HORIZONTAL pattern.
|
||||||
|
*/
|
||||||
NARROW_HORIZONTAL(0X0000001B),
|
NARROW_HORIZONTAL(0X0000001B),
|
||||||
|
/** Specifies lines that are spaced 50 percent closer than lines in the VERTICAL pattern. */
|
||||||
DARK_VERTICAL(0X0000001C),
|
DARK_VERTICAL(0X0000001C),
|
||||||
|
/** Specifies lines that are spaced 50 percent closer than lines in the HORIZONTAL pattern. */
|
||||||
DARK_HORIZONTAL(0X0000001D),
|
DARK_HORIZONTAL(0X0000001D),
|
||||||
|
/** Specifies dashed diagonal lines that slant to the right from top to bottom points. */
|
||||||
DASHED_DOWNWARD_DIAGONAL(0X0000001E),
|
DASHED_DOWNWARD_DIAGONAL(0X0000001E),
|
||||||
|
/** Specifies dashed diagonal lines that slant to the left from top to bottom points. */
|
||||||
DASHED_UPWARD_DIAGONAL(0X0000001F),
|
DASHED_UPWARD_DIAGONAL(0X0000001F),
|
||||||
|
/** Specifies dashed horizontal lines. */
|
||||||
DASHED_HORIZONTAL(0X00000020),
|
DASHED_HORIZONTAL(0X00000020),
|
||||||
|
/** Specifies dashed vertical lines. */
|
||||||
DASHED_VERTICAL(0X00000021),
|
DASHED_VERTICAL(0X00000021),
|
||||||
|
/** Specifies a pattern of lines that has the appearance of confetti. */
|
||||||
SMALL_CONFETTI(0X00000022),
|
SMALL_CONFETTI(0X00000022),
|
||||||
|
/**
|
||||||
|
* Specifies a pattern of lines that has the appearance of confetti, and is composed of larger pieces
|
||||||
|
* than the SMALL_CONFETTI pattern.
|
||||||
|
*/
|
||||||
LARGE_CONFETTI(0X00000023),
|
LARGE_CONFETTI(0X00000023),
|
||||||
|
/** Specifies horizontal lines that are composed of zigzags. */
|
||||||
ZIGZAG(0X00000024),
|
ZIGZAG(0X00000024),
|
||||||
|
/** Specifies horizontal lines that are composed of tildes. */
|
||||||
WAVE(0X00000025),
|
WAVE(0X00000025),
|
||||||
|
/**
|
||||||
|
* Specifies a pattern of lines that has the appearance of layered bricks that slant to the left from
|
||||||
|
* top to bottom points.
|
||||||
|
*/
|
||||||
DIAGONAL_BRICK(0X00000026),
|
DIAGONAL_BRICK(0X00000026),
|
||||||
|
/** Specifies a pattern of lines that has the appearance of horizontally layered bricks. */
|
||||||
HORIZONTAL_BRICK(0X00000027),
|
HORIZONTAL_BRICK(0X00000027),
|
||||||
|
/** Specifies a pattern of lines that has the appearance of a woven material. */
|
||||||
WEAVE(0X00000028),
|
WEAVE(0X00000028),
|
||||||
|
/** Specifies a pattern of lines that has the appearance of a plaid material. */
|
||||||
PLAID(0X00000029),
|
PLAID(0X00000029),
|
||||||
|
/** Specifies a pattern of lines that has the appearance of divots. */
|
||||||
DIVOT(0X0000002A),
|
DIVOT(0X0000002A),
|
||||||
|
/** Specifies crossing horizontal and vertical lines, each of which is composed of dots. */
|
||||||
DOTTED_GRID(0X0000002B),
|
DOTTED_GRID(0X0000002B),
|
||||||
|
/** Specifies crossing forward and backward diagonal lines, each of which is composed of dots. */
|
||||||
DOTTED_DIAMOND(0X0000002C),
|
DOTTED_DIAMOND(0X0000002C),
|
||||||
|
/**
|
||||||
|
* Specifies a pattern of lines that has the appearance of diagonally layered
|
||||||
|
* shingles that slant to the right from top to bottom points.
|
||||||
|
*/
|
||||||
SHINGLE(0X0000002D),
|
SHINGLE(0X0000002D),
|
||||||
|
/** Specifies a pattern of lines that has the appearance of a trellis. */
|
||||||
TRELLIS(0X0000002E),
|
TRELLIS(0X0000002E),
|
||||||
|
/** Specifies a pattern of lines that has the appearance of spheres laid adjacent to each other. */
|
||||||
SPHERE(0X0000002F),
|
SPHERE(0X0000002F),
|
||||||
|
/** Specifies crossing horizontal and vertical lines that are spaced 50 percent closer together than LARGE_GRID. */
|
||||||
SMALL_GRID(0X00000030),
|
SMALL_GRID(0X00000030),
|
||||||
|
/** Specifies a pattern of lines that has the appearance of a checkerboard. */
|
||||||
SMALL_CHECKER_BOARD(0X00000031),
|
SMALL_CHECKER_BOARD(0X00000031),
|
||||||
|
/**
|
||||||
|
* Specifies a pattern of lines that has the appearance of a checkerboard, with squares that are twice the
|
||||||
|
* size of the squares in the SMALL_CHECKER_BOARD pattern.
|
||||||
|
*/
|
||||||
LARGE_CHECKER_BOARD(0X00000032),
|
LARGE_CHECKER_BOARD(0X00000032),
|
||||||
|
/** Specifies crossing forward and backward diagonal lines; the lines are not anti-aliased. */
|
||||||
OUTLINED_DIAMOND(0X00000033),
|
OUTLINED_DIAMOND(0X00000033),
|
||||||
|
/** Specifies a pattern of lines that has the appearance of a checkerboard placed diagonally. */
|
||||||
SOLID_DIAMOND(0X00000034)
|
SOLID_DIAMOND(0X00000034)
|
||||||
;
|
;
|
||||||
|
|
||||||
@ -204,17 +308,19 @@ public class HemfPlusBrush {
|
|||||||
BitField DO_NOT_TRANSFORM = BitFieldFactory.getInstance(0x00000100);
|
BitField DO_NOT_TRANSFORM = BitFieldFactory.getInstance(0x00000100);
|
||||||
|
|
||||||
long init(LittleEndianInputStream leis, long dataSize) throws IOException;
|
long init(LittleEndianInputStream leis, long dataSize) throws IOException;
|
||||||
|
|
||||||
|
void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The EmfPlusBrush object specifies a graphics brush for filling regions. */
|
/** The EmfPlusBrush object specifies a graphics brush for filling regions. */
|
||||||
public static class EmfPlusBrush implements EmfPlusObjectData {
|
public static class EmfPlusBrush implements EmfPlusObjectData {
|
||||||
private final EmfPlusGraphicsVersion version = new EmfPlusGraphicsVersion();
|
private final EmfPlusGraphicsVersion graphicsVersion = new EmfPlusGraphicsVersion();
|
||||||
private EmfPlusBrushType brushType;
|
private EmfPlusBrushType brushType;
|
||||||
private EmfPlusBrushData brushData;
|
private EmfPlusBrushData brushData;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long init(LittleEndianInputStream leis, long dataSize, EmfPlusObjectType objectType, int flags) throws IOException {
|
public long init(LittleEndianInputStream leis, long dataSize, EmfPlusObjectType objectType, int flags) throws IOException {
|
||||||
long size = version.init(leis);
|
long size = graphicsVersion.init(leis);
|
||||||
|
|
||||||
brushType = EmfPlusBrushType.valueOf(leis.readInt());
|
brushType = EmfPlusBrushType.valueOf(leis.readInt());
|
||||||
size += LittleEndianConsts.INT_SIZE;
|
size += LittleEndianConsts.INT_SIZE;
|
||||||
@ -224,6 +330,23 @@ public class HemfPlusBrush {
|
|||||||
|
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
|
||||||
|
brushData.applyObject(ctx, continuedObjectData);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EmfPlusGraphicsVersion getGraphicsVersion() {
|
||||||
|
return graphicsVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return
|
||||||
|
"{ brushType: '"+brushType+"'" +
|
||||||
|
", brushData: "+brushData+" }";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The EmfPlusSolidBrushData object specifies a solid color for a graphics brush. */
|
/** The EmfPlusSolidBrushData object specifies a solid color for a graphics brush. */
|
||||||
@ -234,6 +357,17 @@ public class HemfPlusBrush {
|
|||||||
solidColor = readARGB(leis.readInt());
|
solidColor = readARGB(leis.readInt());
|
||||||
return LittleEndianConsts.INT_SIZE;
|
return LittleEndianConsts.INT_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
|
||||||
|
HemfDrawProperties prop = ctx.getProperties();
|
||||||
|
prop.setBackgroundColor(new HwmfColorRef(solidColor));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "{ solidColor: "+new HwmfColorRef(solidColor)+" }";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -247,6 +381,22 @@ public class HemfPlusBrush {
|
|||||||
backColor = readARGB(leis.readInt());
|
backColor = readARGB(leis.readInt());
|
||||||
return 3*LittleEndianConsts.INT_SIZE;
|
return 3*LittleEndianConsts.INT_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
|
||||||
|
HemfDrawProperties prop = ctx.getProperties();
|
||||||
|
prop.setBrushColor(new HwmfColorRef(foreColor));
|
||||||
|
prop.setBackgroundColor(new HwmfColorRef(backColor));
|
||||||
|
prop.setEmfPlusBrushHatch(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return
|
||||||
|
"{ style: '"+style+"'" +
|
||||||
|
", foreColor: "+new HwmfColorRef(foreColor) +
|
||||||
|
", backColor: "+new HwmfColorRef(backColor) + " }";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The EmfPlusLinearGradientBrushData object specifies a linear gradient for a graphics brush. */
|
/** The EmfPlusLinearGradientBrushData object specifies a linear gradient for a graphics brush. */
|
||||||
@ -303,6 +453,32 @@ public class HemfPlusBrush {
|
|||||||
|
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
|
||||||
|
HemfDrawProperties prop = ctx.getProperties();
|
||||||
|
// TODO: implement
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return
|
||||||
|
"{ flags: "+dataFlags+
|
||||||
|
", wrapMode: '"+wrapMode+"'"+
|
||||||
|
", rect: "+boundsToString(rect)+
|
||||||
|
", startColor: "+new HwmfColorRef(startColor)+
|
||||||
|
", endColor: "+new HwmfColorRef(endColor)+
|
||||||
|
", transform: "+xformToString(transform)+
|
||||||
|
", positions: "+ Arrays.toString(positions)+
|
||||||
|
", blendColors: "+ colorsToString(blendColors)+
|
||||||
|
", positionsV: "+ Arrays.toString(positionsV)+
|
||||||
|
", blendFactorsV: "+ Arrays.toString(blendFactorsV)+
|
||||||
|
", positionsH: "+ Arrays.toString(positionsH)+
|
||||||
|
", blendFactorsH: "+ Arrays.toString(blendFactorsH)+
|
||||||
|
"}";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The EmfPlusPathGradientBrushData object specifies a path gradient for a graphics brush. */
|
/** The EmfPlusPathGradientBrushData object specifies a path gradient for a graphics brush. */
|
||||||
@ -409,6 +585,31 @@ public class HemfPlusBrush {
|
|||||||
|
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return
|
||||||
|
"{ flags: "+dataFlags+
|
||||||
|
", wrapMode: '"+wrapMode+"'"+
|
||||||
|
", centerColor: "+new HwmfColorRef(centerColor)+
|
||||||
|
", centerPoint: "+pointToString(centerPoint)+
|
||||||
|
", surroundingColor: "+colorsToString(surroundingColor)+
|
||||||
|
", boundaryPath: "+(boundaryPath == null ? "null" : boundaryPath)+
|
||||||
|
", boundaryPoints: "+pointsToString(boundaryPoints)+
|
||||||
|
", transform: "+xformToString(transform)+
|
||||||
|
", positions: "+Arrays.toString(positions)+
|
||||||
|
", blendColors: "+colorsToString(blendColors)+
|
||||||
|
", blendFactorsH: "+Arrays.toString(blendFactorsH)+
|
||||||
|
", focusScaleX: "+focusScaleX+
|
||||||
|
", focusScaleY: "+focusScaleY+
|
||||||
|
"}"
|
||||||
|
;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The EmfPlusTextureBrushData object specifies a texture image for a graphics brush. */
|
/** The EmfPlusTextureBrushData object specifies a texture image for a graphics brush. */
|
||||||
@ -440,6 +641,24 @@ public class HemfPlusBrush {
|
|||||||
|
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
|
||||||
|
image.applyObject(ctx, continuedObjectData);
|
||||||
|
HemfDrawProperties prop = ctx.getProperties();
|
||||||
|
prop.setBrushBitmap(prop.getEmfPlusImage());
|
||||||
|
prop.setBrushStyle(HwmfBrushStyle.BS_PATTERN);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return
|
||||||
|
"{ flags: "+dataFlags+
|
||||||
|
", wrapMode: '"+wrapMode+"'"+
|
||||||
|
", transform: "+xformToString(transform)+
|
||||||
|
", image: "+image+
|
||||||
|
"]";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int readPositions(LittleEndianInputStream leis, Consumer<double[]> pos) {
|
private static int readPositions(LittleEndianInputStream leis, Consumer<double[]> pos) {
|
||||||
@ -477,4 +696,18 @@ public class HemfPlusBrush {
|
|||||||
facs.accept(factors);
|
facs.accept(factors);
|
||||||
return size + factors.length * LittleEndianConsts.INT_SIZE;
|
return size + factors.length * LittleEndianConsts.INT_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Internal
|
||||||
|
public static String colorsToString(Color[] colors) {
|
||||||
|
return (colors == null ? "null" :
|
||||||
|
Stream.of(colors).map(HwmfColorRef::new).map(Object::toString).
|
||||||
|
collect(joining(",", "{", "}")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Internal
|
||||||
|
public static String pointsToString(Point2D[] points) {
|
||||||
|
return (points == null ? "null" :
|
||||||
|
Stream.of(points).map(HwmfDraw::pointToString).
|
||||||
|
collect(joining(",", "{", "}")));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,10 +17,18 @@
|
|||||||
|
|
||||||
package org.apache.poi.hemf.record.emfplus;
|
package org.apache.poi.hemf.record.emfplus;
|
||||||
|
|
||||||
|
import static java.util.stream.Collectors.joining;
|
||||||
|
import static org.apache.poi.hemf.record.emf.HemfDraw.xformToString;
|
||||||
|
import static org.apache.poi.hwmf.record.HwmfDraw.boundsToString;
|
||||||
|
import static org.apache.poi.hwmf.record.HwmfDraw.pointToString;
|
||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.awt.geom.AffineTransform;
|
import java.awt.geom.AffineTransform;
|
||||||
|
import java.awt.geom.Area;
|
||||||
|
import java.awt.geom.Path2D;
|
||||||
import java.awt.geom.Point2D;
|
import java.awt.geom.Point2D;
|
||||||
import java.awt.geom.Rectangle2D;
|
import java.awt.geom.Rectangle2D;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -30,8 +38,17 @@ import java.util.function.BiFunction;
|
|||||||
import org.apache.commons.math3.linear.LUDecomposition;
|
import org.apache.commons.math3.linear.LUDecomposition;
|
||||||
import org.apache.commons.math3.linear.MatrixUtils;
|
import org.apache.commons.math3.linear.MatrixUtils;
|
||||||
import org.apache.commons.math3.linear.RealMatrix;
|
import org.apache.commons.math3.linear.RealMatrix;
|
||||||
|
import org.apache.poi.hemf.draw.HemfDrawProperties;
|
||||||
|
import org.apache.poi.hemf.draw.HemfGraphics;
|
||||||
import org.apache.poi.hemf.record.emf.HemfFill;
|
import org.apache.poi.hemf.record.emf.HemfFill;
|
||||||
|
import org.apache.poi.hemf.record.emfplus.HemfPlusImage.EmfPlusImage;
|
||||||
import org.apache.poi.hemf.record.emfplus.HemfPlusMisc.EmfPlusObjectId;
|
import org.apache.poi.hemf.record.emfplus.HemfPlusMisc.EmfPlusObjectId;
|
||||||
|
import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObject;
|
||||||
|
import org.apache.poi.hwmf.record.HwmfBrushStyle;
|
||||||
|
import org.apache.poi.hwmf.record.HwmfColorRef;
|
||||||
|
import org.apache.poi.hwmf.record.HwmfDraw;
|
||||||
|
import org.apache.poi.hwmf.record.HwmfMisc.WmfSetBkMode.HwmfBkMode;
|
||||||
|
import org.apache.poi.hwmf.record.HwmfTernaryRasterOp;
|
||||||
import org.apache.poi.util.BitField;
|
import org.apache.poi.util.BitField;
|
||||||
import org.apache.poi.util.BitFieldFactory;
|
import org.apache.poi.util.BitFieldFactory;
|
||||||
import org.apache.poi.util.IOUtils;
|
import org.apache.poi.util.IOUtils;
|
||||||
@ -116,12 +133,45 @@ public class HemfPlusDraw {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface EmfPlusSolidColor {
|
||||||
|
/**
|
||||||
|
* If set, brushId specifies a color as an EmfPlusARGB object.
|
||||||
|
* If clear, brushId contains the index of an EmfPlusBrush object in the EMF+ Object Table.
|
||||||
|
*/
|
||||||
|
BitField SOLID_COLOR = BitFieldFactory.getInstance(0x8000);
|
||||||
|
|
||||||
|
int getFlags();
|
||||||
|
|
||||||
|
int getBrushIdValue();
|
||||||
|
|
||||||
|
default boolean isSolidColor() {
|
||||||
|
return SOLID_COLOR.isSet(getFlags());
|
||||||
|
}
|
||||||
|
|
||||||
|
default int getBrushId() {
|
||||||
|
return (isSolidColor()) ? -1 : getBrushIdValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
default Color getSolidColor() {
|
||||||
|
return (isSolidColor()) ? readARGB(getBrushIdValue()) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
default void applyColor(HemfGraphics ctx) {
|
||||||
|
HemfDrawProperties prop = ctx.getProperties();
|
||||||
|
if (isSolidColor()) {
|
||||||
|
prop.setBrushStyle(HwmfBrushStyle.BS_SOLID);
|
||||||
|
prop.setBrushColor(new HwmfColorRef(getSolidColor()));
|
||||||
|
} else {
|
||||||
|
ctx.applyObjectTableEntry(getBrushId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The EmfPlusDrawPath record specifies drawing a graphics path
|
* The EmfPlusDrawPath record specifies drawing a graphics path
|
||||||
*/
|
*/
|
||||||
public static class EmfPlusDrawPath implements HemfPlusRecord {
|
public static class EmfPlusDrawPath implements HemfPlusRecord, EmfPlusObjectId {
|
||||||
private int flags;
|
private int flags;
|
||||||
private int penId;
|
private int penId;
|
||||||
|
|
||||||
@ -148,18 +198,31 @@ public class HemfPlusDraw {
|
|||||||
|
|
||||||
return LittleEndianConsts.INT_SIZE;
|
return LittleEndianConsts.INT_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(HemfGraphics ctx) {
|
||||||
|
ctx.applyObjectTableEntry(penId);
|
||||||
|
ctx.applyObjectTableEntry(getObjectId());
|
||||||
|
|
||||||
|
HemfDrawProperties prop = ctx.getProperties();
|
||||||
|
final Path2D path = prop.getPath();
|
||||||
|
if (path != null) {
|
||||||
|
ctx.draw(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return
|
||||||
|
"{ flags: "+flags+
|
||||||
|
", penId: "+penId+" }";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The EmfPlusFillRects record specifies filling the interiors of a series of rectangles.
|
* The EmfPlusFillRects record specifies filling the interiors of a series of rectangles.
|
||||||
*/
|
*/
|
||||||
public static class EmfPlusFillRects implements HemfPlusRecord, EmfPlusCompressed {
|
public static class EmfPlusFillRects implements HemfPlusRecord, EmfPlusCompressed, EmfPlusSolidColor {
|
||||||
/**
|
|
||||||
* If set, brushId specifies a color as an EmfPlusARGB object.
|
|
||||||
* If clear, brushId contains the index of an EmfPlusBrush object in the EMF+ Object Table.
|
|
||||||
*/
|
|
||||||
private static final BitField SOLID_COLOR = BitFieldFactory.getInstance(0x8000);
|
|
||||||
|
|
||||||
private int flags;
|
private int flags;
|
||||||
private int brushId;
|
private int brushId;
|
||||||
private final ArrayList<Rectangle2D> rectData = new ArrayList<>();
|
private final ArrayList<Rectangle2D> rectData = new ArrayList<>();
|
||||||
@ -198,6 +261,29 @@ public class HemfPlusDraw {
|
|||||||
|
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(HemfGraphics ctx) {
|
||||||
|
applyColor(ctx);
|
||||||
|
|
||||||
|
Area area = new Area();
|
||||||
|
rectData.stream().map(Area::new).forEach(area::add);
|
||||||
|
ctx.fill(area);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getBrushIdValue() {
|
||||||
|
return brushId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return
|
||||||
|
"{ flags: "+flags+
|
||||||
|
", brushId: "+brushId+
|
||||||
|
", rectData: "+rectData.stream().map(HwmfDraw::boundsToString).collect(joining(",", "{", "}"))+
|
||||||
|
"}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class EmfPlusDrawImagePoints implements HemfPlusRecord, EmfPlusObjectId, EmfPlusCompressed, EmfPlusRelativePosition {
|
public static class EmfPlusDrawImagePoints implements HemfPlusRecord, EmfPlusObjectId, EmfPlusCompressed, EmfPlusRelativePosition {
|
||||||
@ -302,6 +388,58 @@ public class HemfPlusDraw {
|
|||||||
|
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(HemfGraphics ctx) {
|
||||||
|
HemfDrawProperties prop = ctx.getProperties();
|
||||||
|
|
||||||
|
ctx.applyObjectTableEntry(imageAttributesID);
|
||||||
|
ctx.applyObjectTableEntry(getObjectId());
|
||||||
|
|
||||||
|
AffineTransform txSaved = ctx.getTransform(), tx = new AffineTransform(txSaved);
|
||||||
|
try {
|
||||||
|
tx.concatenate(trans);
|
||||||
|
ctx.setTransform(tx);
|
||||||
|
|
||||||
|
EmfPlusObject imgObj = (EmfPlusObject)ctx.getObjectTableEntry(getObjectId());
|
||||||
|
EmfPlusImage img = imgObj.getObjectData();
|
||||||
|
Rectangle2D srcBounds = img.getBounds(imgObj.getContinuedObject());
|
||||||
|
BufferedImage bi = prop.getEmfPlusImage();
|
||||||
|
|
||||||
|
prop.setRasterOp(HwmfTernaryRasterOp.SRCCOPY);
|
||||||
|
prop.setBkMode(HwmfBkMode.TRANSPARENT);
|
||||||
|
|
||||||
|
// the buffered image might be rescaled, so we need to calculate a new src rect to take
|
||||||
|
// the image data from
|
||||||
|
AffineTransform srcTx = new AffineTransform();
|
||||||
|
srcTx.translate(-srcBounds.getX(), srcBounds.getY());
|
||||||
|
srcTx.scale(bi.getWidth()/srcBounds.getWidth(), bi.getHeight()/srcBounds.getHeight());
|
||||||
|
srcTx.translate(bi.getMinX(), bi.getMinY());
|
||||||
|
|
||||||
|
Rectangle2D biRect = srcTx.createTransformedShape(srcRect).getBounds2D();
|
||||||
|
|
||||||
|
// TODO: handle srcUnit
|
||||||
|
Rectangle2D destRect = new Rectangle2D.Double(0, 0, biRect.getWidth(), biRect.getHeight());
|
||||||
|
ctx.drawImage(bi, srcRect, destRect);
|
||||||
|
} finally {
|
||||||
|
ctx.setTransform(txSaved);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return
|
||||||
|
"{ flags: "+flags+
|
||||||
|
", imageAttributesID: "+imageAttributesID+
|
||||||
|
", srcUnit: '"+srcUnit+"'"+
|
||||||
|
", srcRect: "+boundsToString(srcRect)+
|
||||||
|
", upperLeft: "+pointToString(upperLeft)+
|
||||||
|
", lowerLeft: "+pointToString(lowerLeft)+
|
||||||
|
", lowerRight: "+pointToString(lowerRight)+
|
||||||
|
", transform: "+xformToString(trans)+
|
||||||
|
"}"
|
||||||
|
;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The EmfPlusDrawImage record specifies drawing a scaled image. */
|
/** The EmfPlusDrawImage record specifies drawing a scaled image. */
|
||||||
@ -347,12 +485,34 @@ public class HemfPlusDraw {
|
|||||||
|
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(HemfGraphics ctx) {
|
||||||
|
ctx.applyObjectTableEntry(imageAttributesID);
|
||||||
|
ctx.applyObjectTableEntry(getObjectId());
|
||||||
|
|
||||||
|
HemfDrawProperties prop = ctx.getProperties();
|
||||||
|
prop.setRasterOp(HwmfTernaryRasterOp.SRCCOPY);
|
||||||
|
prop.setBkMode(HwmfBkMode.TRANSPARENT);
|
||||||
|
|
||||||
|
ctx.drawImage(prop.getEmfPlusImage(), srcRect, rectData);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return
|
||||||
|
"{ flags: "+flags+
|
||||||
|
", imageAttributesID: "+imageAttributesID+
|
||||||
|
", srcUnit: '"+srcUnit+"'"+
|
||||||
|
", srcRect: "+boundsToString(srcRect)+
|
||||||
|
", rectData: "+boundsToString(rectData)+
|
||||||
|
"}"
|
||||||
|
;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The EmfPlusFillRegion record specifies filling the interior of a graphics region. */
|
/** The EmfPlusFillRegion record specifies filling the interior of a graphics region. */
|
||||||
public static class EmfPlusFillRegion implements HemfPlusRecord {
|
public static class EmfPlusFillRegion implements HemfPlusRecord, EmfPlusSolidColor, EmfPlusObjectId {
|
||||||
private static final BitField SOLID_COLOR = BitFieldFactory.getInstance(0x8000);
|
|
||||||
|
|
||||||
private int flags;
|
private int flags;
|
||||||
private int brushId;
|
private int brushId;
|
||||||
|
|
||||||
@ -366,16 +526,9 @@ public class HemfPlusDraw {
|
|||||||
return flags;
|
return flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSolidColor() {
|
@Override
|
||||||
return SOLID_COLOR.isSet(getFlags());
|
public int getBrushIdValue() {
|
||||||
}
|
return brushId;
|
||||||
|
|
||||||
public int getBrushId() {
|
|
||||||
return (isSolidColor()) ? -1 : brushId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Color getSolidColor() {
|
|
||||||
return (isSolidColor()) ? readARGB(brushId) : null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -390,6 +543,21 @@ public class HemfPlusDraw {
|
|||||||
|
|
||||||
return LittleEndianConsts.INT_SIZE;
|
return LittleEndianConsts.INT_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(HemfGraphics ctx) {
|
||||||
|
applyColor(ctx);
|
||||||
|
ctx.applyObjectTableEntry(getObjectId());
|
||||||
|
HemfDrawProperties prop = ctx.getProperties();
|
||||||
|
ctx.fill(prop.getPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return
|
||||||
|
"{ flags: "+flags+
|
||||||
|
", brushId: "+brushId+" }";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The EmfPlusFillPath record specifies filling the interior of a graphics path. */
|
/** The EmfPlusFillPath record specifies filling the interior of a graphics path. */
|
||||||
@ -403,13 +571,7 @@ public class HemfPlusDraw {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** The EmfPlusDrawDriverString record specifies text output with character positions. */
|
/** The EmfPlusDrawDriverString record specifies text output with character positions. */
|
||||||
public static class EmfPlusDrawDriverString implements HemfPlusRecord, EmfPlusObjectId {
|
public static class EmfPlusDrawDriverString implements HemfPlusRecord, EmfPlusObjectId, EmfPlusSolidColor {
|
||||||
/**
|
|
||||||
* If set, brushId specifies a color as an EmfPlusARGB object.
|
|
||||||
* If clear, brushId contains the index of an EmfPlusBrush object in the EMF+ Object Table.
|
|
||||||
*/
|
|
||||||
private static final BitField SOLID_COLOR = BitFieldFactory.getInstance(0x8000);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If set, the positions of character glyphs SHOULD be specified in a character map lookup table.
|
* If set, the positions of character glyphs SHOULD be specified in a character map lookup table.
|
||||||
* If clear, the glyph positions SHOULD be obtained from an array of coordinates.
|
* If clear, the glyph positions SHOULD be obtained from an array of coordinates.
|
||||||
@ -453,6 +615,11 @@ public class HemfPlusDraw {
|
|||||||
return flags;
|
return flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getBrushIdValue() {
|
||||||
|
return brushId;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException {
|
public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException {
|
||||||
this.flags = flags;
|
this.flags = flags;
|
||||||
@ -615,7 +782,7 @@ public class HemfPlusDraw {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Color readARGB(int argb) {
|
static Color readARGB(int argb) {
|
||||||
return new Color( (argb >>> 8) & 0xFF, (argb >>> 16) & 0xFF, (argb >>> 24) & 0xFF, argb & 0xFF);
|
return new Color((argb >>> 16) & 0xFF, (argb >>> 8) & 0xFF, argb & 0xFF, (argb >>> 24) & 0xFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,9 @@
|
|||||||
package org.apache.poi.hemf.record.emfplus;
|
package org.apache.poi.hemf.record.emfplus;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.poi.hemf.draw.HemfGraphics;
|
||||||
import org.apache.poi.hemf.record.emfplus.HemfPlusDraw.EmfPlusUnitType;
|
import org.apache.poi.hemf.record.emfplus.HemfPlusDraw.EmfPlusUnitType;
|
||||||
import org.apache.poi.hemf.record.emfplus.HemfPlusHeader.EmfPlusGraphicsVersion;
|
import org.apache.poi.hemf.record.emfplus.HemfPlusHeader.EmfPlusGraphicsVersion;
|
||||||
import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectData;
|
import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectData;
|
||||||
@ -60,7 +62,7 @@ public class HemfPlusFont {
|
|||||||
private static final BitField STRIKEOUT = BitFieldFactory.getInstance(0x00000008);
|
private static final BitField STRIKEOUT = BitFieldFactory.getInstance(0x00000008);
|
||||||
|
|
||||||
|
|
||||||
private final EmfPlusGraphicsVersion version = new EmfPlusGraphicsVersion();
|
private final EmfPlusGraphicsVersion graphicsVersion = new EmfPlusGraphicsVersion();
|
||||||
private double emSize;
|
private double emSize;
|
||||||
private EmfPlusUnitType sizeUnit;
|
private EmfPlusUnitType sizeUnit;
|
||||||
private int styleFlags;
|
private int styleFlags;
|
||||||
@ -70,7 +72,7 @@ public class HemfPlusFont {
|
|||||||
public long init(LittleEndianInputStream leis, long dataSize, HemfPlusObject.EmfPlusObjectType objectType, int flags) throws IOException {
|
public long init(LittleEndianInputStream leis, long dataSize, HemfPlusObject.EmfPlusObjectType objectType, int flags) throws IOException {
|
||||||
// An EmfPlusGraphicsVersion object that specifies the version of operating system graphics that was used
|
// An EmfPlusGraphicsVersion object that specifies the version of operating system graphics that was used
|
||||||
// to create this object.
|
// to create this object.
|
||||||
long size = version.init(leis);
|
long size = graphicsVersion.init(leis);
|
||||||
|
|
||||||
// A 32-bit floating-point value that specifies the em size of the font in units specified by the SizeUnit field.
|
// A 32-bit floating-point value that specifies the em size of the font in units specified by the SizeUnit field.
|
||||||
emSize = leis.readFloat();
|
emSize = leis.readFloat();
|
||||||
@ -96,5 +98,15 @@ public class HemfPlusFont {
|
|||||||
|
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EmfPlusGraphicsVersion getGraphicsVersion() {
|
||||||
|
return graphicsVersion;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,8 @@ package org.apache.poi.hemf.record.emfplus;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.apache.poi.hemf.draw.HemfGraphics;
|
||||||
|
import org.apache.poi.hemf.draw.HemfGraphics.EmfRenderState;
|
||||||
import org.apache.poi.util.BitField;
|
import org.apache.poi.util.BitField;
|
||||||
import org.apache.poi.util.BitFieldFactory;
|
import org.apache.poi.util.BitFieldFactory;
|
||||||
import org.apache.poi.util.Internal;
|
import org.apache.poi.util.Internal;
|
||||||
@ -85,6 +87,19 @@ public class HemfPlusHeader implements HemfPlusRecord {
|
|||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set, this flag indicates that this metafile is "dual-mode", which means that it contains two sets of records,
|
||||||
|
* each of which completely specifies the graphics content. If clear, the graphics content is specified by EMF+
|
||||||
|
* records, and possibly EMF records that are preceded by an EmfPlusGetDC record. If this flag is set, EMF records
|
||||||
|
* alone SHOULD suffice to define the graphics content. Note that whether the "dual-mode" flag is set or not, some
|
||||||
|
* EMF records are always present, namely EMF control records and the EMF records that contain EMF+ records.
|
||||||
|
*
|
||||||
|
* @return {@code true} if dual-mode is enabled
|
||||||
|
*/
|
||||||
|
public boolean isEmfPlusDualMode() {
|
||||||
|
return (emfPlusFlags & 1) == 1;
|
||||||
|
}
|
||||||
|
|
||||||
public long getEmfPlusFlags() {
|
public long getEmfPlusFlags() {
|
||||||
return emfPlusFlags;
|
return emfPlusFlags;
|
||||||
}
|
}
|
||||||
@ -97,6 +112,13 @@ public class HemfPlusHeader implements HemfPlusRecord {
|
|||||||
return logicalDpiY;
|
return logicalDpiY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(HemfGraphics ctx) {
|
||||||
|
// currently EMF is better supported than EMF+ ... so if there's a complete set of EMF records available,
|
||||||
|
// disable EMF+ rendering for now
|
||||||
|
ctx.setRenderState(isEmfPlusDualMode() ? EmfRenderState.EMF_ONLY : EmfRenderState.EMFPLUS_ONLY);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "HemfPlusHeader{" +
|
return "HemfPlusHeader{" +
|
||||||
|
@ -20,18 +20,45 @@ package org.apache.poi.hemf.record.emfplus;
|
|||||||
import static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readARGB;
|
import static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readARGB;
|
||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.RenderingHints;
|
||||||
|
import java.awt.Transparency;
|
||||||
|
import java.awt.color.ColorSpace;
|
||||||
|
import java.awt.geom.Dimension2D;
|
||||||
|
import java.awt.geom.Rectangle2D;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.awt.image.ComponentColorModel;
|
||||||
|
import java.awt.image.DataBuffer;
|
||||||
|
import java.awt.image.DataBufferByte;
|
||||||
|
import java.awt.image.PixelInterleavedSampleModel;
|
||||||
|
import java.awt.image.Raster;
|
||||||
|
import java.awt.image.WritableRaster;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
|
import org.apache.poi.hemf.draw.HemfDrawProperties;
|
||||||
|
import org.apache.poi.hemf.draw.HemfGraphics;
|
||||||
import org.apache.poi.hemf.record.emfplus.HemfPlusHeader.EmfPlusGraphicsVersion;
|
import org.apache.poi.hemf.record.emfplus.HemfPlusHeader.EmfPlusGraphicsVersion;
|
||||||
import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectData;
|
import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectData;
|
||||||
import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectType;
|
import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectType;
|
||||||
|
import org.apache.poi.hemf.usermodel.HemfPicture;
|
||||||
|
import org.apache.poi.hwmf.usermodel.HwmfPicture;
|
||||||
import org.apache.poi.util.BitField;
|
import org.apache.poi.util.BitField;
|
||||||
import org.apache.poi.util.BitFieldFactory;
|
import org.apache.poi.util.BitFieldFactory;
|
||||||
import org.apache.poi.util.IOUtils;
|
import org.apache.poi.util.IOUtils;
|
||||||
import org.apache.poi.util.LittleEndianConsts;
|
import org.apache.poi.util.LittleEndianConsts;
|
||||||
import org.apache.poi.util.LittleEndianInputStream;
|
import org.apache.poi.util.LittleEndianInputStream;
|
||||||
|
import org.apache.poi.util.Units;
|
||||||
|
|
||||||
public class HemfPlusImage {
|
public class HemfPlusImage {
|
||||||
|
/** Maximum image dimension for converting embedded metafiles */
|
||||||
|
private static final int MAX_IMAGE_SIZE = 1500;
|
||||||
|
|
||||||
/** The ImageDataType enumeration defines types of image data formats. */
|
/** The ImageDataType enumeration defines types of image data formats. */
|
||||||
public enum EmfPlusImageDataType {
|
public enum EmfPlusImageDataType {
|
||||||
/** The type of image is not known. */
|
/** The type of image is not known. */
|
||||||
@ -302,7 +329,7 @@ public class HemfPlusImage {
|
|||||||
leis.mark(LittleEndianConsts.INT_SIZE);
|
leis.mark(LittleEndianConsts.INT_SIZE);
|
||||||
long size = graphicsVersion.init(leis);
|
long size = graphicsVersion.init(leis);
|
||||||
|
|
||||||
if (graphicsVersion.getGraphicsVersion() == null || graphicsVersion.getMetafileSignature() != 0xDBC01) {
|
if (isContinuedRecord()) {
|
||||||
// CONTINUABLE is not always correctly set, so we check the version field if this record is continued
|
// CONTINUABLE is not always correctly set, so we check the version field if this record is continued
|
||||||
imageDataType = EmfPlusImageDataType.CONTINUED;
|
imageDataType = EmfPlusImageDataType.CONTINUED;
|
||||||
leis.reset();
|
leis.reset();
|
||||||
@ -385,8 +412,193 @@ public class HemfPlusImage {
|
|||||||
|
|
||||||
return size + fileSize;
|
return size + fileSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EmfPlusGraphicsVersion getGraphicsVersion() {
|
||||||
|
return graphicsVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rectangle2D getBounds(List<? extends EmfPlusObjectData> continuedObjectData) {
|
||||||
|
try {
|
||||||
|
switch (getImageDataType()) {
|
||||||
|
case BITMAP:
|
||||||
|
if (getBitmapType() == EmfPlusBitmapDataType.PIXEL) {
|
||||||
|
return new Rectangle2D.Double(0, 0, bitmapWidth, bitmapHeight);
|
||||||
|
} else {
|
||||||
|
BufferedImage bi = ImageIO.read(new ByteArrayInputStream(getRawData(continuedObjectData)));
|
||||||
|
return new Rectangle2D.Double(bi.getMinX(), bi.getMinY(), bi.getWidth(), bi.getHeight());
|
||||||
|
}
|
||||||
|
case METAFILE:
|
||||||
|
ByteArrayInputStream bis = new ByteArrayInputStream(getRawData(continuedObjectData));
|
||||||
|
switch (getMetafileType()) {
|
||||||
|
case Wmf:
|
||||||
|
case WmfPlaceable:
|
||||||
|
HwmfPicture wmf = new HwmfPicture(bis);
|
||||||
|
return wmf.getBounds();
|
||||||
|
case Emf:
|
||||||
|
case EmfPlusDual:
|
||||||
|
case EmfPlusOnly:
|
||||||
|
HemfPicture emf = new HemfPicture(bis);
|
||||||
|
return emf.getBounds();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
return new Rectangle2D.Double(1,1,1,1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getRawData(List<? extends EmfPlusObjectData> continuedObjectData) {
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
try {
|
||||||
|
bos.write(getImageData());
|
||||||
|
if (continuedObjectData != null) {
|
||||||
|
for (EmfPlusObjectData od : continuedObjectData) {
|
||||||
|
bos.write(((EmfPlusImage)od).getImageData());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return bos.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
|
||||||
|
HemfDrawProperties prop = ctx.getProperties();
|
||||||
|
BufferedImage bi = readImage(getRawData(continuedObjectData));
|
||||||
|
prop.setEmfPlusImage(bi);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the gdi pixel data to a buffered image
|
||||||
|
* @param data the image data of all EmfPlusImage parts
|
||||||
|
* @return the BufferedImage
|
||||||
|
*/
|
||||||
|
public BufferedImage readGDIImage(final byte[] data) {
|
||||||
|
if (getImageDataType() != EmfPlusImageDataType.BITMAP || getBitmapType() != EmfPlusBitmapDataType.PIXEL) {
|
||||||
|
throw new RuntimeException("image data is not a GDI image");
|
||||||
|
}
|
||||||
|
|
||||||
|
final int width = getBitmapWidth();
|
||||||
|
final int height = getBitmapHeight();
|
||||||
|
final int stride = getBitmapStride();
|
||||||
|
final EmfPlusPixelFormat pf = getPixelFormat();
|
||||||
|
|
||||||
|
int[] nBits, bOffs;
|
||||||
|
switch (pf) {
|
||||||
|
case ARGB_32BPP:
|
||||||
|
nBits = new int[]{8, 8, 8, 8};
|
||||||
|
bOffs = new int[]{2, 1, 0, 3};
|
||||||
|
break;
|
||||||
|
case RGB_24BPP:
|
||||||
|
nBits = new int[]{8, 8, 8};
|
||||||
|
bOffs = new int[]{2, 1, 0};
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new RuntimeException("not yet implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
|
||||||
|
ComponentColorModel cm = new ComponentColorModel
|
||||||
|
(cs, nBits, pf.isAlpha(), pf.isPreMultiplied(), Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE);
|
||||||
|
PixelInterleavedSampleModel csm =
|
||||||
|
new PixelInterleavedSampleModel(cm.getTransferType(), width, height, cm.getNumComponents(), stride, bOffs);
|
||||||
|
|
||||||
|
DataBufferByte dbb = new DataBufferByte(data, data.length);
|
||||||
|
WritableRaster raster = (WritableRaster) Raster.createRaster(csm, dbb, null);
|
||||||
|
|
||||||
|
return new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BufferedImage readImage(final byte[] data) {
|
||||||
|
// TODO: instead of returning a BufferedImage, we might return a pair of raw data + image renderer
|
||||||
|
// instead, so metafiles aren't pixelated, but directly written to the output graphics context
|
||||||
|
try {
|
||||||
|
switch (getImageDataType()) {
|
||||||
|
case BITMAP: {
|
||||||
|
BufferedImage bi = (getBitmapType() == EmfPlusBitmapDataType.PIXEL)
|
||||||
|
? readGDIImage(data)
|
||||||
|
: ImageIO.read(new ByteArrayInputStream(data));
|
||||||
|
|
||||||
|
// final int w = bi.getWidth();
|
||||||
|
// final int h = bi.getHeight();
|
||||||
|
//
|
||||||
|
// int[] line = new int[w];
|
||||||
|
//
|
||||||
|
// WritableRaster wr = bi.getRaster();
|
||||||
|
// for (int row=0; row<h; row++) {
|
||||||
|
// wr.get
|
||||||
|
// for (int x=0; x<w; x++) {
|
||||||
|
// // TODO: use clamp color here
|
||||||
|
// if ((line[x] & 0xFFFFFF) == 0) {
|
||||||
|
// // make it transparent
|
||||||
|
// line[x] &= 0xFFFFFF;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// wr.setPixels(0, row, w, 1, line);
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
return bi;
|
||||||
|
}
|
||||||
|
case METAFILE:
|
||||||
|
assert (getMetafileType() != null);
|
||||||
|
switch (getMetafileType()) {
|
||||||
|
case Wmf:
|
||||||
|
case WmfPlaceable:
|
||||||
|
HwmfPicture wmf = new HwmfPicture(new ByteArrayInputStream(data));
|
||||||
|
return readImage(wmf.getSize(), wmf::draw);
|
||||||
|
|
||||||
|
case Emf:
|
||||||
|
case EmfPlusDual:
|
||||||
|
case EmfPlusOnly:
|
||||||
|
HemfPicture emf = new HemfPicture(new ByteArrayInputStream(data));
|
||||||
|
return readImage(emf.getSize(), emf::draw);
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback to empty image
|
||||||
|
return new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BufferedImage readImage(final Dimension2D dim, final BiConsumer<Graphics2D,Rectangle2D> draw) {
|
||||||
|
int width = Units.pointsToPixel(dim.getWidth());
|
||||||
|
// keep aspect ratio for height
|
||||||
|
int height = Units.pointsToPixel(dim.getHeight());
|
||||||
|
double longSide = Math.max(width,height);
|
||||||
|
if (longSide > MAX_IMAGE_SIZE) {
|
||||||
|
double scale = MAX_IMAGE_SIZE / longSide;
|
||||||
|
width *= scale;
|
||||||
|
height *= scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
BufferedImage bufImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
Graphics2D g = bufImg.createGraphics();
|
||||||
|
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||||
|
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
|
||||||
|
g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
|
||||||
|
|
||||||
|
draw.accept(g, new Rectangle2D.Double(0, 0, width, height));
|
||||||
|
|
||||||
|
g.dispose();
|
||||||
|
|
||||||
|
return bufImg;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static class EmfPlusImageAttributes implements EmfPlusObjectData {
|
public static class EmfPlusImageAttributes implements EmfPlusObjectData {
|
||||||
private final EmfPlusGraphicsVersion graphicsVersion = new EmfPlusGraphicsVersion();
|
private final EmfPlusGraphicsVersion graphicsVersion = new EmfPlusGraphicsVersion();
|
||||||
private EmfPlusWrapMode wrapMode;
|
private EmfPlusWrapMode wrapMode;
|
||||||
@ -434,6 +646,10 @@ public class HemfPlusImage {
|
|||||||
public EmfPlusObjectClamp getObjectClamp() {
|
public EmfPlusObjectClamp getObjectClamp() {
|
||||||
return objectClamp;
|
return objectClamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
package org.apache.poi.hemf.record.emfplus;
|
package org.apache.poi.hemf.record.emfplus;
|
||||||
|
|
||||||
|
import static org.apache.poi.hemf.record.emf.HemfMisc.adaptXForm;
|
||||||
import static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readRectF;
|
import static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readRectF;
|
||||||
|
|
||||||
import java.awt.geom.AffineTransform;
|
import java.awt.geom.AffineTransform;
|
||||||
@ -24,10 +25,10 @@ import java.awt.geom.Point2D;
|
|||||||
import java.awt.geom.Rectangle2D;
|
import java.awt.geom.Rectangle2D;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.apache.poi.hemf.draw.HemfGraphics;
|
||||||
import org.apache.poi.hemf.record.emf.HemfFill;
|
import org.apache.poi.hemf.record.emf.HemfFill;
|
||||||
import org.apache.poi.util.BitField;
|
import org.apache.poi.util.BitField;
|
||||||
import org.apache.poi.util.BitFieldFactory;
|
import org.apache.poi.util.BitFieldFactory;
|
||||||
import org.apache.poi.util.LittleEndian;
|
|
||||||
import org.apache.poi.util.LittleEndianConsts;
|
import org.apache.poi.util.LittleEndianConsts;
|
||||||
import org.apache.poi.util.LittleEndianInputStream;
|
import org.apache.poi.util.LittleEndianInputStream;
|
||||||
|
|
||||||
@ -134,6 +135,12 @@ public class HemfPlusMisc {
|
|||||||
* SHOULD be processed.
|
* SHOULD be processed.
|
||||||
*/
|
*/
|
||||||
public static class EmfPlusGetDC extends EmfPlusFlagOnly {
|
public static class EmfPlusGetDC extends EmfPlusFlagOnly {
|
||||||
|
@Override
|
||||||
|
public void draw(HemfGraphics ctx) {
|
||||||
|
if (ctx.getRenderState() == HemfGraphics.EmfRenderState.EMFPLUS_ONLY) {
|
||||||
|
ctx.setRenderState(HemfGraphics.EmfRenderState.EMF_DCONTEXT);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -174,6 +181,18 @@ public class HemfPlusMisc {
|
|||||||
|
|
||||||
return HemfFill.readXForm(leis, matrixData);
|
return HemfFill.readXForm(leis, matrixData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AffineTransform getMatrixData() {
|
||||||
|
return matrixData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(HemfGraphics ctx) {
|
||||||
|
ctx.updateWindowMapMode();
|
||||||
|
AffineTransform tx = ctx.getTransform();
|
||||||
|
tx.concatenate(getMatrixData());
|
||||||
|
ctx.setTransform(tx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -185,6 +204,15 @@ public class HemfPlusMisc {
|
|||||||
public HemfPlusRecordType getEmfPlusRecordType() {
|
public HemfPlusRecordType getEmfPlusRecordType() {
|
||||||
return HemfPlusRecordType.multiplyWorldTransform;
|
return HemfPlusRecordType.multiplyWorldTransform;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(HemfGraphics ctx) {
|
||||||
|
ctx.updateWindowMapMode();
|
||||||
|
AffineTransform tx = ctx.getTransform();
|
||||||
|
tx.preConcatenate(adaptXForm(getMatrixData(), tx));
|
||||||
|
tx.concatenate(getMatrixData());
|
||||||
|
ctx.setTransform(tx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -18,8 +18,11 @@
|
|||||||
package org.apache.poi.hemf.record.emfplus;
|
package org.apache.poi.hemf.record.emfplus;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.apache.poi.hemf.draw.HemfGraphics;
|
||||||
import org.apache.poi.hemf.record.emfplus.HemfPlusBrush.EmfPlusBrush;
|
import org.apache.poi.hemf.record.emfplus.HemfPlusBrush.EmfPlusBrush;
|
||||||
import org.apache.poi.hemf.record.emfplus.HemfPlusFont.EmfPlusFont;
|
import org.apache.poi.hemf.record.emfplus.HemfPlusFont.EmfPlusFont;
|
||||||
import org.apache.poi.hemf.record.emfplus.HemfPlusHeader.EmfPlusGraphicsVersion;
|
import org.apache.poi.hemf.record.emfplus.HemfPlusHeader.EmfPlusGraphicsVersion;
|
||||||
@ -29,6 +32,8 @@ import org.apache.poi.hemf.record.emfplus.HemfPlusMisc.EmfPlusObjectId;
|
|||||||
import org.apache.poi.hemf.record.emfplus.HemfPlusPath.EmfPlusPath;
|
import org.apache.poi.hemf.record.emfplus.HemfPlusPath.EmfPlusPath;
|
||||||
import org.apache.poi.hemf.record.emfplus.HemfPlusPen.EmfPlusPen;
|
import org.apache.poi.hemf.record.emfplus.HemfPlusPen.EmfPlusPen;
|
||||||
import org.apache.poi.hemf.record.emfplus.HemfPlusRegion.EmfPlusRegion;
|
import org.apache.poi.hemf.record.emfplus.HemfPlusRegion.EmfPlusRegion;
|
||||||
|
import org.apache.poi.hwmf.draw.HwmfGraphics;
|
||||||
|
import org.apache.poi.hwmf.record.HwmfObjectTableEntry;
|
||||||
import org.apache.poi.util.BitField;
|
import org.apache.poi.util.BitField;
|
||||||
import org.apache.poi.util.BitFieldFactory;
|
import org.apache.poi.util.BitFieldFactory;
|
||||||
import org.apache.poi.util.IOUtils;
|
import org.apache.poi.util.IOUtils;
|
||||||
@ -107,7 +112,7 @@ public class HemfPlusObject {
|
|||||||
* The EmfPlusObject record specifies an object for use in graphics operations. The object definition
|
* The EmfPlusObject record specifies an object for use in graphics operations. The object definition
|
||||||
* can span multiple records), which is indicated by the value of the Flags field.
|
* can span multiple records), which is indicated by the value of the Flags field.
|
||||||
*/
|
*/
|
||||||
public static class EmfPlusObject implements HemfPlusRecord, EmfPlusObjectId {
|
public static class EmfPlusObject implements HemfPlusRecord, EmfPlusObjectId, HwmfObjectTableEntry {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -126,6 +131,7 @@ public class HemfPlusObject {
|
|||||||
// for debugging
|
// for debugging
|
||||||
private int objectId;
|
private int objectId;
|
||||||
private EmfPlusObjectData objectData;
|
private EmfPlusObjectData objectData;
|
||||||
|
private List<EmfPlusObjectData> continuedObjectData;
|
||||||
private int totalObjectSize;
|
private int totalObjectSize;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -174,10 +180,50 @@ public class HemfPlusObject {
|
|||||||
|
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(HemfGraphics ctx) {
|
||||||
|
HwmfObjectTableEntry entry = ctx.getObjectTableEntry(getObjectId());
|
||||||
|
if (objectData.isContinuedRecord()) {
|
||||||
|
EmfPlusObject other;
|
||||||
|
if (entry instanceof EmfPlusObject && objectData.getClass().isInstance((other = (EmfPlusObject)entry).getObjectData())) {
|
||||||
|
other.linkContinuedObject(objectData);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("can't find previous record for continued record");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.addObjectTableEntry(this, getObjectId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void applyObject(HwmfGraphics ctx) {
|
||||||
|
objectData.applyObject((HemfGraphics)ctx, continuedObjectData);
|
||||||
|
}
|
||||||
|
|
||||||
|
void linkContinuedObject(EmfPlusObjectData continueObject) {
|
||||||
|
if (continuedObjectData == null) {
|
||||||
|
continuedObjectData = new ArrayList<>();
|
||||||
|
}
|
||||||
|
continuedObjectData.add(continueObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<EmfPlusObjectData> getContinuedObject() {
|
||||||
|
return continuedObjectData;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface EmfPlusObjectData {
|
public interface EmfPlusObjectData {
|
||||||
long init(LittleEndianInputStream leis, long dataSize, EmfPlusObjectType objectType, int flags) throws IOException;
|
long init(LittleEndianInputStream leis, long dataSize, EmfPlusObjectType objectType, int flags) throws IOException;
|
||||||
|
|
||||||
|
void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData);
|
||||||
|
|
||||||
|
EmfPlusGraphicsVersion getGraphicsVersion();
|
||||||
|
|
||||||
|
default boolean isContinuedRecord() {
|
||||||
|
EmfPlusGraphicsVersion gv = getGraphicsVersion();
|
||||||
|
return (gv.getGraphicsVersion() == null || gv.getMetafileSignature() != 0xDBC01);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class EmfPlusUnknownData implements EmfPlusObjectData {
|
public static class EmfPlusUnknownData implements EmfPlusObjectData {
|
||||||
@ -195,5 +241,15 @@ public class HemfPlusObject {
|
|||||||
|
|
||||||
return dataSize;
|
return dataSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EmfPlusGraphicsVersion getGraphicsVersion() {
|
||||||
|
return graphicsVersion;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,11 +17,15 @@
|
|||||||
|
|
||||||
package org.apache.poi.hemf.record.emfplus;
|
package org.apache.poi.hemf.record.emfplus;
|
||||||
|
|
||||||
|
import java.awt.geom.Path2D;
|
||||||
import java.awt.geom.Point2D;
|
import java.awt.geom.Point2D;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
|
|
||||||
|
import org.apache.poi.hemf.draw.HemfDrawProperties;
|
||||||
|
import org.apache.poi.hemf.draw.HemfGraphics;
|
||||||
import org.apache.poi.hemf.record.emfplus.HemfPlusDraw.EmfPlusCompressed;
|
import org.apache.poi.hemf.record.emfplus.HemfPlusDraw.EmfPlusCompressed;
|
||||||
import org.apache.poi.hemf.record.emfplus.HemfPlusDraw.EmfPlusRelativePosition;
|
import org.apache.poi.hemf.record.emfplus.HemfPlusDraw.EmfPlusRelativePosition;
|
||||||
import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectData;
|
import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectData;
|
||||||
@ -69,14 +73,14 @@ public class HemfPlusPath {
|
|||||||
|
|
||||||
private static final BitField POINT_RLE_COUNT = BitFieldFactory.getInstance(0x3F);
|
private static final BitField POINT_RLE_COUNT = BitFieldFactory.getInstance(0x3F);
|
||||||
|
|
||||||
private final HemfPlusHeader.EmfPlusGraphicsVersion version = new HemfPlusHeader.EmfPlusGraphicsVersion();
|
private final HemfPlusHeader.EmfPlusGraphicsVersion graphicsVersion = new HemfPlusHeader.EmfPlusGraphicsVersion();
|
||||||
private int pointFlags;
|
private int pointFlags;
|
||||||
private Point2D[] pathPoints;
|
private Point2D[] pathPoints;
|
||||||
private byte[] pointTypes;
|
private byte[] pointTypes;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long init(LittleEndianInputStream leis, long dataSize, EmfPlusObjectType objectType, int flags) throws IOException {
|
public long init(LittleEndianInputStream leis, long dataSize, EmfPlusObjectType objectType, int flags) throws IOException {
|
||||||
long size = version.init(leis);
|
long size = graphicsVersion.init(leis);
|
||||||
|
|
||||||
// A 32-bit unsigned integer that specifies the number of points and associated point types that
|
// A 32-bit unsigned integer that specifies the number of points and associated point types that
|
||||||
// are defined by this object.
|
// are defined by this object.
|
||||||
@ -124,6 +128,11 @@ public class HemfPlusPath {
|
|||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HemfPlusHeader.EmfPlusGraphicsVersion getGraphicsVersion() {
|
||||||
|
return graphicsVersion;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isPointDashed(int index) {
|
public boolean isPointDashed(int index) {
|
||||||
return POINT_TYPE_DASHED.isSet(pointTypes[index]);
|
return POINT_TYPE_DASHED.isSet(pointTypes[index]);
|
||||||
}
|
}
|
||||||
@ -144,6 +153,38 @@ public class HemfPlusPath {
|
|||||||
public int getFlags() {
|
public int getFlags() {
|
||||||
return pointFlags;
|
return pointFlags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
|
||||||
|
HemfDrawProperties prop = ctx.getProperties();
|
||||||
|
Path2D path = new Path2D.Double(Path2D.WIND_NON_ZERO);
|
||||||
|
prop.setPath(path);
|
||||||
|
|
||||||
|
for (int idx=0; idx < pathPoints.length; idx++) {
|
||||||
|
Point2D p1 = pathPoints[idx];
|
||||||
|
switch (getPointType(idx)) {
|
||||||
|
case START:
|
||||||
|
path.moveTo(p1.getX(), p1.getY());
|
||||||
|
break;
|
||||||
|
case LINE:
|
||||||
|
path.lineTo(p1.getX(), p1.getY());
|
||||||
|
break;
|
||||||
|
case BEZIER: {
|
||||||
|
Point2D p2 = pathPoints[++idx];
|
||||||
|
Point2D p3 = pathPoints[++idx];
|
||||||
|
path.curveTo(p1.getX(), p1.getY(), p2.getX(), p2.getY(), p3.getX(), p3.getY());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isPointClosed(idx)) {
|
||||||
|
path.closePath();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,12 +23,17 @@ import static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readPointF;
|
|||||||
import java.awt.geom.AffineTransform;
|
import java.awt.geom.AffineTransform;
|
||||||
import java.awt.geom.Point2D;
|
import java.awt.geom.Point2D;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import org.apache.poi.hemf.draw.HemfDrawProperties;
|
||||||
|
import org.apache.poi.hemf.draw.HemfGraphics;
|
||||||
|
import org.apache.poi.hemf.record.emfplus.HemfPlusDraw.EmfPlusUnitType;
|
||||||
import org.apache.poi.hemf.record.emfplus.HemfPlusHeader.EmfPlusGraphicsVersion;
|
import org.apache.poi.hemf.record.emfplus.HemfPlusHeader.EmfPlusGraphicsVersion;
|
||||||
import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectData;
|
import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectData;
|
||||||
import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectType;
|
import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectType;
|
||||||
import org.apache.poi.hemf.record.emfplus.HemfPlusPath.EmfPlusPath;
|
import org.apache.poi.hemf.record.emfplus.HemfPlusPath.EmfPlusPath;
|
||||||
|
import org.apache.poi.hwmf.record.HwmfPenStyle;
|
||||||
import org.apache.poi.util.BitField;
|
import org.apache.poi.util.BitField;
|
||||||
import org.apache.poi.util.BitFieldFactory;
|
import org.apache.poi.util.BitFieldFactory;
|
||||||
import org.apache.poi.util.Internal;
|
import org.apache.poi.util.Internal;
|
||||||
@ -215,6 +220,13 @@ public class HemfPlusPen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Internal
|
||||||
|
public interface EmfPlusCustomLineCap {
|
||||||
|
long init(LittleEndianInputStream leis) throws IOException;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static class EmfPlusPen implements EmfPlusObjectData {
|
public static class EmfPlusPen implements EmfPlusObjectData {
|
||||||
|
|
||||||
|
|
||||||
@ -283,16 +295,17 @@ public class HemfPlusPen {
|
|||||||
|
|
||||||
private int type;
|
private int type;
|
||||||
private int penDataFlags;
|
private int penDataFlags;
|
||||||
private HemfPlusDraw.EmfPlusUnitType unitType;
|
private EmfPlusUnitType unitType;
|
||||||
private double penWidth;
|
private double penWidth;
|
||||||
private AffineTransform trans;
|
private final AffineTransform trans = new AffineTransform();
|
||||||
private EmfPlusLineCapType startCap, endCap;
|
private EmfPlusLineCapType startCap = EmfPlusLineCapType.FLAT;
|
||||||
private EmfPlusLineJoin join;
|
private EmfPlusLineCapType endCap = startCap;
|
||||||
private Double mitterLimit;
|
private EmfPlusLineJoin join = EmfPlusLineJoin.ROUND;
|
||||||
private EmfPlusLineStyle style;
|
private Double miterLimit = 1.;
|
||||||
EmfPlusDashedLineCapType dashedLineCapType;
|
private EmfPlusLineStyle style = EmfPlusLineStyle.SOLID;
|
||||||
|
private EmfPlusDashedLineCapType dashedLineCapType;
|
||||||
private Double dashOffset;
|
private Double dashOffset;
|
||||||
private double[] dashedLineData;
|
private float[] dashedLineData;
|
||||||
private EmfPlusPenAlignment penAlignment;
|
private EmfPlusPenAlignment penAlignment;
|
||||||
private double[] compoundLineData;
|
private double[] compoundLineData;
|
||||||
private EmfPlusCustomLineCap customStartCap;
|
private EmfPlusCustomLineCap customStartCap;
|
||||||
@ -310,17 +323,16 @@ public class HemfPlusPen {
|
|||||||
penDataFlags = leis.readInt();
|
penDataFlags = leis.readInt();
|
||||||
// A 32-bit unsigned integer that specifies the measuring units for the pen.
|
// A 32-bit unsigned integer that specifies the measuring units for the pen.
|
||||||
// The value MUST be from the UnitType enumeration
|
// The value MUST be from the UnitType enumeration
|
||||||
unitType = HemfPlusDraw.EmfPlusUnitType.valueOf(leis.readInt());
|
unitType = EmfPlusUnitType.valueOf(leis.readInt());
|
||||||
// A 32-bit floating-point value that specifies the width of the line drawn by the pen in the units specified
|
// A 32-bit floating-point value that specifies the width of the line drawn by the pen in the units specified
|
||||||
// by the PenUnit field. If a zero width is specified, a minimum value is used, which is determined by the units.
|
// by the PenUnit field. If a zero width is specified, a minimum value is used, which is determined by the units.
|
||||||
penWidth = leis.readFloat();
|
penWidth = leis.readFloat();
|
||||||
size += 4* LittleEndianConsts.INT_SIZE;
|
size += 4*LittleEndianConsts.INT_SIZE;
|
||||||
|
|
||||||
if (TRANSFORM.isSet(penDataFlags)) {
|
if (TRANSFORM.isSet(penDataFlags)) {
|
||||||
// An optional EmfPlusTransformMatrix object that specifies a world space to device space transform for
|
// An optional EmfPlusTransformMatrix object that specifies a world space to device space transform for
|
||||||
// the pen. This field MUST be present if the PenDataTransform flag is set in the PenDataFlags field of
|
// the pen. This field MUST be present if the PenDataTransform flag is set in the PenDataFlags field of
|
||||||
// the EmfPlusPenData object.
|
// the EmfPlusPenData object.
|
||||||
trans = new AffineTransform();
|
|
||||||
size += readXForm(leis, trans);
|
size += readXForm(leis, trans);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -354,7 +366,7 @@ public class HemfPlusPen {
|
|||||||
// line walls on the inside the join to the intersection of the line walls outside the join. The miter
|
// line walls on the inside the join to the intersection of the line walls outside the join. The miter
|
||||||
// length can be large when the angle between two lines is small. This field MUST be present if the
|
// length can be large when the angle between two lines is small. This field MUST be present if the
|
||||||
// PenDataMiterLimit flag is set in the PenDataFlags field of the EmfPlusPenData object.
|
// PenDataMiterLimit flag is set in the PenDataFlags field of the EmfPlusPenData object.
|
||||||
mitterLimit = (double)leis.readFloat();
|
miterLimit = (double)leis.readFloat();
|
||||||
size += LittleEndianConsts.INT_SIZE;
|
size += LittleEndianConsts.INT_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -390,7 +402,7 @@ public class HemfPlusPen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// An array of DashedLineDataSize floating-point values that specify the lengths of the dashes and spaces in a dashed line.
|
// An array of DashedLineDataSize floating-point values that specify the lengths of the dashes and spaces in a dashed line.
|
||||||
dashedLineData = new double[dashesSize];
|
dashedLineData = new float[dashesSize];
|
||||||
for (int i=0; i<dashesSize; i++) {
|
for (int i=0; i<dashesSize; i++) {
|
||||||
dashedLineData[i] = leis.readFloat();
|
dashedLineData[i] = leis.readFloat();
|
||||||
}
|
}
|
||||||
@ -435,6 +447,11 @@ public class HemfPlusPen {
|
|||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EmfPlusGraphicsVersion getGraphicsVersion() {
|
||||||
|
return graphicsVersion;
|
||||||
|
}
|
||||||
|
|
||||||
private long initCustomCap(Consumer<EmfPlusCustomLineCap> setter, LittleEndianInputStream leis) throws IOException {
|
private long initCustomCap(Consumer<EmfPlusCustomLineCap> setter, LittleEndianInputStream leis) throws IOException {
|
||||||
EmfPlusGraphicsVersion version = new EmfPlusGraphicsVersion();
|
EmfPlusGraphicsVersion version = new EmfPlusGraphicsVersion();
|
||||||
long size = version.init(leis);
|
long size = version.init(leis);
|
||||||
@ -450,153 +467,209 @@ public class HemfPlusPen {
|
|||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Internal
|
@Override
|
||||||
public interface EmfPlusCustomLineCap {
|
public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
|
||||||
long init(LittleEndianInputStream leis) throws IOException;
|
final HemfDrawProperties prop = ctx.getProperties();
|
||||||
}
|
// TOOD:
|
||||||
|
// - set width according unit type
|
||||||
|
// - provide logic for different start and end cap
|
||||||
|
// - provide standard caps like diamondd
|
||||||
|
// - support custom caps
|
||||||
|
|
||||||
public static class EmfPlusPathArrowCap implements EmfPlusCustomLineCap {
|
// workaround for too wide pens ... just arbitrary reduce high values ...
|
||||||
/**
|
prop.setPenWidth(penWidth > 20 ? 1 : penWidth);
|
||||||
* If set, an EmfPlusFillPath object MUST be specified in the OptionalData field of the
|
prop.setPenStyle(new HwmfPenStyle(){
|
||||||
* EmfPlusCustomLineCapData object for filling the custom line cap.
|
@Override
|
||||||
*/
|
public HwmfLineCap getLineCap() {
|
||||||
private static final BitField FILL_PATH = BitFieldFactory.getInstance(0x00000001);
|
// ignore endCap for now
|
||||||
/**
|
switch(startCap) {
|
||||||
* If set, an EmfPlusLinePath object MUST be specified in the OptionalData field of the
|
default:
|
||||||
* EmfPlusCustomLineCapData object for outlining the custom line cap.
|
case FLAT:
|
||||||
*/
|
return HwmfLineCap.FLAT;
|
||||||
private static final BitField LINE_PATH = BitFieldFactory.getInstance(0x00000002);
|
case ROUND:
|
||||||
|
return HwmfLineCap.ROUND;
|
||||||
|
case SQUARE:
|
||||||
private int dataFlags;
|
return HwmfLineCap.SQUARE;
|
||||||
private EmfPlusLineCapType baseCap;
|
}
|
||||||
private double baseInset;
|
|
||||||
private EmfPlusLineCapType startCap;
|
|
||||||
private EmfPlusLineCapType endCap;
|
|
||||||
private EmfPlusLineJoin join;
|
|
||||||
private double mitterLimit;
|
|
||||||
private double widthScale;
|
|
||||||
private final Point2D fillHotSpot = new Point2D.Double();
|
|
||||||
private final Point2D lineHotSpot = new Point2D.Double();
|
|
||||||
private EmfPlusPath fillPath;
|
|
||||||
private EmfPlusPath outlinePath;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long init(LittleEndianInputStream leis) throws IOException {
|
|
||||||
// A 32-bit unsigned integer that specifies the data in the OptionalData field.
|
|
||||||
// This value MUST be composed of CustomLineCapData flags
|
|
||||||
dataFlags = leis.readInt();
|
|
||||||
|
|
||||||
// A 32-bit unsigned integer that specifies the value from the LineCap enumeration on which
|
|
||||||
// the custom line cap is based.
|
|
||||||
baseCap = EmfPlusLineCapType.valueOf(leis.readInt());
|
|
||||||
|
|
||||||
// A 32-bit floating-point value that specifies the distance between the
|
|
||||||
// beginning of the line cap and the end of the line.
|
|
||||||
baseInset = leis.readFloat();
|
|
||||||
|
|
||||||
// A 32-bit unsigned integer that specifies the value in the LineCap enumeration that indicates the line
|
|
||||||
// cap used at the start/end of the line to be drawn.
|
|
||||||
startCap = EmfPlusLineCapType.valueOf(leis.readInt());
|
|
||||||
endCap = EmfPlusLineCapType.valueOf(leis.readInt());
|
|
||||||
|
|
||||||
// A 32-bit unsigned integer that specifies the value in the LineJoin enumeration, which specifies how
|
|
||||||
// to join two lines that are drawn by the same pen and whose ends meet. At the intersection of the two
|
|
||||||
// line ends, a line join makes the connection look more continuous.
|
|
||||||
join = EmfPlusLineJoin.valueOf(leis.readInt());
|
|
||||||
|
|
||||||
// A 32-bit floating-point value that contains the limit of the thickness of the join on a mitered corner
|
|
||||||
// by setting the maximum allowed ratio of miter length to line width.
|
|
||||||
mitterLimit = leis.readFloat();
|
|
||||||
|
|
||||||
// A 32-bit floating-point value that specifies the amount by which to scale the custom line cap with
|
|
||||||
// respect to the width of the EmfPlusPen object that is used to draw the lines.
|
|
||||||
widthScale = leis.readFloat();
|
|
||||||
|
|
||||||
int size = 8* LittleEndianConsts.INT_SIZE;
|
|
||||||
|
|
||||||
// An EmfPlusPointF object that is not currently used. It MUST be set to {0.0, 0.0}.
|
|
||||||
size += readPointF(leis, fillHotSpot);
|
|
||||||
size += readPointF(leis, lineHotSpot);
|
|
||||||
|
|
||||||
if (FILL_PATH.isSet(dataFlags)) {
|
|
||||||
fillPath = new EmfPlusPath();
|
|
||||||
size += fillPath.init(leis, -1, null, -1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (LINE_PATH.isSet(dataFlags)) {
|
@Override
|
||||||
outlinePath = new EmfPlusPath();
|
public HwmfLineJoin getLineJoin() {
|
||||||
size += outlinePath.init(leis, -1, null, -1);
|
switch (join) {
|
||||||
|
default:
|
||||||
|
case BEVEL:
|
||||||
|
return HwmfLineJoin.BEVEL;
|
||||||
|
case ROUND:
|
||||||
|
return HwmfLineJoin.ROUND;
|
||||||
|
case MITER_CLIPPED:
|
||||||
|
case MITER:
|
||||||
|
return HwmfLineJoin.MITER;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return size;
|
@Override
|
||||||
}
|
public HwmfLineDash getLineDash() {
|
||||||
|
return dashedLineData == null ? HwmfLineDash.SOLID : HwmfLineDash.USERSTYLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float[] getLineDashes() {
|
||||||
|
return dashedLineData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAlternateDash() {
|
||||||
|
return (getLineDash() != HwmfLineDash.SOLID && dashOffset != null && dashOffset == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isGeometric() {
|
||||||
|
return (unitType == EmfPlusUnitType.World || unitType == EmfPlusUnitType.Display);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static class EmfPlusAdjustableArrowCap implements EmfPlusCustomLineCap {
|
public static class EmfPlusPathArrowCap implements EmfPlusCustomLineCap {
|
||||||
private double width;
|
/**
|
||||||
private double height;
|
* If set, an EmfPlusFillPath object MUST be specified in the OptionalData field of the
|
||||||
private double middleInset;
|
* EmfPlusCustomLineCapData object for filling the custom line cap.
|
||||||
private boolean isFilled;
|
*/
|
||||||
private EmfPlusLineCapType startCap;
|
private static final BitField FILL_PATH = BitFieldFactory.getInstance(0x00000001);
|
||||||
private EmfPlusLineCapType endCap;
|
/**
|
||||||
private EmfPlusLineJoin join;
|
* If set, an EmfPlusLinePath object MUST be specified in the OptionalData field of the
|
||||||
private double mitterLimit;
|
* EmfPlusCustomLineCapData object for outlining the custom line cap.
|
||||||
private double widthScale;
|
*/
|
||||||
private final Point2D fillHotSpot = new Point2D.Double();
|
private static final BitField LINE_PATH = BitFieldFactory.getInstance(0x00000002);
|
||||||
private final Point2D lineHotSpot = new Point2D.Double();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long init(LittleEndianInputStream leis) throws IOException {
|
|
||||||
// A 32-bit floating-point value that specifies the width of the arrow cap.
|
|
||||||
// The width of the arrow cap is scaled by the width of the EmfPlusPen object that is used to draw the
|
|
||||||
// line being capped. For example, when drawing a capped line with a pen that has a width of 5 pixels,
|
|
||||||
// and the adjustable arrow cap object has a width of 3, the actual arrow cap is drawn 15 pixels wide.
|
|
||||||
width = leis.readFloat();
|
|
||||||
|
|
||||||
// A 32-bit floating-point value that specifies the height of the arrow cap.
|
private int dataFlags;
|
||||||
// The height of the arrow cap is scaled by the width of the EmfPlusPen object that is used to draw the
|
private EmfPlusLineCapType baseCap;
|
||||||
// line being capped. For example, when drawing a capped line with a pen that has a width of 5 pixels,
|
private double baseInset;
|
||||||
// and the adjustable arrow cap object has a height of 3, the actual arrow cap is drawn 15 pixels high.
|
private EmfPlusLineCapType startCap;
|
||||||
height = leis.readFloat();
|
private EmfPlusLineCapType endCap;
|
||||||
|
private EmfPlusLineJoin join;
|
||||||
|
private double mitterLimit;
|
||||||
|
private double widthScale;
|
||||||
|
private final Point2D fillHotSpot = new Point2D.Double();
|
||||||
|
private final Point2D lineHotSpot = new Point2D.Double();
|
||||||
|
private EmfPlusPath fillPath;
|
||||||
|
private EmfPlusPath outlinePath;
|
||||||
|
|
||||||
// A 32-bit floating-point value that specifies the number of pixels between the outline of the arrow
|
@Override
|
||||||
// cap and the fill of the arrow cap.
|
public long init(LittleEndianInputStream leis) throws IOException {
|
||||||
middleInset = leis.readFloat();
|
// A 32-bit unsigned integer that specifies the data in the OptionalData field.
|
||||||
|
// This value MUST be composed of CustomLineCapData flags
|
||||||
|
dataFlags = leis.readInt();
|
||||||
|
|
||||||
// A 32-bit Boolean value that specifies whether the arrow cap is filled.
|
// A 32-bit unsigned integer that specifies the value from the LineCap enumeration on which
|
||||||
// If the arrow cap is not filled, only the outline is drawn.
|
// the custom line cap is based.
|
||||||
isFilled = (leis.readInt() != 0);
|
baseCap = EmfPlusLineCapType.valueOf(leis.readInt());
|
||||||
|
|
||||||
// A 32-bit unsigned integer that specifies the value in the LineCap enumeration that indicates
|
// A 32-bit floating-point value that specifies the distance between the
|
||||||
// the line cap to be used at the start/end of the line to be drawn.
|
// beginning of the line cap and the end of the line.
|
||||||
startCap = EmfPlusLineCapType.valueOf(leis.readInt());
|
baseInset = leis.readFloat();
|
||||||
endCap = EmfPlusLineCapType.valueOf(leis.readInt());
|
|
||||||
|
|
||||||
// 32-bit unsigned integer that specifies the value in the LineJoin enumeration that specifies how to
|
// A 32-bit unsigned integer that specifies the value in the LineCap enumeration that indicates the line
|
||||||
// join two lines that are drawn by the same pen and whose ends meet. At the intersection of the two
|
// cap used at the start/end of the line to be drawn.
|
||||||
// line ends, a line join makes the connection look more continuous.
|
startCap = EmfPlusLineCapType.valueOf(leis.readInt());
|
||||||
join = EmfPlusLineJoin.valueOf(leis.readInt());
|
endCap = EmfPlusLineCapType.valueOf(leis.readInt());
|
||||||
|
|
||||||
// A 32-bit floating-point value that specifies the limit of the thickness of the join on a mitered
|
// A 32-bit unsigned integer that specifies the value in the LineJoin enumeration, which specifies how
|
||||||
// corner by setting the maximum allowed ratio of miter length to line width.
|
// to join two lines that are drawn by the same pen and whose ends meet. At the intersection of the two
|
||||||
mitterLimit = leis.readFloat();
|
// line ends, a line join makes the connection look more continuous.
|
||||||
|
join = EmfPlusLineJoin.valueOf(leis.readInt());
|
||||||
|
|
||||||
// A 32-bit floating-point value that specifies the amount by which to scale an EmfPlusCustomLineCap
|
// A 32-bit floating-point value that contains the limit of the thickness of the join on a mitered corner
|
||||||
// object with respect to the width of the graphics pen that is used to draw the lines.
|
// by setting the maximum allowed ratio of miter length to line width.
|
||||||
widthScale = leis.readFloat();
|
mitterLimit = leis.readFloat();
|
||||||
|
|
||||||
int size = 9 * LittleEndianConsts.INT_SIZE;
|
// A 32-bit floating-point value that specifies the amount by which to scale the custom line cap with
|
||||||
|
// respect to the width of the EmfPlusPen object that is used to draw the lines.
|
||||||
|
widthScale = leis.readFloat();
|
||||||
|
|
||||||
// An EmfPlusPointF object that is not currently used. It MUST be set to {0.0, 0.0}.
|
int size = 8* LittleEndianConsts.INT_SIZE;
|
||||||
size += readPointF(leis, fillHotSpot);
|
|
||||||
|
|
||||||
// An EmfPlusPointF object that is not currently used. It MUST be set to {0.0, 0.0}.
|
// An EmfPlusPointF object that is not currently used. It MUST be set to {0.0, 0.0}.
|
||||||
size += readPointF(leis, lineHotSpot);
|
size += readPointF(leis, fillHotSpot);
|
||||||
|
size += readPointF(leis, lineHotSpot);
|
||||||
|
|
||||||
return size;
|
if (FILL_PATH.isSet(dataFlags)) {
|
||||||
|
fillPath = new EmfPlusPath();
|
||||||
|
size += fillPath.init(leis, -1, null, -1);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
if (LINE_PATH.isSet(dataFlags)) {
|
||||||
|
outlinePath = new EmfPlusPath();
|
||||||
|
size += outlinePath.init(leis, -1, null, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class EmfPlusAdjustableArrowCap implements EmfPlusCustomLineCap {
|
||||||
|
private double width;
|
||||||
|
private double height;
|
||||||
|
private double middleInset;
|
||||||
|
private boolean isFilled;
|
||||||
|
private EmfPlusLineCapType startCap;
|
||||||
|
private EmfPlusLineCapType endCap;
|
||||||
|
private EmfPlusLineJoin join;
|
||||||
|
private double mitterLimit;
|
||||||
|
private double widthScale;
|
||||||
|
private final Point2D fillHotSpot = new Point2D.Double();
|
||||||
|
private final Point2D lineHotSpot = new Point2D.Double();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long init(LittleEndianInputStream leis) throws IOException {
|
||||||
|
// A 32-bit floating-point value that specifies the width of the arrow cap.
|
||||||
|
// The width of the arrow cap is scaled by the width of the EmfPlusPen object that is used to draw the
|
||||||
|
// line being capped. For example, when drawing a capped line with a pen that has a width of 5 pixels,
|
||||||
|
// and the adjustable arrow cap object has a width of 3, the actual arrow cap is drawn 15 pixels wide.
|
||||||
|
width = leis.readFloat();
|
||||||
|
|
||||||
|
// A 32-bit floating-point value that specifies the height of the arrow cap.
|
||||||
|
// The height of the arrow cap is scaled by the width of the EmfPlusPen object that is used to draw the
|
||||||
|
// line being capped. For example, when drawing a capped line with a pen that has a width of 5 pixels,
|
||||||
|
// and the adjustable arrow cap object has a height of 3, the actual arrow cap is drawn 15 pixels high.
|
||||||
|
height = leis.readFloat();
|
||||||
|
|
||||||
|
// A 32-bit floating-point value that specifies the number of pixels between the outline of the arrow
|
||||||
|
// cap and the fill of the arrow cap.
|
||||||
|
middleInset = leis.readFloat();
|
||||||
|
|
||||||
|
// A 32-bit Boolean value that specifies whether the arrow cap is filled.
|
||||||
|
// If the arrow cap is not filled, only the outline is drawn.
|
||||||
|
isFilled = (leis.readInt() != 0);
|
||||||
|
|
||||||
|
// A 32-bit unsigned integer that specifies the value in the LineCap enumeration that indicates
|
||||||
|
// the line cap to be used at the start/end of the line to be drawn.
|
||||||
|
startCap = EmfPlusLineCapType.valueOf(leis.readInt());
|
||||||
|
endCap = EmfPlusLineCapType.valueOf(leis.readInt());
|
||||||
|
|
||||||
|
// 32-bit unsigned integer that specifies the value in the LineJoin enumeration that specifies how to
|
||||||
|
// join two lines that are drawn by the same pen and whose ends meet. At the intersection of the two
|
||||||
|
// line ends, a line join makes the connection look more continuous.
|
||||||
|
join = EmfPlusLineJoin.valueOf(leis.readInt());
|
||||||
|
|
||||||
|
// A 32-bit floating-point value that specifies the limit of the thickness of the join on a mitered
|
||||||
|
// corner by setting the maximum allowed ratio of miter length to line width.
|
||||||
|
mitterLimit = leis.readFloat();
|
||||||
|
|
||||||
|
// A 32-bit floating-point value that specifies the amount by which to scale an EmfPlusCustomLineCap
|
||||||
|
// object with respect to the width of the graphics pen that is used to draw the lines.
|
||||||
|
widthScale = leis.readFloat();
|
||||||
|
|
||||||
|
int size = 9 * LittleEndianConsts.INT_SIZE;
|
||||||
|
|
||||||
|
// An EmfPlusPointF object that is not currently used. It MUST be set to {0.0, 0.0}.
|
||||||
|
size += readPointF(leis, fillHotSpot);
|
||||||
|
|
||||||
|
// An EmfPlusPointF object that is not currently used. It MUST be set to {0.0, 0.0}.
|
||||||
|
size += readPointF(leis, lineHotSpot);
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ package org.apache.poi.hemf.record.emfplus;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.apache.poi.hemf.record.emf.HemfRecordType;
|
import org.apache.poi.hemf.draw.HemfGraphics;
|
||||||
import org.apache.poi.util.Internal;
|
import org.apache.poi.util.Internal;
|
||||||
import org.apache.poi.util.LittleEndianInputStream;
|
import org.apache.poi.util.LittleEndianInputStream;
|
||||||
|
|
||||||
@ -45,4 +45,13 @@ public interface HemfPlusRecord {
|
|||||||
*/
|
*/
|
||||||
long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException;
|
long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws the record, the default redirects to the parent WMF record drawing
|
||||||
|
* @param ctx the drawing context
|
||||||
|
*/
|
||||||
|
default void draw(HemfGraphics ctx) {
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -21,9 +21,11 @@ import static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readRectF;
|
|||||||
|
|
||||||
import java.awt.geom.Rectangle2D;
|
import java.awt.geom.Rectangle2D;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.apache.poi.hemf.draw.HemfGraphics;
|
||||||
import org.apache.poi.hemf.record.emfplus.HemfPlusHeader.EmfPlusGraphicsVersion;
|
import org.apache.poi.hemf.record.emfplus.HemfPlusHeader.EmfPlusGraphicsVersion;
|
||||||
import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectData;
|
import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectData;
|
||||||
import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectType;
|
import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectType;
|
||||||
@ -92,12 +94,12 @@ public class HemfPlusRegion {
|
|||||||
|
|
||||||
public static class EmfPlusRegion implements EmfPlusObjectData {
|
public static class EmfPlusRegion implements EmfPlusObjectData {
|
||||||
|
|
||||||
private final EmfPlusGraphicsVersion version = new EmfPlusGraphicsVersion();
|
private final EmfPlusGraphicsVersion graphicsVersion = new EmfPlusGraphicsVersion();
|
||||||
private EmfPlusRegionNodeData regionNode;
|
private EmfPlusRegionNodeData regionNode;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long init(LittleEndianInputStream leis, long dataSize, EmfPlusObjectType objectType, int flags) throws IOException {
|
public long init(LittleEndianInputStream leis, long dataSize, EmfPlusObjectType objectType, int flags) throws IOException {
|
||||||
long size = version.init(leis);
|
long size = graphicsVersion.init(leis);
|
||||||
|
|
||||||
// A 32-bit unsigned integer that specifies the number of child nodes in the RegionNode field.
|
// A 32-bit unsigned integer that specifies the number of child nodes in the RegionNode field.
|
||||||
int nodeCount = leis.readInt();
|
int nodeCount = leis.readInt();
|
||||||
@ -111,7 +113,15 @@ public class HemfPlusRegion {
|
|||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EmfPlusGraphicsVersion getGraphicsVersion() {
|
||||||
|
return graphicsVersion;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -267,36 +267,7 @@ public class HemfEmbeddedIterator implements Iterator<HwmfEmbedded> {
|
|||||||
* Compress GDIs internal format to something useful
|
* Compress GDIs internal format to something useful
|
||||||
*/
|
*/
|
||||||
private void compressGDIBitmap(EmfPlusImage img, HwmfEmbedded emb, HwmfEmbeddedType et) {
|
private void compressGDIBitmap(EmfPlusImage img, HwmfEmbedded emb, HwmfEmbeddedType et) {
|
||||||
final int width = img.getBitmapWidth();
|
BufferedImage bi = img.readGDIImage(emb.getRawData());
|
||||||
final int height = img.getBitmapHeight();
|
|
||||||
final int stride = img.getBitmapStride();
|
|
||||||
final EmfPlusPixelFormat pf = img.getPixelFormat();
|
|
||||||
|
|
||||||
int[] nBits, bOffs;
|
|
||||||
switch (pf) {
|
|
||||||
case ARGB_32BPP:
|
|
||||||
nBits = new int[]{8, 8, 8, 8};
|
|
||||||
bOffs = new int[]{2, 1, 0, 3};
|
|
||||||
break;
|
|
||||||
case RGB_24BPP:
|
|
||||||
nBits = new int[]{8, 8, 8};
|
|
||||||
bOffs = new int[]{2, 1, 0};
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new RuntimeException("not yet implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
|
|
||||||
ComponentColorModel cm = new ComponentColorModel
|
|
||||||
(cs, nBits, pf.isAlpha(), pf.isPreMultiplied(), Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE);
|
|
||||||
PixelInterleavedSampleModel csm =
|
|
||||||
new PixelInterleavedSampleModel(cm.getTransferType(), width, height, cm.getNumColorComponents(), stride, bOffs);
|
|
||||||
|
|
||||||
byte d[] = emb.getRawData();
|
|
||||||
WritableRaster raster = (WritableRaster) Raster.createRaster(csm, new DataBufferByte(d, d.length), null);
|
|
||||||
|
|
||||||
BufferedImage bi = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
// use HwmfEmbeddedType literal for conversion
|
// use HwmfEmbeddedType literal for conversion
|
||||||
|
@ -46,7 +46,6 @@ import org.apache.poi.util.Units;
|
|||||||
*/
|
*/
|
||||||
@Internal
|
@Internal
|
||||||
public class HemfPicture implements Iterable<HemfRecord> {
|
public class HemfPicture implements Iterable<HemfRecord> {
|
||||||
|
|
||||||
private final LittleEndianInputStream stream;
|
private final LittleEndianInputStream stream;
|
||||||
private final List<HemfRecord> records = new ArrayList<>();
|
private final List<HemfRecord> records = new ArrayList<>();
|
||||||
private boolean isParsed = false;
|
private boolean isParsed = false;
|
||||||
@ -95,33 +94,53 @@ public class HemfPicture implements Iterable<HemfRecord> {
|
|||||||
getRecords().forEach(action);
|
getRecords().forEach(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the bounding box in device-independent units. Usually this is taken from the placeable header.
|
||||||
|
*
|
||||||
|
* @return the bounding box
|
||||||
|
*/
|
||||||
|
public Rectangle2D getBounds() {
|
||||||
|
HemfHeader header = (HemfHeader)getRecords().get(0);
|
||||||
|
Rectangle2D dim = header.getFrameRectangle();
|
||||||
|
double x = dim.getX(), y = dim.getY();
|
||||||
|
double width = dim.getWidth(), height = dim.getHeight();
|
||||||
|
if (dim.isEmpty() || Math.rint(width) == 0 || Math.rint(height) == 0) {
|
||||||
|
for (HemfRecord r : getRecords()) {
|
||||||
|
if (r instanceof HemfWindowing.EmfSetWindowExtEx) {
|
||||||
|
HemfWindowing.EmfSetWindowExtEx extEx = (HemfWindowing.EmfSetWindowExtEx)r;
|
||||||
|
Dimension2D d = extEx.getSize();
|
||||||
|
width = d.getWidth();
|
||||||
|
height = d.getHeight();
|
||||||
|
// keep searching - sometimes there's another record
|
||||||
|
}
|
||||||
|
if (r instanceof HemfWindowing.EmfSetWindowOrgEx) {
|
||||||
|
HemfWindowing.EmfSetWindowOrgEx orgEx = (HemfWindowing.EmfSetWindowOrgEx)r;
|
||||||
|
x = orgEx.getX();
|
||||||
|
y = orgEx.getY();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Rectangle2D.Double(x, y, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the image size in points
|
* Return the image size in points
|
||||||
*
|
*
|
||||||
* @return the image size in points
|
* @return the image size in points
|
||||||
*/
|
*/
|
||||||
public Dimension2D getSize() {
|
public Dimension2D getSize() {
|
||||||
HemfHeader header = (HemfHeader)getRecords().get(0);
|
final Rectangle2D bounds = getBounds();
|
||||||
|
|
||||||
|
if (bounds.isEmpty()) {
|
||||||
|
return new Dimension2DDouble(100,100);
|
||||||
|
}
|
||||||
|
|
||||||
final double coeff = (double) Units.EMU_PER_CENTIMETER / Units.EMU_PER_POINT / 10.;
|
final double coeff = (double) Units.EMU_PER_CENTIMETER / Units.EMU_PER_POINT / 10.;
|
||||||
Rectangle2D dim = header.getFrameRectangle();
|
double width = Math.abs(bounds.getWidth()*coeff);
|
||||||
double width = dim.getWidth(), height = dim.getHeight();
|
double height = Math.abs(bounds.getHeight()*coeff);
|
||||||
if (dim.isEmpty() || Math.rint(width*coeff) == 0 || Math.rint(height*coeff) == 0) {
|
|
||||||
for (HemfRecord r : getRecords()) {
|
|
||||||
if (r instanceof HemfWindowing.EmfSetWindowExtEx) {
|
|
||||||
Dimension2D d = ((HemfWindowing.EmfSetWindowExtEx)r).getSize();
|
|
||||||
width = d.getWidth();
|
|
||||||
height = d.getHeight();
|
|
||||||
// keep searching - sometimes there's another record
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Math.rint(width*coeff) == 0 || Math.rint(height*coeff) == 0) {
|
return new Dimension2DDouble(width, height);
|
||||||
width = 100;
|
|
||||||
height = 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Dimension2DDouble(Math.abs(width*coeff), Math.abs(height*coeff));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static double minX(Rectangle2D bounds) {
|
private static double minX(Rectangle2D bounds) {
|
||||||
|
@ -17,8 +17,10 @@
|
|||||||
|
|
||||||
package org.apache.poi.hwmf.draw;
|
package org.apache.poi.hwmf.draw;
|
||||||
|
|
||||||
|
import java.awt.AlphaComposite;
|
||||||
import java.awt.BasicStroke;
|
import java.awt.BasicStroke;
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
|
import java.awt.Composite;
|
||||||
import java.awt.Graphics2D;
|
import java.awt.Graphics2D;
|
||||||
import java.awt.GraphicsConfiguration;
|
import java.awt.GraphicsConfiguration;
|
||||||
import java.awt.Paint;
|
import java.awt.Paint;
|
||||||
@ -132,6 +134,9 @@ public class HwmfGraphics {
|
|||||||
|
|
||||||
public void fill(Shape shape) {
|
public void fill(Shape shape) {
|
||||||
HwmfDrawProperties prop = getProperties();
|
HwmfDrawProperties prop = getProperties();
|
||||||
|
|
||||||
|
Composite old = graphicsCtx.getComposite();
|
||||||
|
graphicsCtx.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
|
||||||
if (prop.getBrushStyle() != HwmfBrushStyle.BS_NULL) {
|
if (prop.getBrushStyle() != HwmfBrushStyle.BS_NULL) {
|
||||||
if (prop.getBkMode() == HwmfBkMode.OPAQUE) {
|
if (prop.getBkMode() == HwmfBkMode.OPAQUE) {
|
||||||
graphicsCtx.setPaint(prop.getBackgroundColor().getColor());
|
graphicsCtx.setPaint(prop.getBackgroundColor().getColor());
|
||||||
@ -141,20 +146,22 @@ public class HwmfGraphics {
|
|||||||
graphicsCtx.setPaint(getFill());
|
graphicsCtx.setPaint(getFill());
|
||||||
graphicsCtx.fill(shape);
|
graphicsCtx.fill(shape);
|
||||||
}
|
}
|
||||||
|
graphicsCtx.setComposite(old);
|
||||||
|
|
||||||
draw(shape);
|
draw(shape);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected BasicStroke getStroke() {
|
protected BasicStroke getStroke() {
|
||||||
|
HwmfDrawProperties prop = getProperties();
|
||||||
|
HwmfPenStyle ps = prop.getPenStyle();
|
||||||
// TODO: fix line width calculation
|
// TODO: fix line width calculation
|
||||||
float width = (float)getProperties().getPenWidth();
|
float width = (float)prop.getPenWidth();
|
||||||
if (width == 0) {
|
if (width == 0) {
|
||||||
width = 1;
|
width = 1;
|
||||||
}
|
}
|
||||||
HwmfPenStyle ps = getProperties().getPenStyle();
|
|
||||||
int cap = ps.getLineCap().awtFlag;
|
int cap = ps.getLineCap().awtFlag;
|
||||||
int join = ps.getLineJoin().awtFlag;
|
int join = ps.getLineJoin().awtFlag;
|
||||||
float miterLimit = (float)getProperties().getPenMiterLimit();
|
float miterLimit = (float)prop.getPenMiterLimit();
|
||||||
float[] dashes = ps.getLineDashes();
|
float[] dashes = ps.getLineDashes();
|
||||||
boolean dashAlt = ps.isAlternateDash();
|
boolean dashAlt = ps.isAlternateDash();
|
||||||
// This value is not an integer index into the dash pattern array.
|
// This value is not an integer index into the dash pattern array.
|
||||||
@ -602,7 +609,14 @@ public class HwmfGraphics {
|
|||||||
graphicsCtx.scale(dstBounds.getWidth()/srcBounds2.getWidth(), dstBounds.getHeight()/srcBounds2.getHeight());
|
graphicsCtx.scale(dstBounds.getWidth()/srcBounds2.getWidth(), dstBounds.getHeight()/srcBounds2.getHeight());
|
||||||
graphicsCtx.translate(-srcBounds2.getX(), -srcBounds2.getY());
|
graphicsCtx.translate(-srcBounds2.getX(), -srcBounds2.getY());
|
||||||
|
|
||||||
graphicsCtx.drawImage(img, 0, 0, prop.getBackgroundColor().getColor(), null);
|
if (prop.getBkMode() == HwmfBkMode.OPAQUE) {
|
||||||
|
graphicsCtx.drawImage(img, 0, 0, prop.getBackgroundColor().getColor(), null);
|
||||||
|
} else {
|
||||||
|
Composite old = graphicsCtx.getComposite();
|
||||||
|
graphicsCtx.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
|
||||||
|
graphicsCtx.drawImage(img, 0, 0, null);
|
||||||
|
graphicsCtx.setComposite(old);
|
||||||
|
}
|
||||||
|
|
||||||
graphicsCtx.setTransform(at);
|
graphicsCtx.setTransform(at);
|
||||||
graphicsCtx.setClip(clip);
|
graphicsCtx.setClip(clip);
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
package org.apache.poi.hwmf.record;
|
package org.apache.poi.hwmf.record;
|
||||||
|
|
||||||
import java.awt.Shape;
|
import java.awt.Shape;
|
||||||
|
import java.awt.geom.AffineTransform;
|
||||||
import java.awt.geom.Arc2D;
|
import java.awt.geom.Arc2D;
|
||||||
import java.awt.geom.Area;
|
import java.awt.geom.Area;
|
||||||
import java.awt.geom.Dimension2D;
|
import java.awt.geom.Dimension2D;
|
||||||
@ -749,17 +750,17 @@ public class HwmfDraw {
|
|||||||
|
|
||||||
@Internal
|
@Internal
|
||||||
public static String pointToString(Point2D point) {
|
public static String pointToString(Point2D point) {
|
||||||
return "{ x: "+point.getX()+", y: "+point.getY()+" }";
|
return (point == null) ? "null" : "{ x: "+point.getX()+", y: "+point.getY()+" }";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Internal
|
@Internal
|
||||||
public static String boundsToString(Rectangle2D bounds) {
|
public static String boundsToString(Rectangle2D bounds) {
|
||||||
return "{ x: "+bounds.getX()+", y: "+bounds.getY()+", w: "+bounds.getWidth()+", h: "+bounds.getHeight()+" }";
|
return (bounds == null) ? "null" : "{ x: "+bounds.getX()+", y: "+bounds.getY()+", w: "+bounds.getWidth()+", h: "+bounds.getHeight()+" }";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Internal
|
@Internal
|
||||||
public static String dimToString(Dimension2D dim) {
|
public static String dimToString(Dimension2D dim) {
|
||||||
return "{ w: "+dim.getWidth()+", h: "+dim.getHeight()+" }";
|
return (dim == null) ? "null" : "{ w: "+dim.getWidth()+", h: "+dim.getHeight()+" }";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Internal
|
@Internal
|
||||||
@ -772,5 +773,4 @@ public class HwmfDraw {
|
|||||||
Math.abs(bounds.getHeight())
|
Math.abs(bounds.getHeight())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -24,22 +24,14 @@ import static org.junit.Assert.assertNotNull;
|
|||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
import java.awt.geom.Point2D;
|
import java.awt.geom.Point2D;
|
||||||
import java.io.BufferedWriter;
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.FileWriter;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.nio.file.StandardOpenOption;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import org.apache.poi.POIDataSamples;
|
import org.apache.poi.POIDataSamples;
|
||||||
import org.apache.poi.hemf.record.emf.HemfComment;
|
import org.apache.poi.hemf.record.emf.HemfComment;
|
||||||
@ -64,11 +56,10 @@ public class HemfPictureTest {
|
|||||||
private static final POIDataSamples ss_samples = POIDataSamples.getSpreadSheetInstance();
|
private static final POIDataSamples ss_samples = POIDataSamples.getSpreadSheetInstance();
|
||||||
private static final POIDataSamples sl_samples = POIDataSamples.getSlideShowInstance();
|
private static final POIDataSamples sl_samples = POIDataSamples.getSlideShowInstance();
|
||||||
|
|
||||||
/*
|
/* @Test
|
||||||
@Test
|
|
||||||
@Ignore("Only for manual tests - need to add org.tukaani:xz:1.8 for this to work")
|
@Ignore("Only for manual tests - need to add org.tukaani:xz:1.8 for this to work")
|
||||||
public void paint() throws IOException {
|
public void paint() throws IOException {
|
||||||
byte buf[] = new byte[50_000_000];
|
final byte buf[] = new byte[50_000_000];
|
||||||
|
|
||||||
// good test samples to validate rendering:
|
// good test samples to validate rendering:
|
||||||
// emfs/commoncrawl2/NB/NBWN2YH5VFCLZRFDQU7PB7IDD4UKY7DN_2.emf
|
// emfs/commoncrawl2/NB/NBWN2YH5VFCLZRFDQU7PB7IDD4UKY7DN_2.emf
|
||||||
@ -78,15 +69,16 @@ public class HemfPictureTest {
|
|||||||
|
|
||||||
final boolean writeLog = false;
|
final boolean writeLog = false;
|
||||||
final boolean dumpRecords = false;
|
final boolean dumpRecords = false;
|
||||||
final boolean savePng = false;
|
final boolean savePng = true;
|
||||||
final boolean dumpEmbedded = true;
|
final boolean dumpEmbedded = false;
|
||||||
|
|
||||||
Set<String> passed = new HashSet<>();
|
Set<String> passed = new HashSet<>();
|
||||||
|
|
||||||
try (BufferedWriter sucWrite = parseEmfLog(passed, "emf-success.txt");
|
try (BufferedWriter sucWrite = parseEmfLog(passed, "emf-success.txt")
|
||||||
BufferedWriter parseError = parseEmfLog(passed, "emf-parse.txt");
|
;BufferedWriter parseError = parseEmfLog(passed, "emf-parse.txt")
|
||||||
BufferedWriter renderError = parseEmfLog(passed, "emf-render.txt");
|
;BufferedWriter renderError = parseEmfLog(passed, "emf-render.txt")
|
||||||
SevenZFile sevenZFile = new SevenZFile(new File("tmp/plus_emf.7z"))) {
|
;SevenZFile sevenZFile = new SevenZFile(new File("tmp/plus_emf.7z"))
|
||||||
|
) {
|
||||||
for (int idx=0;;idx++) {
|
for (int idx=0;;idx++) {
|
||||||
SevenZArchiveEntry entry = sevenZFile.getNextEntry();
|
SevenZArchiveEntry entry = sevenZFile.getNextEntry();
|
||||||
if (entry == null) break;
|
if (entry == null) break;
|
||||||
@ -94,10 +86,14 @@ public class HemfPictureTest {
|
|||||||
|
|
||||||
if (entry.isDirectory() || !etName.endsWith(".emf") || passed.contains(etName)) continue;
|
if (entry.isDirectory() || !etName.endsWith(".emf") || passed.contains(etName)) continue;
|
||||||
|
|
||||||
// if (!etName.equals("emfs/commoncrawl2/2S/2SYMYPLNJURGCXJKLNZCJQGIBHVMQTRS_0.emf")) continue;
|
|
||||||
|
|
||||||
// emfs/commoncrawl2/ZJ/ZJT2BZPLQR7DKSKYLYL6GRDEUM2KIO5F_4.emf
|
// KEEDHN6XES4EKK52E3AJHKCARNTQF7PO_0.emf takes ages, time is spent while drawing paths
|
||||||
// emfs/govdocs1/005/005203.ppt_3.emf
|
// if (!etName.contains("KEEDHN6XES4EKK52E3AJHKCARNTQF7PO_0.emf")) continue;
|
||||||
|
|
||||||
|
// F7GK5XOLERFURVTQALOCX3GJ6FH45LNQ strange colors
|
||||||
|
// ISS3ANIX2PL4PXR7SZSJSPBZI7YQQE3U stroke wrong
|
||||||
|
// if (!etName.contains("ISS3ANIX2PL4PXR7SZSJSPBZI7YQQE3U")) continue;
|
||||||
|
|
||||||
|
|
||||||
System.out.println(etName);
|
System.out.println(etName);
|
||||||
|
|
||||||
@ -129,9 +125,9 @@ public class HemfPictureTest {
|
|||||||
int embIdx = 0;
|
int embIdx = 0;
|
||||||
for (HwmfEmbedded emb : emf.getEmbeddings()) {
|
for (HwmfEmbedded emb : emf.getEmbeddings()) {
|
||||||
final File embName = new File("build/tmp", "emb_"+etName.replaceFirst(".+/", "").replace(".emf", "_"+embIdx + emb.getEmbeddedType().extension) );
|
final File embName = new File("build/tmp", "emb_"+etName.replaceFirst(".+/", "").replace(".emf", "_"+embIdx + emb.getEmbeddedType().extension) );
|
||||||
// try (FileOutputStream fos = new FileOutputStream(embName)) {
|
try (FileOutputStream fos = new FileOutputStream(embName)) {
|
||||||
// fos.write(emb.getRawData());
|
fos.write(emb.getRawData());
|
||||||
// }
|
}
|
||||||
embIdx++;
|
embIdx++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -154,17 +150,19 @@ public class HemfPictureTest {
|
|||||||
BufferedImage bufImg = new BufferedImage((int)width, (int)height, BufferedImage.TYPE_INT_ARGB);
|
BufferedImage bufImg = new BufferedImage((int)width, (int)height, BufferedImage.TYPE_INT_ARGB);
|
||||||
g = bufImg.createGraphics();
|
g = bufImg.createGraphics();
|
||||||
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
|
||||||
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
|
g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_SPEED);
|
||||||
|
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
|
||||||
g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
|
g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
|
||||||
|
|
||||||
g.setComposite(AlphaComposite.Clear);
|
g.setComposite(AlphaComposite.Clear);
|
||||||
g.fillRect(0, 0, (int)width, (int)height);
|
g.fillRect(0, 0, (int)width, (int)height);
|
||||||
g.setComposite(AlphaComposite.Src);
|
g.setComposite(AlphaComposite.Src);
|
||||||
|
|
||||||
|
final File pngName = new File("build/tmp", etName.replaceFirst(".+/", "").replace(".emf", ".png"));
|
||||||
|
|
||||||
emf.draw(g, new Rectangle2D.Double(0, 0, width, height));
|
emf.draw(g, new Rectangle2D.Double(0, 0, width, height));
|
||||||
|
|
||||||
final File pngName = new File("build/tmp", etName.replaceFirst(".+/", "").replace(".emf", ".png"));
|
|
||||||
if (savePng) {
|
if (savePng) {
|
||||||
ImageIO.write(bufImg, "PNG", pngName);
|
ImageIO.write(bufImg, "PNG", pngName);
|
||||||
}
|
}
|
||||||
@ -186,7 +184,7 @@ public class HemfPictureTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} */
|
}
|
||||||
|
|
||||||
private static int hashException(Throwable e) {
|
private static int hashException(Throwable e) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
@ -222,7 +220,7 @@ public class HemfPictureTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Files.newBufferedWriter(log, StandardCharsets.UTF_8, soo);
|
return Files.newBufferedWriter(log, StandardCharsets.UTF_8, soo);
|
||||||
}
|
}*/
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBasicWindows() throws Exception {
|
public void testBasicWindows() throws Exception {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user