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#3095dschmidt wants to merge 21 commits into
Conversation
…mation 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.
5cafd74 to
e966adc
Compare
…o 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.
# Conflicts: # CHANGES.txt
# Conflicts: # CHANGES.txt
There was a problem hiding this comment.
Pull request overview
This PR adds cross-platform rasterization and extraction of Office document preview thumbnails by leveraging Apache POI’s EMF/WMF drawing support, and wires this into Tika’s embedded-document model (THUMBNAIL + optional PNG RENDERING) for both OOXML docProps thumbnails and OLE2 SummaryInformation thumbnails.
Changes:
- Introduces
POIMetafileRendererto rasterizeimage/emfandimage/wmfinto PNG at a configurable width. - Extends
EMFParser/WMFParserto optionally emit a rendered PNG as an embedded document via a sharedMetafileParserConfig+MetafileRenderinghelper. - Updates
OfficeParser/SummaryExtractorand tests to emit OLE2 SummaryInformation thumbnails asTHUMBNAILembedded documents, and adjusts integration test expectations.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
tika-parsers/.../src/test/resources/configs/tika-config-emf-render.json |
Adds JSON config used by tests to enable EMF rendering. |
tika-parsers/.../WMFParserTest.java |
Adds unit tests for WMF rendering and thumbnail rendering behavior. |
tika-parsers/.../PowerPointParserTest.java |
Updates expected embedded document counts to include OLE2 thumbnails. |
tika-parsers/.../OLE2ThumbnailTest.java |
New tests verifying OLE2 SummaryInformation thumbnail emission and rendering. |
tika-parsers/.../EMFParserTest.java |
Adds tests for EMF rendering defaults, config-based rendering, and thumbnail rendering constraints. |
tika-parsers/.../POIMetafileRenderer.java |
New renderer implementation using POI HEMF/HWMF to produce PNG renderings. |
tika-parsers/.../WMFParser.java |
Adds rendering support via RenderingParser and parse-context JSON config. |
tika-parsers/.../SummaryExtractor.java |
Extracts WMF thumbnails from OLE2 SummaryInformation via POI HPSF Thumbnail. |
tika-parsers/.../OfficeParser.java |
Emits SummaryInformation thumbnail as a THUMBNAIL embedded document (thumbnail.wmf). |
tika-parsers/.../MetafileRendering.java |
New helper to render metafiles and emit the results as embedded documents. |
tika-parsers/.../MetafileParserConfig.java |
New config bean controlling whether/how metafiles are rendered and filtered by embedded type. |
tika-parsers-standard-integration-tests/.../POIContainerExtractionTest.java |
Adjusts integration test expectations for added thumbnail embedded docs. |
CHANGES.txt |
Adds release note entry for the new thumbnail rendering/extraction behavior. |
Suppressed comments (1)
tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/renderer/microsoft/POIMetafileRenderer.java:258
- setImageFormatName accepts null/blank values, which will later produce invalid content types ("image/null") and likely failures in ImageIO.write. This should validate and normalize the format name.
public void setImageFormatName(String imageFormatName) {
this.imageFormatName = imageFormatName;
}
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| /** | ||
| * This parser offers a very rough capability to extract text if there | ||
| * is text stored in the WMF files. | ||
| */ |
| if (results == null) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
It is recorded on the metafile's metadata now.
| } catch (SecurityException e) { | ||
| throw e; | ||
| } catch (Exception e) { | ||
| results.add(new RenderResult(RenderResult.STATUS.EXCEPTION, id, null, | ||
| renderingMetadata)); | ||
| } |
There was a problem hiding this comment.
Recorded, like PDFBoxRenderer does.
| "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 (TIKA-4855). |
| * 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... | ||
| */ | ||
| /** | ||
| * Extracts the text of an EMF image and its embedded WMF and multi-format |
…ded on the metafile's metadata and in the renderer's result, CHANGES wrapped
…ting; record failures on the metafile's metadata
| Metadata embeddedMetadata = Metadata.newInstance(context); | ||
| embeddedMetadata.set(TikaCoreProperties.RESOURCE_NAME_KEY, "thumbnail.wmf"); | ||
| embeddedMetadata.set(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE, | ||
| TikaCoreProperties.EmbeddedResourceType.THUMBNAIL.toString()); |
There was a problem hiding this comment.
Changed to name().
| for (RenderResult result : results.getResults()) { | ||
| if (result.getStatus() != RenderResult.STATUS.SUCCESS) { | ||
| EmbeddedDocumentUtil.recordException( | ||
| new TikaException("metafile rendering failed"), metadata, context); | ||
| continue; | ||
| } |
There was a problem hiding this comment.
The renderer's recorded warnings are copied to the metafile's metadata now; the generic exception only remains as a fallback when the renderer recorded nothing.
| 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; | ||
| } |
There was a problem hiding this comment.
It is tied in: RenderResult registers the path with its TemporaryResources (deleted on close), RenderResults.add chains the result into its own TemporaryResources, and MetafileRendering closes the RenderResults with try-with-resources. Same lifecycle as PDFBoxRenderer, which also uses Files.createTempFile.
…stics to the metafile on failure
# Conflicts: # CHANGES.txt
# Conflicts: # CHANGES.txt
# Conflicts: # CHANGES.txt
# Conflicts: # CHANGES.txt
# Conflicts: # CHANGES.txt
|
From my 🤖 ... I think most are useful. One or two are puntable. Let me know what you think. Thank you for iterating. |
…, bounded rendering height, renderer reads the metafile itself, gate before rendering, validated resource types, thumbnail switch, shared parser base
|
Thanks, that list was worth it. All nine are in, one I would like to keep and explain. renderWidth was dead, confirmed before fixing: requesting 200 or 400 rendered at 800, because the SPI-injected renderer wins over the config-built one. The renderer now reads the metafile parser config from the ParseContext, the way PDFBoxRenderer reads dpi and imageType from PDFParserConfig, and a parameterized test asserts the width in the PNG header. That gap is why no test caught it. Unbounded height: capped at 10000 like the width, with an IOException instead of an OutOfMemoryError; both paths share one height helper now. Bare RuntimeException in draw(HwmfPicture): narrowed to IllegalStateException, which is what POI throws for Word's bitmap-in-WMF thumbnails ("invalid wmf file - window records are incomplete."), verified against testControlCharacters.doc. Injected renderers got an empty stream: the parser spools when it is going to render, so the renderer is handed the metafile itself; the parsed picture stays attached as the open container for the fast path. Rendering before shouldParseEmbedded: gated now, with provisional metadata (name, image/png, resource type) before the raster work. renderingName: FilenameUtils.getName. renderOnlyEmbeddedResourceTypes: validated against EmbeddedResourceType, a typo throws instead of silently disabling rendering. OLE2 thumbnail: OfficeParserConfig.extractThumbnail, default true to match the OOXML parsers, which emit docProps/thumbnail unconditionally. Duplicate wiring: AbstractMetafileParser holds the config and renderer plumbing for both parsers. The one I kept: the rendering of a THUMBNAIL is typed THUMBNAIL rather than RENDERING. It is deliberate and the reason this PR chain exists: a client should find the preview picture of any file the same way, and for an Office document the stored thumbnail is an EMF/WMF whose only displayable form is that rendering. The two THUMBNAIL entries are the vector original and its raster rendering, so the client rule is "the first raster THUMBNAIL". If you would rather keep the type strictly about provenance, I will change it back and pair the rendering with its parent by embedded path instead. |
# Conflicts: # CHANGES.txt
Office documents carry their preview image as a vector metafile: the OOXML
docProps/thumbnail.emfof Word (an EMF wrapping a WMF) andthumbnail.wmfof Excel, and the SummaryInformation thumbnail (PIDSI_THUMBNAIL,CF_METAFILEPICT, a WMF) of the OLE2 formats. Neither is usable as a preview outside Windows, and outside the JVM there is no maintained EMF/WMF rasterizer, while POI's HEMF/HWMF can draw both.Following the PDF parser's rendering design:
POIMetafileRenderer(poi-metafile-renderer) is aRendererforimage/emfandimage/wmf: the POI picture drawn onto a white canvas, a PNG of a configurable width. The WMF thumbnails Word writes consist of asetWindowExtand adibStretchBltrecord only, for which POI cannot compute bounds; those are rendered from the record's bitmap.EMFParserandWMFParserimplementRenderingParserand, with"emf-parser"/"wmf-parser": {"renderImage": true, "renderWidth": 800}(off by default), emit the rendering as aRENDERINGembedded document named after the image. An injected renderer is used when it supports the type, the POI one otherwise. The shared config and emission live inMetafileParserConfigandMetafileRendering.OfficeParseremits the SummaryInformation thumbnail as aTHUMBNAILembedded document (image/wmf,thumbnail.wmf), consistent with the docProps thumbnail of the OOXML parsers. Two PowerPoint tests count one embedded document more because of it.Verified against tika-server with
/unpack: docx, xlsx, doc, xls and ppt files that carry a thumbnail yield it at depth 1 and its PNG rendering at depth 2.https://issues.apache.org/jira/browse/TIKA-4855