#64716 - wmf display error

add anothger heuristic - cumulate the bounding box of the shape records and compare it to window, viewport and emfbounds

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1894176 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Andreas Beeker 2021-10-12 22:30:30 +00:00
parent 91d502732b
commit 24b3dac0c0
9 changed files with 205 additions and 46 deletions

View File

@ -23,6 +23,7 @@ import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeFalse;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@ -44,12 +45,14 @@ import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.internal.util.io.IOUtil;
/**
* Test class for testing PPTX2PNG utility which renders .ppt and .pptx slideshows
*/
@SuppressWarnings("ConstantConditions")
class TestPPTX2PNG {
private static Closeable archive;
private static boolean xslfOnly;
private static final POIDataSamples samples = POIDataSamples.getSlideShowInstance();
@ -82,15 +85,21 @@ class TestPPTX2PNG {
@AfterAll
public static void resetStdin() {
System.setIn(defStdin);
IOUtil.closeQuietly(archive);
}
public static Stream<Arguments> data() throws IOException {
// Junit closes all closable arguments after the usage
// therefore we need to wrap the archive in non-closable arrays
if (basedir != null && basedir.getName().endsWith(".zip")) {
ZipFile zipFile = new ZipFile(basedir);
return zipFile.stream().map(f -> Arguments.of(f.getName(), f, zipFile));
archive = zipFile;
return zipFile.stream().map(f -> Arguments.of(f.getName(), f, new ZipFile[]{zipFile}));
} else if (basedir != null && basedir.getName().endsWith(".7z")) {
SevenZFile sevenZFile = new SevenZFile(basedir);
return ((ArrayList<SevenZArchiveEntry>)sevenZFile.getEntries()).stream().filter(f -> !f.isDirectory()).map(f -> Arguments.of(f.getName(), f, sevenZFile));
archive = sevenZFile;
return ((ArrayList<SevenZArchiveEntry>)sevenZFile.getEntries()).stream().filter(f -> !f.isDirectory()).map(f -> Arguments.of(f.getName(), f, new SevenZFile[]{sevenZFile}));
} else {
return Stream.of(files.split(", ?")).
map(basedir == null ? samples::getFile : f -> new File(basedir, f)).
@ -127,7 +136,7 @@ class TestPPTX2PNG {
"-format", format, // png,gif,jpg,svg,pdf or null for test
"-slide", "-1", // -1 for all
"-outdir", tmpDir.getCanonicalPath(),
// "-dump", new File("build/tmp/", pptFile+".json").getCanonicalPath(),
// "-dump", new File("build/tmp/", fileName+".json").getCanonicalPath(),
"-dump", "null",
"-quiet",
"-ignoreParse",
@ -162,14 +171,14 @@ class TestPPTX2PNG {
if (fileObj instanceof ZipEntry) {
ZipEntry ze = (ZipEntry)fileObj;
ZipFile zf = (ZipFile)fileContainer;
ZipFile zf = ((ZipFile[])fileContainer)[0];
System.setIn(zf.getInputStream(ze));
args.add("-outpat");
args.add(basename+"-${slideno}-"+ext+".${format}");
args.add("stdin");
} else if (fileObj instanceof SevenZArchiveEntry) {
SevenZArchiveEntry ze = (SevenZArchiveEntry)fileObj;
SevenZFile zf = (SevenZFile)fileContainer;
SevenZFile zf = ((SevenZFile[])fileContainer)[0];
System.setIn(zf.getInputStream(ze));
args.add("-outpat");
args.add(basename+"-${slideno}-"+ext+".${format}");

View File

@ -17,6 +17,8 @@
package org.apache.poi.hemf.record.emf;
import static org.apache.logging.log4j.util.Unbox.box;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.nio.charset.Charset;
@ -34,6 +36,7 @@ import org.apache.logging.log4j.Logger;
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.emf.HemfRecord.RenderBounds;
import org.apache.poi.hemf.record.emfplus.HemfPlusRecord;
import org.apache.poi.hemf.record.emfplus.HemfPlusRecordIterator;
import org.apache.poi.hwmf.usermodel.HwmfCharsetAware;
@ -47,8 +50,6 @@ import org.apache.poi.util.LittleEndianInputStream;
import org.apache.poi.util.LocaleUtil;
import org.apache.poi.util.RecordFormatException;
import static org.apache.logging.log4j.util.Unbox.box;
/**
* Contains arbitrary data
*/
@ -103,7 +104,7 @@ public class HemfComment {
*/
default void draw(HemfGraphics ctx) {}
default void calcBounds(Rectangle2D bounds, Rectangle2D viewport, EmfRenderState[] renderState) { }
default void calcBounds(RenderBounds holder) { }
@Override
@ -137,8 +138,8 @@ public class HemfComment {
}
@Override
public void calcBounds(Rectangle2D window, Rectangle2D viewport, EmfRenderState[] renderState) {
data.calcBounds(window, viewport, renderState);
public void calcBounds(RenderBounds holder) {
data.calcBounds(holder);
}
@Override
@ -343,11 +344,11 @@ public class HemfComment {
}
@Override
public void calcBounds(Rectangle2D window, Rectangle2D viewport, EmfRenderState[] renderState) {
renderState[0] = EmfRenderState.EMFPLUS_ONLY;
public void calcBounds(RenderBounds holder) {
holder.setState(EmfRenderState.EMFPLUS_ONLY);
for (HemfPlusRecord r : records) {
r.calcBounds(window, viewport, renderState);
if (!window.isEmpty() && !viewport.isEmpty()) {
r.calcBounds(holder);
if (!holder.getWindow().isEmpty() && !holder.getViewport().isEmpty()) {
break;
}
}

View File

@ -222,6 +222,16 @@ public final class HemfDraw {
public HemfRecordType getGenericRecordType() {
return getEmfRecordType();
}
@Override
public void calcBounds(RenderBounds holder) {
Rectangle2D b = holder.getBounds();
if (b.isEmpty()) {
b.setRect(bounds);
} else {
b.add(bounds);
}
}
}
/**
@ -323,6 +333,16 @@ public final class HemfDraw {
public HemfRecordType getGenericRecordType() {
return getEmfRecordType();
}
@Override
public void calcBounds(RenderBounds holder) {
Rectangle2D b = holder.getBounds();
if (b.isEmpty()) {
b.setRect(bounds);
} else {
b.add(bounds);
}
}
}
/**
@ -775,6 +795,16 @@ public final class HemfDraw {
public HemfRecordType getGenericRecordType() {
return getEmfRecordType();
}
@Override
public void calcBounds(RenderBounds holder) {
Rectangle2D b = holder.getBounds();
if (b.isEmpty()) {
b.setRect(bounds);
} else {
b.add(bounds);
}
}
}
/**
@ -808,6 +838,16 @@ public final class HemfDraw {
public HemfRecordType getGenericRecordType() {
return getEmfRecordType();
}
@Override
public void calcBounds(RenderBounds holder) {
Rectangle2D b = holder.getBounds();
if (b.isEmpty()) {
b.setRect(bounds);
} else {
b.add(bounds);
}
}
}
/**
@ -834,6 +874,14 @@ public final class HemfDraw {
public HemfRecordType getGenericRecordType() {
return getEmfRecordType();
}
@Override
public void calcBounds(RenderBounds holder) {
Rectangle2D b = holder.getBounds();
if (!b.isEmpty()) {
b.add(point);
}
}
}
/**
@ -864,6 +912,16 @@ public final class HemfDraw {
public HemfRecordType getGenericRecordType() {
return getEmfRecordType();
}
@Override
public void calcBounds(RenderBounds holder) {
Rectangle2D b = holder.getBounds();
if (b.isEmpty()) {
b.setRect(bounds);
} else {
b.add(bounds);
}
}
}
/** The EMR_POLYDRAW record specifies a set of line segments and Bezier curves. */
@ -979,6 +1037,16 @@ public final class HemfDraw {
public HemfRecordType getGenericRecordType() {
return getEmfRecordType();
}
@Override
public void calcBounds(RenderBounds holder) {
Rectangle2D b = holder.getBounds();
if (b.isEmpty()) {
b.setRect(bounds);
} else {
b.add(bounds);
}
}
}
public static class EmfPolyDraw16 extends EmfPolyDraw {
@ -1201,6 +1269,16 @@ public final class HemfDraw {
public Map<String, Supplier<?>> getGenericProperties() {
return GenericRecordUtil.getGenericProperties("bounds", this::getBounds);
}
@Override
public void calcBounds(RenderBounds holder) {
Rectangle2D b = holder.getBounds();
if (b.isEmpty()) {
b.setRect(bounds);
} else {
b.add(bounds);
}
}
}

View File

@ -57,7 +57,16 @@ public interface HemfRecord extends GenericRecord {
}
}
default void calcBounds(Rectangle2D window, Rectangle2D viewport, HemfGraphics.EmfRenderState[] renderState) {
interface RenderBounds {
HemfGraphics.EmfRenderState getState();
void setState(HemfGraphics.EmfRenderState state);
Rectangle2D getWindow();
Rectangle2D getViewport();
Rectangle2D getBounds();
}
default void calcBounds(RenderBounds holder) {
}
/**

View File

@ -59,7 +59,8 @@ public class HemfWindowing {
}
@Override
public void calcBounds(Rectangle2D window, Rectangle2D viewport, HemfGraphics.EmfRenderState[] renderState) {
public void calcBounds(RenderBounds holder) {
Rectangle2D window = holder.getWindow();
double x = window.getX();
double y = window.getY();
window.setRect(x,y,size.getWidth(),size.getHeight());
@ -86,7 +87,8 @@ public class HemfWindowing {
}
@Override
public void calcBounds(Rectangle2D window, Rectangle2D viewport, HemfGraphics.EmfRenderState[] renderState) {
public void calcBounds(RenderBounds holder) {
Rectangle2D window = holder.getWindow();
double w = window.getWidth();
double h = window.getHeight();
window.setRect(origin.getX(),origin.getY(),w,h);
@ -113,7 +115,8 @@ public class HemfWindowing {
}
@Override
public void calcBounds(Rectangle2D window, Rectangle2D viewport, HemfGraphics.EmfRenderState[] renderState) {
public void calcBounds(RenderBounds holder) {
Rectangle2D viewport = holder.getViewport();
double x = viewport.getX();
double y = viewport.getY();
viewport.setRect(x,y,extents.getWidth(),extents.getHeight());
@ -140,7 +143,8 @@ public class HemfWindowing {
}
@Override
public void calcBounds(Rectangle2D window, Rectangle2D viewport, HemfGraphics.EmfRenderState[] renderState) {
public void calcBounds(RenderBounds holder) {
Rectangle2D viewport = holder.getViewport();
double w = viewport.getWidth();
double h = viewport.getHeight();
viewport.setRect(origin.getX(), origin.getY(), w, h);
@ -208,6 +212,16 @@ public class HemfWindowing {
public HemfRecordType getGenericRecordType() {
return getEmfRecordType();
}
@Override
public void calcBounds(RenderBounds holder) {
Rectangle2D b = holder.getBounds();
if (b.isEmpty()) {
b.setRect(bounds);
} else {
b.add(bounds);
}
}
}
/**
@ -231,7 +245,8 @@ public class HemfWindowing {
}
@Override
public void calcBounds(Rectangle2D window, Rectangle2D viewport, HemfGraphics.EmfRenderState[] renderState) {
public void calcBounds(RenderBounds holder) {
Rectangle2D viewport = holder.getViewport();
double x = viewport.getX();
double y = viewport.getY();
double w = viewport.getWidth();
@ -262,7 +277,8 @@ public class HemfWindowing {
}
@Override
public void calcBounds(Rectangle2D window, Rectangle2D viewport, HemfGraphics.EmfRenderState[] renderState) {
public void calcBounds(RenderBounds holder) {
Rectangle2D window = holder.getWindow();
double x = window.getX();
double y = window.getY();
double w = window.getWidth();

View File

@ -20,7 +20,6 @@ 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;
@ -28,6 +27,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.emf.HemfRecord.RenderBounds;
import org.apache.poi.util.BitField;
import org.apache.poi.util.BitFieldFactory;
import org.apache.poi.util.GenericRecordJsonWriter;
@ -136,8 +136,8 @@ public class HemfPlusHeader implements HemfPlusRecord {
}
@Override
public void calcBounds(Rectangle2D window, Rectangle2D viewport, EmfRenderState[] renderState) {
renderState[0] = EmfRenderState.EMF_DCONTEXT;
public void calcBounds(RenderBounds holder) {
holder.setState(EmfRenderState.EMF_DCONTEXT);
}
@Override

View File

@ -31,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.hemf.record.emf.HemfRecord.RenderBounds;
import org.apache.poi.hwmf.record.HwmfRegionMode;
import org.apache.poi.util.BitField;
import org.apache.poi.util.BitFieldFactory;
@ -165,8 +166,8 @@ public class HemfPlusMisc {
}
@Override
public void calcBounds(Rectangle2D window, Rectangle2D viewport, HemfGraphics.EmfRenderState[] renderState) {
renderState[0] = HemfGraphics.EmfRenderState.EMF_DCONTEXT;
public void calcBounds(RenderBounds holder) {
holder.setState(HemfGraphics.EmfRenderState.EMF_DCONTEXT);
}
}

View File

@ -18,11 +18,11 @@
package org.apache.poi.hemf.record.emfplus;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import org.apache.poi.common.usermodel.GenericRecord;
import org.apache.poi.hemf.draw.HemfGraphics;
import org.apache.poi.hemf.record.emf.HemfRecord.RenderBounds;
import org.apache.poi.util.Internal;
import org.apache.poi.util.LittleEndianInputStream;
@ -56,7 +56,7 @@ public interface HemfPlusRecord extends GenericRecord {
default void draw(HemfGraphics ctx) {
}
default void calcBounds(Rectangle2D window, Rectangle2D viewport, HemfGraphics.EmfRenderState[] renderState) {
default void calcBounds(RenderBounds holder) {
}
@Override

View File

@ -19,6 +19,7 @@ package org.apache.poi.hemf.usermodel;
import static java.lang.Math.abs;
import static java.util.Comparator.comparingDouble;
import static org.apache.poi.hemf.draw.HemfGraphics.EmfRenderState.EMFPLUS_ONLY;
import static org.apache.poi.hemf.draw.HemfGraphics.EmfRenderState.EMF_ONLY;
@ -36,12 +37,14 @@ import java.util.Map;
import java.util.Spliterator;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Stream;
import org.apache.poi.common.usermodel.GenericRecord;
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.HemfRecord.RenderBounds;
import org.apache.poi.hemf.record.emf.HemfRecordIterator;
import org.apache.poi.hwmf.usermodel.HwmfCharsetAware;
import org.apache.poi.hwmf.usermodel.HwmfEmbedded;
@ -125,7 +128,7 @@ public class HemfPicture implements Iterable<HemfRecord>, GenericRecord {
boolean isInvalid = ReluctantRectangle2D.isEmpty(dim);
if (isInvalid) {
Rectangle2D lastDim = new ReluctantRectangle2D();
getInnerBounds(lastDim, new ReluctantRectangle2D());
getInnerBounds(lastDim, new Rectangle2D.Double(), new Rectangle2D.Double());
if (!lastDim.isEmpty()) {
return lastDim;
}
@ -133,24 +136,52 @@ public class HemfPicture implements Iterable<HemfRecord>, GenericRecord {
return dim;
}
public void getInnerBounds(Rectangle2D window, Rectangle2D viewport) {
HemfGraphics.EmfRenderState[] renderState = { HemfGraphics.EmfRenderState.INITIAL };
public void getInnerBounds(Rectangle2D window, Rectangle2D viewport, Rectangle2D bounds) {
RenderBounds holder = new RenderBounds() {
private HemfGraphics.EmfRenderState state = HemfGraphics.EmfRenderState.INITIAL;
@Override
public HemfGraphics.EmfRenderState getState() {
return state;
}
@Override
public void setState(HemfGraphics.EmfRenderState state) {
this.state = state;
}
@Override
public Rectangle2D getWindow() {
return window;
}
@Override
public Rectangle2D getViewport() {
return viewport;
}
@Override
public Rectangle2D getBounds() {
return bounds;
}
};
for (HemfRecord r : getRecords()) {
if (
(renderState[0] == EMF_ONLY && r instanceof HemfComment.EmfComment) ||
(renderState[0] == EMFPLUS_ONLY && !(r instanceof HemfComment.EmfComment))
(holder.getState() == EMF_ONLY && r instanceof HemfComment.EmfComment) ||
(holder.getState() == EMFPLUS_ONLY && !(r instanceof HemfComment.EmfComment))
) {
continue;
}
try {
r.calcBounds(window, viewport, renderState);
r.calcBounds(holder);
} catch (RuntimeException ignored) {
}
if (!window.isEmpty() && !viewport.isEmpty()) {
break;
}
// if (!window.isEmpty() && !viewport.isEmpty()) {
// break;
// }
}
}
@ -179,20 +210,30 @@ public class HemfPicture implements Iterable<HemfRecord>, GenericRecord {
Rectangle2D emfBounds = getHeader().getBoundsRectangle();
Rectangle2D winBounds = new ReluctantRectangle2D();
Rectangle2D viewBounds = new ReluctantRectangle2D();
getInnerBounds(winBounds, viewBounds);
Rectangle2D recBounds = new Rectangle2D.Double();
getInnerBounds(winBounds, viewBounds, recBounds);
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;
Rectangle2D b;
if (forceHeader) {
b = emfBounds;
} else if (recBounds.isEmpty()) {
// this is a compromise ... sometimes winBounds are totally off :(
// but mostly they fit better than the header bounds
b = !viewBounds.isEmpty()
? viewBounds
: !winBounds.isEmpty()
? winBounds
: emfBounds;
} else {
double recHyp = dia(recBounds);
b = Stream.of(emfBounds, winBounds, viewBounds).
min(comparingDouble(r -> abs(dia(r) - recHyp))).get();
}
ctx.translate(graphicsBounds.getCenterX(), graphicsBounds.getCenterY());
ctx.scale(
@ -217,6 +258,10 @@ public class HemfPicture implements Iterable<HemfRecord>, GenericRecord {
}
}
private static double dia(Rectangle2D bounds) {
return Math.sqrt(bounds.getWidth()*bounds.getWidth() + bounds.getHeight()*bounds.getWidth());
}
public Iterable<HwmfEmbedded> getEmbeddings() {
return () -> new HemfEmbeddedIterator(HemfPicture.this);
}