mirror of https://github.com/apache/poi.git
Bug 60656 - Emf image support in slideshows
- use Rectangle2D instead of Dimension2D for image bounds - fix shearing transformation - fix rendering of font attributes (bold/italic/...) - emf+: needs its own object table and properties table - emf+: add linear gradient handler - emf+: handle brush data of pens - wmf/emf/emf+: position right aligned text correctly - emf+: use emf+ instead of emf records in dual-mode - emf+: handle region data and operations correctly - emf/+: map font weight to awt font weight correctly git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1870566 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
7aab19c3b5
commit
1562175343
|
@ -17,7 +17,6 @@
|
|||
|
||||
package org.apache.poi.sl.draw;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Insets;
|
||||
|
@ -255,10 +254,10 @@ public class BitmapImageRenderer implements ImageRenderer {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Dimension getDimension() {
|
||||
public Rectangle2D getBounds() {
|
||||
return (img == null)
|
||||
? new Dimension(0,0)
|
||||
: new Dimension(img.getWidth(),img.getHeight());
|
||||
? new Rectangle2D.Double()
|
||||
: new Rectangle2D.Double(0, 0, img.getWidth(), img.getHeight());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -316,7 +315,9 @@ public class BitmapImageRenderer implements ImageRenderer {
|
|||
AffineTransform at = new AffineTransform(sx, 0, 0, sy, tx, ty) ;
|
||||
|
||||
Shape clipOld = graphics.getClip();
|
||||
if (isClipped) graphics.clip(anchor.getBounds2D());
|
||||
if (isClipped) {
|
||||
graphics.clip(anchor.getBounds2D());
|
||||
}
|
||||
graphics.drawRenderedImage(img, at);
|
||||
graphics.setClip(clipOld);
|
||||
|
||||
|
|
|
@ -583,8 +583,7 @@ public class DrawPaint {
|
|||
*
|
||||
* @return an array containing the 3 HSL values.
|
||||
*/
|
||||
private static double[] RGB2HSL(Color color)
|
||||
{
|
||||
public static double[] RGB2HSL(Color color) {
|
||||
// Get RGB values in the range 0 - 1
|
||||
|
||||
float[] rgb = color.getRGBColorComponents( null );
|
||||
|
|
|
@ -46,6 +46,8 @@ public interface Drawable {
|
|||
case 9: return "FONT_MAP";
|
||||
case 10: return "GSAVE";
|
||||
case 11: return "GRESTORE";
|
||||
case 12: return "CURRENT_SLIDE";
|
||||
case 13: return "BUFFERED_IMAGE";
|
||||
default: return "UNKNOWN_ID "+intKey();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
|
||||
import org.apache.poi.common.usermodel.GenericRecord;
|
||||
import org.apache.poi.util.Dimension2DDouble;
|
||||
|
||||
/**
|
||||
* Classes can implement this interfaces to support other formats, for
|
||||
|
@ -105,10 +106,18 @@ public interface ImageRenderer {
|
|||
*/
|
||||
Rectangle2D getNativeBounds();
|
||||
|
||||
/**
|
||||
* @return the bounds of the buffered image in pixel
|
||||
*/
|
||||
Rectangle2D getBounds();
|
||||
|
||||
/**
|
||||
* @return the dimension of the buffered image in pixel
|
||||
*/
|
||||
Dimension2D getDimension();
|
||||
default Dimension2D getDimension() {
|
||||
Rectangle2D r = getBounds();
|
||||
return new Dimension2DDouble(Math.abs(r.getWidth()), Math.abs(r.getHeight()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param alpha the alpha [0..1] to be added to the image (possibly already containing an alpha channel)
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package org.apache.poi.util;
|
||||
|
||||
import java.awt.geom.Dimension2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
|
||||
public class Units {
|
||||
/**
|
||||
|
@ -158,6 +159,22 @@ public class Units {
|
|||
return new Dimension2DDouble(width, height);
|
||||
}
|
||||
|
||||
public static Rectangle2D pointsToPixel(Rectangle2D pointsDim) {
|
||||
double x = pointsDim.getX() * PIXEL_DPI / POINT_DPI;
|
||||
double y = pointsDim.getY() * PIXEL_DPI / POINT_DPI;
|
||||
double width = pointsDim.getWidth() * PIXEL_DPI / POINT_DPI;
|
||||
double height = pointsDim.getHeight() * PIXEL_DPI / POINT_DPI;
|
||||
return new Rectangle2D.Double(x, y, width, height);
|
||||
}
|
||||
|
||||
public static Rectangle2D pixelToPoints(Rectangle2D pointsDim) {
|
||||
double x = pointsDim.getX() * POINT_DPI / PIXEL_DPI;
|
||||
double y = pointsDim.getY() * POINT_DPI / PIXEL_DPI;
|
||||
double width = pointsDim.getWidth() * POINT_DPI / PIXEL_DPI;
|
||||
double height = pointsDim.getHeight() * POINT_DPI / PIXEL_DPI;
|
||||
return new Rectangle2D.Double(x, y, width, height);
|
||||
}
|
||||
|
||||
public static int charactersToEMU(double characters) {
|
||||
return (int) characters * EMU_PER_CHARACTER;
|
||||
}
|
||||
|
|
|
@ -42,7 +42,6 @@ import org.apache.batik.util.XMLResourceDescriptor;
|
|||
import org.apache.poi.sl.draw.Drawable;
|
||||
import org.apache.poi.sl.draw.ImageRenderer;
|
||||
import org.apache.poi.sl.usermodel.PictureData;
|
||||
import org.apache.poi.util.Dimension2DDouble;
|
||||
import org.w3c.dom.Document;
|
||||
|
||||
public class SVGImageRenderer implements ImageRenderer {
|
||||
|
@ -76,9 +75,8 @@ public class SVGImageRenderer implements ImageRenderer {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Dimension2D getDimension() {
|
||||
Rectangle2D r = svgRoot.getPrimitiveBounds();
|
||||
return new Dimension2DDouble(Math.ceil(r.getWidth()), Math.ceil(r.getHeight()));
|
||||
public Rectangle2D getBounds() {
|
||||
return svgRoot.getPrimitiveBounds();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -17,10 +17,13 @@
|
|||
|
||||
package org.apache.poi.hemf.draw;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Path2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import org.apache.poi.hemf.record.emfplus.HemfPlusBrush.EmfPlusHatchStyle;
|
||||
|
@ -47,6 +50,10 @@ public class HemfDrawProperties extends HwmfDrawProperties {
|
|||
private final List<AffineTransform> transXForm = new ArrayList<>();
|
||||
private final List<TransOperand> transOper = new ArrayList<>();
|
||||
|
||||
private Rectangle2D brushRect;
|
||||
private List<? extends Map.Entry<Float,Color>> brushColorsV;
|
||||
private List<? extends Map.Entry<Float,Color>> brushColorsH;
|
||||
|
||||
public HemfDrawProperties() {
|
||||
}
|
||||
|
||||
|
@ -60,6 +67,15 @@ public class HemfDrawProperties extends HwmfDrawProperties {
|
|||
emfPlusImage = other.emfPlusImage;
|
||||
transXForm.addAll(other.transXForm);
|
||||
transOper.addAll(other.transOper);
|
||||
if (other.brushRect != null) {
|
||||
brushRect = (Rectangle2D)other.brushRect.clone();
|
||||
}
|
||||
if (other.brushColorsV != null) {
|
||||
brushColorsV = new ArrayList<>(other.brushColorsV);
|
||||
}
|
||||
if (other.brushColorsH != null) {
|
||||
brushColorsH = new ArrayList<>(other.brushColorsH);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -139,4 +155,28 @@ public class HemfDrawProperties extends HwmfDrawProperties {
|
|||
List<TransOperand> getTransOper() {
|
||||
return transOper;
|
||||
}
|
||||
|
||||
public Rectangle2D getBrushRect() {
|
||||
return brushRect;
|
||||
}
|
||||
|
||||
public void setBrushRect(Rectangle2D brushRect) {
|
||||
this.brushRect = brushRect;
|
||||
}
|
||||
|
||||
public List<? extends Map.Entry<Float, Color>> getBrushColorsV() {
|
||||
return brushColorsV;
|
||||
}
|
||||
|
||||
public void setBrushColorsV(List<? extends Map.Entry<Float, Color>> brushColorsV) {
|
||||
this.brushColorsV = brushColorsV;
|
||||
}
|
||||
|
||||
public List<? extends Map.Entry<Float, Color>> getBrushColorsH() {
|
||||
return brushColorsH;
|
||||
}
|
||||
|
||||
public void setBrushColorsH(List<? extends Map.Entry<Float, Color>> brushColorsH) {
|
||||
this.brushColorsH = brushColorsH;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,16 +20,25 @@ package org.apache.poi.hemf.draw;
|
|||
import static org.apache.poi.hwmf.record.HwmfBrushStyle.BS_NULL;
|
||||
import static org.apache.poi.hwmf.record.HwmfBrushStyle.BS_SOLID;
|
||||
|
||||
import java.awt.AlphaComposite;
|
||||
import java.awt.Color;
|
||||
import java.awt.Composite;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.LinearGradientPaint;
|
||||
import java.awt.MultipleGradientPaint;
|
||||
import java.awt.Paint;
|
||||
import java.awt.Shape;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Path2D;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.poi.hemf.draw.HemfDrawProperties.TransOperand;
|
||||
import org.apache.poi.hemf.record.emf.HemfComment.EmfComment;
|
||||
|
@ -37,7 +46,9 @@ 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.HwmfGraphics;
|
||||
import org.apache.poi.hwmf.record.HwmfBrushStyle;
|
||||
import org.apache.poi.hwmf.record.HwmfColorRef;
|
||||
import org.apache.poi.hwmf.record.HwmfMisc;
|
||||
import org.apache.poi.hwmf.record.HwmfObjectTableEntry;
|
||||
import org.apache.poi.hwmf.record.HwmfPenStyle;
|
||||
import org.apache.poi.util.Internal;
|
||||
|
@ -58,11 +69,14 @@ public class HemfGraphics extends HwmfGraphics {
|
|||
private static final HwmfColorRef BLACK = new HwmfColorRef(Color.BLACK);
|
||||
|
||||
private EmfRenderState renderState = EmfRenderState.INITIAL;
|
||||
private final Map<Integer,HwmfObjectTableEntry> plusObjectTable = new HashMap<>();
|
||||
private final Map<Integer,HemfDrawProperties> plusPropStack = new HashMap<>();
|
||||
|
||||
public HemfGraphics(Graphics2D graphicsCtx, Rectangle2D bbox) {
|
||||
super(graphicsCtx,bbox);
|
||||
// add dummy entry for object ind ex 0, as emf is 1-based
|
||||
objectIndexes.set(0);
|
||||
getProperties().setBkMode(HwmfMisc.WmfSetBkMode.HwmfBkMode.TRANSPARENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -87,14 +101,8 @@ public class HemfGraphics extends HwmfGraphics {
|
|||
|
||||
public void draw(HemfRecord r) {
|
||||
switch (getRenderState()) {
|
||||
default:
|
||||
case EMF_DCONTEXT:
|
||||
// This state specifies that subsequent EMF records encountered in the metafile SHOULD be processed.
|
||||
// EMF records cease being processed when the next EMF+ record is encountered.
|
||||
if (r instanceof EmfComment) {
|
||||
setRenderState(EmfRenderState.EMFPLUS_ONLY);
|
||||
}
|
||||
r.draw(this);
|
||||
break;
|
||||
case INITIAL:
|
||||
r.draw(this);
|
||||
break;
|
||||
|
@ -108,8 +116,6 @@ public class HemfGraphics extends HwmfGraphics {
|
|||
r.draw(this);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -171,10 +177,7 @@ public class HemfGraphics extends HwmfGraphics {
|
|||
|
||||
/**
|
||||
* Adds or sets an record of type {@link HwmfObjectTableEntry} to the object table.
|
||||
* If the {@code index} is less than 1, the method acts the same as
|
||||
* {@link HwmfGraphics#addObjectTableEntry(HwmfObjectTableEntry)}, otherwise the
|
||||
* index is used to access the object table.
|
||||
* As the table is filled successively, the index must be between 1 and size+1
|
||||
* The index must be > 0
|
||||
*
|
||||
* @param entry the record to be stored
|
||||
* @param index the index to be overwritten, regardless if its content was unset before
|
||||
|
@ -182,42 +185,74 @@ public class HemfGraphics extends HwmfGraphics {
|
|||
* @see HwmfGraphics#addObjectTableEntry(HwmfObjectTableEntry)
|
||||
*/
|
||||
public void addObjectTableEntry(HwmfObjectTableEntry entry, int index) {
|
||||
checkTableEntryIndex(index);
|
||||
// in EMF the index must > 0
|
||||
if (index < 1) {
|
||||
throw new IndexOutOfBoundsException("Object table entry index in EMF must be > 0 - invalid index: "+index);
|
||||
}
|
||||
objectIndexes.set(index);
|
||||
objectTable.put(index, entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a record which was registered earliser
|
||||
* Adds or sets an record of type {@link HwmfObjectTableEntry} to the plus object table.
|
||||
* The index must be in the range [0..63]
|
||||
*
|
||||
* @param entry the record to be stored
|
||||
* @param index the index to be overwritten, regardless if its content was unset before
|
||||
*
|
||||
* @see HwmfGraphics#addObjectTableEntry(HwmfObjectTableEntry)
|
||||
*/
|
||||
public void addPlusObjectTableEntry(HwmfObjectTableEntry entry, int index) {
|
||||
// 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);
|
||||
}
|
||||
plusObjectTable.put(index, entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a record which was registered earlier
|
||||
* @param index the record index
|
||||
* @return the record or {@code null} if it doesn't exist
|
||||
*/
|
||||
public HwmfObjectTableEntry getObjectTableEntry(int index) {
|
||||
checkTableEntryIndex(index);
|
||||
// in EMF the index must > 0
|
||||
if (index < 1) {
|
||||
throw new IndexOutOfBoundsException("Object table entry index in EMF must be > 0 - invalid index: "+index);
|
||||
}
|
||||
return objectTable.get(index);
|
||||
}
|
||||
|
||||
private void checkTableEntryIndex(int index) {
|
||||
if (renderState != EmfRenderState.EMFPLUS_ONLY && renderState != EmfRenderState.EMF_DCONTEXT) {
|
||||
// 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);
|
||||
}
|
||||
public HwmfObjectTableEntry getPlusObjectTableEntry(int index) {
|
||||
// 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);
|
||||
}
|
||||
return plusObjectTable.get(index);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void applyObjectTableEntry(int index) {
|
||||
if ((index & 0x80000000) != 0) {
|
||||
selectStockObject(index);
|
||||
} else {
|
||||
super.applyObjectTableEntry(index);
|
||||
HwmfObjectTableEntry ote = objectTable.get(index);
|
||||
if (ote == null) {
|
||||
throw new NoSuchElementException("EMF reference exception - object table entry on index "+index+" was deleted before.");
|
||||
}
|
||||
ote.applyObject(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void applyPlusObjectTableEntry(int index) {
|
||||
if ((index & 0x80000000) != 0) {
|
||||
selectStockObject(index);
|
||||
} else {
|
||||
HwmfObjectTableEntry ote = plusObjectTable.get(index);
|
||||
if (ote == null) {
|
||||
throw new NoSuchElementException("EMF+ reference exception - plus object table entry on index "+index+" was deleted before.");
|
||||
}
|
||||
ote.applyObject(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -351,4 +386,82 @@ public class HemfGraphics extends HwmfGraphics {
|
|||
|
||||
graphicsCtx.setTransform(tx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fill(Shape shape) {
|
||||
HemfDrawProperties prop = getProperties();
|
||||
|
||||
Composite old = graphicsCtx.getComposite();
|
||||
graphicsCtx.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
|
||||
if (prop.getBrushStyle() != HwmfBrushStyle.BS_NULL) {
|
||||
if (prop.getBkMode() == HwmfMisc.WmfSetBkMode.HwmfBkMode.OPAQUE) {
|
||||
graphicsCtx.setPaint(prop.getBackgroundColor().getColor());
|
||||
graphicsCtx.fill(shape);
|
||||
}
|
||||
|
||||
graphicsCtx.setPaint(getFill());
|
||||
graphicsCtx.fill(shape);
|
||||
}
|
||||
graphicsCtx.setComposite(old);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Paint getLinearGradient() {
|
||||
HemfDrawProperties prop = getProperties();
|
||||
Rectangle2D rect = prop.getBrushRect();
|
||||
List<? extends Map.Entry<Float, Color>> colorsH = prop.getBrushColorsH();
|
||||
assert(rect != null && colorsH != null);
|
||||
|
||||
// TODO: handle ColorsV list with a custom GradientPaint
|
||||
// for an idea on how to handle 2d-gradients google "bilinear color interpolation".
|
||||
// basically use two linear interpolations for x/y or vertical/horizontal axis.
|
||||
// the resulting two colors need to be interpolated by 50%.
|
||||
|
||||
return new LinearGradientPaint(
|
||||
new Point2D.Double(rect.getMinX(),rect.getCenterY()),
|
||||
new Point2D.Double(rect.getMaxX(),rect.getCenterY()),
|
||||
toArray(colorsH.stream().map(Map.Entry::getKey), colorsH.size()),
|
||||
colorsH.stream().map(Map.Entry::getValue).toArray(Color[]::new),
|
||||
MultipleGradientPaint.CycleMethod.NO_CYCLE,
|
||||
MultipleGradientPaint.ColorSpaceType.SRGB,
|
||||
prop.getBrushTransform()
|
||||
);
|
||||
}
|
||||
|
||||
private static float[] toArray(Stream<? extends Number> numbers, int size) {
|
||||
float[] arr = new float[size];
|
||||
final int[] i = {0};
|
||||
numbers.forEach(n -> arr[i[0]++] = n.floatValue());
|
||||
return arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the current properties to the plus stack
|
||||
*/
|
||||
public void savePlusProperties(int index) {
|
||||
final HemfDrawProperties p = getProperties();
|
||||
assert(p != null);
|
||||
p.setTransform(graphicsCtx.getTransform());
|
||||
p.setClip(graphicsCtx.getClip());
|
||||
plusPropStack.put(index,p);
|
||||
prop = newProperties(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores the properties from the plus stack
|
||||
*
|
||||
* @param index the index of the previously saved properties
|
||||
*/
|
||||
public void restorePlusProperties(int index) {
|
||||
if (!plusPropStack.containsKey(index)) {
|
||||
return;
|
||||
}
|
||||
|
||||
prop = new HemfDrawProperties(plusPropStack.get(index));
|
||||
|
||||
graphicsCtx.setTransform(prop.getTransform());
|
||||
graphicsCtx.setClip(prop.getClip());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -19,11 +19,9 @@ package org.apache.poi.hemf.draw;
|
|||
|
||||
import static org.apache.poi.hwmf.draw.HwmfImageRenderer.getOuterBounds;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Insets;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.Shape;
|
||||
import java.awt.geom.Dimension2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
@ -33,6 +31,7 @@ import java.io.InputStream;
|
|||
|
||||
import org.apache.poi.common.usermodel.GenericRecord;
|
||||
import org.apache.poi.hemf.usermodel.HemfPicture;
|
||||
import org.apache.poi.hwmf.draw.HwmfGraphicsState;
|
||||
import org.apache.poi.hwmf.draw.HwmfImageRenderer;
|
||||
import org.apache.poi.sl.draw.BitmapImageRenderer;
|
||||
import org.apache.poi.sl.draw.EmbeddedExtractor;
|
||||
|
@ -66,11 +65,6 @@ public class HemfImageRenderer implements ImageRenderer, EmbeddedExtractor {
|
|||
image = new HemfPicture(new ByteArrayInputStream(data));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension2D getDimension() {
|
||||
return Units.pointsToPixel(image == null ? new Dimension() : image.getSize());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(double alpha) {
|
||||
this.alpha = alpha;
|
||||
|
@ -110,21 +104,21 @@ public class HemfImageRenderer implements ImageRenderer, EmbeddedExtractor {
|
|||
return false;
|
||||
}
|
||||
|
||||
boolean isClipped = true;
|
||||
if (clip == null) {
|
||||
isClipped = false;
|
||||
clip = new Insets(0,0,0,0);
|
||||
HwmfGraphicsState graphicsState = new HwmfGraphicsState();
|
||||
graphicsState.backup(graphics);
|
||||
|
||||
try {
|
||||
if (clip != null) {
|
||||
graphics.clip(anchor);
|
||||
} else {
|
||||
clip = new Insets(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
image.draw(graphics, getOuterBounds(anchor, clip));
|
||||
} finally {
|
||||
graphicsState.restore(graphics);
|
||||
}
|
||||
|
||||
Shape clipOld = graphics.getClip();
|
||||
if (isClipped) {
|
||||
graphics.clip(anchor);
|
||||
}
|
||||
|
||||
image.draw(graphics, getOuterBounds(anchor, clip));
|
||||
|
||||
graphics.setClip(clipOld);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -142,4 +136,9 @@ public class HemfImageRenderer implements ImageRenderer, EmbeddedExtractor {
|
|||
public Rectangle2D getNativeBounds() {
|
||||
return image.getBounds();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rectangle2D getBounds() {
|
||||
return Units.pointsToPixel(image == null ? new Rectangle2D.Double() : image.getBoundsInPoints());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ import java.util.function.Supplier;
|
|||
|
||||
import org.apache.poi.common.usermodel.GenericRecord;
|
||||
import org.apache.poi.hemf.draw.HemfGraphics;
|
||||
import org.apache.poi.hemf.draw.HemfGraphics.EmfRenderState;
|
||||
import org.apache.poi.hemf.record.emfplus.HemfPlusRecord;
|
||||
import org.apache.poi.hemf.record.emfplus.HemfPlusRecordIterator;
|
||||
import org.apache.poi.hwmf.usermodel.HwmfPicture;
|
||||
|
@ -300,6 +301,9 @@ public class HemfComment {
|
|||
|
||||
@Override
|
||||
public void draw(HemfGraphics ctx) {
|
||||
// This state specifies that subsequent EMF records encountered in the metafile SHOULD be processed.
|
||||
// EMF records cease being processed when the next EMF+ record is encountered.
|
||||
ctx.setRenderState(EmfRenderState.EMFPLUS_ONLY);
|
||||
records.forEach(ctx::draw);
|
||||
}
|
||||
|
||||
|
|
|
@ -862,7 +862,9 @@ public class HemfFill {
|
|||
// m12 (translateY) = eDy (The vertical translation component, in logical units.)
|
||||
double m12 = leis.readFloat();
|
||||
|
||||
xform.setTransform(m00, m10, m01, m11, m02, m12);
|
||||
// TODO: not sure, why the shearing has to be inverted here,
|
||||
// probably because of the different world/user space transformation
|
||||
xform.setTransform(m00, -m10, -m01, m11, m02, m12);
|
||||
|
||||
if (xform.isIdentity()) {
|
||||
xform.setToIdentity();
|
||||
|
|
|
@ -492,6 +492,30 @@ public class HemfFont extends HwmfFont {
|
|||
);
|
||||
}
|
||||
|
||||
public void setHeight(double height) {
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public void setWeight(int weight) {
|
||||
this.weight = weight;
|
||||
}
|
||||
|
||||
public void setItalic(boolean italic) {
|
||||
this.italic = italic;
|
||||
}
|
||||
|
||||
public void setUnderline(boolean underline) {
|
||||
this.underline = underline;
|
||||
}
|
||||
|
||||
public void setStrikeOut(boolean strikeOut) {
|
||||
this.strikeOut = strikeOut;
|
||||
}
|
||||
|
||||
public void setTypeface(String typeface) {
|
||||
this.facename = typeface;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int readString(LittleEndianInputStream leis, StringBuilder sb, int limit) throws IOException {
|
||||
sb.setLength(0);
|
||||
|
|
|
@ -559,6 +559,9 @@ public class HemfMisc {
|
|||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
final int startIdx = leis.getReadIndex();
|
||||
|
||||
// An unsigned integer that specifies the index of the extended logical pen object in
|
||||
// the EMF object table. This index MUST be saved so that this object can be
|
||||
// reused or modified.
|
||||
penIndex = (int) leis.readUInt();
|
||||
|
||||
// A 32-bit unsigned integer that specifies the offset from the start of this
|
||||
|
|
|
@ -72,10 +72,12 @@ public class HemfRecordIterator implements Iterator<HemfRecord> {
|
|||
final HemfRecord record = type.constructor.get();
|
||||
|
||||
try {
|
||||
long remBytes = recordSize-HEADER_SIZE;
|
||||
long remBytes = recordSize - HEADER_SIZE;
|
||||
long readBytes = record.init(stream, remBytes, recordId);
|
||||
assert (readBytes <= remBytes);
|
||||
stream.skipFully((int)(remBytes-readBytes));
|
||||
stream.skipFully((int) (remBytes - readBytes));
|
||||
} catch (RecordFormatException e) {
|
||||
throw e;
|
||||
} catch (IOException|RuntimeException e) {
|
||||
throw new RecordFormatException(e);
|
||||
}
|
||||
|
|
|
@ -29,12 +29,18 @@ import java.awt.geom.Rectangle2D;
|
|||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import org.apache.poi.common.usermodel.GenericRecord;
|
||||
import org.apache.poi.hemf.draw.HemfDrawProperties;
|
||||
|
@ -47,6 +53,7 @@ import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectType;
|
|||
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.sl.draw.DrawPaint;
|
||||
import org.apache.poi.util.BitField;
|
||||
import org.apache.poi.util.BitFieldFactory;
|
||||
import org.apache.poi.util.GenericRecordJsonWriter;
|
||||
|
@ -310,7 +317,19 @@ public class HemfPlusBrush {
|
|||
|
||||
long init(LittleEndianInputStream leis, long dataSize) throws IOException;
|
||||
|
||||
/**
|
||||
* Apply brush data to graphics properties
|
||||
* @param ctx the graphics context
|
||||
* @param continuedObjectData the list continued object data
|
||||
*/
|
||||
void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData);
|
||||
|
||||
/**
|
||||
* Apply brush data to pen properties
|
||||
* @param ctx the graphics context
|
||||
* @param continuedObjectData the list continued object data
|
||||
*/
|
||||
void applyPen(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData);
|
||||
}
|
||||
|
||||
/** The EmfPlusBrush object specifies a graphics brush for filling regions. */
|
||||
|
@ -347,6 +366,13 @@ public class HemfPlusBrush {
|
|||
brushData.applyObject(ctx, continuedObjectData);
|
||||
}
|
||||
|
||||
|
||||
public void applyPen(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
|
||||
EmfPlusBrushData brushData = getBrushData(continuedObjectData);
|
||||
brushData.applyPen(ctx, continuedObjectData);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public EmfPlusGraphicsVersion getGraphicsVersion() {
|
||||
return graphicsVersion;
|
||||
|
@ -372,7 +398,6 @@ public class HemfPlusBrush {
|
|||
return brushData;
|
||||
}
|
||||
|
||||
|
||||
public byte[] getRawData(List<? extends EmfPlusObjectData> continuedObjectData) {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
try {
|
||||
|
@ -416,11 +441,17 @@ public class HemfPlusBrush {
|
|||
@Override
|
||||
public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
|
||||
HemfDrawProperties prop = ctx.getProperties();
|
||||
prop.setBackgroundColor(new HwmfColorRef(solidColor));
|
||||
prop.setBrushColor(new HwmfColorRef(solidColor));
|
||||
prop.setBrushTransform(null);
|
||||
prop.setBrushStyle(HwmfBrushStyle.BS_SOLID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyPen(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
|
||||
HemfDrawProperties prop = ctx.getProperties();
|
||||
prop.setPenColor(new HwmfColorRef(solidColor));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return GenericRecordJsonWriter.marshal(this);
|
||||
|
@ -457,6 +488,12 @@ public class HemfPlusBrush {
|
|||
prop.setEmfPlusBrushHatch(style);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyPen(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
|
||||
HemfDrawProperties prop = ctx.getProperties();
|
||||
prop.setPenColor(new HwmfColorRef(foreColor));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return GenericRecordJsonWriter.marshal(this);
|
||||
|
@ -484,12 +521,15 @@ public class HemfPlusBrush {
|
|||
private Rectangle2D rect = new Rectangle2D.Double();
|
||||
private Color startColor, endColor;
|
||||
private AffineTransform transform;
|
||||
private double[] positions;
|
||||
private float[] positions;
|
||||
private Color[] blendColors;
|
||||
private double[] positionsV;
|
||||
private double[] blendFactorsV;
|
||||
private double[] positionsH;
|
||||
private double[] blendFactorsH;
|
||||
private float[] positionsV;
|
||||
private float[] blendFactorsV;
|
||||
private float[] positionsH;
|
||||
private float[] blendFactorsH;
|
||||
|
||||
private static int[] FLAG_MASKS = { 0x02, 0x04, 0x08, 0x10, 0x80 };
|
||||
private static String[] FLAG_NAMES = { "TRANSFORM", "PRESET_COLORS", "BLEND_FACTORS_H", "BLEND_FACTORS_V", "BRUSH_DATA_IS_GAMMA_CORRECTED" };
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long dataSize) throws IOException {
|
||||
|
@ -518,16 +558,13 @@ public class HemfPlusBrush {
|
|||
size += readXForm(leis, (transform = new AffineTransform()));
|
||||
}
|
||||
|
||||
final boolean isPreset = PRESET_COLORS.isSet(dataFlags);
|
||||
final boolean blendH = BLEND_FACTORS_H.isSet(dataFlags);
|
||||
final boolean blendV = BLEND_FACTORS_V.isSet(dataFlags);
|
||||
if (isPreset && (blendH || blendV)) {
|
||||
if (isPreset() && (isBlendH() || isBlendV())) {
|
||||
throw new RuntimeException("invalid combination of preset colors and blend factors v/h");
|
||||
}
|
||||
|
||||
size += (isPreset) ? readColors(leis, d -> positions = d, c -> blendColors = c) : 0;
|
||||
size += (blendV) ? readFactors(leis, d -> positionsV = d, f -> blendFactorsV = f) : 0;
|
||||
size += (blendH) ? readFactors(leis, d -> positionsH = d, f -> blendFactorsH = f) : 0;
|
||||
size += (isPreset()) ? readColors(leis, d -> positions = d, c -> blendColors = c) : 0;
|
||||
size += (isBlendV()) ? readFactors(leis, d -> positionsV = d, f -> blendFactorsV = f) : 0;
|
||||
size += (isBlendH()) ? readFactors(leis, d -> positionsH = d, f -> blendFactorsH = f) : 0;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
@ -535,7 +572,26 @@ public class HemfPlusBrush {
|
|||
@Override
|
||||
public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
|
||||
HemfDrawProperties prop = ctx.getProperties();
|
||||
// TODO: implement
|
||||
prop.setBrushStyle(HwmfBrushStyle.BS_LINEAR_GRADIENT);
|
||||
prop.setBrushRect(rect);
|
||||
prop.setBrushTransform(transform);
|
||||
|
||||
// Preset colors and BlendH/V are mutual exclusive
|
||||
if (isPreset()) {
|
||||
setColorProps(prop::setBrushColorsH, positions, this::getBlendColorAt);
|
||||
} else {
|
||||
setColorProps(prop::setBrushColorsH, positionsH, this::getBlendHColorAt);
|
||||
}
|
||||
setColorProps(prop::setBrushColorsV, positionsV, this::getBlendVColorAt);
|
||||
|
||||
if (!(isPreset() || isBlendH() || isBlendV())) {
|
||||
prop.setBrushColorsH(Arrays.asList(kv(0f,startColor), kv(1f,endColor)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyPen(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -551,7 +607,7 @@ public class HemfPlusBrush {
|
|||
@Override
|
||||
public Map<String, Supplier<?>> getGenericProperties() {
|
||||
final Map<String,Supplier<?>> m = new LinkedHashMap<>();
|
||||
m.put("flags", () -> dataFlags);
|
||||
m.put("flags", GenericRecordUtil.getBitsAsString(() -> dataFlags, FLAG_MASKS, FLAG_NAMES));
|
||||
m.put("wrapMode", () -> wrapMode);
|
||||
m.put("rect", () -> rect);
|
||||
m.put("startColor", () -> startColor);
|
||||
|
@ -565,6 +621,67 @@ public class HemfPlusBrush {
|
|||
m.put("blendFactorsH", () -> blendFactorsH);
|
||||
return Collections.unmodifiableMap(m);
|
||||
}
|
||||
|
||||
private boolean isPreset() {
|
||||
return PRESET_COLORS.isSet(dataFlags);
|
||||
}
|
||||
|
||||
private boolean isBlendH() {
|
||||
return BLEND_FACTORS_H.isSet(dataFlags);
|
||||
}
|
||||
|
||||
private boolean isBlendV() {
|
||||
return BLEND_FACTORS_V.isSet(dataFlags);
|
||||
}
|
||||
|
||||
private Map.Entry<Float,Color> getBlendColorAt(int index) {
|
||||
return kv(positions[index], blendColors[index]);
|
||||
}
|
||||
|
||||
private Map.Entry<Float,Color> getBlendHColorAt(int index) {
|
||||
return kv(positionsH[index],interpolateColors(blendFactorsH[index]));
|
||||
}
|
||||
|
||||
private Map.Entry<Float,Color> getBlendVColorAt(int index) {
|
||||
return kv(positionsV[index],interpolateColors(blendFactorsV[index]));
|
||||
}
|
||||
|
||||
private static Map.Entry<Float,Color> kv(Float position, Color color) {
|
||||
return new AbstractMap.SimpleEntry<>(position, color);
|
||||
}
|
||||
|
||||
private static void setColorProps(
|
||||
Consumer<List<? extends Map.Entry<Float, Color>>> setter, float[] positions, Function<Integer,? extends Map.Entry<Float, Color>> sup) {
|
||||
if (positions == null) {
|
||||
setter.accept(null);
|
||||
} else {
|
||||
setter.accept(IntStream.range(0, positions.length).boxed().map(sup).collect(Collectors.toList()));
|
||||
}
|
||||
}
|
||||
|
||||
private Color interpolateColors(final double factor) {
|
||||
// https://stackoverflow.com/questions/1416560/hsl-interpolation
|
||||
|
||||
final double[] hslStart = DrawPaint.RGB2HSL(startColor);
|
||||
final double[] hslStop = DrawPaint.RGB2HSL(endColor);
|
||||
|
||||
BiFunction<Number,Number,Double> linearInter = (start, stop) ->
|
||||
start.doubleValue()+(stop.doubleValue()-start.doubleValue())*factor;
|
||||
|
||||
double alpha = linearInter.apply(startColor.getAlpha(),endColor.getAlpha());
|
||||
double sat = linearInter.apply(hslStart[1],hslStop[1]);
|
||||
double lum = linearInter.apply(hslStart[2],hslStop[2]);
|
||||
|
||||
double hue1 = (hslStart[0]+hslStop[0])/2.;
|
||||
double hue2 = (hslStart[0]+hslStop[0]+360.)/2.;
|
||||
|
||||
Function<Double,Double> hueDelta = (hue) ->
|
||||
Math.min(Math.abs(hslStart[0]-hue), Math.abs(hslStop[0]-hue));
|
||||
|
||||
double hue = hueDelta.apply(hue1) < hueDelta.apply(hue2) ? hue1 : hue2;
|
||||
|
||||
return DrawPaint.HSL2RGB(hue, sat, lum, alpha/255.);
|
||||
}
|
||||
}
|
||||
|
||||
/** The EmfPlusPathGradientBrushData object specifies a path gradient for a graphics brush. */
|
||||
|
@ -577,9 +694,9 @@ public class HemfPlusBrush {
|
|||
private EmfPlusPath boundaryPath;
|
||||
private Point2D[] boundaryPoints;
|
||||
private AffineTransform transform;
|
||||
private double[] positions;
|
||||
private float[] positions;
|
||||
private Color[] blendColors;
|
||||
private double[] blendFactorsH;
|
||||
private float[] blendFactorsH;
|
||||
private Double focusScaleX, focusScaleY;
|
||||
|
||||
@Override
|
||||
|
@ -597,8 +714,12 @@ public class HemfPlusBrush {
|
|||
// that appears at the center point of the brush. The color of the brush changes gradually from the
|
||||
// boundary color to the center color as it moves from the boundary to the center point.
|
||||
centerColor = readARGB(leis.readInt());
|
||||
|
||||
int size = 3*LittleEndianConsts.INT_SIZE;
|
||||
|
||||
if (wrapMode == null) {
|
||||
return size;
|
||||
}
|
||||
|
||||
size += readPointF(leis, centerPoint);
|
||||
|
||||
// An unsigned 32-bit integer that specifies the number of colors specified in the SurroundingColor field.
|
||||
|
@ -608,10 +729,10 @@ public class HemfPlusBrush {
|
|||
// An array of SurroundingColorCount EmfPlusARGB objects that specify the colors for discrete points on the
|
||||
// boundary of the brush.
|
||||
surroundingColor = new Color[colorCount];
|
||||
for (int i=0; i<colorCount; i++) {
|
||||
for (int i = 0; i < colorCount; i++) {
|
||||
surroundingColor[i] = readARGB(leis.readInt());
|
||||
}
|
||||
size += (colorCount+1) * LittleEndianConsts.INT_SIZE;
|
||||
size += (colorCount + 1) * LittleEndianConsts.INT_SIZE;
|
||||
|
||||
// The boundary of the path gradient brush, which is specified by either a path or a closed cardinal spline.
|
||||
// If the BrushDataPath flag is set in the BrushDataFlags field, this field MUST contain an
|
||||
|
@ -677,6 +798,11 @@ public class HemfPlusBrush {
|
|||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyPen(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return GenericRecordJsonWriter.marshal(this);
|
||||
|
@ -747,6 +873,11 @@ public class HemfPlusBrush {
|
|||
prop.setBrushTransform(transform);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyPen(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return GenericRecordJsonWriter.marshal(this);
|
||||
|
@ -768,11 +899,11 @@ public class HemfPlusBrush {
|
|||
}
|
||||
}
|
||||
|
||||
private static int readPositions(LittleEndianInputStream leis, Consumer<double[]> pos) {
|
||||
private static int readPositions(LittleEndianInputStream leis, Consumer<float[]> pos) {
|
||||
final int count = leis.readInt();
|
||||
int size = LittleEndianConsts.INT_SIZE;
|
||||
|
||||
double[] positions = new double[count];
|
||||
float[] positions = new float[count];
|
||||
for (int i=0; i<count; i++) {
|
||||
positions[i] = leis.readFloat();
|
||||
size += LittleEndianConsts.INT_SIZE;
|
||||
|
@ -782,7 +913,7 @@ public class HemfPlusBrush {
|
|||
return size;
|
||||
}
|
||||
|
||||
private static int readColors(LittleEndianInputStream leis, Consumer<double[]> pos, Consumer<Color[]> cols) {
|
||||
private static int readColors(LittleEndianInputStream leis, Consumer<float[]> pos, Consumer<Color[]> cols) {
|
||||
int[] count = { 0 };
|
||||
int size = readPositions(leis, p -> { count[0] = p.length; pos.accept(p); });
|
||||
Color[] colors = new Color[count[0]];
|
||||
|
@ -793,10 +924,10 @@ public class HemfPlusBrush {
|
|||
return size + colors.length * LittleEndianConsts.INT_SIZE;
|
||||
}
|
||||
|
||||
private static int readFactors(LittleEndianInputStream leis, Consumer<double[]> pos, Consumer<double[]> facs) {
|
||||
private static int readFactors(LittleEndianInputStream leis, Consumer<float[]> pos, Consumer<float[]> facs) {
|
||||
int[] count = { 0 };
|
||||
int size = readPositions(leis, p -> { count[0] = p.length; pos.accept(p); });
|
||||
double[] factors = new double[count[0]];
|
||||
float[] factors = new float[count[0]];
|
||||
for (int i=0; i<factors.length; i++) {
|
||||
factors[i] = leis.readFloat();
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ import static org.apache.poi.util.GenericRecordUtil.getBitsAsString;
|
|||
import java.awt.Color;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Area;
|
||||
import java.awt.geom.Dimension2D;
|
||||
import java.awt.geom.Path2D;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
|
@ -34,9 +33,11 @@ import java.util.Collections;
|
|||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.PrimitiveIterator.OfInt;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.commons.codec.Charsets;
|
||||
import org.apache.commons.math3.linear.LUDecomposition;
|
||||
import org.apache.commons.math3.linear.MatrixUtils;
|
||||
import org.apache.commons.math3.linear.RealMatrix;
|
||||
|
@ -47,7 +48,9 @@ import org.apache.poi.hemf.record.emfplus.HemfPlusMisc.EmfPlusObjectId;
|
|||
import org.apache.poi.hwmf.record.HwmfBrushStyle;
|
||||
import org.apache.poi.hwmf.record.HwmfColorRef;
|
||||
import org.apache.poi.hwmf.record.HwmfMisc.WmfSetBkMode.HwmfBkMode;
|
||||
import org.apache.poi.hwmf.record.HwmfPenStyle;
|
||||
import org.apache.poi.hwmf.record.HwmfTernaryRasterOp;
|
||||
import org.apache.poi.hwmf.record.HwmfText;
|
||||
import org.apache.poi.sl.draw.ImageRenderer;
|
||||
import org.apache.poi.util.BitField;
|
||||
import org.apache.poi.util.BitFieldFactory;
|
||||
|
@ -165,7 +168,7 @@ public class HemfPlusDraw {
|
|||
prop.setBrushStyle(HwmfBrushStyle.BS_SOLID);
|
||||
prop.setBrushColor(new HwmfColorRef(getSolidColor()));
|
||||
} else {
|
||||
ctx.applyObjectTableEntry(getBrushId());
|
||||
ctx.applyPlusObjectTableEntry(getBrushId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -208,8 +211,8 @@ public class HemfPlusDraw {
|
|||
|
||||
@Override
|
||||
public void draw(HemfGraphics ctx) {
|
||||
ctx.applyObjectTableEntry(penId);
|
||||
ctx.applyObjectTableEntry(getObjectId());
|
||||
ctx.applyPlusObjectTableEntry(penId);
|
||||
ctx.applyPlusObjectTableEntry(getObjectId());
|
||||
|
||||
HemfDrawProperties prop = ctx.getProperties();
|
||||
final Path2D path = prop.getPath();
|
||||
|
@ -282,11 +285,18 @@ public class HemfPlusDraw {
|
|||
|
||||
@Override
|
||||
public void draw(HemfGraphics ctx) {
|
||||
HemfDrawProperties prop = ctx.getProperties();
|
||||
applyColor(ctx);
|
||||
|
||||
Area area = new Area();
|
||||
rectData.stream().map(Area::new).forEach(area::add);
|
||||
ctx.fill(area);
|
||||
HwmfPenStyle ps = prop.getPenStyle();
|
||||
try {
|
||||
prop.setPenStyle(null);
|
||||
ctx.fill(area);
|
||||
} finally {
|
||||
prop.setPenStyle(ps);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -319,6 +329,7 @@ public class HemfPlusDraw {
|
|||
}
|
||||
}
|
||||
|
||||
/** The EmfPlusDrawImagePoints record specifies drawing a scaled image inside a parallelogram. */
|
||||
public static class EmfPlusDrawImagePoints implements HemfPlusRecord, EmfPlusObjectId, EmfPlusCompressed, EmfPlusRelativePosition {
|
||||
/**
|
||||
* This bit indicates that the rendering of the image includes applying an effect.
|
||||
|
@ -426,39 +437,31 @@ public class HemfPlusDraw {
|
|||
public void draw(HemfGraphics ctx) {
|
||||
HemfDrawProperties prop = ctx.getProperties();
|
||||
|
||||
ctx.applyObjectTableEntry(imageAttributesID);
|
||||
ctx.applyObjectTableEntry(getObjectId());
|
||||
ctx.applyPlusObjectTableEntry(imageAttributesID);
|
||||
ctx.applyPlusObjectTableEntry(getObjectId());
|
||||
|
||||
final ImageRenderer ir = prop.getEmfPlusImage();
|
||||
if (ir == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
AffineTransform txSaved = ctx.getTransform();
|
||||
AffineTransform tx = new AffineTransform(txSaved);
|
||||
AffineTransform tx = (AffineTransform)txSaved.clone();
|
||||
HwmfTernaryRasterOp oldOp = prop.getRasterOp();
|
||||
HwmfBkMode oldBk = prop.getBkMode();
|
||||
try {
|
||||
tx.concatenate(trans);
|
||||
ctx.setTransform(tx);
|
||||
|
||||
final Rectangle2D srcBounds = ir.getNativeBounds();
|
||||
final Dimension2D dim = ir.getDimension();
|
||||
|
||||
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
|
||||
final AffineTransform srcTx = new AffineTransform();
|
||||
srcTx.translate(-srcBounds.getX(), srcBounds.getY());
|
||||
srcTx.scale(dim.getWidth()/srcBounds.getWidth(), dim.getHeight()/srcBounds.getHeight());
|
||||
|
||||
final Rectangle2D biRect = srcTx.createTransformedShape(srcRect).getBounds2D();
|
||||
|
||||
// TODO: handle srcUnit
|
||||
Rectangle2D destRect = new Rectangle2D.Double(0, 0, biRect.getWidth(), biRect.getHeight());
|
||||
ctx.drawImage(ir, srcRect, destRect);
|
||||
// transformation from srcRect to destRect was already applied,
|
||||
// therefore use srcRect as third parameter
|
||||
ctx.drawImage(ir, srcRect, srcRect);
|
||||
} finally {
|
||||
prop.setBkMode(oldBk);
|
||||
prop.setRasterOp(oldOp);
|
||||
ctx.setTransform(txSaved);
|
||||
}
|
||||
}
|
||||
|
@ -529,8 +532,8 @@ public class HemfPlusDraw {
|
|||
|
||||
@Override
|
||||
public void draw(HemfGraphics ctx) {
|
||||
ctx.applyObjectTableEntry(imageAttributesID);
|
||||
ctx.applyObjectTableEntry(getObjectId());
|
||||
ctx.applyPlusObjectTableEntry(imageAttributesID);
|
||||
ctx.applyPlusObjectTableEntry(getObjectId());
|
||||
|
||||
HemfDrawProperties prop = ctx.getProperties();
|
||||
prop.setRasterOp(HwmfTernaryRasterOp.SRCCOPY);
|
||||
|
@ -592,7 +595,7 @@ public class HemfPlusDraw {
|
|||
@Override
|
||||
public void draw(HemfGraphics ctx) {
|
||||
applyColor(ctx);
|
||||
ctx.applyObjectTableEntry(getObjectId());
|
||||
ctx.applyPlusObjectTableEntry(getObjectId());
|
||||
HemfDrawProperties prop = ctx.getProperties();
|
||||
ctx.fill(prop.getPath());
|
||||
}
|
||||
|
@ -657,12 +660,12 @@ public class HemfPlusDraw {
|
|||
private int brushId;
|
||||
private int optionsFlags;
|
||||
private String glyphs;
|
||||
private final List<Point2D> glpyhPos = new ArrayList<>();
|
||||
private final List<Point2D> glyphPos = new ArrayList<>();
|
||||
private final AffineTransform transformMatrix = new AffineTransform();
|
||||
|
||||
@Override
|
||||
public HemfPlusRecordType getEmfPlusRecordType() {
|
||||
return HemfPlusRecordType.drawDriverstring;
|
||||
return HemfPlusRecordType.drawDriverString;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -689,7 +692,7 @@ public class HemfPlusDraw {
|
|||
|
||||
// A 32-bit unsigned integer that specifies whether a transform matrix is present in the
|
||||
// TransformMatrix field.
|
||||
boolean hasMatrix = leis.readInt() == 1;
|
||||
int matrixPresent = leis.readInt();
|
||||
|
||||
// A 32-bit unsigned integer that specifies number of glyphs in the string.
|
||||
int glyphCount = leis.readInt();
|
||||
|
@ -716,10 +719,10 @@ public class HemfPlusDraw {
|
|||
for (int i=0; i<glyphCount; i++) {
|
||||
Point2D p = new Point2D.Double();
|
||||
size += readPointF(leis, p);
|
||||
glpyhPos.add(p);
|
||||
glyphPos.add(p);
|
||||
}
|
||||
|
||||
if (hasMatrix) {
|
||||
if (matrixPresent != 0) {
|
||||
size += HemfFill.readXForm(leis, transformMatrix);
|
||||
}
|
||||
|
||||
|
@ -727,6 +730,31 @@ public class HemfPlusDraw {
|
|||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(HemfGraphics ctx) {
|
||||
HemfDrawProperties prop = ctx.getProperties();
|
||||
prop.setTextAlignLatin(HwmfText.HwmfTextAlignment.LEFT);
|
||||
prop.setTextVAlignLatin(HwmfText.HwmfTextVerticalAlignment.BASELINE);
|
||||
|
||||
ctx.applyPlusObjectTableEntry(getObjectId());
|
||||
if (isSolidColor()) {
|
||||
prop.setTextColor(new HwmfColorRef(getSolidColor()));
|
||||
} else {
|
||||
ctx.applyPlusObjectTableEntry(getBrushId());
|
||||
}
|
||||
|
||||
if (REALIZED_ADVANCE.isSet(optionsFlags)) {
|
||||
byte[] buf = glyphs.getBytes(Charsets.UTF_16LE);
|
||||
ctx.drawString(buf, buf.length, glyphPos.get(0), null, null, null, null, true);
|
||||
} else {
|
||||
final OfInt glyphIter = glyphs.codePoints().iterator();
|
||||
glyphPos.forEach(p -> {
|
||||
byte[] buf = new String(new int[]{glyphIter.next()}, 0, 1).getBytes(Charsets.UTF_16LE);
|
||||
ctx.drawString(buf, buf.length, p, null, null, null, null, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return GenericRecordJsonWriter.marshal(this);
|
||||
|
@ -739,7 +767,7 @@ public class HemfPlusDraw {
|
|||
"brushId", this::getBrushId,
|
||||
"optionsFlags", getBitsAsString(() -> optionsFlags, OPTIONS_MASK, OPTIONS_NAMES),
|
||||
"glyphs", () -> glyphs,
|
||||
"glyphPos", () -> glpyhPos,
|
||||
"glyphPos", () -> glyphPos,
|
||||
"transform", () -> transformMatrix
|
||||
);
|
||||
}
|
||||
|
|
|
@ -22,7 +22,10 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.poi.common.usermodel.fonts.FontHeader;
|
||||
import org.apache.poi.hemf.draw.HemfDrawProperties;
|
||||
import org.apache.poi.hemf.draw.HemfGraphics;
|
||||
import org.apache.poi.hemf.record.emf.HemfFont;
|
||||
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.HemfPlusObject.EmfPlusObjectData;
|
||||
|
@ -106,7 +109,17 @@ public class HemfPlusFont {
|
|||
|
||||
@Override
|
||||
public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
|
||||
|
||||
HemfDrawProperties prop = ctx.getProperties();
|
||||
HemfFont font = new HemfFont();
|
||||
font.initDefaults();
|
||||
font.setTypeface(family);
|
||||
// TODO: check how to calculate the font size
|
||||
font.setHeight(emSize);
|
||||
font.setStrikeOut(STRIKEOUT.isSet(styleFlags));
|
||||
font.setUnderline(UNDERLINE.isSet(styleFlags));
|
||||
font.setWeight(BOLD.isSet(styleFlags) ? 700 : FontHeader.REGULAR_WEIGHT);
|
||||
font.setItalic(ITALIC.isSet(styleFlags));
|
||||
prop.setFont(font);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
package org.apache.poi.hemf.record.emfplus;
|
||||
|
||||
|
||||
import static org.apache.poi.util.GenericRecordUtil.getBitsAsString;
|
||||
import static org.apache.poi.util.GenericRecordUtil.getEnumBitsAsString;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
@ -60,8 +60,13 @@ public class HemfPlusHeader implements HemfPlusRecord {
|
|||
}
|
||||
}
|
||||
|
||||
private static final int[] FLAGS_MASK = { 0x0001 };
|
||||
private static final String[] FLAGS_NAMES = { "DUAL_MODE" };
|
||||
private static final int[] FLAGS_MASK = { 0x0000, 0x0001 };
|
||||
private static final String[] FLAGS_NAMES = { "EMF_PLUS_MODE", "DUAL_MODE" };
|
||||
|
||||
private static final int[] EMFFLAGS_MASK = { 0x0000, 0x0001 };
|
||||
private static final String[] EMFFLAGS_NAMES = { "CONTEXT_PRINTER", "CONTEXT_VIDEO" };
|
||||
|
||||
|
||||
|
||||
private int flags;
|
||||
private final EmfPlusGraphicsVersion version = new EmfPlusGraphicsVersion();
|
||||
|
@ -125,7 +130,7 @@ public class HemfPlusHeader implements HemfPlusRecord {
|
|||
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.EMF_DCONTEXT);
|
||||
ctx.setRenderState(EmfRenderState.EMF_DCONTEXT);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -136,9 +141,9 @@ public class HemfPlusHeader implements HemfPlusRecord {
|
|||
@Override
|
||||
public Map<String, Supplier<?>> getGenericProperties() {
|
||||
return GenericRecordUtil.getGenericProperties(
|
||||
"flags", this::getFlags,
|
||||
"flags", getEnumBitsAsString(this::getFlags, FLAGS_MASK, FLAGS_NAMES),
|
||||
"version", this::getVersion,
|
||||
"emfPlusFlags", getBitsAsString(this::getEmfPlusFlags, FLAGS_MASK, FLAGS_NAMES),
|
||||
"emfPlusFlags", getEnumBitsAsString(this::getEmfPlusFlags, EMFFLAGS_MASK, EMFFLAGS_NAMES),
|
||||
"logicalDpiX", this::getLogicalDpiX,
|
||||
"logicalDpiY", this::getLogicalDpiY
|
||||
);
|
||||
|
|
|
@ -20,6 +20,7 @@ package org.apache.poi.hemf.record.emfplus;
|
|||
import static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readRectF;
|
||||
import static org.apache.poi.util.GenericRecordUtil.getBitsAsString;
|
||||
|
||||
import java.awt.Shape;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
|
@ -30,6 +31,7 @@ import java.util.function.Supplier;
|
|||
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.hwmf.record.HwmfRegionMode;
|
||||
import org.apache.poi.util.BitField;
|
||||
import org.apache.poi.util.BitFieldFactory;
|
||||
import org.apache.poi.util.GenericRecordUtil;
|
||||
|
@ -53,18 +55,20 @@ public class HemfPlusMisc {
|
|||
}
|
||||
|
||||
public enum CombineMode {
|
||||
CombineModeReplace(0x00000000),
|
||||
CombineModeIntersect(0x00000001),
|
||||
CombineModeUnion(0x00000002),
|
||||
CombineModeXOR(0x00000003),
|
||||
CombineModeExclude(0x00000004),
|
||||
CombineModeComplement(0x00000005)
|
||||
REPLACE(0x00000000, HwmfRegionMode.RGN_COPY),
|
||||
INTERSECT(0x00000001, HwmfRegionMode.RGN_AND),
|
||||
UNION(0x00000002, HwmfRegionMode.RGN_OR),
|
||||
XOR(0x00000003, HwmfRegionMode.RGN_XOR),
|
||||
EXCLUDE(0x00000004, HwmfRegionMode.RGN_DIFF),
|
||||
COMPLEMENT(0x00000005, HwmfRegionMode.RGN_COMPLEMENT)
|
||||
;
|
||||
|
||||
public final int id;
|
||||
public final HwmfRegionMode regionMode;
|
||||
|
||||
CombineMode(int id) {
|
||||
CombineMode(int id, HwmfRegionMode regionMode) {
|
||||
this.id = id;
|
||||
this.regionMode = regionMode;
|
||||
}
|
||||
|
||||
public static CombineMode valueOf(int id) {
|
||||
|
@ -303,6 +307,14 @@ public class HemfPlusMisc {
|
|||
public CombineMode getCombineMode() {
|
||||
return CombineMode.valueOf(COMBINE_MODE.getValue(getFlags()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(HemfGraphics ctx) {
|
||||
HemfDrawProperties prop = ctx.getProperties();
|
||||
ctx.applyPlusObjectTableEntry(getObjectId());
|
||||
Shape clip = prop.getPath();
|
||||
ctx.setClip(clip, clip == null ? HwmfRegionMode.RGN_COPY : getCombineMode().regionMode, false);
|
||||
}
|
||||
}
|
||||
|
||||
/** The EmfPlusSetClipRect record combines the current clipping region with a rectangle. */
|
||||
|
@ -389,6 +401,11 @@ public class HemfPlusMisc {
|
|||
return LittleEndianConsts.INT_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(HemfGraphics ctx) {
|
||||
ctx.savePlusProperties(getStackIndex());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Supplier<?>> getGenericProperties() {
|
||||
return GenericRecordUtil.getGenericProperties(
|
||||
|
@ -407,6 +424,11 @@ public class HemfPlusMisc {
|
|||
public HemfPlusRecordType getEmfPlusRecordType() {
|
||||
return HemfPlusRecordType.restore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(HemfGraphics ctx) {
|
||||
ctx.restorePlusProperties(getStackIndex());
|
||||
}
|
||||
}
|
||||
|
||||
/** The EmfPlusSetRenderingOrigin record specifies the rendering origin for graphics output. */
|
||||
|
@ -432,10 +454,11 @@ public class HemfPlusMisc {
|
|||
public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException {
|
||||
this.flags = flags;
|
||||
|
||||
// A 32-bit unsigned integer that defines the horizontal coordinate value of the rendering origin.
|
||||
double x = leis.readUInt();
|
||||
// A 32-bit unsigned integer that defines the vertical coordinate value of the rendering origin.
|
||||
double y = leis.readUInt();
|
||||
// error in the MS-EMFPLUS docs - its a signed integer instead of an unsigned
|
||||
// A 32-bit signed integer that defines the horizontal coordinate value of the rendering origin.
|
||||
int x = leis.readInt();
|
||||
// A 32-bit signed integer that defines the vertical coordinate value of the rendering origin.
|
||||
int y = leis.readInt();
|
||||
|
||||
origin.setLocation(x,y);
|
||||
|
||||
|
|
|
@ -196,7 +196,7 @@ public class HemfPlusObject {
|
|||
public void draw(HemfGraphics ctx) {
|
||||
if (objectData.isContinuedRecord()) {
|
||||
EmfPlusObject other;
|
||||
HwmfObjectTableEntry entry = ctx.getObjectTableEntry(getObjectId());
|
||||
HwmfObjectTableEntry entry = ctx.getPlusObjectTableEntry(getObjectId());
|
||||
if (entry instanceof EmfPlusObject &&
|
||||
objectData.getClass().isInstance((other = (EmfPlusObject)entry).getObjectData())
|
||||
) {
|
||||
|
@ -205,7 +205,7 @@ public class HemfPlusObject {
|
|||
throw new RuntimeException("can't find previous record for continued record");
|
||||
}
|
||||
} else {
|
||||
ctx.addObjectTableEntry(this, getObjectId());
|
||||
ctx.addPlusObjectTableEntry(this, getObjectId());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ 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.EmfPlusRelativePosition;
|
||||
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.EmfPlusObjectType;
|
||||
import org.apache.poi.util.BitField;
|
||||
|
@ -87,7 +88,7 @@ public class HemfPlusPath {
|
|||
private static final int[] TYPE_MASKS = { 0x10, 0x20, 0x80 };
|
||||
private static final String[] TYPE_NAMES = { "DASHED", "MARKER", "CLOSE" };
|
||||
|
||||
private final HemfPlusHeader.EmfPlusGraphicsVersion graphicsVersion = new HemfPlusHeader.EmfPlusGraphicsVersion();
|
||||
private final EmfPlusGraphicsVersion graphicsVersion = new EmfPlusGraphicsVersion();
|
||||
private int pointFlags;
|
||||
private Point2D[] pathPoints;
|
||||
private byte[] pointTypes;
|
||||
|
@ -143,7 +144,7 @@ public class HemfPlusPath {
|
|||
}
|
||||
|
||||
@Override
|
||||
public HemfPlusHeader.EmfPlusGraphicsVersion getGraphicsVersion() {
|
||||
public EmfPlusGraphicsVersion getGraphicsVersion() {
|
||||
return graphicsVersion;
|
||||
}
|
||||
|
||||
|
@ -175,9 +176,15 @@ public class HemfPlusPath {
|
|||
@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);
|
||||
prop.setPath(getPath());
|
||||
}
|
||||
|
||||
public Path2D getPath() {
|
||||
return getPath(Path2D.WIND_NON_ZERO);
|
||||
}
|
||||
|
||||
public Path2D getPath(int windingRule) {
|
||||
Path2D path = new Path2D.Double(windingRule);
|
||||
for (int idx=0; idx < pathPoints.length; idx++) {
|
||||
Point2D p1 = pathPoints[idx];
|
||||
switch (getPointType(idx)) {
|
||||
|
@ -198,10 +205,11 @@ public class HemfPlusPath {
|
|||
path.closePath();
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EmfPlusObjectType getGenericRecordType() {
|
||||
public Enum getGenericRecordType() {
|
||||
return EmfPlusObjectType.PATH;
|
||||
}
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ import java.util.function.Supplier;
|
|||
import org.apache.poi.common.usermodel.GenericRecord;
|
||||
import org.apache.poi.hemf.draw.HemfDrawProperties;
|
||||
import org.apache.poi.hemf.draw.HemfGraphics;
|
||||
import org.apache.poi.hemf.record.emfplus.HemfPlusBrush.EmfPlusBrush;
|
||||
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.HemfPlusObject.EmfPlusObjectData;
|
||||
|
@ -330,6 +331,8 @@ public class HemfPlusPen {
|
|||
private EmfPlusCustomLineCap customStartCap;
|
||||
private EmfPlusCustomLineCap customEndCap;
|
||||
|
||||
private final EmfPlusBrush brush = new EmfPlusBrush();
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long dataSize, EmfPlusObjectType objectType, int flags) throws IOException {
|
||||
// An EmfPlusGraphicsVersion object that specifies the version of operating system graphics that
|
||||
|
@ -463,6 +466,8 @@ public class HemfPlusPen {
|
|||
size += initCustomCap(c -> customEndCap = c, leis);
|
||||
}
|
||||
|
||||
size += brush.init(leis, dataSize-size, EmfPlusObjectType.BRUSH, 0);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
|
@ -472,8 +477,12 @@ public class HemfPlusPen {
|
|||
}
|
||||
|
||||
private long initCustomCap(Consumer<EmfPlusCustomLineCap> setter, LittleEndianInputStream leis) throws IOException {
|
||||
int CustomStartCapSize = leis.readInt();
|
||||
int size = LittleEndianConsts.INT_SIZE;
|
||||
|
||||
EmfPlusGraphicsVersion version = new EmfPlusGraphicsVersion();
|
||||
long size = version.init(leis);
|
||||
size += version.init(leis);
|
||||
assert(version.getGraphicsVersion() != null);
|
||||
|
||||
boolean adjustableArrow = (leis.readInt() != 0);
|
||||
size += LittleEndianConsts.INT_SIZE;
|
||||
|
@ -492,11 +501,11 @@ public class HemfPlusPen {
|
|||
// TOOD:
|
||||
// - set width according unit type
|
||||
// - provide logic for different start and end cap
|
||||
// - provide standard caps like diamondd
|
||||
// - provide standard caps like diamond
|
||||
// - support custom caps
|
||||
|
||||
// workaround for too wide pens ... just arbitrary reduce high values ...
|
||||
prop.setPenWidth(penWidth > 20 ? 1 : penWidth);
|
||||
brush.applyPen(ctx, continuedObjectData);
|
||||
prop.setPenWidth(penWidth);
|
||||
prop.setPenStyle(new HwmfPenStyle(){
|
||||
@Override
|
||||
public HwmfLineCap getLineCap() {
|
||||
|
@ -573,6 +582,7 @@ public class HemfPlusPen {
|
|||
m.put("compoundLineData", () -> compoundLineData);
|
||||
m.put("customStartCap", () -> customStartCap);
|
||||
m.put("customEndCap", () -> customEndCap);
|
||||
m.put("brush", () -> brush);
|
||||
return Collections.unmodifiableMap(m);
|
||||
}
|
||||
}
|
||||
|
@ -645,13 +655,17 @@ public class HemfPlusPen {
|
|||
size += readPointF(leis, lineHotSpot);
|
||||
|
||||
if (FILL_PATH.isSet(dataFlags)) {
|
||||
int fillSize = leis.readInt();
|
||||
size += LittleEndianConsts.INT_SIZE;
|
||||
fillPath = new EmfPlusPath();
|
||||
size += fillPath.init(leis, -1, null, -1);
|
||||
size += fillPath.init(leis, fillSize, EmfPlusObjectType.PATH, -1);
|
||||
}
|
||||
|
||||
if (LINE_PATH.isSet(dataFlags)) {
|
||||
int pathSize = leis.readInt();
|
||||
size += LittleEndianConsts.INT_SIZE;
|
||||
outlinePath = new EmfPlusPath();
|
||||
size += outlinePath.init(leis, -1, null, -1);
|
||||
size += outlinePath.init(leis, pathSize, EmfPlusObjectType.PATH, -1);
|
||||
}
|
||||
|
||||
return size;
|
||||
|
|
|
@ -55,4 +55,8 @@ public interface HemfPlusRecord extends GenericRecord {
|
|||
default void draw(HemfGraphics ctx) {
|
||||
}
|
||||
|
||||
@Override
|
||||
default Enum getGenericRecordType() {
|
||||
return getEmfPlusRecordType();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,7 +76,7 @@ public enum HemfPlusRecordType {
|
|||
setClipRegion(0x4033, HemfPlusMisc.EmfPlusSetClipRegion::new),
|
||||
setClipPath(0x4034, HemfPlusMisc.EmfPlusSetClipPath::new),
|
||||
offsetClip(0x4035, UnimplementedHemfPlusRecord::new),
|
||||
drawDriverstring(0x4036, HemfPlusDraw.EmfPlusDrawDriverString::new),
|
||||
drawDriverString(0x4036, HemfPlusDraw.EmfPlusDrawDriverString::new),
|
||||
strokeFillPath(0x4037, UnimplementedHemfPlusRecord::new),
|
||||
serializableObject(0x4038, UnimplementedHemfPlusRecord::new),
|
||||
setTSGraphics(0x4039, UnimplementedHemfPlusRecord::new),
|
||||
|
|
|
@ -19,14 +19,19 @@ package org.apache.poi.hemf.record.emfplus;
|
|||
|
||||
import static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readRectF;
|
||||
|
||||
import java.awt.Shape;
|
||||
import java.awt.geom.Area;
|
||||
import java.awt.geom.Path2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.poi.common.usermodel.GenericRecord;
|
||||
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.HemfPlusObject.EmfPlusObjectData;
|
||||
|
@ -42,49 +47,51 @@ public class HemfPlusRegion {
|
|||
* Specifies a region node with child nodes. A Boolean AND operation SHOULD be applied to the left and right
|
||||
* child nodes specified by an EmfPlusRegionNodeChildNodes object
|
||||
*/
|
||||
AND(0X00000001, EmfPlusRegionNode::new),
|
||||
AND(0X00000001, EmfPlusRegionNode::new, Area::intersect),
|
||||
/**
|
||||
* Specifies a region node with child nodes. A Boolean OR operation SHOULD be applied to the left and right
|
||||
* child nodes specified by an EmfPlusRegionNodeChildNodes object.
|
||||
*/
|
||||
OR(0X00000002, EmfPlusRegionNode::new),
|
||||
OR(0X00000002, EmfPlusRegionNode::new, Area::add),
|
||||
/**
|
||||
* Specifies a region node with child nodes. A Boolean XOR operation SHOULD be applied to the left and right
|
||||
* child nodes specified by an EmfPlusRegionNodeChildNodes object.
|
||||
*/
|
||||
XOR(0X00000003, EmfPlusRegionNode::new),
|
||||
XOR(0X00000003, EmfPlusRegionNode::new, Area::exclusiveOr),
|
||||
/**
|
||||
* Specifies a region node with child nodes. A Boolean operation, defined as "the part of region 1 that is excluded
|
||||
* from region 2", SHOULD be applied to the left and right child nodes specified by an EmfPlusRegionNodeChildNodes object.
|
||||
*/
|
||||
EXCLUDE(0X00000004, EmfPlusRegionNode::new),
|
||||
EXCLUDE(0X00000004, EmfPlusRegionNode::new, Area::subtract),
|
||||
/**
|
||||
* Specifies a region node with child nodes. A Boolean operation, defined as "the part of region 2 that is excluded
|
||||
* from region 1", SHOULD be applied to the left and right child nodes specified by an EmfPlusRegionNodeChildNodes object.
|
||||
*/
|
||||
COMPLEMENT(0X00000005, EmfPlusRegionNode::new),
|
||||
COMPLEMENT(0X00000005, EmfPlusRegionNode::new, Area::subtract),
|
||||
/**
|
||||
* Specifies a region node with no child nodes.
|
||||
* The RegionNodeData field SHOULD specify a boundary with an EmfPlusRectF object.
|
||||
*/
|
||||
RECT(0X10000000, EmfPlusRegionRect::new),
|
||||
RECT(0X10000000, EmfPlusRegionRect::new, null),
|
||||
/**
|
||||
* Specifies a region node with no child nodes.
|
||||
* The RegionNodeData field SHOULD specify a boundary with an EmfPlusRegionNodePath object
|
||||
*/
|
||||
PATH(0X10000001, EmfPlusRegionPath::new),
|
||||
PATH(0X10000001, EmfPlusRegionPath::new, null),
|
||||
/** Specifies a region node with no child nodes. The RegionNodeData field SHOULD NOT be present. */
|
||||
EMPTY(0X10000002, EmfPlusRegionEmpty::new),
|
||||
EMPTY(0X10000002, EmfPlusRegionEmpty::new, null),
|
||||
/** Specifies a region node with no child nodes, and its bounds are not defined. */
|
||||
INFINITE(0X10000003, EmfPlusRegionInfinite::new)
|
||||
INFINITE(0X10000003, EmfPlusRegionInfinite::new, null)
|
||||
;
|
||||
|
||||
public final int id;
|
||||
public final Supplier<EmfPlusRegionNodeData> constructor;
|
||||
public final BiConsumer<Area,Area> operation;
|
||||
|
||||
EmfPlusRegionNodeDataType(int id, Supplier<EmfPlusRegionNodeData> constructor) {
|
||||
EmfPlusRegionNodeDataType(int id, Supplier<EmfPlusRegionNodeData> constructor, BiConsumer<Area,Area> operation) {
|
||||
this.id = id;
|
||||
this.constructor = constructor;
|
||||
this.operation = operation;
|
||||
}
|
||||
|
||||
public static EmfPlusRegionNodeDataType valueOf(int id) {
|
||||
|
@ -95,6 +102,7 @@ public class HemfPlusRegion {
|
|||
}
|
||||
}
|
||||
|
||||
/** The EmfPlusRegion object specifies line and curve segments that define a nonrectilinear shape. */
|
||||
public static class EmfPlusRegion implements EmfPlusObjectData {
|
||||
|
||||
private final EmfPlusGraphicsVersion graphicsVersion = new EmfPlusGraphicsVersion();
|
||||
|
@ -111,14 +119,16 @@ public class HemfPlusRegion {
|
|||
// An array of RegionNodeCount+1 EmfPlusRegionNode objects. Regions are specified as a binary tree of
|
||||
// region nodes, and each node MUST either be a terminal node or specify one or two child nodes.
|
||||
// RegionNode MUST contain at least one element.
|
||||
size += readNode(leis, d -> regionNode = d);
|
||||
size += readNode(leis, this::setRegionNode);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
|
||||
|
||||
HemfDrawProperties prop = ctx.getProperties();
|
||||
Shape shape = regionNode.getShape();
|
||||
prop.setPath(shape == null ? null : new Path2D.Double(shape));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -131,11 +141,19 @@ public class HemfPlusRegion {
|
|||
return EmfPlusObjectType.REGION;
|
||||
}
|
||||
|
||||
private void setRegionNode(EmfPlusRegionNodeData regionNode) {
|
||||
this.regionNode = regionNode;
|
||||
}
|
||||
|
||||
public EmfPlusRegionNodeData getRegionNode() {
|
||||
return regionNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Supplier<?>> getGenericProperties() {
|
||||
return GenericRecordUtil.getGenericProperties(
|
||||
"graphicsVersion", this::getGraphicsVersion,
|
||||
"regionNode", () -> regionNode
|
||||
"regionNode", this::getRegionNode
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -143,6 +161,8 @@ public class HemfPlusRegion {
|
|||
|
||||
public interface EmfPlusRegionNodeData extends GenericRecord {
|
||||
long init(LittleEndianInputStream leis) throws IOException;
|
||||
Shape getShape();
|
||||
default void setNodeType(EmfPlusRegionNodeDataType type) {}
|
||||
}
|
||||
|
||||
public static class EmfPlusRegionPath extends EmfPlusPath implements EmfPlusRegionNodeData {
|
||||
|
@ -150,6 +170,16 @@ public class HemfPlusRegion {
|
|||
int dataSize = leis.readInt();
|
||||
return super.init(leis, dataSize, EmfPlusObjectType.PATH, 0) + LittleEndianConsts.INT_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape getShape() {
|
||||
return getPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EmfPlusRegionNodeDataType getGenericRecordType() {
|
||||
return EmfPlusRegionNodeDataType.PATH;
|
||||
}
|
||||
}
|
||||
|
||||
public static class EmfPlusRegionInfinite implements EmfPlusRegionNodeData {
|
||||
|
@ -162,6 +192,16 @@ public class HemfPlusRegion {
|
|||
public Map<String, Supplier<?>> getGenericProperties() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape getShape() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EmfPlusRegionNodeDataType getGenericRecordType() {
|
||||
return EmfPlusRegionNodeDataType.INFINITE;
|
||||
}
|
||||
}
|
||||
|
||||
public static class EmfPlusRegionEmpty implements EmfPlusRegionNodeData {
|
||||
|
@ -174,6 +214,16 @@ public class HemfPlusRegion {
|
|||
public Map<String, Supplier<?>> getGenericProperties() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape getShape() {
|
||||
return new Rectangle2D.Double(0,0,0,0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EmfPlusRegionNodeDataType getGenericRecordType() {
|
||||
return EmfPlusRegionNodeDataType.EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
public static class EmfPlusRegionRect implements EmfPlusRegionNodeData {
|
||||
|
@ -188,25 +238,90 @@ public class HemfPlusRegion {
|
|||
public Map<String, Supplier<?>> getGenericProperties() {
|
||||
return GenericRecordUtil.getGenericProperties("rect", () -> rect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape getShape() {
|
||||
return rect;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EmfPlusRegionNodeDataType getGenericRecordType() {
|
||||
return EmfPlusRegionNodeDataType.RECT;
|
||||
}
|
||||
}
|
||||
|
||||
/** The EmfPlusRegionNode object specifies nodes of a graphics region. */
|
||||
public static class EmfPlusRegionNode implements EmfPlusRegionNodeData {
|
||||
private EmfPlusRegionNodeData left, right;
|
||||
private EmfPlusRegionNodeDataType nodeType;
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis) throws IOException {
|
||||
long size = readNode(leis, n -> left = n);
|
||||
size += readNode(leis, n -> right = n);
|
||||
long size = readNode(leis, this::setLeft);
|
||||
size += readNode(leis, this::setRight);
|
||||
return size;
|
||||
}
|
||||
|
||||
private void setLeft(EmfPlusRegionNodeData left) {
|
||||
this.left = left;
|
||||
}
|
||||
|
||||
private void setRight(EmfPlusRegionNodeData right) {
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
public EmfPlusRegionNodeData getLeft() {
|
||||
return left;
|
||||
}
|
||||
|
||||
public EmfPlusRegionNodeData getRight() {
|
||||
return right;
|
||||
}
|
||||
|
||||
public EmfPlusRegionNodeDataType getNodeType() {
|
||||
return nodeType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNodeType(EmfPlusRegionNodeDataType nodeType) {
|
||||
this.nodeType = nodeType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Supplier<?>> getGenericProperties() {
|
||||
return GenericRecordUtil.getGenericProperties(
|
||||
"left", () -> left,
|
||||
"right", () -> right
|
||||
"nodeType", this::getNodeType,
|
||||
"left", this::getLeft,
|
||||
"right", this::getRight
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape getShape() {
|
||||
boolean com = (nodeType == EmfPlusRegionNodeDataType.COMPLEMENT);
|
||||
final Shape leftShape = (com ? right : left).getShape();
|
||||
final Shape rightShape = (com ? left : right).getShape();
|
||||
|
||||
if (leftShape == null) {
|
||||
return rightShape;
|
||||
} else if (rightShape == null) {
|
||||
return leftShape;
|
||||
}
|
||||
|
||||
// TODO: check Area vs. Path manipulation
|
||||
Area leftArea = new Area(leftShape);
|
||||
Area rightArea = new Area(rightShape);
|
||||
|
||||
assert(nodeType.operation != null);
|
||||
nodeType.operation.accept(leftArea, rightArea);
|
||||
|
||||
return leftArea;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EmfPlusRegionNodeDataType getGenericRecordType() {
|
||||
return nodeType;
|
||||
}
|
||||
}
|
||||
|
||||
private static long readNode(LittleEndianInputStream leis, Consumer<EmfPlusRegionNodeData> con) throws IOException {
|
||||
|
@ -216,6 +331,7 @@ public class HemfPlusRegion {
|
|||
assert(type != null);
|
||||
EmfPlusRegionNodeData nd = type.constructor.get();
|
||||
con.accept(nd);
|
||||
nd.setNodeType(type);
|
||||
return LittleEndianConsts.INT_SIZE + nd.init(leis);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -130,14 +130,23 @@ public class HemfPicture implements Iterable<HemfRecord>, GenericRecord {
|
|||
return new Rectangle2D.Double(x, y, width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the image bounds in points
|
||||
*
|
||||
* @return the image bounds in points
|
||||
*/
|
||||
public Rectangle2D getBoundsInPoints() {
|
||||
return Units.pixelToPoints(getHeader().getBoundsRectangle());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the image size in points
|
||||
*
|
||||
* @return the image size in points
|
||||
*/
|
||||
public Dimension2D getSize() {
|
||||
final Rectangle2D b = getHeader().getBoundsRectangle();
|
||||
return Units.pixelToPoints(new Dimension2DDouble(abs(b.getWidth()), abs(b.getHeight())));
|
||||
final Rectangle2D b = getBoundsInPoints();
|
||||
return new Dimension2DDouble(abs(b.getWidth()), abs(b.getHeight()));
|
||||
}
|
||||
|
||||
private static double minX(Rectangle2D bounds) {
|
||||
|
|
|
@ -93,12 +93,27 @@ public class HwmfGraphics {
|
|||
}
|
||||
}
|
||||
|
||||
protected final List<HwmfDrawProperties> propStack = new LinkedList<>();
|
||||
private static final Float[] WEIGHT_MAP = {
|
||||
900f, TextAttribute.WEIGHT_ULTRABOLD,
|
||||
800f, TextAttribute.WEIGHT_EXTRABOLD,
|
||||
750f, TextAttribute.WEIGHT_HEAVY,
|
||||
700f, TextAttribute.WEIGHT_BOLD,
|
||||
600f, TextAttribute.WEIGHT_DEMIBOLD,
|
||||
500f, TextAttribute.WEIGHT_MEDIUM,
|
||||
450f, TextAttribute.WEIGHT_SEMIBOLD,
|
||||
400f, TextAttribute.WEIGHT_REGULAR,
|
||||
300f, TextAttribute.WEIGHT_DEMILIGHT,
|
||||
200f, TextAttribute.WEIGHT_LIGHT,
|
||||
1f, TextAttribute.WEIGHT_EXTRA_LIGHT
|
||||
};
|
||||
|
||||
|
||||
private final List<HwmfDrawProperties> propStack = new LinkedList<>();
|
||||
protected HwmfDrawProperties prop;
|
||||
protected final Graphics2D graphicsCtx;
|
||||
protected final BitSet objectIndexes = new BitSet();
|
||||
protected final TreeMap<Integer,HwmfObjectTableEntry> objectTable = new TreeMap<>();
|
||||
protected final AffineTransform initialAT = new AffineTransform();
|
||||
private final AffineTransform initialAT = new AffineTransform();
|
||||
|
||||
|
||||
private static final Charset DEFAULT_CHARSET = LocaleUtil.CHARSET_1252;
|
||||
|
@ -207,9 +222,14 @@ public class HwmfGraphics {
|
|||
case BS_DIBPATTERNPT: return getPatternPaint();
|
||||
case BS_SOLID: return getSolidFill();
|
||||
case BS_HATCHED: return getHatchedFill();
|
||||
case BS_LINEAR_GRADIENT: return getLinearGradient();
|
||||
}
|
||||
}
|
||||
|
||||
protected Paint getLinearGradient() {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected Paint getSolidFill() {
|
||||
return getProperties().getBrushColor().getColor();
|
||||
}
|
||||
|
@ -440,8 +460,13 @@ public class HwmfGraphics {
|
|||
}
|
||||
|
||||
int trimLen;
|
||||
for (trimLen=0; trimLen<text.length-1; trimLen+=2) {
|
||||
if ((text[trimLen] == -1 && text[trimLen+1] == -1) ||
|
||||
for (trimLen=0; trimLen<text.length; trimLen+=2) {
|
||||
if (trimLen == text.length-1) {
|
||||
if (text[trimLen] != 0) {
|
||||
trimLen++;
|
||||
}
|
||||
break;
|
||||
} else if ((text[trimLen] == -1 && text[trimLen+1] == -1) ||
|
||||
((text[trimLen] & 0xE0) == 0 && text[trimLen+1] == 0)) {
|
||||
break;
|
||||
}
|
||||
|
@ -519,7 +544,7 @@ public class HwmfGraphics {
|
|||
tx.translate(-pixelBounds.getWidth() / 2., 0);
|
||||
break;
|
||||
case RIGHT:
|
||||
tx.translate(-pixelBounds.getWidth(), 0);
|
||||
tx.translate(-layout.getAdvance(), 0);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -581,7 +606,16 @@ public class HwmfGraphics {
|
|||
if (font.isItalic()) {
|
||||
as.addAttribute(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE);
|
||||
}
|
||||
as.addAttribute(TextAttribute.WEIGHT, font.getWeight());
|
||||
// convert font weight to awt font weight - usually a font weight of 400 is regarded as regular
|
||||
final int fw = font.getWeight();
|
||||
Float awtFW = TextAttribute.WEIGHT_REGULAR;
|
||||
for (int i=0; i<WEIGHT_MAP.length; i+=2) {
|
||||
if (fw >= WEIGHT_MAP[i]) {
|
||||
awtFW = WEIGHT_MAP[i+1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
as.addAttribute(TextAttribute.WEIGHT, awtFW);
|
||||
}
|
||||
|
||||
private double getFontHeight(HwmfFont font) {
|
||||
|
@ -661,7 +695,11 @@ public class HwmfGraphics {
|
|||
// of the referenced image and can be also negative
|
||||
Composite old = graphicsCtx.getComposite();
|
||||
graphicsCtx.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
|
||||
img.drawImage(graphicsCtx, normBounds, getSubImageInsets(srcBounds, img.getNativeBounds()));
|
||||
|
||||
boolean useDeviceBounds = (img instanceof HwmfImageRenderer);
|
||||
|
||||
img.drawImage(graphicsCtx, normBounds,
|
||||
getSubImageInsets(srcBounds, useDeviceBounds ? img.getNativeBounds() : img.getBounds()));
|
||||
graphicsCtx.setComposite(old);
|
||||
|
||||
graphicsCtx.setTransform(oldTrans);
|
||||
|
@ -683,9 +721,9 @@ public class HwmfGraphics {
|
|||
// Todo: check if we need to normalize srcBounds x/y, in case of flipped images
|
||||
// for now we assume the width/height is positive
|
||||
int left = (int)Math.round((srcBounds.getX()-nativeBounds.getX())/nativeBounds.getWidth()*100_000.);
|
||||
int top = (int)Math.round((srcBounds.getY()-nativeBounds.getY())/nativeBounds.getWidth()*100_000.);
|
||||
int top = (int)Math.round((srcBounds.getY()-nativeBounds.getY())/nativeBounds.getHeight()*100_000.);
|
||||
int right = (int)Math.round((nativeBounds.getMaxX()-srcBounds.getMaxX())/nativeBounds.getWidth()*100_000.);
|
||||
int bottom = (int)Math.round((nativeBounds.getMaxY()-srcBounds.getMaxY())/nativeBounds.getWidth()*100_000.);
|
||||
int bottom = (int)Math.round((nativeBounds.getMaxY()-srcBounds.getMaxY())/nativeBounds.getHeight()*100_000.);
|
||||
|
||||
return new Insets(top, left, bottom, right);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
==================================================================== */
|
||||
|
||||
package org.apache.poi.hwmf.draw;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Composite;
|
||||
import java.awt.Font;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Paint;
|
||||
import java.awt.Shape;
|
||||
import java.awt.Stroke;
|
||||
import java.awt.geom.AffineTransform;
|
||||
|
||||
import org.apache.poi.util.Internal;
|
||||
|
||||
/**
|
||||
* An util class for saving the state of a {@link java.awt.Graphics2D} object
|
||||
*/
|
||||
@Internal
|
||||
public class HwmfGraphicsState {
|
||||
private Color background;
|
||||
private Shape clip;
|
||||
private Color color;
|
||||
private Composite composite;
|
||||
private Font font;
|
||||
private Paint paint;
|
||||
private Stroke stroke;
|
||||
private AffineTransform trans;
|
||||
|
||||
/**
|
||||
* Saves the state of the graphics2D object
|
||||
*/
|
||||
public void backup(Graphics2D graphics2D) {
|
||||
background = graphics2D.getBackground();
|
||||
clip = graphics2D.getClip();
|
||||
color = graphics2D.getColor();
|
||||
composite = graphics2D.getComposite();
|
||||
font = graphics2D.getFont();
|
||||
paint = graphics2D.getPaint();
|
||||
stroke = graphics2D.getStroke();
|
||||
trans = graphics2D.getTransform();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the state into the graphics2D object
|
||||
*/
|
||||
public void restore(Graphics2D graphics2D) {
|
||||
graphics2D.setBackground(background);
|
||||
graphics2D.setClip(clip);
|
||||
graphics2D.setColor(color);
|
||||
graphics2D.setComposite(composite);
|
||||
graphics2D.setFont(font);
|
||||
graphics2D.setPaint(paint);
|
||||
graphics2D.setStroke(stroke);
|
||||
graphics2D.setTransform(trans);
|
||||
}
|
||||
}
|
|
@ -21,7 +21,6 @@ import java.awt.Dimension;
|
|||
import java.awt.Graphics2D;
|
||||
import java.awt.Insets;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.Shape;
|
||||
import java.awt.geom.Dimension2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
@ -114,20 +113,22 @@ public class HwmfImageRenderer implements ImageRenderer, EmbeddedExtractor {
|
|||
return false;
|
||||
}
|
||||
|
||||
HwmfGraphicsState graphicsState = new HwmfGraphicsState();
|
||||
graphicsState.backup(graphics);
|
||||
|
||||
boolean isClipped = true;
|
||||
if (clip == null) {
|
||||
isClipped = false;
|
||||
clip = new Insets(0,0,0,0);
|
||||
}
|
||||
|
||||
Shape clipOld = graphics.getClip();
|
||||
if (isClipped) {
|
||||
graphics.clip(anchor);
|
||||
}
|
||||
|
||||
image.draw(graphics, getOuterBounds(anchor, clip));
|
||||
|
||||
graphics.setClip(clipOld);
|
||||
graphicsState.restore(graphics);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -179,4 +180,9 @@ public class HwmfImageRenderer implements ImageRenderer, EmbeddedExtractor {
|
|||
public Rectangle2D getNativeBounds() {
|
||||
return image.getBounds();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rectangle2D getBounds() {
|
||||
return Units.pointsToPixel(image == null ? new Rectangle2D.Double() : image.getBoundsInPoints());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,7 +64,11 @@ public enum HwmfBrushStyle {
|
|||
/**
|
||||
* Not supported
|
||||
*/
|
||||
BS_MONOPATTERN(0x0009);
|
||||
BS_MONOPATTERN(0x0009),
|
||||
/**
|
||||
* (POI arbitrary:) EMF/EMF+ specific value for linear gradient paint
|
||||
*/
|
||||
BS_LINEAR_GRADIENT(0x0100);
|
||||
|
||||
int flag;
|
||||
HwmfBrushStyle(int flag) {
|
||||
|
|
|
@ -288,7 +288,7 @@ public class HwmfFont implements FontInfo, GenericRecord {
|
|||
* For all height comparisons, the font mapper SHOULD find the largest physical
|
||||
* font that does not exceed the requested size.
|
||||
*/
|
||||
protected int height;
|
||||
protected double height;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the average width, in logical units, of
|
||||
|
@ -433,7 +433,7 @@ public class HwmfFont implements FontInfo, GenericRecord {
|
|||
facename = "SansSerif";
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
public double getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
|
|
|
@ -45,7 +45,11 @@ public enum HwmfRegionMode {
|
|||
/**
|
||||
* The new clipping region is the current path (or the new region).
|
||||
*/
|
||||
RGN_COPY(0x05, HwmfRegionMode::copyOp);
|
||||
RGN_COPY(0x05, HwmfRegionMode::copyOp),
|
||||
/**
|
||||
* This is the opposite of {@link #RGN_DIFF}, and only made-up for compatibility with EMF+
|
||||
*/
|
||||
RGN_COMPLEMENT(-1, HwmfRegionMode::complementOp);
|
||||
|
||||
private final int flag;
|
||||
private final BiFunction<Shape,Shape,Shape> op;
|
||||
|
@ -125,4 +129,17 @@ public enum HwmfRegionMode {
|
|||
private static Shape copyOp(final Shape oldClip, final Shape newClip) {
|
||||
return (newClip == null || newClip.getBounds2D().isEmpty()) ? null : newClip;
|
||||
}
|
||||
|
||||
private static Shape complementOp(final Shape oldClip, final Shape newClip) {
|
||||
assert(newClip != null);
|
||||
if (newClip.getBounds2D().isEmpty()) {
|
||||
return oldClip;
|
||||
} else if (oldClip == null) {
|
||||
return newClip;
|
||||
} else {
|
||||
Area newArea = new Area(newClip);
|
||||
newArea.subtract(new Area(oldClip));
|
||||
return newArea;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -232,7 +232,10 @@ public class HwmfText {
|
|||
|
||||
@Override
|
||||
public Map<String, Supplier<?>> getGenericProperties() {
|
||||
return GenericRecordUtil.getGenericProperties("text", () -> getText(StandardCharsets.US_ASCII));
|
||||
return GenericRecordUtil.getGenericProperties(
|
||||
"text", () -> getText(StandardCharsets.US_ASCII),
|
||||
"reference", () -> reference
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ import java.util.function.Supplier;
|
|||
import org.apache.poi.common.usermodel.GenericRecord;
|
||||
import org.apache.poi.hwmf.draw.HwmfDrawProperties;
|
||||
import org.apache.poi.hwmf.draw.HwmfGraphics;
|
||||
import org.apache.poi.hwmf.draw.HwmfGraphicsState;
|
||||
import org.apache.poi.hwmf.record.HwmfHeader;
|
||||
import org.apache.poi.hwmf.record.HwmfPlaceableHeader;
|
||||
import org.apache.poi.hwmf.record.HwmfRecord;
|
||||
|
@ -127,8 +128,8 @@ public class HwmfPicture implements Iterable<HwmfRecord>, GenericRecord {
|
|||
}
|
||||
|
||||
public void draw(Graphics2D ctx, Rectangle2D graphicsBounds) {
|
||||
final Shape clip = ctx.getClip();
|
||||
final AffineTransform at = ctx.getTransform();
|
||||
HwmfGraphicsState state = new HwmfGraphicsState();
|
||||
state.backup(ctx);
|
||||
try {
|
||||
Rectangle2D wmfBounds = getBounds();
|
||||
Rectangle2D innerBounds = getInnnerBounds();
|
||||
|
@ -137,13 +138,10 @@ public class HwmfPicture implements Iterable<HwmfRecord>, GenericRecord {
|
|||
}
|
||||
|
||||
// scale output bounds to image bounds
|
||||
ctx.translate(graphicsBounds.getX(), graphicsBounds.getY());
|
||||
ctx.scale(graphicsBounds.getWidth()/wmfBounds.getWidth(), graphicsBounds.getHeight()/wmfBounds.getHeight());
|
||||
ctx.translate(graphicsBounds.getCenterX(), graphicsBounds.getCenterY());
|
||||
ctx.scale(graphicsBounds.getWidth()/innerBounds.getWidth(), graphicsBounds.getHeight()/innerBounds.getHeight());
|
||||
ctx.translate(-innerBounds.getCenterX(), -innerBounds.getCenterY());
|
||||
|
||||
ctx.translate(-wmfBounds.getX(), -wmfBounds.getY());
|
||||
ctx.translate(innerBounds.getCenterX(), innerBounds.getCenterY());
|
||||
ctx.scale(wmfBounds.getWidth()/innerBounds.getWidth(), wmfBounds.getHeight()/innerBounds.getHeight());
|
||||
ctx.translate(-wmfBounds.getCenterX(), -wmfBounds.getCenterY());
|
||||
|
||||
HwmfGraphics g = new HwmfGraphics(ctx, innerBounds);
|
||||
HwmfDrawProperties prop = g.getProperties();
|
||||
|
@ -162,8 +160,7 @@ public class HwmfPicture implements Iterable<HwmfRecord>, GenericRecord {
|
|||
idx++;
|
||||
}
|
||||
} finally {
|
||||
ctx.setTransform(at);
|
||||
ctx.setClip(clip);
|
||||
state.restore(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -216,17 +213,28 @@ public class HwmfPicture implements Iterable<HwmfRecord>, GenericRecord {
|
|||
}
|
||||
|
||||
/**
|
||||
* Return the image size in points
|
||||
* Return the image bound in points
|
||||
*
|
||||
* @return the image size in points
|
||||
* @return the image bound in points
|
||||
*/
|
||||
public Dimension2D getSize() {
|
||||
public Rectangle2D getBoundsInPoints() {
|
||||
double inch = (placeableHeader == null) ? 1440 : placeableHeader.getUnitsPerInch();
|
||||
Rectangle2D bounds = getBounds();
|
||||
|
||||
//coefficient to translate from WMF dpi to 72dpi
|
||||
double coeff = Units.POINT_DPI/inch;
|
||||
return new Dimension2DDouble(bounds.getWidth()*coeff, bounds.getHeight()*coeff);
|
||||
return AffineTransform.getScaleInstance(coeff, coeff).createTransformedShape(bounds).getBounds2D();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the image size in points
|
||||
*
|
||||
* @return the image size in points
|
||||
*/
|
||||
public Dimension2D getSize() {
|
||||
Rectangle2D bounds = getBoundsInPoints();
|
||||
return new Dimension2DDouble(bounds.getWidth(), bounds.getHeight());
|
||||
}
|
||||
|
||||
public Iterable<HwmfEmbedded> getEmbeddings() {
|
||||
|
|
Loading…
Reference in New Issue