aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndreas Beeker <kiwiwings@apache.org>2020-11-01 15:37:20 +0000
committerAndreas Beeker <kiwiwings@apache.org>2020-11-01 15:37:20 +0000
commit38b415e26af27aa7a22d1d1ea13eb87f445a9066 (patch)
treee06ef82c2e34261388cdf55d1092842a955a5421
parentd835bdef42a46f6b70baaf558e0a716914c2a2cb (diff)
downloadpoi-38b415e26af27aa7a22d1d1ea13eb87f445a9066.tar.gz
poi-38b415e26af27aa7a22d1d1ea13eb87f445a9066.zip
#64716 - wmf display error
EMF: workaround for invalid EMF header bounds EMF: add option to PPTX2PNG / DrawableHint to fallback to force EMF header bounds EMF: use RGB instead of HSL gradiants git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1883051 13f79535-47bb-0310-9956-ffa450edef68
-rw-r--r--src/java/org/apache/poi/common/usermodel/PictureType.java35
-rw-r--r--src/java/org/apache/poi/sl/draw/DrawPictureShape.java17
-rw-r--r--src/java/org/apache/poi/sl/draw/Drawable.java12
-rw-r--r--src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureLine.java61
-rw-r--r--src/ooxml/java/org/apache/poi/xslf/util/PPTX2PNG.java9
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java19
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecord.java4
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java48
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusBrush.java67
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusHeader.java6
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusMisc.java5
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecord.java4
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java129
13 files changed, 296 insertions, 120 deletions
diff --git a/src/java/org/apache/poi/common/usermodel/PictureType.java b/src/java/org/apache/poi/common/usermodel/PictureType.java
index 4aceb624a0..d3ed67fc2e 100644
--- a/src/java/org/apache/poi/common/usermodel/PictureType.java
+++ b/src/java/org/apache/poi/common/usermodel/PictureType.java
@@ -17,6 +17,8 @@
package org.apache.poi.common.usermodel;
+import org.apache.poi.poifs.filesystem.FileMagic;
+
/**
* General enum class to define a picture format/type
*
@@ -65,4 +67,37 @@ public enum PictureType {
this.contentType = contentType;
this.extension = extension;
}
+
+ public String getContentType() {
+ return contentType;
+ }
+
+ public String getExtension() {
+ return extension;
+ }
+
+ public static PictureType valueOf(FileMagic fm) {
+ switch (fm) {
+ case BMP:
+ return PictureType.BMP;
+ case GIF:
+ return PictureType.GIF;
+ case JPEG:
+ return PictureType.JPEG;
+ case PNG:
+ return PictureType.PNG;
+ case XML:
+ // this is quite fuzzy, to suppose all XMLs are SVGs when handling pictures ...
+ return PictureType.SVG;
+ case WMF:
+ return PictureType.WMF;
+ case EMF:
+ return PictureType.EMF;
+ case TIFF:
+ return PictureType.TIFF;
+ default:
+ case UNKNOWN:
+ return PictureType.UNKNOWN;
+ }
+ }
}
diff --git a/src/java/org/apache/poi/sl/draw/DrawPictureShape.java b/src/java/org/apache/poi/sl/draw/DrawPictureShape.java
index 695f4ec708..2e25d75a6a 100644
--- a/src/java/org/apache/poi/sl/draw/DrawPictureShape.java
+++ b/src/java/org/apache/poi/sl/draw/DrawPictureShape.java
@@ -27,6 +27,8 @@ import java.util.ServiceLoader;
import java.util.function.Supplier;
import java.util.stream.StreamSupport;
+import org.apache.poi.common.usermodel.PictureType;
+import org.apache.poi.poifs.filesystem.FileMagic;
import org.apache.poi.sl.usermodel.PictureData;
import org.apache.poi.sl.usermodel.PictureShape;
import org.apache.poi.sl.usermodel.RectAlign;
@@ -36,11 +38,6 @@ import org.apache.poi.util.POILogger;
public class DrawPictureShape extends DrawSimpleShape {
private static final POILogger LOG = POILogFactory.getLogger(DrawPictureShape.class);
- private static final String[] KNOWN_RENDERER = {
- "org.apache.poi.hwmf.draw.HwmfImageRenderer",
- "org.apache.poi.hemf.draw.HemfImageRenderer",
- "org.apache.poi.xslf.draw.SVGImageRenderer"
- };
public DrawPictureShape(PictureShape<?,?> shape) {
super(shape);
@@ -60,10 +57,14 @@ public class DrawPictureShape extends DrawSimpleShape {
}
try {
- String ct = data.getContentType();
+ byte[] dataBytes = data.getData();
+
+ PictureType type = PictureType.valueOf(FileMagic.valueOf(dataBytes));
+ String ct = (type == PictureType.UNKNOWN) ? data.getContentType() : type.getContentType();
+
ImageRenderer renderer = getImageRenderer(graphics, ct);
if (renderer.canRender(ct)) {
- renderer.loadImage(data.getData(), ct);
+ renderer.loadImage(dataBytes, ct);
renderer.drawImage(graphics, anchor, insets);
return;
}
@@ -92,7 +93,7 @@ public class DrawPictureShape extends DrawSimpleShape {
// the fallback is the BitmapImageRenderer, at least it gracefully handles invalid images
final Supplier<ImageRenderer> getFallback = () -> {
- LOG.log(POILogger.WARN, "No suiteable image renderer found for content-type '"+
+ LOG.log(POILogger.WARN, "No suitable image renderer found for content-type '"+
contentType+"' - include poi-scratchpad (for wmf/emf) or poi-ooxml (for svg) jars!");
return fallback;
};
diff --git a/src/java/org/apache/poi/sl/draw/Drawable.java b/src/java/org/apache/poi/sl/draw/Drawable.java
index 919e1a6f25..cb5f8592f9 100644
--- a/src/java/org/apache/poi/sl/draw/Drawable.java
+++ b/src/java/org/apache/poi/sl/draw/Drawable.java
@@ -49,6 +49,7 @@ public interface Drawable {
case 12: return "CURRENT_SLIDE";
case 13: return "BUFFERED_IMAGE";
case 14: return "DEFAULT_CHARSET";
+ case 15: return "EMF_FORCE_HEADER_BOUNDS";
default: return "UNKNOWN_ID "+intKey();
}
}
@@ -153,6 +154,17 @@ public interface Drawable {
*/
DrawableHint DEFAULT_CHARSET = new DrawableHint(14);
+ /**
+ * A boolean value to force the usage of the bounding box, which is specified in the EMF header.
+ * Defaults to {@code FALSE} - in this case the records are scanned for window and
+ * viewport records to determine the initial bounding box by using the following
+ * condition: {@code isValid(viewport) ? viewport : isValid(window) ? window : headerBounds }
+ * <p>
+ * This is a workaround switch, which might be removed in future releases, when the bounding box
+ * determination for the special cases is fixed.
+ * In most cases it's recommended to leave the default value.
+ */
+ DrawableHint EMF_FORCE_HEADER_BOUNDS = new DrawableHint(15);
/**
* Apply 2-D transforms before drawing this shape. This includes rotation and flipping.
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureLine.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureLine.java
index 8d9ffe4024..075ac95a6a 100644
--- a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureLine.java
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureLine.java
@@ -321,34 +321,11 @@ public abstract class SignatureLine {
*/
protected byte[] plainPng() throws IOException {
byte[] plain = getPlainSignature();
- PictureType pictureType;
- switch (FileMagic.valueOf(plain)) {
- case PNG:
- return plain;
- case BMP:
- pictureType = PictureType.BMP;
- break;
- case EMF:
- pictureType = PictureType.EMF;
- break;
- case GIF:
- pictureType = PictureType.GIF;
- break;
- case JPEG:
- pictureType = PictureType.JPEG;
- break;
- case XML:
- pictureType = PictureType.SVG;
- break;
- case TIFF:
- pictureType = PictureType.TIFF;
- break;
- default:
- throw new IllegalArgumentException("Unsupported picture format");
+ PictureType pictureType = PictureType.valueOf(FileMagic.valueOf(plain));
+ if (pictureType == PictureType.UNKNOWN) {
+ throw new IllegalArgumentException("Unsupported picture format");
}
-
-
ImageRenderer rnd = DrawPictureShape.getImageRenderer(null, pictureType.contentType);
if (rnd == null) {
throw new UnsupportedOperationException(pictureType + " can't be rendered - did you provide poi-scratchpad and its dependencies (batik et al.)");
@@ -375,11 +352,8 @@ public abstract class SignatureLine {
/**
* Generate the image for a signature line
- * @param caption three lines separated by "\n" - usually something like "First name Last name\nRole\nname of the key"
- * @param inputImage the plain signature - supported formats are PNG,GIF,JPEG,(SVG),EMF,WMF.
- * for SVG,EMF,WMF poi-scratchpad needs to be in the class-/modulepath
- * if {@code null}, the inputImage is not rendered
- * @param invalidText for invalid signature images, use the given text
+ * @param showSignature show signature image - use {@code false} for placeholder images in to-be-signed documents
+ * @param showInvalidStamp print invalid stamp over the signature
* @return the signature image in PNG format as byte array
*/
protected byte[] generateImage(boolean showSignature, boolean showInvalidStamp) throws IOException {
@@ -462,28 +436,11 @@ public abstract class SignatureLine {
private void determineContentType() {
FileMagic fm = FileMagic.valueOf(plainSignature);
- switch (fm) {
- case GIF:
- contentType = PictureType.GIF.contentType;
- break;
- case PNG:
- contentType = PictureType.PNG.contentType;
- break;
- case JPEG:
- contentType = PictureType.JPEG.contentType;
- break;
- case XML:
- contentType = PictureType.SVG.contentType;
- break;
- case EMF:
- contentType = PictureType.EMF.contentType;
- break;
- case WMF:
- contentType = PictureType.WMF.contentType;
- break;
- default:
- throw new IllegalArgumentException("unknown image type");
+ PictureType type = PictureType.valueOf(fm);
+ if (type == PictureType.UNKNOWN) {
+ throw new IllegalArgumentException("unknown image type");
}
+ contentType = type.contentType;
}
}
diff --git a/src/ooxml/java/org/apache/poi/xslf/util/PPTX2PNG.java b/src/ooxml/java/org/apache/poi/xslf/util/PPTX2PNG.java
index 46d3da99fb..f4c57325da 100644
--- a/src/ooxml/java/org/apache/poi/xslf/util/PPTX2PNG.java
+++ b/src/ooxml/java/org/apache/poi/xslf/util/PPTX2PNG.java
@@ -75,7 +75,8 @@ public final class PPTX2PNG {
" some files (usually wmf) don't have a header, i.e. an identifiable file magic\n" +
" -textAsShapes text elements are saved as shapes in SVG, necessary for variable spacing\n" +
" often found in math formulas\n" +
- " -charset <cs> sets the default charset to be used, defaults to Windows-1252";
+ " -charset <cs> sets the default charset to be used, defaults to Windows-1252\n" +
+ " -emfHeaderBounds force the usage of the emf header bounds to calculate the bounding box";
System.out.println(msg);
// no System.exit here, as we also run in junit tests!
@@ -104,6 +105,7 @@ public final class PPTX2PNG {
private FileMagic defaultFileType = FileMagic.OLE2;
private boolean textAsShapes = false;
private Charset charset = LocaleUtil.CHARSET_1252;
+ private boolean emfHeaderBounds = false;
private PPTX2PNG() {
}
@@ -189,7 +191,9 @@ public final class PPTX2PNG {
charset = LocaleUtil.CHARSET_1252;
}
break;
-
+ case "-emfheaderbounds":
+ emfHeaderBounds = true;
+ break;
default:
file = new File(args[i]);
break;
@@ -279,6 +283,7 @@ public final class PPTX2PNG {
graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
graphics.setRenderingHint(Drawable.DEFAULT_CHARSET, getDefaultCharset());
+ graphics.setRenderingHint(Drawable.EMF_FORCE_HEADER_BOUNDS, emfHeaderBounds);
graphics.scale(scale / lenSide, scale / lenSide);
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java
index 0d055d340f..83e8db7598 100644
--- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java
+++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java
@@ -101,6 +101,9 @@ public class HemfComment {
*/
default void draw(HemfGraphics ctx) {}
+ default void calcBounds(Rectangle2D bounds, Rectangle2D viewport, HemfGraphics.EmfRenderState[] renderState) { }
+
+
@Override
default HemfCommentRecordType getGenericRecordType() {
return getCommentRecordType();
@@ -132,6 +135,11 @@ public class HemfComment {
}
@Override
+ public void calcBounds(Rectangle2D window, Rectangle2D viewport, HemfGraphics.EmfRenderState[] renderState) {
+ data.calcBounds(window, viewport, renderState);
+ }
+
+ @Override
public String toString() {
return GenericRecordJsonWriter.marshal(this);
}
@@ -333,6 +341,17 @@ public class HemfComment {
}
@Override
+ public void calcBounds(Rectangle2D window, Rectangle2D viewport, EmfRenderState[] renderState) {
+ renderState[0] = EmfRenderState.EMFPLUS_ONLY;
+ for (HemfPlusRecord r : records) {
+ r.calcBounds(window, viewport, renderState);
+ if (!window.isEmpty() && !viewport.isEmpty()) {
+ break;
+ }
+ }
+ }
+
+ @Override
public Map<String, Supplier<?>> getGenericProperties() {
return null;
}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecord.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecord.java
index 0ab50e819d..fe6a6ccf68 100644
--- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecord.java
+++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecord.java
@@ -18,6 +18,7 @@
package org.apache.poi.hemf.record.emf;
+import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.util.Map;
import java.util.function.Supplier;
@@ -56,6 +57,9 @@ public interface HemfRecord extends GenericRecord {
}
}
+ default void calcBounds(Rectangle2D window, Rectangle2D viewport, HemfGraphics.EmfRenderState[] renderState) {
+ }
+
/**
* Sets the header reference, in case the record needs to refer to it
* @param header the emf header
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java
index 6167cd6028..46e28209f8 100644
--- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java
+++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java
@@ -22,6 +22,7 @@ import static org.apache.poi.hemf.record.emf.HemfDraw.readPointL;
import static org.apache.poi.hwmf.record.HwmfDraw.normalizeBounds;
import java.awt.geom.Dimension2D;
+import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.util.Map;
import java.util.function.Supplier;
@@ -56,6 +57,13 @@ public class HemfWindowing {
public HemfRecordType getGenericRecordType() {
return getEmfRecordType();
}
+
+ @Override
+ public void calcBounds(Rectangle2D window, Rectangle2D viewport, HemfGraphics.EmfRenderState[] renderState) {
+ double x = window.getX();
+ double y = window.getY();
+ window.setRect(x,y,size.getWidth(),size.getHeight());
+ }
}
/**
@@ -76,6 +84,13 @@ public class HemfWindowing {
public HemfRecordType getGenericRecordType() {
return getEmfRecordType();
}
+
+ @Override
+ public void calcBounds(Rectangle2D window, Rectangle2D viewport, HemfGraphics.EmfRenderState[] renderState) {
+ double w = window.getWidth();
+ double h = window.getHeight();
+ window.setRect(origin.getX(),origin.getY(),w,h);
+ }
}
/**
@@ -96,6 +111,13 @@ public class HemfWindowing {
public HemfRecordType getGenericRecordType() {
return getEmfRecordType();
}
+
+ @Override
+ public void calcBounds(Rectangle2D window, Rectangle2D viewport, HemfGraphics.EmfRenderState[] renderState) {
+ double x = viewport.getX();
+ double y = viewport.getY();
+ viewport.setRect(x,y,extents.getWidth(),extents.getHeight());
+ }
}
/**
@@ -116,6 +138,13 @@ public class HemfWindowing {
public HemfRecordType getGenericRecordType() {
return getEmfRecordType();
}
+
+ @Override
+ public void calcBounds(Rectangle2D window, Rectangle2D viewport, HemfGraphics.EmfRenderState[] renderState) {
+ double w = viewport.getWidth();
+ double h = viewport.getHeight();
+ viewport.setRect(origin.getX(), origin.getY(), w, h);
+ }
}
/**
@@ -200,6 +229,16 @@ public class HemfWindowing {
public HemfRecordType getGenericRecordType() {
return getEmfRecordType();
}
+
+ @Override
+ public void calcBounds(Rectangle2D window, Rectangle2D viewport, HemfGraphics.EmfRenderState[] renderState) {
+ double x = viewport.getX();
+ double y = viewport.getY();
+ double w = viewport.getWidth();
+ double h = viewport.getHeight();
+ viewport.setRect(x,y,w * scale.getWidth(),h * scale.getHeight());
+ }
+
}
/**
@@ -221,6 +260,15 @@ public class HemfWindowing {
public HemfRecordType getGenericRecordType() {
return getEmfRecordType();
}
+
+ @Override
+ public void calcBounds(Rectangle2D window, Rectangle2D viewport, HemfGraphics.EmfRenderState[] renderState) {
+ double x = window.getX();
+ double y = window.getY();
+ double w = window.getWidth();
+ double h = window.getHeight();
+ window.setRect(x,y,w * scale.getWidth(),h * scale.getHeight());
+ }
}
/**
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusBrush.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusBrush.java
index 34bda1e4be..91f8d31c95 100644
--- a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusBrush.java
+++ b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusBrush.java
@@ -35,7 +35,6 @@ 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;
@@ -519,7 +518,7 @@ public class HemfPlusBrush {
public static class EmfPlusLinearGradientBrushData implements EmfPlusBrushData {
private int dataFlags;
private EmfPlusWrapMode wrapMode;
- private Rectangle2D rect = new Rectangle2D.Double();
+ private final Rectangle2D rect = new Rectangle2D.Double();
private Color startColor, endColor;
private AffineTransform blendTransform;
private float[] positions;
@@ -529,8 +528,8 @@ public class HemfPlusBrush {
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" };
+ private static final int[] FLAG_MASKS = {0x02, 0x04, 0x08, 0x10, 0x80};
+ private static final 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 {
@@ -543,7 +542,7 @@ public class HemfPlusBrush {
// gradient is repeated.
wrapMode = EmfPlusWrapMode.valueOf(leis.readInt());
- int size = 2*LittleEndianConsts.INT_SIZE;
+ int size = 2 * LittleEndianConsts.INT_SIZE;
size += readRectF(leis, rect);
// An EmfPlusARGB object that specifies the color at the starting/ending boundary point of the linear gradient brush.
@@ -551,9 +550,9 @@ public class HemfPlusBrush {
endColor = readARGB(leis.readInt());
// skip reserved1/2 fields
- leis.skipFully(2*LittleEndianConsts.INT_SIZE);
+ leis.skipFully(2 * LittleEndianConsts.INT_SIZE);
- size += 4*LittleEndianConsts.INT_SIZE;
+ size += 4 * LittleEndianConsts.INT_SIZE;
if (TRANSFORM.isSet(dataFlags)) {
size += readXForm(leis, (blendTransform = new AffineTransform()));
@@ -586,7 +585,7 @@ public class HemfPlusBrush {
setColorProps(prop::setBrushColorsV, positionsV, this::getBlendVColorAt);
if (!(isPreset() || isBlendH() || isBlendV())) {
- prop.setBrushColorsH(Arrays.asList(kv(0f,startColor), kv(1f,endColor)));
+ prop.setBrushColorsH(Arrays.asList(kv(0f, startColor), kv(1f, endColor)));
}
}
@@ -607,7 +606,7 @@ public class HemfPlusBrush {
@Override
public Map<String, Supplier<?>> getGenericProperties() {
- final Map<String,Supplier<?>> m = new LinkedHashMap<>();
+ final Map<String, Supplier<?>> m = new LinkedHashMap<>();
m.put("flags", GenericRecordUtil.getBitsAsString(() -> dataFlags, FLAG_MASKS, FLAG_NAMES));
m.put("wrapMode", () -> wrapMode);
m.put("rect", () -> rect);
@@ -635,24 +634,24 @@ public class HemfPlusBrush {
return BLEND_FACTORS_V.isSet(dataFlags);
}
- private Map.Entry<Float,Color> getBlendColorAt(int index) {
+ 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> 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 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) {
+ 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) {
+ 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 {
@@ -661,8 +660,26 @@ public class HemfPlusBrush {
}
private Color interpolateColors(final double factor) {
- // https://stackoverflow.com/questions/1416560/hsl-interpolation
+ return interpolateColorsRGB(factor);
+ }
+
+ private Color interpolateColorsRGB(final double factor) {
+ // TODO: check IS_GAMMA_CORRECTED flag and maybe don't convert into scRGB
+ double[] start = DrawPaint.RGB2SCRGB(startColor);
+ double[] end = DrawPaint.RGB2SCRGB(endColor);
+
+ // compute the interpolated color in linear space
+ int a = (int)Math.round(startColor.getAlpha() + factor * (endColor.getAlpha() - startColor.getAlpha()));
+ double r = start[0] + factor * (end[0] - start[0]);
+ double g = start[1] + factor * (end[1] - start[1]);
+ double b = start[2] + factor * (end[2] - start[2]);
+ Color inter = DrawPaint.SCRGB2RGB(r,g,b);
+ return new Color(inter.getRed(), inter.getGreen(), inter.getBlue(), a);
+ }
+
+ /*
+ private Color interpolateColorsHSL(final double factor) {
final double[] hslStart = DrawPaint.RGB2HSL(startColor);
final double[] hslStop = DrawPaint.RGB2HSL(endColor);
@@ -673,16 +690,24 @@ public class HemfPlusBrush {
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.;
+ // find closest match - decide if need to go clockwise or counter-clockwise
+ // https://stackoverflow.com/questions/1416560/hsl-interpolation
+ double hueMidCW = (hslStart[0]+hslStop[0])/2.;
+ double hueMidCCW = (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;
+ double hslDiff;
+ if (hueDelta.apply(hueMidCW) > hueDelta.apply(hueMidCCW)) {
+ hslDiff = (hslStart[0] < hslStop[0]) ? hslStop[0]-hslStart[0] : (360-hslStart[0])+hslStop[0];
+ } else {
+ hslDiff = (hslStart[0] < hslStop[0]) ? -hslStart[0]-(360-hslStop[0]) : -(hslStart[0]-hslStop[0]);
+ }
+ double hue = (hslStart[0]+hslDiff*factor)%360.;
return DrawPaint.HSL2RGB(hue, sat, lum, alpha/255.);
- }
+ } */
}
/** The EmfPlusPathGradientBrushData object specifies a path gradient for a graphics brush. */
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusHeader.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusHeader.java
index a492985c9b..a271e77de1 100644
--- a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusHeader.java
+++ b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusHeader.java
@@ -20,6 +20,7 @@ package org.apache.poi.hemf.record.emfplus;
import static org.apache.poi.util.GenericRecordUtil.getEnumBitsAsString;
+import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.util.Map;
import java.util.function.Supplier;
@@ -135,6 +136,11 @@ public class HemfPlusHeader implements HemfPlusRecord {
}
@Override
+ public void calcBounds(Rectangle2D window, Rectangle2D viewport, EmfRenderState[] renderState) {
+ renderState[0] = EmfRenderState.EMF_DCONTEXT;
+ }
+
+ @Override
public String toString() {
return GenericRecordJsonWriter.marshal(this);
}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusMisc.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusMisc.java
index 104863f8cd..eaaf0a88f1 100644
--- a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusMisc.java
+++ b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusMisc.java
@@ -163,6 +163,11 @@ public class HemfPlusMisc {
public void draw(HemfGraphics ctx) {
ctx.setRenderState(HemfGraphics.EmfRenderState.EMF_DCONTEXT);
}
+
+ @Override
+ public void calcBounds(Rectangle2D window, Rectangle2D viewport, HemfGraphics.EmfRenderState[] renderState) {
+ renderState[0] = HemfGraphics.EmfRenderState.EMF_DCONTEXT;
+ }
}
/**
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecord.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecord.java
index 3ac27f0dec..99210ad7ab 100644
--- a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecord.java
+++ b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecord.java
@@ -18,6 +18,7 @@
package org.apache.poi.hemf.record.emfplus;
+import java.awt.geom.Rectangle2D;
import java.io.IOException;
import org.apache.poi.common.usermodel.GenericRecord;
@@ -55,6 +56,9 @@ public interface HemfPlusRecord extends GenericRecord {
default void draw(HemfGraphics ctx) {
}
+ default void calcBounds(Rectangle2D window, Rectangle2D viewport, HemfGraphics.EmfRenderState[] renderState) {
+ }
+
@Override
default HemfPlusRecordType getGenericRecordType() {
return getEmfPlusRecordType();
diff --git a/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java b/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java
index c94fb8ac4b..deae850ceb 100644
--- a/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java
+++ b/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java
@@ -19,6 +19,8 @@ package org.apache.poi.hemf.usermodel;
import static java.lang.Math.abs;
+import static org.apache.poi.hemf.draw.HemfGraphics.EmfRenderState.EMFPLUS_ONLY;
+import static org.apache.poi.hemf.draw.HemfGraphics.EmfRenderState.EMF_ONLY;
import java.awt.Graphics2D;
import java.awt.Shape;
@@ -36,14 +38,14 @@ 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.emf.HemfComment;
import org.apache.poi.hemf.record.emf.HemfHeader;
import org.apache.poi.hemf.record.emf.HemfRecord;
import org.apache.poi.hemf.record.emf.HemfRecordIterator;
-import org.apache.poi.hemf.record.emf.HemfWindowing;
import org.apache.poi.hwmf.usermodel.HwmfCharsetAware;
import org.apache.poi.hwmf.usermodel.HwmfEmbedded;
+import org.apache.poi.sl.draw.Drawable;
import org.apache.poi.util.Dimension2DDouble;
import org.apache.poi.util.Internal;
import org.apache.poi.util.LittleEndianInputStream;
@@ -114,26 +116,36 @@ public class HemfPicture implements Iterable<HemfRecord>, GenericRecord {
*/
public Rectangle2D getBounds() {
Rectangle2D dim = getHeader().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();
- }
+ boolean isInvalid = ReluctantRectangle2D.isEmpty(dim);
+ if (isInvalid) {
+ Rectangle2D lastDim = new ReluctantRectangle2D();
+ getInnerBounds(lastDim, new ReluctantRectangle2D());
+ if (!lastDim.isEmpty()) {
+ return lastDim;
}
}
+ return dim;
+ }
+
+ public void getInnerBounds(Rectangle2D window, Rectangle2D viewport) {
+ HemfGraphics.EmfRenderState[] renderState = { HemfGraphics.EmfRenderState.INITIAL };
+ for (HemfRecord r : getRecords()) {
+ if (
+ (renderState[0] == EMF_ONLY && r instanceof HemfComment.EmfComment) ||
+ (renderState[0] == EMFPLUS_ONLY && !(r instanceof HemfComment.EmfComment))
+ ) {
+ continue;
+ }
+
+ try {
+ r.calcBounds(window, viewport, renderState);
+ } catch (RuntimeException ignored) {
+ }
- return new Rectangle2D.Double(x, y, width, height);
+ if (!window.isEmpty() && !viewport.isEmpty()) {
+ break;
+ }
+ }
}
/**
@@ -154,32 +166,36 @@ public class HemfPicture implements Iterable<HemfRecord>, GenericRecord {
final Rectangle2D b = getBoundsInPoints();
return new Dimension2DDouble(abs(b.getWidth()), abs(b.getHeight()));
}
-
- private static double minX(Rectangle2D bounds) {
- return Math.min(bounds.getMinX(), bounds.getMaxX());
- }
-
- private static double minY(Rectangle2D bounds) {
- return Math.min(bounds.getMinY(), bounds.getMaxY());
- }
-
public void draw(Graphics2D ctx, Rectangle2D graphicsBounds) {
final Shape clip = ctx.getClip();
final AffineTransform at = ctx.getTransform();
try {
Rectangle2D emfBounds = getHeader().getBoundsRectangle();
+ Rectangle2D winBounds = new ReluctantRectangle2D();
+ Rectangle2D viewBounds = new ReluctantRectangle2D();
+ getInnerBounds(winBounds, viewBounds);
+
+ Boolean forceHeader = (Boolean)ctx.getRenderingHint(Drawable.EMF_FORCE_HEADER_BOUNDS);
+ if (forceHeader == null) {
+ forceHeader = false;
+ }
+ // this is a compromise ... sometimes winBounds are totally off :(
+ // but mostly they fit better than the header bounds
+ Rectangle2D b =
+ !viewBounds.isEmpty() && !forceHeader
+ ? viewBounds
+ : !winBounds.isEmpty() && !forceHeader
+ ? winBounds
+ : emfBounds;
- // scale output bounds to image bounds
ctx.translate(graphicsBounds.getCenterX(), graphicsBounds.getCenterY());
- ctx.scale(graphicsBounds.getWidth()/emfBounds.getWidth(), graphicsBounds.getHeight()/emfBounds.getHeight());
- ctx.translate(-emfBounds.getCenterX(), -emfBounds.getCenterY());
+ ctx.scale(
+ graphicsBounds.getWidth()/b.getWidth(),
+ graphicsBounds.getHeight()/b.getHeight()
+ );
+ ctx.translate(-b.getCenterX(),-b.getCenterY());
- HemfGraphics g = new HemfGraphics(ctx, emfBounds);
- HemfDrawProperties prop = g.getProperties();
- prop.setWindowOrg(emfBounds.getX(), emfBounds.getY());
- prop.setWindowExt(emfBounds.getWidth(), emfBounds.getHeight());
- prop.setViewportOrg(emfBounds.getX(), emfBounds.getY());
- prop.setViewportExt(emfBounds.getWidth(), emfBounds.getHeight());
+ HemfGraphics g = new HemfGraphics(ctx, b);
for (HemfRecord r : getRecords()) {
try {
@@ -214,4 +230,43 @@ public class HemfPicture implements Iterable<HemfRecord>, GenericRecord {
public Charset getDefaultCharset() {
return defaultCharset;
}
+
+
+ private static class ReluctantRectangle2D extends Rectangle2D.Double {
+ private boolean offsetSet = false;
+ private boolean rangeSet = false;
+
+ public ReluctantRectangle2D() {
+ super(-1,-1,0,0);
+ }
+
+ @Override
+ public void setRect(double x, double y, double w, double h) {
+ if (offsetSet && rangeSet) {
+ return;
+ }
+ super.setRect(
+ offsetSet ? this.x : x,
+ offsetSet ? this.y : y,
+ rangeSet ? this.width : w,
+ rangeSet ? this.height : h);
+ offsetSet |= (x != -1 || y != -1);
+ rangeSet |= (w != 0 || h != 0);
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return isEmpty(this);
+ }
+
+ public static boolean isEmpty(Rectangle2D r) {
+ double w = Math.rint(r.getWidth());
+ double h = Math.rint(r.getHeight());
+ return
+ (w <= 0.0) || (h <= 0.0) ||
+ (r.getX() == -1 && r.getY() == -1) ||
+ // invalid emf bound have sometimes 1,1 as dimension
+ (w == 1 && h == 1);
+ }
+ }
}