Bladeren bron

#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
tags/before_ooxml_3rd_edition
Andreas Beeker 3 jaren geleden
bovenliggende
commit
38b415e26a

+ 35
- 0
src/java/org/apache/poi/common/usermodel/PictureType.java Bestand weergeven

@@ -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;
}
}
}

+ 9
- 8
src/java/org/apache/poi/sl/draw/DrawPictureShape.java Bestand weergeven

@@ -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;
};

+ 12
- 0
src/java/org/apache/poi/sl/draw/Drawable.java Bestand weergeven

@@ -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.

+ 9
- 52
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureLine.java Bestand weergeven

@@ -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;
}

}

+ 7
- 2
src/ooxml/java/org/apache/poi/xslf/util/PPTX2PNG.java Bestand weergeven

@@ -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);


+ 19
- 0
src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java Bestand weergeven

@@ -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();
@@ -131,6 +134,11 @@ public class HemfComment {
data.draw(ctx);
}

@Override
public void calcBounds(Rectangle2D window, Rectangle2D viewport, HemfGraphics.EmfRenderState[] renderState) {
data.calcBounds(window, viewport, renderState);
}

@Override
public String toString() {
return GenericRecordJsonWriter.marshal(this);
@@ -332,6 +340,17 @@ public class HemfComment {
records.forEach(ctx::draw);
}

@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;

+ 4
- 0
src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecord.java Bestand weergeven

@@ -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

+ 48
- 0
src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java Bestand weergeven

@@ -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());
}
}

/**

+ 46
- 21
src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusBrush.java Bestand weergeven

@@ -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. */

+ 6
- 0
src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusHeader.java Bestand weergeven

@@ -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;
@@ -134,6 +135,11 @@ public class HemfPlusHeader implements HemfPlusRecord {
ctx.setRenderState(EmfRenderState.EMF_DCONTEXT);
}

@Override
public void calcBounds(Rectangle2D window, Rectangle2D viewport, EmfRenderState[] renderState) {
renderState[0] = EmfRenderState.EMF_DCONTEXT;
}

@Override
public String toString() {
return GenericRecordJsonWriter.marshal(this);

+ 5
- 0
src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusMisc.java Bestand weergeven

@@ -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;
}
}

/**

+ 4
- 0
src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecord.java Bestand weergeven

@@ -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();

+ 92
- 37
src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java Bestand weergeven

@@ -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);
}
}
}

Laden…
Annuleren
Opslaan