import java.io.File;
import java.io.IOException;
+import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
-import java.util.function.Function;
+import java.util.Locale;
import java.util.stream.Stream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
+import org.apache.commons.compress.archivers.sevenz.SevenZFile;
import org.apache.poi.POIDataSamples;
+import org.apache.poi.poifs.filesystem.FileMagic;
import org.apache.poi.xslf.util.PPTX2PNG;
+import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
class TestPPTX2PNG {
private static boolean xslfOnly;
private static final POIDataSamples samples = POIDataSamples.getSlideShowInstance();
+
private static final File basedir = null;
private static final String files =
private static final String pdfFiles =
"alterman_security.ppt";
+ private static InputStream defStdin;
+
@BeforeAll
- public static void checkHslf() {
+ public static void init() {
try {
Class.forName("org.apache.poi.hslf.usermodel.HSLFSlideShow");
} catch (Exception e) {
xslfOnly = true;
}
+ defStdin = System.in;
}
- public static Stream<Arguments> data() {
- Function<String, Stream<Arguments>> fun = (basedir == null)
- ? (f) -> Stream.of(Arguments.of(f))
- : (f) -> Stream.of(basedir.listFiles(p -> p.getName().matches(f))).map(File::getName).map(Arguments::of);
+ @AfterAll
+ public static void resetStdin() {
+ System.setIn(defStdin);
+ }
- return Stream.of(files.split(", ?")).flatMap(fun);
+ public static Stream<Arguments> data() throws IOException {
+ if (basedir != null && basedir.getName().endsWith(".zip")) {
+ ZipFile zipFile = new ZipFile(basedir);
+ return zipFile.stream().map(f -> Arguments.of(f.getName(), f, 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));
+ } else {
+ return Stream.of(files.split(", ?")).
+ map(basedir == null ? samples::getFile : f -> new File(basedir, f)).
+ map(f -> Arguments.of(f.getName(), f, f.getParentFile()));
+ }
}
// use filename instead of File object to omit full pathname in test name
- @ParameterizedTest
+ @ParameterizedTest(name = "{0} ({index})")
@MethodSource("data")
- void render(String pptFile) throws Exception {
- assumeFalse(xslfOnly && pptFile.matches(".*\\.(ppt|emf|wmf)$"), "ignore HSLF (.ppt) / HEMF (.emf) / HWMF (.wmf) files in no-scratchpad run");
- PPTX2PNG.main(getArgs(pptFile, "null"));
- if (svgFiles.contains(pptFile)) {
- PPTX2PNG.main(getArgs(pptFile, "svg"));
+ void render(String fileName, Object fileObj, Object fileContainer) throws Exception {
+ assumeFalse(xslfOnly && fileName.matches(".*\\.(ppt|emf|wmf)$"), "ignore HSLF (.ppt) / HEMF (.emf) / HWMF (.wmf) files in no-scratchpad run");
+ PPTX2PNG.main(getArgs(fileName, fileObj, fileContainer, "null"));
+ if (svgFiles.contains(fileName)) {
+ PPTX2PNG.main(getArgs(fileName, fileObj, fileContainer, "svg"));
+ }
+ if (pdfFiles.contains(fileName)) {
+ PPTX2PNG.main(getArgs(fileName, fileObj, fileContainer, "pdf"));
}
- if (pdfFiles.contains(pptFile)) {
- PPTX2PNG.main(getArgs(pptFile, "pdf"));
+ if (System.in != defStdin) {
+ System.in.close();
}
}
- private String[] getArgs(String pptFile, String format) throws IOException {
+ private String[] getArgs(String fileName, Object fileObj, Object fileContainer, String format) throws IOException {
File tmpDir = new File("build/tmp/");
// fix maven build errors
// "-dump", new File("build/tmp/", pptFile+".json").getCanonicalPath(),
"-dump", "null",
"-quiet",
+ "-ignoreParse",
// "-charset", "GBK",
// "-emfHeaderBounds",
// "-textAsShapes",
+ // "-extractEmbedded",
"-fixside", "long",
"-scale", "800"
));
- if ("bug64693.pptx".equals(pptFile)) {
- args.addAll(asList(
- "-charset", "GBK"
- ));
+ String lName = fileName.toLowerCase(Locale.ROOT);
+ FileMagic inputType = null;
+ if (lName.endsWith(".emf")) {
+ inputType = FileMagic.EMF;
+ } else if (lName.endsWith(".wmf")) {
+ inputType = FileMagic.WMF;
}
- args.add((basedir == null ? samples.getFile(pptFile) : new File(basedir, pptFile)).getAbsolutePath());
+ if (inputType != null) {
+ args.add("-inputtype");
+ args.add(inputType.toString());
+ }
+
+ if (fileName.endsWith("bug64693.pptx")) {
+ args.add("-charset");
+ args.add("GBK");
+ }
+
+ if (fileObj instanceof ZipEntry) {
+ ZipEntry ze = (ZipEntry)fileObj;
+ ZipFile zf = (ZipFile)fileContainer;
+ System.setIn(zf.getInputStream(ze));
+ args.add("stdin");
+ } else if (fileObj instanceof SevenZArchiveEntry) {
+ SevenZArchiveEntry ze = (SevenZArchiveEntry)fileObj;
+ SevenZFile zf = (SevenZFile)fileContainer;
+ System.setIn(zf.getInputStream(ze));
+ args.add("stdin");
+ } else if (fileObj instanceof File) {
+ args.add(((File)fileObj).getAbsolutePath());
+ }
return args.toArray(new String[0]);
}
private static final POIDataSamples ss_samples = POIDataSamples.getSpreadSheetInstance();
private static final POIDataSamples sl_samples = POIDataSamples.getSlideShowInstance();
-/*
- @Test
- @Disabled("Only for manual tests")
- void paintSingle() throws Exception {
- File fileIn = new File("tmp/emfs/govdocs1/844/844795.ppt_2.emf");
-
- String[] args = {
- "-format", "png", // png,gif,jpg or null for test
- "-outdir", new File("build/tmp/").getCanonicalPath(),
- "-outfile", fileIn.getName().replaceAll("\\.[^.]+?$", ".png"),
- "-fixside", "long",
- "-scale", "800",
- "-ignoreParse",
- // "-dump", new File("build/tmp/", fileIn.getName().replaceAll("\\.[^.]+?$",".json")).getCanonicalPath(),
- // "-quiet",
- // "-extractEmbedded",
- fileIn.getPath()
- };
- PPTX2PNG.main(args);
- }
-*/
-
-/*
- @Test
- @Disabled("Only for manual tests - need to add org.tukaani:xz:1.8 for this to work")
- void paintMultiple() throws Exception {
- Pattern fileExt = Pattern.compile("(?i)^(.+/)*(.+)\\.(emf|wmf)$");
- final byte[] buf = new byte[50_000_000];
- try (SevenZFile sevenZFile = new SevenZFile(new File("tmp/plus_emf.7z"))
- ) {
- SevenZArchiveEntry entry;
- while ((entry = sevenZFile.getNextEntry()) != null) {
- if (entry.isDirectory() || entry.getSize() == 0) continue;
- Matcher m = fileExt.matcher(entry.getName());
- if (!m.matches()) continue;
-
- int size = sevenZFile.read(buf);
-
- ByteArrayInputStream bis = new ByteArrayInputStream(buf, 0, size);
- System.setIn(bis);
-
- String[] args = {
- "-format", "png", // png,gif,jpg or null for test
- "-outdir", new File("build/tmp/").getCanonicalPath(),
- "-outfile", m.replaceAll("$2.png"),
- "-fixside", "long",
- "-scale", "800",
- "-ignoreParse",
- "-inputtype", m.replaceAll("$3").toUpperCase(),
- // "-dump", new File("build/tmp/", lastName.replace(".emf",".json")).getCanonicalPath(),
- "-quiet",
- // "-extractEmbedded",
- "stdin"
- };
- try {
- PPTX2PNG.main(args);
- System.out.println("Processing "+entry.getName()+" ok");
- } catch (Exception e) {
- System.out.println("Processing "+entry.getName()+" failed");
- }
- }
- }
- }
-*/
@Test
void testBasicWindows() throws Exception {
try (InputStream is = ss_samples.openResourceAsStream("SimpleEMF_windows.emf")) {
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.awt.image.RescaleOp;
-import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.MemoryCacheImageInputStream;
+import org.apache.commons.collections4.iterators.IteratorIterable;
import org.apache.commons.io.input.UnsynchronizedByteArrayInputStream;
import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream;
import org.apache.logging.log4j.LogManager;
**/
public class BitmapImageRenderer implements ImageRenderer {
private static final Logger LOG = LogManager.getLogger(BitmapImageRenderer.class);
+ private static final ImageLoader[] IMAGE_LOADERS = {
+ BitmapImageRenderer::loadColored,
+ BitmapImageRenderer::loadGrayScaled,
+ BitmapImageRenderer::loadTruncated
+ };
+ private static final String UNSUPPORTED_IMAGE_TYPE = "Unsupported Image Type";
+ private static final PictureType[] ALLOWED_TYPES = {
+ PictureType.JPEG,
+ PictureType.PNG,
+ PictureType.BMP,
+ PictureType.GIF
+ };
protected BufferedImage img;
private boolean doCache;
private byte[] cachedImage;
private String cachedContentType;
+ private interface ImageLoader {
+ BufferedImage load(ImageReader reader, ImageInputStream iis, ImageReadParam param) throws IOException;
+ }
+
+
@Override
public boolean canRender(String contentType) {
- PictureType[] pts = {
- PictureType.JPEG, PictureType.PNG, PictureType.BMP, PictureType.GIF
- };
- for (PictureType pt : pts) {
- if (pt.contentType.equalsIgnoreCase(contentType)) {
- return true;
- }
- }
- return false;
+ return Stream.of(ALLOWED_TYPES).anyMatch(t -> t.contentType.equalsIgnoreCase(contentType));
}
@Override
IOException lastException = null;
BufferedImage img = null;
- final InputStream bis;
- if (data instanceof ByteArrayInputStream) {
- bis = data;
- } else if (data instanceof UnsynchronizedByteArrayInputStream) {
- bis = data;
- } else {
- UnsynchronizedByteArrayOutputStream bos = new UnsynchronizedByteArrayOutputStream(0x3FFFF);
- IOUtils.copy(data, bos);
- bis = bos.toInputStream();
- }
-
-
// currently don't use FileCacheImageInputStream,
// because of the risk of filling the file handles (see #59166)
- ImageInputStream iis = new MemoryCacheImageInputStream(bis);
- try {
+ try (ImageInputStream iis = new MemoryCacheImageInputStream(data)) {
+
Iterator<ImageReader> iter = ImageIO.getImageReaders(iis);
while (img==null && iter.hasNext()) {
+ lastException = null;
ImageReader reader = iter.next();
ImageReadParam param = reader.getDefaultReadParam();
- // 0:default mode, 1:fallback mode
- for (int mode=0; img==null && mode<3; mode++) {
- lastException = null;
- if (mode > 0) {
- bis.reset();
- iis.close();
- iis = new MemoryCacheImageInputStream(bis);
- }
+ for (ImageLoader il : IMAGE_LOADERS) {
+ iis.reset();
+ iis.mark();
try {
-
- switch (mode) {
- case 0:
- reader.setInput(iis, false, true);
- img = reader.read(0, param);
- break;
- case 1: {
- // try to load picture in gray scale mode
- // fallback mode for invalid image band metadata
- // see http://stackoverflow.com/questions/10416378
- Iterator<ImageTypeSpecifier> imageTypes = reader.getImageTypes(0);
- while (imageTypes.hasNext()) {
- ImageTypeSpecifier imageTypeSpecifier = imageTypes.next();
- int bufferedImageType = imageTypeSpecifier.getBufferedImageType();
- if (bufferedImageType == BufferedImage.TYPE_BYTE_GRAY) {
- param.setDestinationType(imageTypeSpecifier);
- break;
- }
- }
- reader.setInput(iis, false, true);
- img = reader.read(0, param);
- break;
- }
- case 2: {
- // try to load truncated pictures by supplying a BufferedImage
- // and use the processed data up till the point of error
- reader.setInput(iis, false, true);
- int height = reader.getHeight(0);
- int width = reader.getWidth(0);
-
- Iterator<ImageTypeSpecifier> imageTypes = reader.getImageTypes(0);
- if (imageTypes.hasNext()) {
- ImageTypeSpecifier imageTypeSpecifier = imageTypes.next();
- img = imageTypeSpecifier.createBufferedImage(width, height);
- param.setDestination(img);
- } else {
- lastException = new IOException("unable to load even a truncated version of the image.");
- break;
- }
-
- try {
- reader.read(0, param);
- } finally {
- if (img.getType() != BufferedImage.TYPE_INT_ARGB) {
- int y = findTruncatedBlackBox(img, width, height);
- if (y < height) {
- BufferedImage argbImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
- Graphics2D g = argbImg.createGraphics();
- g.clipRect(0, 0, width, y);
- g.drawImage(img, 0, 0, null);
- g.dispose();
- img.flush();
- img = argbImg;
- }
- }
- }
- break;
- }
+ img = il.load(reader, iis, param);
+ if (img != null) {
+ break;
}
-
} catch (IOException e) {
- if (mode < 2) {
- lastException = e;
+ lastException = e;
+ if (UNSUPPORTED_IMAGE_TYPE.equals(e.getMessage())) {
+ // fail early
+ break;
}
} catch (RuntimeException e) {
- if (mode < 2) {
- lastException = new IOException("ImageIO runtime exception - "+(mode==0 ? "normal" : "fallback"), e);
- }
+ lastException = new IOException("ImageIO runtime exception", e);
}
}
reader.dispose();
}
- } finally {
- iis.close();
}
// If you don't have an image at the end of all readers
// multiple locations above ...
throw lastException;
}
- LOG.atWarn().log("Content-type: {} is not support. Image ignored.", contentType);
+ LOG.atWarn().log("Content-type: {} is not supported. Image ignored.", contentType);
return null;
}
+ if (img.getColorModel().hasAlpha()) {
+ return img;
+ }
+
// add alpha channel
- if (img.getType() != BufferedImage.TYPE_INT_ARGB) {
- BufferedImage argbImg = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB);
- Graphics g = argbImg.getGraphics();
- g.drawImage(img, 0, 0, null);
- g.dispose();
- return argbImg;
+ BufferedImage argbImg = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB);
+ Graphics g = argbImg.getGraphics();
+ g.drawImage(img, 0, 0, null);
+ g.dispose();
+ return argbImg;
+ }
+
+ private static BufferedImage loadColored(ImageReader reader, ImageInputStream iis, ImageReadParam param) throws IOException {
+ reader.setInput(iis, false, true);
+ return reader.read(0, param);
+ }
+
+ private static BufferedImage loadGrayScaled(ImageReader reader, ImageInputStream iis, ImageReadParam param) throws IOException {
+ // try to load picture in gray scale mode
+ // fallback mode for invalid image band metadata
+ Iterable<ImageTypeSpecifier> specs = new IteratorIterable<>(reader.getImageTypes(0));
+ StreamSupport.stream(specs.spliterator(), false).
+ filter(its -> its.getBufferedImageType() == BufferedImage.TYPE_BYTE_GRAY).findFirst().
+ ifPresent(param::setDestinationType);
+
+ reader.setInput(iis, false, true);
+ return reader.read(0, param);
+ }
+
+ private static BufferedImage loadTruncated(ImageReader reader, ImageInputStream iis, ImageReadParam param) throws IOException {
+ // try to load truncated pictures by supplying a BufferedImage
+ // and use the processed data up till the point of error
+ reader.setInput(iis, false, true);
+ int height = reader.getHeight(0);
+ int width = reader.getWidth(0);
+
+ Iterator<ImageTypeSpecifier> imageTypes = reader.getImageTypes(0);
+ if (!imageTypes.hasNext()) {
+ // unable to load even a truncated version of the image
+ // implicitly throwing previous error
+ return null;
}
+ ImageTypeSpecifier imageTypeSpecifier = imageTypes.next();
+ BufferedImage img = imageTypeSpecifier.createBufferedImage(width, height);
+ param.setDestination(img);
- return img;
+ try {
+ reader.read(0, param);
+ } catch (IOException ignored) {
+ }
+
+ if (img.getColorModel().hasAlpha()) {
+ return img;
+ }
+
+ int y = findTruncatedBlackBox(img, width, height);
+ if (y >= height) {
+ return img;
+ }
+
+ BufferedImage argbImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+ Graphics2D g = argbImg.createGraphics();
+ g.clipRect(0, 0, width, y);
+ g.drawImage(img, 0, 0, null);
+ g.dispose();
+ img.flush();
+ return argbImg;
}
private static int findTruncatedBlackBox(BufferedImage img, int width, int height) {