This is an automated email from the ASF dual-hosted git repository.
tballison pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tika.git
The following commit(s) were added to refs/heads/main by this push:
new 1e3d8f8883 TIKA-4855: Render EMF/WMF through POI and emit the OLE2
SummaryInformation thumbnail (#3095)
1e3d8f8883 is described below
commit 1e3d8f888380d8b302ce4787bc7d5fbb513f1867
Author: Dominik Schmidt <[email protected]>
AuthorDate: Tue Sep 1 15:54:47 2026 +0200
TIKA-4855: Render EMF/WMF through POI and emit the OLE2 SummaryInformation
thumbnail (#3095)
* TIKA-4855 - render EMF/WMF through POI and emit the OLE2
SummaryInformation thumbnail
POIMetafileRenderer (poi-metafile-renderer) draws EMF and WMF images to a
PNG of a configurable width; Word's bitmap-in-WMF thumbnails, which POI has
no bounds for, are rendered from the bitmap. EMFParser and WMFParser are
RenderingParsers and emit the rendering as a RENDERING embedded document
with "emf-parser" / "wmf-parser": {"renderImage": true}, off by
default, the way the PDF parser emits page renderings. OfficeParser emits
the SummaryInformation thumbnail of the OLE2 formats (a WMF) as a THUMBNAIL
embedded document, as the OOXML parsers do with the docProps thumbnail.
* TIKA-4855 - the embedded-file integration test sees the ppt's thumbnail
* TIKA-4855 - renderOnlyEmbeddedResourceTypes restricts the rendering to
e.g. thumbnails
With "renderOnlyEmbeddedResourceTypes": ["THUMBNAIL"] the parsers render
the document's thumbnail but not the metafiles of embedded objects or
pictures; empty (the default) renders every image.
* TIKA-4855 - the rendering of a THUMBNAIL is a THUMBNAIL
* TIKA-4855 - address review: merged javadocs, rendering failures recorded
on the metafile's metadata and in the renderer's result, CHANGES wrapped
* TIKA-4855 - keep the render metadata for the composite renderer's
routing; record failures on the metafile's metadata
* TIKA-4855 - name() for the resource type; carry the renderer's
diagnostics to the metafile on failure
* TIKA-4855 - address review: renderWidth reaches the injected renderer,
bounded rendering height, renderer reads the metafile itself, gate before
rendering, validated resource types, thumbnail switch, shared parser base
---------
Co-authored-by: Tilman Hausherr <[email protected]>
Co-authored-by: Tim Allison <[email protected]>
---
CHANGES.txt | 13 +
.../microsoft/POIContainerExtractionTest.java | 14 +-
.../parser/microsoft/AbstractMetafileParser.java | 74 +++++
.../apache/tika/parser/microsoft/EMFParser.java | 40 ++-
.../parser/microsoft/MetafileParserConfig.java | 104 +++++++
.../tika/parser/microsoft/MetafileRendering.java | 183 ++++++++++++
.../apache/tika/parser/microsoft/OfficeParser.java | 33 ++-
.../tika/parser/microsoft/OfficeParserConfig.java | 15 +
.../tika/parser/microsoft/SummaryExtractor.java | 31 ++
.../apache/tika/parser/microsoft/WMFParser.java | 38 ++-
.../renderer/microsoft/POIMetafileRenderer.java | 311 +++++++++++++++++++++
.../tika/parser/microsoft/EMFParserTest.java | 161 +++++++++++
.../tika/parser/microsoft/OLE2ThumbnailTest.java | 146 ++++++++++
.../parser/microsoft/PowerPointParserTest.java | 6 +-
.../tika/parser/microsoft/WMFParserTest.java | 56 ++++
.../resources/configs/tika-config-emf-render.json | 11 +
16 files changed, 1224 insertions(+), 12 deletions(-)
diff --git a/CHANGES.txt b/CHANGES.txt
index ce1baa8f55..80e898aa72 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,18 @@
Release 4.1.0 - unreleased
+ * Raster previews for the vector thumbnails of Office documents: the new
+ poi-metafile-renderer draws EMF and WMF images through POI (a PNG of
+ a configurable width; Word's bitmap-in-WMF thumbnails from the bitmap
+ directly), EMFParser and WMFParser are RenderingParsers that emit the
+ rendering as a RENDERING embedded document with "emf-parser" /
+ "wmf-parser": {"renderImage": true, "renderWidth": 800}, off by
+ default and restrictable to e.g. THUMBNAIL embedded documents with
+ "renderOnlyEmbeddedResourceTypes", and OfficeParser emits the
+ SummaryInformation thumbnail of the OLE2 formats (a WMF) as a THUMBNAIL
+ embedded document, as the OOXML parsers do with the docProps thumbnail,
+ switchable with "office-parser": {"extractThumbnail": false}
+ (TIKA-4855).
+
* Add "exception-reporting" parse-context config to redact and bound
exception text in metadata, tika-server error bodies and pipes/grpc
messages; FileSystemEmitter writes atomically (TIKA-4848).
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/java/org/apache/tika/parser/microsoft/POIContainerExtractionTest.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/java/org/apache/tika/parser/microsoft/POIContainerExtractionTest.java
index cae0d63310..38624d0f19 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/java/org/apache/tika/parser/microsoft/POIContainerExtractionTest.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/java/org/apache/tika/parser/microsoft/POIContainerExtractionTest.java
@@ -160,10 +160,10 @@ public class POIContainerExtractionTest extends
AbstractPOIContainerExtractionTe
assertEquals(TYPE_PNG, handler.mediaTypes.get(15)); // PNG inside
.xls
- // PowerPoint with excel and word
+ // PowerPoint with excel and word; its SummaryInformation thumbnail
comes last
handler = process("testPPT_embeded.ppt", extractor, false);
- assertEquals(7, handler.filenames.size());
- assertEquals(7, handler.mediaTypes.size());
+ assertEquals(8, handler.filenames.size());
+ assertEquals(8, handler.mediaTypes.size());
// Embedded objects get OLE IDs, slide images now get generated names
assertEquals("1", handler.filenames.get(0));
@@ -171,6 +171,7 @@ public class POIContainerExtractionTest extends
AbstractPOIContainerExtractionTe
for (int i = 2; i < 7; i++) {
assertNotNull(handler.filenames.get(i));
}
+ assertEquals("thumbnail.wmf", handler.filenames.get(7));
// But we do know their types
assertEquals(TYPE_XLS, handler.mediaTypes.get(0)); // Embedded office
doc
assertEquals(TYPE_DOC, handler.mediaTypes.get(1)); // Embedded office
doc
@@ -179,11 +180,12 @@ public class POIContainerExtractionTest extends
AbstractPOIContainerExtractionTe
assertEquals(TYPE_PNG, handler.mediaTypes.get(4)); // Embedded image
assertEquals(TYPE_PNG, handler.mediaTypes.get(5)); // Embedded image
assertEquals(TYPE_PNG, handler.mediaTypes.get(6)); // Embedded image
+ assertEquals(TYPE_WMF, handler.mediaTypes.get(7)); // Document
thumbnail
// Run again on PowerPoint but with recursion
handler = process("testPPT_embeded.ppt", extractor, true);
- assertEquals(11, handler.filenames.size());
- assertEquals(11, handler.mediaTypes.size());
+ assertEquals(12, handler.filenames.size());
+ assertEquals(12, handler.mediaTypes.size());
assertEquals("1", handler.filenames.get(0));
assertEquals(null, handler.filenames.get(1));
@@ -195,6 +197,7 @@ public class POIContainerExtractionTest extends
AbstractPOIContainerExtractionTe
for (int i = 6; i < 11; i++) {
assertNotNull(handler.filenames.get(i));
}
+ assertEquals("thumbnail.wmf", handler.filenames.get(11));
assertEquals(TYPE_XLS, handler.mediaTypes.get(0)); // Embedded office
doc
assertEquals(TYPE_PNG, handler.mediaTypes.get(1)); // PNG inside
.xls
@@ -207,6 +210,7 @@ public class POIContainerExtractionTest extends
AbstractPOIContainerExtractionTe
assertEquals(TYPE_PNG, handler.mediaTypes.get(8)); // Embedded image
assertEquals(TYPE_PNG, handler.mediaTypes.get(9)); // Embedded image
assertEquals(TYPE_PNG, handler.mediaTypes.get(10)); // Embedded image
+ assertEquals(TYPE_WMF, handler.mediaTypes.get(11)); // Document
thumbnail
// Word, with a non-office file (PDF)
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/AbstractMetafileParser.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/AbstractMetafileParser.java
new file mode 100644
index 0000000000..2c73b16a07
--- /dev/null
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/AbstractMetafileParser.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tika.parser.microsoft;
+
+import java.io.IOException;
+
+import org.apache.tika.config.ParseContextConfig;
+import org.apache.tika.exception.TikaException;
+import org.apache.tika.io.TikaInputStream;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.parser.ParseContext;
+import org.apache.tika.parser.Parser;
+import org.apache.tika.parser.RenderingParser;
+import org.apache.tika.renderer.Renderer;
+
+/**
+ * What {@link EMFParser} and {@link WMFParser} share: the configuration of
+ * the metafile parsers and the renderer they may be handed.
+ */
+abstract class AbstractMetafileParser implements Parser, RenderingParser {
+
+ private final MetafileParserConfig defaultConfig;
+ private Renderer renderer;
+
+ AbstractMetafileParser(MetafileParserConfig defaultConfig) {
+ this.defaultConfig = defaultConfig;
+ }
+
+ /**
+ * The component name this parser reads its configuration from,
+ * {@code emf-parser} or {@code wmf-parser}.
+ */
+ abstract String componentName();
+
+ MetafileParserConfig getConfig(ParseContext context) throws TikaException,
IOException {
+ return ParseContextConfig.getConfig(context, componentName(),
MetafileParserConfig.class,
+ defaultConfig);
+ }
+
+ /**
+ * Spools the stream when the image is going to be rendered, so the
+ * renderer can read the metafile itself rather than only the parsed
+ * picture.
+ */
+ static void prepareForRendering(TikaInputStream tis, MetafileParserConfig
config,
+ Metadata metadata) throws IOException {
+ if (config.shouldRender(metadata)) {
+ tis.getFile();
+ }
+ }
+
+ Renderer getRenderer() {
+ return renderer;
+ }
+
+ @Override
+ public void setRenderer(Renderer renderer) {
+ this.renderer = renderer;
+ }
+}
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/EMFParser.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/EMFParser.java
index ba7bc8efc6..dc6c57c6c2 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/EMFParser.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/EMFParser.java
@@ -35,6 +35,8 @@ import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.apache.tika.annotation.TikaComponent;
+import org.apache.tika.config.ConfigDeserializer;
+import org.apache.tika.config.JsonConfig;
import org.apache.tika.exception.TikaException;
import org.apache.tika.extractor.EmbeddedDocumentExtractor;
import org.apache.tika.extractor.EmbeddedDocumentUtil;
@@ -42,9 +44,10 @@ import org.apache.tika.io.TikaInputStream;
import org.apache.tika.metadata.HttpHeaders;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.metadata.Property;
+import org.apache.tika.metadata.TikaCoreProperties;
import org.apache.tika.mime.MediaType;
import org.apache.tika.parser.ParseContext;
-import org.apache.tika.parser.Parser;
+import org.apache.tika.renderer.Renderer;
import org.apache.tika.sax.EmbeddedContentHandler;
import org.apache.tika.sax.XHTMLContentHandler;
@@ -61,13 +64,40 @@ import org.apache.tika.sax.XHTMLContentHandler;
* We're also relying on storage order for text order, which isn't great.
* We'd have to do something like what PDFBox or XPS do to sort the
* runs and then put the cow back together from the hamburger...lol...
+ * <p/>
+ * With {@link MetafileParserConfig#setRenderImage(boolean)}
+ * ("emf-parser": {"renderImage": true}) the image is rendered through the
+ * configured {@link Renderer}, the
+ * {@link org.apache.tika.renderer.microsoft.POIMetafileRenderer} by
+ * default, and emitted as a
+ * {@link TikaCoreProperties.EmbeddedResourceType#RENDERING} embedded document,
+ * the way the PDF parser emits page renderings, so a client can obtain a
+ * raster preview of a vector thumbnail such as the docProps thumbnail of a
+ * Word document.
*/
@TikaComponent
-public class EMFParser implements Parser {
+public class EMFParser extends AbstractMetafileParser {
public static Property EMF_ICON_ONLY =
Property.internalBoolean("emf:icon-only");
public static Property EMF_ICON_STRING =
Property.internalText("emf:icon-string");
+ public EMFParser() {
+ this(new MetafileParserConfig());
+ }
+
+ public EMFParser(MetafileParserConfig config) {
+ super(config);
+ }
+
+ public EMFParser(JsonConfig jsonConfig) {
+ this(ConfigDeserializer.buildConfig(jsonConfig,
MetafileParserConfig.class));
+ }
+
+ @Override
+ String componentName() {
+ return "emf-parser";
+ }
+
private static String ICON_ONLY = "IconOnly";
private static final MediaType MEDIA_TYPE = MediaType.image("emf");
@@ -102,6 +132,8 @@ public class EMFParser implements Parser {
XHTMLContentHandler xhtml = new XHTMLContentHandler(handler, metadata,
context);
xhtml.startDocument();
try {
+ MetafileParserConfig config = getConfig(context);
+ prepareForRendering(tis, config, metadata);
HemfPicture ex = new HemfPicture(tis);
ParseState parseState = new ParseState();
long fudgeFactorX = 10;//derive this from the font or frame/bounds
information
@@ -139,6 +171,10 @@ public class EMFParser implements Parser {
xhtml.characters(buffer.toString());
xhtml.endElement("p");
}
+ if (config.shouldRender(metadata)) {
+ MetafileRendering.render(getRenderer(), config, MEDIA_TYPE,
tis, ex, xhtml,
+ metadata, context);
+ }
} catch (RecordFormatException e) { //POI's hemfparser can throw these
for "parse
// exceptions"
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/MetafileParserConfig.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/MetafileParserConfig.java
new file mode 100644
index 0000000000..2db698f672
--- /dev/null
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/MetafileParserConfig.java
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tika.parser.microsoft;
+
+import java.io.Serializable;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.TikaCoreProperties;
+import org.apache.tika.renderer.microsoft.POIMetafileRenderer;
+
+/**
+ * Configuration of the {@link EMFParser} ("emf-parser") and the
+ * {@link WMFParser} ("wmf-parser").
+ */
+public class MetafileParserConfig implements Serializable {
+
+ private static final long serialVersionUID = -6371049153052164071L;
+
+ private boolean renderImage = false;
+ private int renderWidth = 800;
+ private Set<String> renderOnlyEmbeddedResourceTypes = new HashSet<>();
+
+ /**
+ * Whether to render the image and emit the rendering as a RENDERING
+ * embedded document. Off by default.
+ */
+ public boolean isRenderImage() {
+ return renderImage;
+ }
+
+ public void setRenderImage(boolean renderImage) {
+ this.renderImage = renderImage;
+ }
+
+ /**
+ * Width of the rendering in pixels when the default
+ * {@link POIMetafileRenderer} is used; the height follows the image's
+ * aspect ratio. Default 800.
+ */
+ public int getRenderWidth() {
+ return renderWidth;
+ }
+
+ /**
+ * Restricts the rendering to images that are embedded documents of one
+ * of these {@code tk:embedded-resource-type}s, e.g. {@code ["THUMBNAIL"]}
+ * to render the thumbnail of an Office document but not the pictures of
+ * its embedded objects. Empty (the default) renders every image.
+ */
+ public Set<String> getRenderOnlyEmbeddedResourceTypes() {
+ return renderOnlyEmbeddedResourceTypes;
+ }
+
+ public void setRenderOnlyEmbeddedResourceTypes(Set<String>
renderOnlyEmbeddedResourceTypes) {
+ if (renderOnlyEmbeddedResourceTypes == null) {
+ this.renderOnlyEmbeddedResourceTypes = new HashSet<>();
+ return;
+ }
+ Set<String> types = new HashSet<>();
+ for (String type : renderOnlyEmbeddedResourceTypes) {
+ //a typo would silently disable rendering
+
types.add(TikaCoreProperties.EmbeddedResourceType.valueOf(type).name());
+ }
+ this.renderOnlyEmbeddedResourceTypes = types;
+ }
+
+ /**
+ * Whether an image with this metadata is to be rendered.
+ */
+ public boolean shouldRender(Metadata metadata) {
+ if (!renderImage) {
+ return false;
+ }
+ if (renderOnlyEmbeddedResourceTypes.isEmpty()) {
+ return true;
+ }
+ String type = metadata.get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE);
+ return type != null && renderOnlyEmbeddedResourceTypes.contains(type);
+ }
+
+ public void setRenderWidth(int renderWidth) {
+ if (renderWidth < 1 || renderWidth > 10000) {
+ throw new IllegalArgumentException(
+ "renderWidth must be between 1 and 10000, got: " +
renderWidth);
+ }
+ this.renderWidth = renderWidth;
+ }
+}
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/MetafileRendering.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/MetafileRendering.java
new file mode 100644
index 0000000000..d4efd0a12c
--- /dev/null
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/MetafileRendering.java
@@ -0,0 +1,183 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tika.parser.microsoft;
+
+import java.io.IOException;
+
+import org.xml.sax.SAXException;
+
+import org.apache.tika.exception.TikaException;
+import org.apache.tika.extractor.EmbeddedDocumentExtractor;
+import org.apache.tika.extractor.EmbeddedDocumentUtil;
+import org.apache.tika.io.FilenameUtils;
+import org.apache.tika.io.TikaInputStream;
+import org.apache.tika.metadata.HttpHeaders;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.TikaCoreProperties;
+import org.apache.tika.mime.MediaType;
+import org.apache.tika.parser.ParseContext;
+import org.apache.tika.renderer.RenderResult;
+import org.apache.tika.renderer.RenderResults;
+import org.apache.tika.renderer.Renderer;
+import org.apache.tika.renderer.microsoft.POIMetafileRenderer;
+import org.apache.tika.sax.EmbeddedContentHandler;
+import org.apache.tika.sax.XHTMLContentHandler;
+
+/**
+ * Shared by {@link EMFParser} and {@link WMFParser}: renders a parsed
+ * metafile through a {@link Renderer} and emits the result as an embedded
+ * document, the way the PDF parser emits page renderings. The rendering of
+ * a {@link TikaCoreProperties.EmbeddedResourceType#THUMBNAIL} is itself a
+ * THUMBNAIL (it is the same picture in a form a client can display); any
+ * other rendering is a {@link
TikaCoreProperties.EmbeddedResourceType#RENDERING}.
+ */
+final class MetafileRendering {
+
+ private MetafileRendering() {
+ }
+
+ /**
+ * @param injected the renderer set on the parser, or null
+ * @param picture the parsed {@code HemfPicture} or {@code HwmfPicture}
+ */
+ static void render(Renderer injected, MetafileParserConfig config,
MediaType type,
+ TikaInputStream source, Object picture,
XHTMLContentHandler xhtml,
+ Metadata metadata, ParseContext context) throws
IOException, SAXException {
+ //like the PDF parser: the injected renderer if it handles the type,
+ //the default one otherwise
+ Renderer renderer = injected != null &&
injected.getSupportedTypes(context).contains(type)
+ ? injected : defaultRenderer(config);
+ Metadata renderMetadata = Metadata.newInstance(context);
+ renderMetadata.set(TikaCoreProperties.TYPE, type.toString());
+ EmbeddedDocumentExtractor extractor =
+ EmbeddedDocumentUtil.getEmbeddedDocumentExtractor(context);
+ //rendering is the expensive part: ask before paying for it. The name
+ //assumes the renderer's default format; the result's own metadata
+ //replaces it below.
+ Metadata gate = Metadata.newInstance(context);
+ gate.set(TikaCoreProperties.RESOURCE_NAME_KEY, renderingName(metadata,
"png"));
+ gate.set(HttpHeaders.CONTENT_TYPE, "image/png");
+ gate.set(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE,
renderingType(metadata).name());
+ if (!extractor.shouldParseEmbedded(gate, context)) {
+ return;
+ }
+ try (TikaInputStream pictureStream = pictureStream(source);
+ RenderResults results = render(renderer, pictureStream, picture,
renderMetadata,
+ metadata, context)) {
+ if (results == null) {
+ return;
+ }
+ for (RenderResult result : results.getResults()) {
+ if (result.getStatus() != RenderResult.STATUS.SUCCESS) {
+ //carry the renderer's diagnostics over to the metafile
+ String[] warnings = result.getMetadata()
+
.getValues(TikaCoreProperties.TIKA_META_EXCEPTION_WARNING);
+ if (warnings.length == 0) {
+ EmbeddedDocumentUtil.recordException(
+ new TikaException("metafile rendering
failed"), metadata, context);
+ }
+ for (String warning : warnings) {
+
metadata.add(TikaCoreProperties.TIKA_META_EXCEPTION_WARNING, warning);
+ }
+ continue;
+ }
+ Metadata renderingMetadata = result.getMetadata();
+ renderingMetadata.set(TikaCoreProperties.RESOURCE_NAME_KEY,
+ renderingName(metadata, extension(renderingMetadata)));
+
renderingMetadata.set(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE,
+ renderingType(metadata).name());
+ if (extractor.shouldParseEmbedded(renderingMetadata, context))
{
+ try (TikaInputStream tis = result.getInputStream()) {
+ extractor.parseEmbedded(tis, new
EmbeddedContentHandler(xhtml),
+ renderingMetadata, context, false);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * The stream the renderer reads the picture from: the metafile itself
+ * where the parser spooled it, so a renderer that does not know the
+ * open-container shortcut still sees the bytes.
+ */
+ private static TikaInputStream pictureStream(TikaInputStream source)
throws IOException {
+ if (source != null && source.hasFile()) {
+ return TikaInputStream.get(source.getPath());
+ }
+ return TikaInputStream.get(new byte[0]);
+ }
+
+ /**
+ * The rendering of a THUMBNAIL is itself a THUMBNAIL: it is the same
+ * picture in a form a client can display. Any other rendering is a
+ * RENDERING.
+ */
+ private static TikaCoreProperties.EmbeddedResourceType
renderingType(Metadata metadata) {
+ return TikaCoreProperties.EmbeddedResourceType.THUMBNAIL.name()
+
.equals(metadata.get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE))
+ ? TikaCoreProperties.EmbeddedResourceType.THUMBNAIL
+ : TikaCoreProperties.EmbeddedResourceType.RENDERING;
+ }
+
+ private static String extension(Metadata renderingMetadata) {
+ String contentType = renderingMetadata.get(HttpHeaders.CONTENT_TYPE);
+ return contentType != null && contentType.startsWith("image/")
+ ? contentType.substring("image/".length()) : "png";
+ }
+
+ /**
+ * @param renderMetadata what the renderer is told about the picture (its
+ * type, so a composite renderer can route it)
+ * @param parentMetadata the metafile's metadata; a rendering failure is
+ * recorded there, so it is not silently swallowed
+ */
+ private static RenderResults render(Renderer renderer, TikaInputStream
pictureStream,
+ Object picture, Metadata
renderMetadata,
+ Metadata parentMetadata, ParseContext
context)
+ throws IOException {
+ //hand the parsed picture over instead of re-reading the stream
+ pictureStream.setOpenContainer(picture);
+ try {
+ return renderer.render(pictureStream, renderMetadata, context);
+ } catch (SecurityException e) {
+ throw e;
+ } catch (Exception e) {
+ EmbeddedDocumentUtil.recordException(e, parentMetadata, context);
+ return null;
+ }
+ }
+
+ private static Renderer defaultRenderer(MetafileParserConfig config) {
+ POIMetafileRenderer renderer = new POIMetafileRenderer();
+ renderer.setWidth(config.getRenderWidth());
+ return renderer;
+ }
+
+ /**
+ * The rendering is named after the image, with the rendering's format as
+ * its extension.
+ */
+ private static String renderingName(Metadata metadata, String extension) {
+ String name =
FilenameUtils.getName(metadata.get(TikaCoreProperties.RESOURCE_NAME_KEY));
+ if (name == null || name.isEmpty()) {
+ return "rendering." + extension;
+ }
+ int dot = name.lastIndexOf('.');
+ return (dot > 0 ? name.substring(0, dot) : name) + "." + extension;
+ }
+}
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/OfficeParser.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/OfficeParser.java
index a88bf609a4..423a51f86b 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/OfficeParser.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/OfficeParser.java
@@ -214,7 +214,8 @@ public class OfficeParser extends AbstractOfficeParser {
throws IOException, SAXException, TikaException {
// Parse summary entries first, to make metadata available early
- new SummaryExtractor(metadata).parseSummaries(root);
+ SummaryExtractor summaryExtractor = new SummaryExtractor(metadata);
+ summaryExtractor.parseSummaries(root);
// Parse remaining document entries
POIFSDocumentType type = POIFSDocumentType.detectType(root);
@@ -306,6 +307,36 @@ public class OfficeParser extends AbstractOfficeParser {
// is extracted, which happened above
break;
}
+ OfficeParserConfig config = context.get(OfficeParserConfig.class);
+ if (config == null || config.isExtractThumbnail()) {
+ handleThumbnail(summaryExtractor.getThumbnailWmf(), xhtml,
context);
+ }
+ }
+
+ /**
+ * Emits the document thumbnail from the SummaryInformation, a WMF, as a
+ * {@link TikaCoreProperties.EmbeddedResourceType#THUMBNAIL} embedded
+ * document, as the OOXML parsers do with the docProps thumbnail. Switched
+ * off with {@code "office-parser": {"extractThumbnail": false}}.
+ */
+ private void handleThumbnail(byte[] wmf, XHTMLContentHandler xhtml,
ParseContext context)
+ throws IOException, SAXException {
+ if (wmf == null) {
+ return;
+ }
+ Metadata embeddedMetadata = Metadata.newInstance(context);
+ embeddedMetadata.set(TikaCoreProperties.RESOURCE_NAME_KEY,
"thumbnail.wmf");
+ embeddedMetadata.set(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE,
+ TikaCoreProperties.EmbeddedResourceType.THUMBNAIL.name());
+ embeddedMetadata.set(HttpHeaders.CONTENT_TYPE, "image/wmf");
+ EmbeddedDocumentExtractor extractor =
+ EmbeddedDocumentUtil.getEmbeddedDocumentExtractor(context);
+ if (extractor.shouldParseEmbedded(embeddedMetadata, context)) {
+ try (TikaInputStream tis = TikaInputStream.get(wmf)) {
+ extractor.parseEmbedded(tis, new
EmbeddedContentHandler(xhtml), embeddedMetadata,
+ context, false);
+ }
+ }
}
private void setType(Metadata metadata, MediaType type) {
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/OfficeParserConfig.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/OfficeParserConfig.java
index bb2ff73af5..5b174caaaa 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/OfficeParserConfig.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/OfficeParserConfig.java
@@ -36,6 +36,8 @@ public class OfficeParserConfig implements Serializable {
private boolean writeSelectHeadersInBody = false;
+ private boolean extractThumbnail = true;
+
/**
* Maximum bytes per embedded object/pict when extracting from RTF within
* MSG files. Since embedded data is streamed to disk (not held in
memory),
@@ -302,4 +304,17 @@ public class OfficeParserConfig implements Serializable {
public void setRtfEmbeddedMaxBytesInKb(int rtfEmbeddedMaxBytesInKb) {
this.rtfEmbeddedMaxBytesInKb = rtfEmbeddedMaxBytesInKb;
}
+
+ /**
+ * Whether to emit the document thumbnail an OLE2 file stores in its
+ * SummaryInformation (a WMF) as a THUMBNAIL embedded document, the way
+ * the OOXML parsers emit the docProps thumbnail. On by default.
+ */
+ public boolean isExtractThumbnail() {
+ return extractThumbnail;
+ }
+
+ public void setExtractThumbnail(boolean extractThumbnail) {
+ this.extractThumbnail = extractThumbnail;
+ }
}
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/SummaryExtractor.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/SummaryExtractor.java
index 238974eec8..eeeabdc7e7 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/SummaryExtractor.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/SummaryExtractor.java
@@ -28,6 +28,7 @@ import org.apache.poi.hpsf.DocumentSummaryInformation;
import org.apache.poi.hpsf.NoPropertySetStreamException;
import org.apache.poi.hpsf.PropertySet;
import org.apache.poi.hpsf.SummaryInformation;
+import org.apache.poi.hpsf.Thumbnail;
import org.apache.poi.hpsf.UnexpectedPropertySetTypeException;
import org.apache.poi.poifs.filesystem.DirectoryNode;
import org.apache.poi.poifs.filesystem.DocumentEntry;
@@ -61,6 +62,8 @@ public class SummaryExtractor {
private final Metadata metadata;
+ private byte[] thumbnailWmf;
+
public SummaryExtractor(Metadata metadata) {
this.metadata = metadata;
}
@@ -131,7 +134,16 @@ public class SummaryExtractor {
}
}
+ /**
+ * The document thumbnail stored in the SummaryInformation as a WMF, or
+ * null if there is none or it is not a metafile picture.
+ */
+ public byte[] getThumbnailWmf() {
+ return thumbnailWmf;
+ }
+
private void parse(SummaryInformation summary) {
+ thumbnailWmf = extractThumbnailWmf(summary);
set(TikaCoreProperties.TITLE, summary.getTitle());
addMulti(metadata, TikaCoreProperties.CREATOR, summary.getAuthor());
//make sure these are retrievable specifically
@@ -158,6 +170,25 @@ public class SummaryExtractor {
}
}
+ /**
+ * PIDSI_THUMBNAIL is a clipboard object; Office stores the thumbnail as
+ * CF_METAFILEPICT, a WMF. Anything else, or a malformed one, yields null.
+ */
+ private static byte[] extractThumbnailWmf(SummaryInformation summary) {
+ byte[] raw = summary.getThumbnail();
+ if (raw == null || raw.length == 0) {
+ return null;
+ }
+ try {
+ byte[] wmf = new Thumbnail(raw).getThumbnailAsWMF();
+ return wmf != null && wmf.length > 0 ? wmf : null;
+ } catch (SecurityException e) {
+ throw e;
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
private void parse(DocumentSummaryInformation summary) {
set(OfficeOpenXMLExtended.COMPANY, summary.getCompany());
addMulti(metadata, OfficeOpenXMLExtended.MANAGER,
summary.getManager());
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/WMFParser.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/WMFParser.java
index 41b9ecd2c2..9bbf578d11 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/WMFParser.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/WMFParser.java
@@ -32,25 +32,53 @@ import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.apache.tika.annotation.TikaComponent;
+import org.apache.tika.config.ConfigDeserializer;
+import org.apache.tika.config.JsonConfig;
import org.apache.tika.exception.TikaException;
import org.apache.tika.io.TikaInputStream;
import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.TikaCoreProperties;
import org.apache.tika.mime.MediaType;
import org.apache.tika.parser.ParseContext;
-import org.apache.tika.parser.Parser;
+import org.apache.tika.renderer.Renderer;
import org.apache.tika.sax.XHTMLContentHandler;
/**
* This parser offers a very rough capability to extract text if there
* is text stored in the WMF files.
+ * <p/>
+ * With {@link MetafileParserConfig#setRenderImage(boolean)}
+ * ("wmf-parser": {"renderImage": true}) the image is also rendered through the
+ * configured {@link Renderer}, the
+ * {@link org.apache.tika.renderer.microsoft.POIMetafileRenderer} by
+ * default, and emitted as a
+ * {@link TikaCoreProperties.EmbeddedResourceType#RENDERING} embedded document,
+ * the way the PDF parser emits page renderings.
*/
@TikaComponent
-public class WMFParser implements Parser {
+public class WMFParser extends AbstractMetafileParser {
private static final MediaType MEDIA_TYPE = MediaType.image("wmf");
private static final Set<MediaType> SUPPORTED_TYPES =
Collections.singleton(MEDIA_TYPE);
+ public WMFParser() {
+ this(new MetafileParserConfig());
+ }
+
+ public WMFParser(MetafileParserConfig config) {
+ super(config);
+ }
+
+ public WMFParser(JsonConfig jsonConfig) {
+ this(ConfigDeserializer.buildConfig(jsonConfig,
MetafileParserConfig.class));
+ }
+
+ @Override
+ String componentName() {
+ return "wmf-parser";
+ }
+
@Override
public Set<MediaType> getSupportedTypes(ParseContext context) {
return SUPPORTED_TYPES;
@@ -63,6 +91,8 @@ public class WMFParser implements Parser {
xhtml.startDocument();
tis.setCloseShield();
try {
+ MetafileParserConfig config = getConfig(context);
+ prepareForRendering(tis, config, metadata);
HwmfPicture picture = null;
try {
picture = new HwmfPicture(tis);
@@ -95,6 +125,10 @@ public class WMFParser implements Parser {
xhtml.endElement("p");
}
}
+ if (config.shouldRender(metadata)) {
+ MetafileRendering.render(getRenderer(), config, MEDIA_TYPE,
tis, picture, xhtml,
+ metadata, context);
+ }
} catch (RecordFormatException e) { //POI's hwmfparser can \ throw
these for "parse
// exceptions"
throw new TikaException(e.getMessage(), e);
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/renderer/microsoft/POIMetafileRenderer.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/renderer/microsoft/POIMetafileRenderer.java
new file mode 100644
index 0000000000..c2fde311e2
--- /dev/null
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/renderer/microsoft/POIMetafileRenderer.java
@@ -0,0 +1,311 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tika.renderer.microsoft;
+
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.geom.Dimension2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import javax.imageio.ImageIO;
+
+import org.apache.poi.hemf.usermodel.HemfPicture;
+import org.apache.poi.hwmf.record.HwmfFill;
+import org.apache.poi.hwmf.record.HwmfRecord;
+import org.apache.poi.hwmf.usermodel.HwmfPicture;
+
+import org.apache.tika.annotation.TikaComponent;
+import org.apache.tika.config.ParseContextConfig;
+import org.apache.tika.exception.TikaException;
+import org.apache.tika.extractor.EmbeddedDocumentUtil;
+import org.apache.tika.io.TemporaryResources;
+import org.apache.tika.io.TikaInputStream;
+import org.apache.tika.metadata.HttpHeaders;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.Rendering;
+import org.apache.tika.metadata.TikaCoreProperties;
+import org.apache.tika.mime.MediaType;
+import org.apache.tika.parser.ParseContext;
+import org.apache.tika.parser.microsoft.MetafileParserConfig;
+import org.apache.tika.renderer.RenderRequest;
+import org.apache.tika.renderer.RenderResult;
+import org.apache.tika.renderer.RenderResults;
+import org.apache.tika.renderer.Renderer;
+import org.apache.tika.renderer.RenderingTracker;
+
+/**
+ * Renders EMF and WMF images to a raster image through POI's HEMF and HWMF,
+ * the way {@code PDFBoxRenderer} renders PDF pages. The rendering has the
+ * configured width, its height follows the image's aspect ratio, and it is
+ * drawn on a white canvas. Metafiles have no pages, so the render requests
+ * are ignored and a single result is returned.
+ * <p>
+ * The WMF thumbnails that Word stores in the SummaryInformation of a .doc
+ * consist of a window extent and a single {@code dibStretchBlt} record, for
+ * which POI cannot compute bounds; those are rendered from the record's
+ * bitmap directly.
+ */
+@TikaComponent(name = "poi-metafile-renderer")
+public class POIMetafileRenderer implements Renderer {
+
+ public static final String RENDERED_BY = "poi-metafile-renderer";
+
+ public static final MediaType EMF = MediaType.image("emf");
+ public static final MediaType WMF = MediaType.image("wmf");
+
+ private static final Set<MediaType> SUPPORTED_TYPES =
+ Collections.unmodifiableSet(new HashSet<>(Arrays.asList(EMF,
WMF)));
+
+ private static final int MAX_WIDTH = 10000;
+
+ /**
+ * A metafile declares its own aspect ratio, so a hostile one could ask
+ * for an arbitrarily tall canvas at any width. The renderer refuses
+ * beyond this height; an OutOfMemoryError would escape every catch in
+ * the parse.
+ */
+ private static final int MAX_HEIGHT = 10000;
+
+ private int width = 800;
+ private String imageFormatName = "png";
+
+ @Override
+ public Set<MediaType> getSupportedTypes(ParseContext context) {
+ return SUPPORTED_TYPES;
+ }
+
+ /**
+ * Renders the metafile in the stream, or the {@link HemfPicture} or
+ * {@link HwmfPicture} set as the stream's open container. The metadata's
+ * {@link TikaCoreProperties#TYPE} tells EMF from WMF when a stream is
+ * parsed; it defaults to EMF.
+ */
+ @Override
+ public RenderResults render(TikaInputStream tis, Metadata metadata,
ParseContext parseContext,
+ RenderRequest... requests) throws IOException,
TikaException {
+ Object picture = tis.getOpenContainer();
+ if (!(picture instanceof HemfPicture) && !(picture instanceof
HwmfPicture)) {
+ picture =
WMF.toString().equals(metadata.get(TikaCoreProperties.TYPE))
+ ? new HwmfPicture(tis) : new HemfPicture(tis);
+ }
+ RenderingTracker tracker = parseContext.get(RenderingTracker.class);
+ if (tracker == null) {
+ tracker = new RenderingTracker();
+ parseContext.set(RenderingTracker.class, tracker);
+ }
+ int id = tracker.getNextId();
+ int width = width(parseContext, metadata);
+ Metadata renderingMetadata = Metadata.newInstance(parseContext);
+ renderingMetadata.set(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE,
+ TikaCoreProperties.EmbeddedResourceType.RENDERING.name());
+ RenderResults results = new RenderResults(new TemporaryResources());
+ try {
+ long start = System.currentTimeMillis();
+ BufferedImage image = picture instanceof HemfPicture
+ ? draw((HemfPicture) picture, width) : draw((HwmfPicture)
picture, width);
+ Path tmpFile = write(image, id);
+ renderingMetadata.set(Rendering.RENDERED_MS,
System.currentTimeMillis() - start);
+ renderingMetadata.add(Rendering.RENDERED_BY, RENDERED_BY);
+ renderingMetadata.set(HttpHeaders.CONTENT_TYPE, "image/" +
imageFormatName);
+ results.add(new RenderResult(RenderResult.STATUS.SUCCESS, id,
tmpFile,
+ renderingMetadata));
+ } catch (SecurityException e) {
+ throw e;
+ } catch (Exception e) {
+ //record the cause, as PDFBoxRenderer does, so the failure is
diagnosable
+ EmbeddedDocumentUtil.recordException(e, renderingMetadata,
parseContext);
+ results.add(new RenderResult(RenderResult.STATUS.EXCEPTION, id,
null,
+ renderingMetadata));
+ }
+ return results;
+ }
+
+ private BufferedImage draw(HemfPicture picture, int width) throws
IOException {
+ Dimension2D size = picture.getSize();
+ BufferedImage image = canvas(size, width);
+ Graphics2D graphics = image.createGraphics();
+ try {
+ picture.draw(graphics, new Rectangle2D.Double(0, 0,
image.getWidth(),
+ image.getHeight()));
+ } finally {
+ graphics.dispose();
+ }
+ return image;
+ }
+
+ private BufferedImage draw(HwmfPicture picture, int width) throws
IOException {
+ Dimension2D size;
+ try {
+ size = picture.getSize();
+ } catch (IllegalStateException e) {
+ //POI throws this for "window records are incomplete": a bitmap
+ //wrapped in a metafile, as Word's .doc thumbnails are
+ BufferedImage bitmap = firstBitmap(picture);
+ if (bitmap == null) {
+ throw new IOException("WMF without bounds and without a
bitmap", e);
+ }
+ return scale(bitmap, width);
+ }
+ BufferedImage image = canvas(size, width);
+ Graphics2D graphics = image.createGraphics();
+ try {
+ picture.draw(graphics, new Rectangle2D.Double(0, 0,
image.getWidth(),
+ image.getHeight()));
+ } finally {
+ graphics.dispose();
+ }
+ return image;
+ }
+
+ private static BufferedImage firstBitmap(HwmfPicture picture) {
+ for (HwmfRecord record : picture.getRecords()) {
+ if (record instanceof HwmfFill.HwmfImageRecord) {
+ BufferedImage image = ((HwmfFill.HwmfImageRecord)
record).getImage();
+ if (image != null) {
+ return image;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * The width of the rendering: the {@code renderWidth} of the metafile
+ * parser's configuration where the parse has one, else this renderer's
+ * own {@link #setWidth(int)}. The parser configuration wins so that a
+ * request configuring {@code "emf-parser": {"renderWidth": N}} reaches
+ * the renderer the parser was handed, which need not be this instance.
+ */
+ private int width(ParseContext parseContext, Metadata metadata)
+ throws IOException, TikaException {
+ MetafileParserConfig config =
parseContext.get(MetafileParserConfig.class);
+ if (config == null) {
+ String component =
WMF.toString().equals(metadata.get(TikaCoreProperties.TYPE))
+ ? "wmf-parser" : "emf-parser";
+ if (parseContext.getJsonConfig(component) == null) {
+ return width;
+ }
+ config = ParseContextConfig.getConfig(parseContext, component,
+ MetafileParserConfig.class, new MetafileParserConfig());
+ }
+ return config.getRenderWidth();
+ }
+
+ private BufferedImage canvas(Dimension2D size, int width) throws
IOException {
+ if (size == null || size.getWidth() <= 0 || size.getHeight() <= 0) {
+ throw new IOException("metafile without a usable size: " + size);
+ }
+ BufferedImage image = new BufferedImage(width, height(size.getWidth(),
size.getHeight(),
+ width), BufferedImage.TYPE_INT_RGB);
+ Graphics2D graphics = image.createGraphics();
+ try {
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
+ RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+ graphics.setRenderingHint(RenderingHints.KEY_RENDERING,
+ RenderingHints.VALUE_RENDER_QUALITY);
+ graphics.setColor(Color.WHITE);
+ graphics.fillRect(0, 0, image.getWidth(), image.getHeight());
+ } finally {
+ graphics.dispose();
+ }
+ return image;
+ }
+
+ /**
+ * The height that keeps the aspect ratio at the rendering's width.
+ *
+ * @throws IOException if the ratio asks for an image taller than
+ * {@link #MAX_HEIGHT}
+ */
+ private static int height(double sourceWidth, double sourceHeight, int
width)
+ throws IOException {
+ long height = Math.max(1, Math.round(sourceHeight * width /
sourceWidth));
+ if (height > MAX_HEIGHT) {
+ throw new IOException("metafile aspect ratio asks for a " + height
+ + " pixel high rendering at width " + width + ", the
maximum is " + MAX_HEIGHT);
+ }
+ return (int) height;
+ }
+
+ private BufferedImage scale(BufferedImage bitmap, int width) throws
IOException {
+ int height = height(bitmap.getWidth(), bitmap.getHeight(), width);
+ BufferedImage image = new BufferedImage(width, height,
BufferedImage.TYPE_INT_RGB);
+ Graphics2D graphics = image.createGraphics();
+ try {
+ graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+ RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+ graphics.setColor(Color.WHITE);
+ graphics.fillRect(0, 0, width, height);
+ graphics.drawImage(bitmap, 0, 0, width, height, null);
+ } finally {
+ graphics.dispose();
+ }
+ return image;
+ }
+
+ private Path write(BufferedImage image, int id) throws IOException {
+ Path tmpFile = Files.createTempFile("tika-metafile-rendering-",
+ "-" + id + "." + imageFormatName);
+ try (OutputStream os = Files.newOutputStream(tmpFile)) {
+ if (!ImageIO.write(image, imageFormatName, os)) {
+ throw new IOException("no ImageIO writer for " +
imageFormatName);
+ }
+ } catch (IOException | RuntimeException e) {
+ Files.deleteIfExists(tmpFile);
+ throw e;
+ }
+ return tmpFile;
+ }
+
+ public int getWidth() {
+ return width;
+ }
+
+ /**
+ * @param width the rendering's width in pixels, 1 to 10000; the height
+ * follows the image's aspect ratio. Default 800.
+ */
+ public void setWidth(int width) {
+ if (width < 1 || width > MAX_WIDTH) {
+ throw new IllegalArgumentException(
+ "width must be between 1 and " + MAX_WIDTH + ", got: " +
width);
+ }
+ this.width = width;
+ }
+
+ public String getImageFormatName() {
+ return imageFormatName;
+ }
+
+ /**
+ * @param imageFormatName an ImageIO format name, "png" (default) or "jpeg"
+ */
+ public void setImageFormatName(String imageFormatName) {
+ this.imageFormatName = imageFormatName;
+ }
+}
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/EMFParserTest.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/EMFParserTest.java
index e296c5ef2d..60bb779a21 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/EMFParserTest.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/EMFParserTest.java
@@ -17,15 +17,31 @@
package org.apache.tika.parser.microsoft;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.xml.sax.ContentHandler;
import org.apache.tika.TikaTest;
+import org.apache.tika.config.loader.TikaLoader;
+import org.apache.tika.extractor.EmbeddedDocumentExtractor;
+import org.apache.tika.io.TikaInputStream;
import org.apache.tika.metadata.HttpHeaders;
import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.Rendering;
import org.apache.tika.metadata.TikaCoreProperties;
+import org.apache.tika.parser.ParseContext;
+import org.apache.tika.parser.Parser;
+import org.apache.tika.sax.BodyContentHandler;
public class EMFParserTest extends TikaTest {
@@ -54,6 +70,151 @@ public class EMFParserTest extends TikaTest {
metadataList.get(0).get(TikaCoreProperties.TIKA_CONTENT));
}
+ /**
+ * Rendering is off by default: an EMF yields no embedded document of
+ * its own.
+ */
+ @Test
+ public void testNoRenderingByDefault() throws Exception {
+ List<Metadata> metadataList = getRecursiveMetadata("testEMF.emf");
+ assertEquals(1, metadataList.size());
+ }
+
+ @Test
+ public void testRenderingFromConfig() throws Exception {
+ Parser parser = TikaLoader
+ .load(getConfigPath(EMFParserTest.class,
"tika-config-emf-render.json"))
+ .loadParsers();
+ Metadata metadata = new Metadata();
+ metadata.set(TikaCoreProperties.RESOURCE_NAME_KEY, "testEMF.emf");
+ metadata.set(HttpHeaders.CONTENT_TYPE, "image/emf");
+ List<Metadata> metadataList =
+ getRecursiveMetadata("testEMF.emf", parser, metadata, new
ParseContext(), false);
+ assertEquals(2, metadataList.size());
+ assertRendering(metadataList.get(1), "testEMF.png",
+ TikaCoreProperties.EmbeddedResourceType.RENDERING);
+ }
+
+ /**
+ * The per-request form: the parser config is supplied through the
+ * ParseContext, as tika-server does for a multipart config part.
+ */
+ @Test
+ public void testRenderingFromParseContext() throws Exception {
+ ParseContext context = new ParseContext();
+ context.setJsonConfig("emf-parser", "{\"renderImage\": true}");
+ List<Metadata> metadataList = getRecursiveMetadata("testEMF.emf",
context);
+ assertEquals(2, metadataList.size());
+ assertRendering(metadataList.get(1), "testEMF.png",
+ TikaCoreProperties.EmbeddedResourceType.RENDERING);
+ assertEquals("1",
metadataList.get(1).get(TikaCoreProperties.EMBEDDED_DEPTH));
+ }
+
+ /**
+ * The docProps thumbnail of a Word document is an EMF; with rendering on
+ * its PNG rendering follows it, one level deeper.
+ */
+ @Test
+ public void testDocxThumbnailRendering() throws Exception {
+ ParseContext context = new ParseContext();
+ context.setJsonConfig("emf-parser", "{\"renderImage\": true,
\"renderWidth\": 200}");
+ List<Metadata> metadataList =
getRecursiveMetadata("testDOCX_Thumbnail.docx", context);
+ //the document, its thumbnail, the WMF picture inside the thumbnail's
+ //EMF and the thumbnail's rendering
+ assertEquals(4, metadataList.size());
+ Metadata thumbnail = byName(metadataList, "thumbnail.emf");
+ assertEquals("image/emf", thumbnail.get(HttpHeaders.CONTENT_TYPE));
+ assertEquals(TikaCoreProperties.EmbeddedResourceType.THUMBNAIL.name(),
+ thumbnail.get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE));
+ assertEquals("1", thumbnail.get(TikaCoreProperties.EMBEDDED_DEPTH));
+ Metadata rendering = byName(metadataList, "thumbnail.png");
+ assertRendering(rendering, "thumbnail.png",
+ TikaCoreProperties.EmbeddedResourceType.THUMBNAIL);
+ assertEquals("2", rendering.get(TikaCoreProperties.EMBEDDED_DEPTH));
+ assertEquals("/thumbnail.emf/thumbnail.png",
+ rendering.get(TikaCoreProperties.EMBEDDED_RESOURCE_PATH));
+ }
+
+ /**
+ * Restricted to THUMBNAIL embedded documents, the parser renders the
+ * document's thumbnail but not a picture that is merely embedded.
+ */
+ @Test
+ public void testRenderOnlyThumbnails() throws Exception {
+ ParseContext context = new ParseContext();
+ context.setJsonConfig("emf-parser",
+ "{\"renderImage\": true, \"renderOnlyEmbeddedResourceTypes\":
[\"THUMBNAIL\"]}");
+ List<Metadata> metadataList =
getRecursiveMetadata("testDOCX_Thumbnail.docx", context);
+ assertRendering(byName(metadataList, "thumbnail.png"), "thumbnail.png",
+ TikaCoreProperties.EmbeddedResourceType.THUMBNAIL);
+
+ //a bare EMF is the document itself, not a THUMBNAIL: no rendering
+ metadataList = getRecursiveMetadata("testEMF.emf", context);
+ assertEquals(1, metadataList.size());
+ }
+
+ /**
+ * The configured width reaches the renderer the parser was handed, which
+ * is the SPI-injected one in a default setup, not the parser's own
+ * instance.
+ */
+ @ParameterizedTest
+ @ValueSource(ints = {200, 400})
+ public void testRenderWidth(int width) throws Exception {
+ ParseContext context = new ParseContext();
+ context.setJsonConfig("emf-parser",
+ "{\"renderImage\": true, \"renderWidth\": " + width + "}");
+ List<byte[]> renderings = new ArrayList<>();
+ context.set(EmbeddedDocumentExtractor.class, collector(renderings));
+ try (InputStream is =
getResourceAsStream("/test-documents/testEMF.emf")) {
+ AUTO_DETECT_PARSER.parse(TikaInputStream.get(is), new
BodyContentHandler(-1),
+ new Metadata(), context);
+ }
+ assertEquals(1, renderings.size());
+ //the PNG header carries the width at offset 16
+ assertEquals(width, ByteBuffer.wrap(renderings.get(0), 16,
4).getInt());
+ }
+
+ private static EmbeddedDocumentExtractor collector(List<byte[]>
renderings) {
+ return new EmbeddedDocumentExtractor() {
+ @Override
+ public boolean shouldParseEmbedded(Metadata metadata, ParseContext
parseContext) {
+ return true;
+ }
+
+ @Override
+ public void parseEmbedded(TikaInputStream stream, ContentHandler
handler,
+ Metadata metadata, ParseContext
parseContext,
+ boolean outputHtml) throws IOException {
+ renderings.add(stream.readAllBytes());
+ }
+ };
+ }
+
+ private static Metadata byName(List<Metadata> metadataList, String name) {
+ for (Metadata m : metadataList) {
+ if (name.equals(m.get(TikaCoreProperties.RESOURCE_NAME_KEY))) {
+ return m;
+ }
+ }
+ throw new AssertionError("no embedded document named " + name);
+ }
+
+ /**
+ * There is no image parser on this module's test classpath, so the PNG
+ * is checked by its type, name and size rather than its dimensions.
+ * The rendering of a THUMBNAIL is a THUMBNAIL, any other a RENDERING.
+ */
+ private static void assertRendering(Metadata rendering, String name,
+
TikaCoreProperties.EmbeddedResourceType type) {
+ assertEquals("image/png", rendering.get(HttpHeaders.CONTENT_TYPE));
+ assertEquals(type.name(),
rendering.get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE));
+ assertEquals(name,
rendering.get(TikaCoreProperties.RESOURCE_NAME_KEY));
+ assertEquals("poi-metafile-renderer",
rendering.get(Rendering.RENDERED_BY));
+ assertTrue(Long.parseLong(rendering.get(HttpHeaders.CONTENT_LENGTH)) >
100);
+
assertNull(rendering.get(TikaCoreProperties.TIKA_META_EXCEPTION_EMBEDDED_STREAM));
+ }
+
@Test
public void testMissingCoords() throws Exception {
//TIKA-4432
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/OLE2ThumbnailTest.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/OLE2ThumbnailTest.java
new file mode 100644
index 0000000000..c91ff74dcc
--- /dev/null
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/OLE2ThumbnailTest.java
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tika.parser.microsoft;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import org.apache.tika.TikaTest;
+import org.apache.tika.metadata.HttpHeaders;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.TikaCoreProperties;
+import org.apache.tika.parser.ParseContext;
+
+/**
+ * The thumbnail that Office stores in the SummaryInformation of the OLE2
+ * formats (PIDSI_THUMBNAIL, a WMF) is emitted as a THUMBNAIL embedded
+ * document, as the docProps thumbnail of the OOXML formats is.
+ */
+public class OLE2ThumbnailTest extends TikaTest {
+
+ @Test
+ public void testPptThumbnail() throws Exception {
+ List<Metadata> metadataList =
getRecursiveMetadata("testPPT_various.ppt");
+ Metadata thumbnail = byTypeAndContentType(metadataList,
+ TikaCoreProperties.EmbeddedResourceType.THUMBNAIL,
"image/wmf");
+ assertNotNull(thumbnail);
+ assertEquals("image/wmf", thumbnail.get(HttpHeaders.CONTENT_TYPE));
+ assertEquals("thumbnail.wmf",
thumbnail.get(TikaCoreProperties.RESOURCE_NAME_KEY));
+ assertEquals("1", thumbnail.get(TikaCoreProperties.EMBEDDED_DEPTH));
+ //exactly one document thumbnail
+ assertEquals(1, count(metadataList,
TikaCoreProperties.EmbeddedResourceType.THUMBNAIL));
+ }
+
+ @Test
+ public void testPptThumbnailRendering() throws Exception {
+ ParseContext context = new ParseContext();
+ context.setJsonConfig("wmf-parser", "{\"renderImage\": true,
\"renderWidth\": 400}");
+ List<Metadata> metadataList =
getRecursiveMetadata("testPPT_various.ppt", context);
+ //the rendering of the thumbnail is a THUMBNAIL as well
+ Metadata rendering = byTypeAndContentType(metadataList,
+ TikaCoreProperties.EmbeddedResourceType.THUMBNAIL,
"image/png");
+ assertNotNull(rendering);
+ assertEquals("thumbnail.png",
rendering.get(TikaCoreProperties.RESOURCE_NAME_KEY));
+ assertEquals("2", rendering.get(TikaCoreProperties.EMBEDDED_DEPTH));
+ assertTrue(Long.parseLong(rendering.get(HttpHeaders.CONTENT_LENGTH)) >
100);
+ }
+
+ /**
+ * Word wraps its thumbnail bitmap in a WMF with a window extent and a
+ * single dibStretchBlt record, which has no bounds POI can compute; the
+ * renderer falls back to the bitmap.
+ */
+ @Test
+ public void testDocThumbnailRendering() throws Exception {
+ ParseContext context = new ParseContext();
+ context.setJsonConfig("wmf-parser", "{\"renderImage\": true}");
+ List<Metadata> metadataList =
getRecursiveMetadata("testControlCharacters.doc", context);
+ Metadata thumbnail = byTypeAndContentType(metadataList,
+ TikaCoreProperties.EmbeddedResourceType.THUMBNAIL,
"image/wmf");
+ assertNotNull(thumbnail);
+ assertEquals("image/wmf", thumbnail.get(HttpHeaders.CONTENT_TYPE));
+ Metadata rendering = byTypeAndContentType(metadataList,
+ TikaCoreProperties.EmbeddedResourceType.THUMBNAIL,
"image/png");
+ assertNotNull(rendering);
+
assertNull(thumbnail.get(TikaCoreProperties.TIKA_META_EXCEPTION_EMBEDDED_STREAM));
+ }
+
+ /**
+ * A thumbnail that is not a metafile picture is left alone.
+ */
+ @Test
+ public void testUnusableThumbnailIsSkipped() throws Exception {
+ List<Metadata> metadataList =
getRecursiveMetadata("testEXCEL_embeddedPDF_mac.xls");
+ assertEquals(0, count(metadataList,
TikaCoreProperties.EmbeddedResourceType.THUMBNAIL));
+ }
+
+ /**
+ * The thumbnail can be switched off for callers that do not want the
+ * extra embedded document.
+ */
+ @Test
+ public void testThumbnailCanBeSwitchedOff() throws Exception {
+ OfficeParserConfig config = new OfficeParserConfig();
+ config.setExtractThumbnail(false);
+ ParseContext context = new ParseContext();
+ context.set(OfficeParserConfig.class, config);
+ List<Metadata> metadataList =
getRecursiveMetadata("testPPT_various.ppt", context);
+ assertEquals(0, count(metadataList,
TikaCoreProperties.EmbeddedResourceType.THUMBNAIL));
+ }
+
+ /**
+ * A misspelt resource type would silently disable rendering.
+ */
+ @Test
+ public void testRenderOnlyTypesAreValidated() {
+ MetafileParserConfig config = new MetafileParserConfig();
+ assertThrows(IllegalArgumentException.class,
+ () -> config.setRenderOnlyEmbeddedResourceTypes(
+ Collections.singleton("THUMBNAILS")));
+ }
+
+ private static Metadata byTypeAndContentType(List<Metadata> metadataList,
+
TikaCoreProperties.EmbeddedResourceType type,
+ String contentType) {
+ for (Metadata m : metadataList) {
+ if
(type.name().equals(m.get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE))
+ && contentType.equals(m.get(HttpHeaders.CONTENT_TYPE))) {
+ return m;
+ }
+ }
+ return null;
+ }
+
+ private static int count(List<Metadata> metadataList,
+ TikaCoreProperties.EmbeddedResourceType type) {
+ int n = 0;
+ for (Metadata m : metadataList) {
+ if
(type.name().equals(m.get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE))) {
+ n++;
+ }
+ }
+ return n;
+ }
+}
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/PowerPointParserTest.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/PowerPointParserTest.java
index d2ec1f4718..87e6c4b205 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/PowerPointParserTest.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/PowerPointParserTest.java
@@ -331,7 +331,8 @@ public class PowerPointParserTest extends TikaTest {
@Test
public void testGroups() throws Exception {
List<Metadata> metadataList =
getRecursiveMetadata("testPPT_groups.ppt");
- assertEquals(3, metadataList.size());
+ //two pictures and the SummaryInformation thumbnail
+ assertEquals(4, metadataList.size());
String content =
metadataList.get(0).get(TikaCoreProperties.TIKA_CONTENT);
//this tests that we're ignoring text shapes at depth=0
//i.e. POI has already included them in the slide's getTextParagraphs()
@@ -379,7 +380,8 @@ public class PowerPointParserTest extends TikaTest {
@Test
public void testEmbeddedXLSInOLEObject() throws Exception {
List<Metadata> metadataList =
getRecursiveMetadata("testPPT_oleWorkbook.ppt");
- assertEquals(3, metadataList.size());
+ //the workbook, its picture and the SummaryInformation thumbnail
+ assertEquals(4, metadataList.size());
Metadata xlsx = metadataList.get(1);
assertContains("<h1>Sheet1</h1>",
xlsx.get(TikaCoreProperties.TIKA_CONTENT));
assertContains("<td>1</td>",
xlsx.get(TikaCoreProperties.TIKA_CONTENT));
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/WMFParserTest.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/WMFParserTest.java
index 3317c0bae0..5aca34707c 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/WMFParserTest.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/WMFParserTest.java
@@ -17,6 +17,8 @@
package org.apache.tika.parser.microsoft;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.List;
@@ -25,7 +27,9 @@ import org.junit.jupiter.api.Test;
import org.apache.tika.TikaTest;
import org.apache.tika.metadata.HttpHeaders;
import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.Rendering;
import org.apache.tika.metadata.TikaCoreProperties;
+import org.apache.tika.parser.ParseContext;
public class WMFParserTest extends TikaTest {
@@ -40,6 +44,58 @@ public class WMFParserTest extends TikaTest {
testTextExtraction("testWMF_charset.wmf", 0, "普林斯");
}
+ /**
+ * Rendering is off by default; with "wmf-parser": {"renderImage": true}
+ * the rendering follows the image as a RENDERING embedded document.
+ */
+ @Test
+ public void testRendering() throws Exception {
+ assertEquals(1, getRecursiveMetadata("testWMF.wmf").size());
+
+ ParseContext context = new ParseContext();
+ context.setJsonConfig("wmf-parser", "{\"renderImage\": true,
\"renderWidth\": 300}");
+ List<Metadata> metadataList = getRecursiveMetadata("testWMF.wmf",
context);
+ assertEquals(2, metadataList.size());
+ Metadata rendering = metadataList.get(1);
+ assertEquals("image/png", rendering.get(HttpHeaders.CONTENT_TYPE));
+ assertEquals(TikaCoreProperties.EmbeddedResourceType.RENDERING.name(),
+ rendering.get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE));
+ assertEquals("testWMF.png",
rendering.get(TikaCoreProperties.RESOURCE_NAME_KEY));
+ assertEquals("poi-metafile-renderer",
rendering.get(Rendering.RENDERED_BY));
+ assertTrue(Long.parseLong(rendering.get(HttpHeaders.CONTENT_LENGTH)) >
100);
+ }
+
+ /**
+ * The docProps thumbnail of this workbook is a WMF; with rendering on its
+ * PNG rendering follows it, one level deeper, as a THUMBNAIL as well.
+ */
+ @Test
+ public void testXlsxThumbnailRendering() throws Exception {
+ ParseContext context = new ParseContext();
+ context.setJsonConfig("wmf-parser", "{\"renderImage\": true}");
+ List<Metadata> metadataList =
getRecursiveMetadata("testXLSX_Thumbnail.xlsx", context);
+ Metadata thumbnail = null;
+ Metadata rendering = null;
+ for (Metadata m : metadataList) {
+ if (!TikaCoreProperties.EmbeddedResourceType.THUMBNAIL.name()
+ .equals(m.get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE)))
{
+ continue;
+ }
+ if ("image/wmf".equals(m.get(HttpHeaders.CONTENT_TYPE))) {
+ thumbnail = m;
+ } else {
+ rendering = m;
+ }
+ }
+ assertNotNull(thumbnail);
+ assertEquals("image/wmf", thumbnail.get(HttpHeaders.CONTENT_TYPE));
+ assertEquals("1", thumbnail.get(TikaCoreProperties.EMBEDDED_DEPTH));
+ assertNotNull(rendering);
+ assertEquals("image/png", rendering.get(HttpHeaders.CONTENT_TYPE));
+ assertEquals("2", rendering.get(TikaCoreProperties.EMBEDDED_DEPTH));
+ assertEquals("thumbnail.png",
rendering.get(TikaCoreProperties.RESOURCE_NAME_KEY));
+ }
+
private void testTextExtraction(String fileName, int metaDataItemIndex,
String expectedText)
throws Exception {
List<Metadata> metadataList = getRecursiveMetadata(fileName);
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/resources/configs/tika-config-emf-render.json
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/resources/configs/tika-config-emf-render.json
new file mode 100644
index 0000000000..a5f9941468
--- /dev/null
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/resources/configs/tika-config-emf-render.json
@@ -0,0 +1,11 @@
+{
+ "parsers": [
+ "default-parser",
+ {
+ "emf-parser": {
+ "renderImage": true,
+ "renderWidth": 400
+ }
+ }
+ ]
+}