[
https://issues.apache.org/jira/browse/TIKA-4855?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18109832#comment-18109832
]
ASF GitHub Bot commented on TIKA-4855:
--------------------------------------
dschmidt commented on code in PR #3095:
URL: https://github.com/apache/tika/pull/3095#discussion_r3893404828
##########
tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/OfficeParser.java:
##########
@@ -306,6 +307,32 @@ protected void parse(DirectoryNode root, ParseContext
context, Metadata metadata
// is extracted, which happened above
break;
}
+ 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.
+ */
+ 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.toString());
Review Comment:
Changed to name().
##########
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,142 @@
+/*
+ * 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.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,
+ 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);
+ try (TikaInputStream pictureStream = TikaInputStream.get(new byte[0]);
+ RenderResults results = render(renderer, pictureStream, picture,
renderMetadata,
+ metadata, context)) {
+ if (results == null) {
+ return;
+ }
+ for (RenderResult result : results.getResults()) {
+ if (result.getStatus() != RenderResult.STATUS.SUCCESS) {
+ EmbeddedDocumentUtil.recordException(
+ new TikaException("metafile rendering failed"),
metadata, context);
+ continue;
+ }
Review 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.
##########
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,262 @@
+/*
+ * 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.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.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;
+
+ 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();
+ 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) : draw((HwmfPicture)
picture);
+ 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) throws IOException {
+ Dimension2D size = picture.getSize();
+ BufferedImage image = canvas(size);
+ 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) throws IOException {
+ Dimension2D size;
+ try {
+ size = picture.getSize();
+ } catch (RuntimeException e) {
+ //no usable window records: a bitmap wrapped in a metafile
+ BufferedImage bitmap = firstBitmap(picture);
+ if (bitmap == null) {
+ throw new IOException("WMF without bounds and without a
bitmap", e);
+ }
+ return scale(bitmap);
+ }
+ BufferedImage image = canvas(size);
+ 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;
+ }
+
+ private BufferedImage canvas(Dimension2D size) throws IOException {
+ if (size == null || size.getWidth() <= 0 || size.getHeight() <= 0) {
+ throw new IOException("metafile without a usable size: " + size);
+ }
+ int height = (int) Math.max(1, Math.round(size.getHeight() * width /
size.getWidth()));
+ BufferedImage image = new BufferedImage(width, height,
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, width, height);
+ } finally {
+ graphics.dispose();
+ }
+ return image;
+ }
+
+ private BufferedImage scale(BufferedImage bitmap) {
+ int height = (int) Math.max(1,
+ Math.round((double) bitmap.getHeight() * width /
bitmap.getWidth()));
+ 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;
+ }
Review 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.
> Render EMF/WMF images to raster previews and emit the OLE2 SummaryInformation
> thumbnail
> ---------------------------------------------------------------------------------------
>
> Key: TIKA-4855
> URL: https://issues.apache.org/jira/browse/TIKA-4855
> Project: Tika
> Issue Type: Improvement
> Reporter: Dominik Schmidt
> Priority: Major
>
> Office documents carry their preview image as a vector metafile: the OOXML
> docProps/thumbnail.emf of Word (an EMF wrapping a WMF) and thumbnail.wmf of
> Excel, and the SummaryInformation thumbnail (PIDSI_THUMBNAIL,
> CF_METAFILEPICT, a WMF) of the OLE2 formats, which PowerPoint writes almost
> always and Word/Excel on request. Neither is usable as a preview outside
> Windows, and outside the JVM there is no maintained EMF/WMF rasterizer. POI's
> HEMF/HWMF can draw both and Tika already bundles them.
> Proposal, following the PDF parser's rendering design:
> 1. A Renderer for image/emf and image/wmf (POI HemfPicture/HwmfPicture drawn
> onto a white canvas, a PNG of a configurable width, the height following the
> aspect ratio). The WMF thumbnails Word writes consist of a setWindowExt and a
> dibStretchBlt record only, for which POI cannot compute bounds; for those the
> renderer emits the record's bitmap.
> 2. EMFParser and WMFParser implement RenderingParser and, with "emf-parser" /
> "wmf-parser": {"renderImage": true, "renderWidth": 800} (off by default),
> emit the rendering as a RENDERING embedded document named after the image,
> the way the PDF parser emits page renderings. An injected renderer is used
> when it supports the type, the POI one otherwise.
> 3. OfficeParser emits the SummaryInformation thumbnail of the OLE2 formats as
> a THUMBNAIL embedded document (image/wmf, named thumbnail.wmf), consistent
> with the docProps thumbnail of the OOXML parsers. Existing tests that count
> the embedded documents of .ppt fixtures gain one.
> With that, a client that asks for THUMBNAIL and RENDERING embedded documents
> gets a raster preview for docx, xlsx, doc, xls and ppt files that carry a
> thumbnail, with the same request it uses for every other format. Verified
> against tika-server with /unpack: each of these yields the thumbnail at depth
> 1 and its PNG rendering at depth 2.
> Note for clients: with rendering enabled every embedded metafile is rendered,
> e.g. the picture of an OLE object, so the preview is the RENDERING under the
> THUMBNAIL, not the first RENDERING.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)