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
+      }
+    }
+  ]
+}

Reply via email to