From 846466957f70686ac409900723e67dd80103d8bb Mon Sep 17 00:00:00 2001 From: Thomas Gutmann Date: Tue, 8 Aug 2023 21:57:12 +0200 Subject: [PATCH] enhancement to improve the svg-image-handling and the usage of data-url (#1386) --- .../model/schematic/ImageHandleAdapter.java | 3 +- .../report/designer/util/ImageManager.java | 152 ++++++++++++++---- .../emitter/html/HTMLReportEmitter.java | 43 ++++- .../emitter/wpml/AbstractEmitterImpl.java | 30 +++- .../report/engine/emitter/EmitterUtil.java | 50 +++++- .../report/engine/emitter/ImageReader.java | 33 +++- .../presentation/LocalizedContentVisitor.java | 5 +- .../birt/report/engine/util/SvgFile.java | 50 +++++- .../excel/handlers/CellContentHandler.java | 118 +++++++++++--- 9 files changed, 406 insertions(+), 78 deletions(-) diff --git a/UI/org.eclipse.birt.report.designer.core/src/org/eclipse/birt/report/designer/core/model/schematic/ImageHandleAdapter.java b/UI/org.eclipse.birt.report.designer.core/src/org/eclipse/birt/report/designer/core/model/schematic/ImageHandleAdapter.java index 663b9e66b5..3e639f84a9 100644 --- a/UI/org.eclipse.birt.report.designer.core/src/org/eclipse/birt/report/designer/core/model/schematic/ImageHandleAdapter.java +++ b/UI/org.eclipse.birt.report.designer.core/src/org/eclipse/birt/report/designer/core/model/schematic/ImageHandleAdapter.java @@ -67,9 +67,8 @@ public Image getImage() { } else if (DesignChoiceConstants.IMAGE_REF_TYPE_FILE.equalsIgnoreCase(imageSource)) { if (URIUtil.isValidResourcePath(url)) { return ImageManager.getInstance().getImage(imageHandel.getModuleHandle(), URIUtil.getLocalPath(url)); - } else { - return ImageManager.getInstance().getImage(imageHandel.getModuleHandle(), url); } + return ImageManager.getInstance().getImage(imageHandel.getModuleHandle(), url); } else if (DesignChoiceConstants.IMAGE_REF_TYPE_URL.equalsIgnoreCase(imageSource)) { // bugzilla 245641 diff --git a/UI/org.eclipse.birt.report.designer.core/src/org/eclipse/birt/report/designer/util/ImageManager.java b/UI/org.eclipse.birt.report.designer.core/src/org/eclipse/birt/report/designer/util/ImageManager.java index bb513a4a1e..358d7816a5 100644 --- a/UI/org.eclipse.birt.report.designer.core/src/org/eclipse/birt/report/designer/util/ImageManager.java +++ b/UI/org.eclipse.birt.report.designer.core/src/org/eclipse/birt/report/designer/util/ImageManager.java @@ -20,10 +20,13 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.io.StringReader; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Base64; import java.util.Base64.Decoder; import java.util.List; @@ -31,6 +34,7 @@ import org.apache.batik.transcoder.TranscoderInput; import org.apache.batik.transcoder.TranscoderOutput; import org.apache.batik.transcoder.image.JPEGTranscoder; +import org.apache.batik.transcoder.image.PNGTranscoder; import org.eclipse.birt.report.designer.core.CorePlugin; import org.eclipse.birt.report.designer.core.DesignerConstants; import org.eclipse.birt.report.designer.core.model.SessionHandleAdapter; @@ -51,7 +55,9 @@ public class ImageManager { private static final String EMBEDDED_SUFFIX = ".Embedded."; //$NON-NLS-1$ - private static final String DATA_PROTOCOL = "data:"; + private static final String URL_IMAGE_TYPE_SVG = "image/svg+xml"; + private static final String URL_PROTOCOL_TYPE_DATA = "data:"; + private static final String URL_PROTOCOL_TYPE_DATA_BASE = ";base64,"; private static final ImageManager instance = new ImageManager(); @@ -95,7 +101,7 @@ public Image getImage(ModuleHandle handle, String uri, boolean refresh) { URL url = null; try { - if (uri.contains(DATA_PROTOCOL)) { + if (uri.contains(URL_PROTOCOL_TYPE_DATA)) { image = getEmbeddedImageDataURL(uri, refresh); } else { url = generateURL(handle, uri); @@ -175,15 +181,16 @@ public Image getEmbeddedImage(ModuleHandle handle, String name) { InputStream in = null; try { - if (key.toLowerCase().endsWith(".svg")) //$NON-NLS-1$ + if (key.toLowerCase().endsWith(".svg") //$NON-NLS-1$ + || embeddedImage.getType(handle.getModule()).equalsIgnoreCase("image/svg+xml")) { // convert svg image to JPEG image bytes - JPEGTranscoder transcoder = new JPEGTranscoder(); + JPEGTranscoder jpegTranscoder = new JPEGTranscoder(); // set the transcoding hints - transcoder.addTranscodingHint(JPEGTranscoder.KEY_QUALITY, .8f); + jpegTranscoder.addTranscodingHint(JPEGTranscoder.KEY_QUALITY, .8f); // create the transcoder input - TranscoderInput input = new TranscoderInput( - new ByteArrayInputStream(embeddedImage.getData(handle.getModule()))); + ByteArrayInputStream reader = new ByteArrayInputStream(embeddedImage.getData(handle.getModule())); + TranscoderInput input = new TranscoderInput(reader); // For embedded image we have't a file URI, so set handle // filename as URI. // See Bugzilla Bug 167395 @@ -192,11 +199,22 @@ public Image getEmbeddedImage(ModuleHandle handle, String name) { ByteArrayOutputStream ostream = new ByteArrayOutputStream(); TranscoderOutput output = new TranscoderOutput(ostream); try { - transcoder.transcode(input, output); - } catch (TranscoderException e) { + // issue with batik JPEGTranscoder (since version 1.8) + // JPEGTranscoder is not longer part of apache-xmlgraphics + jpegTranscoder.transcode(input, output); + } catch (TranscoderException excJpg) { + try { + // fallback of preview image converting from svg to png + reader = new ByteArrayInputStream(embeddedImage.getData(handle.getModule())); + input = new TranscoderInput(reader); + PNGTranscoder pngConverter = new PNGTranscoder(); + pngConverter.transcode(input, output); + } catch (TranscoderException excPng) { + } } // flush the stream ostream.flush(); + ostream.close(); // use the outputstream as Image input stream. in = new ByteArrayInputStream(ostream.toByteArray()); } else { @@ -205,10 +223,6 @@ public Image getEmbeddedImage(ModuleHandle handle, String name) { ImageData[] datas = new ImageLoader().load(in); if (datas != null && datas.length != 0) { ImageData cur; - // if (datas.length == 1) - // { - // cur = datas[0]; - // } int index = 0; for (int i = 0; i < datas.length; i++) { ImageData temp = datas[i]; @@ -262,31 +276,14 @@ public Image getEmbeddedImageDataURL(String url, boolean refresh) throws IOExcep removeCachedImage(key); } InputStream in = null; - String[] imageDataArray = key.split(";base64,"); + String[] imageDataArray = key.split(URL_PROTOCOL_TYPE_DATA_BASE); String imageDataBase64 = imageDataArray[1]; Decoder decoder = java.util.Base64.getDecoder(); try { - if (url.toLowerCase().contains("svg+xml")) //$NON-NLS-1$ + if (url.toLowerCase().contains(URL_PROTOCOL_TYPE_DATA_BASE)) // $NON-NLS-1$ { - // convert svg image to JPEG image bytes - JPEGTranscoder transcoder = new JPEGTranscoder(); - // set the transcoding hints - transcoder.addTranscodingHint(JPEGTranscoder.KEY_QUALITY, .8f); - // create the transcoder input - String svgURI = url; - TranscoderInput input = new TranscoderInput(svgURI); - // create the transcoder output - ByteArrayOutputStream ostream = new ByteArrayOutputStream(); - TranscoderOutput output = new TranscoderOutput(ostream); - try { - transcoder.transcode(input, output); - } catch (TranscoderException e) { - } - // flush the stream - ostream.flush(); - // use the outputstream as Image input stream. - in = new ByteArrayInputStream(ostream.toByteArray()); + in = convertSvgToRasterImage(url.toString()); } else { in = new ByteArrayInputStream(decoder.decode(imageDataBase64)); } @@ -427,6 +424,16 @@ public Image loadImage(URL url, boolean reload) throws IOException { try { transcoder.transcode(input, output); } catch (TranscoderException e) { + + PNGTranscoder pngTranscoder = new PNGTranscoder(); + input = new TranscoderInput(svgURI); + // create the transcoder output + ostream = new ByteArrayOutputStream(); + output = new TranscoderOutput(ostream); + try { + pngTranscoder.transcode(input, output); + } catch (TranscoderException eJpeg) { + } } // flush the stream ostream.flush(); @@ -463,6 +470,30 @@ public Image loadImage(URL url, boolean reload) throws IOException { return image; } + /** + * Converter to create raster image based on svg image + */ + private InputStream convertSvgToRasterImage(String imageSvg) throws IOException { + + // convert svg image to JPEG image bytes + PNGTranscoder pngTranscoder = new PNGTranscoder(); + // create the transcoder input + StringReader reader = new StringReader(imageSvg); + TranscoderInput input = new TranscoderInput(reader); + // create the transcoder output + ByteArrayOutputStream ostream = new ByteArrayOutputStream(); + TranscoderOutput output = new TranscoderOutput(ostream); + try { + pngTranscoder.transcode(input, output); + } catch (TranscoderException eJpeg) { + } + // flush the stream + ostream.flush(); + ostream.close(); + // use the outputstream as Image input stream. + return new ByteArrayInputStream(ostream.toByteArray()); + } + private ImageRegistry getImageRegistry() { return CorePlugin.getDefault().getImageRegistry(); } @@ -568,10 +599,61 @@ public URL createURIURL(String uri) { */ // bugzilla 245641 public Image getURIImage(ModuleHandle moduleHandel, String uri) { - URL url = createURIURL(uri); Image image = null; + URL url = null; + String uriParts[] = null; try { - image = getImageFromURL(url, false); + // data protocol raster image + if (uri.startsWith(URL_PROTOCOL_TYPE_DATA) && uri.contains(URL_PROTOCOL_TYPE_DATA_BASE) + && !uri.contains(URL_IMAGE_TYPE_SVG)) { + uriParts = uri.split(URL_PROTOCOL_TYPE_DATA_BASE); + if (uriParts.length >= 2) { + String encodedImg = uriParts[1]; + InputStream in = new ByteArrayInputStream( + Base64.getDecoder().decode(encodedImg.getBytes(StandardCharsets.UTF_8))); + ImageData[] datas = new ImageLoader().load(in); + if (datas != null && datas.length != 0) { + image = new Image(null, datas[0]); + in.close(); + } + } + // data protocol svg image + } else if (uri.startsWith(URL_PROTOCOL_TYPE_DATA) && uri.contains(URL_IMAGE_TYPE_SVG)) { + + String svgSplitter = "svg\\+xml,"; + if (uri.contains("svg+xml;utf8,")) { + svgSplitter = "svg\\+xml;utf8,"; + } else if (uri.contains("svg+xml;base64,")) { + svgSplitter = "svg\\+xml;base64,"; + } + uriParts = uri.split(svgSplitter); + if (uriParts.length >= 2) { + String encodedImg = uriParts[1]; + String decodedImg = encodedImg; + if (uri.contains(URL_PROTOCOL_TYPE_DATA_BASE)) { // "svg+xml;base64," + decodedImg = new String( + Base64.getDecoder().decode(encodedImg.getBytes(StandardCharsets.UTF_8))); + } + decodedImg = java.net.URLDecoder.decode(decodedImg, StandardCharsets.UTF_8); + InputStream in = convertSvgToRasterImage(decodedImg); + ImageData[] datas = new ImageLoader().load(in); + if (datas != null && datas.length != 0) { + ImageData cur; + int index = 0; + for (int i = 0; i < datas.length; i++) { + ImageData temp = datas[i]; + if (temp.width * temp.height > datas[index].width * datas[index].height) { + index = i; + } + } + cur = datas[index]; + image = new Image(null, cur); + } + } + } else { + url = createURIURL(uri); + image = getImageFromURL(url, false); + } } catch (Exception e) { if (url != null && !invalidUrlList.contains(url.toString())) { invalidUrlList.add(url.toString()); diff --git a/engine/org.eclipse.birt.report.engine.emitter.html/src/org/eclipse/birt/report/engine/emitter/html/HTMLReportEmitter.java b/engine/org.eclipse.birt.report.engine.emitter.html/src/org/eclipse/birt/report/engine/emitter/html/HTMLReportEmitter.java index cb91c3a016..a27d1c75ee 100644 --- a/engine/org.eclipse.birt.report.engine.emitter.html/src/org/eclipse/birt/report/engine/emitter/html/HTMLReportEmitter.java +++ b/engine/org.eclipse.birt.report.engine.emitter.html/src/org/eclipse/birt/report/engine/emitter/html/HTMLReportEmitter.java @@ -17,6 +17,8 @@ import java.io.OutputStream; import java.io.PrintWriter; import java.io.StringWriter; +import java.net.MalformedURLException; +import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; @@ -362,6 +364,9 @@ public class HTMLReportEmitter extends ContentEmitterAdapter { */ private static int DEFAULT_IMAGE_PX_HEIGHT = 200; + private static final String URL_PROTOCOL_TYPE_FILE = "file:"; + private static final String URL_PROTOCOL_TYPE_DATA = "data:"; + /** * the constructor */ @@ -2995,10 +3000,24 @@ protected void resetImageDefaultBorders(IImageContent image, StringBuffer styleB */ protected String getImageURI(IImageContent image) { String imgUri = null; + String tmpImgUri = null; if (imageHandler != null) { - if (image.getImageSource() == IImageContent.IMAGE_URL) { - return image.getURI(); + imgUri = image.getURI(); + + // embedded images w/o URI check + if (image.getImageSource() != IImageContent.IMAGE_NAME) { + tmpImgUri = this.verifyURI(imgUri); + if (imgUri != tmpImgUri) { + imgUri = tmpImgUri; + image.setURI(tmpImgUri); + } + } + + // image URI with http/https + if (image.getImageSource() == IImageContent.IMAGE_URL && !imgUri.contains(URL_PROTOCOL_TYPE_FILE)) { + return imgUri; } + Image img = new Image(image); img.setRenderOption(renderOption); img.setReportRunnable(runnable); @@ -3559,6 +3578,26 @@ protected void retrieveRtLFlag() { } } } + + /** + * Check the URL to be valid and fall back try it like file-URL + */ + private String verifyURI(String uri) { + if (uri != null && !uri.toLowerCase().startsWith(URL_PROTOCOL_TYPE_DATA)) { + try { + new URL(uri).toURI(); + } catch (MalformedURLException | URISyntaxException excUrl) { + // invalid URI try it like "file:" + try { + String tmpUrl = URL_PROTOCOL_TYPE_FILE + "///" + uri; + new URL(tmpUrl).toURI(); + uri = tmpUrl; + } catch (MalformedURLException | URISyntaxException excFile) { + } + } + } + return uri; + } } class IDGenerator { diff --git a/engine/org.eclipse.birt.report.engine.emitter.wpml/src/org/eclipse/birt/report/engine/emitter/wpml/AbstractEmitterImpl.java b/engine/org.eclipse.birt.report.engine.emitter.wpml/src/org/eclipse/birt/report/engine/emitter/wpml/AbstractEmitterImpl.java index 8cc687fb09..499c9eeaf7 100644 --- a/engine/org.eclipse.birt.report.engine.emitter.wpml/src/org/eclipse/birt/report/engine/emitter/wpml/AbstractEmitterImpl.java +++ b/engine/org.eclipse.birt.report.engine.emitter.wpml/src/org/eclipse/birt/report/engine/emitter/wpml/AbstractEmitterImpl.java @@ -16,6 +16,9 @@ import java.io.IOException; import java.io.OutputStream; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -255,6 +258,10 @@ public enum TextFlag { protected static final String EMPTY_FOOTER = " "; + private static final String URL_PROTOCOL_TYPE_DATA = "data:"; + + private static final String URL_PROTOCOL_TYPE_FILE = "file:"; + /** * Initialize of the service * @@ -1005,7 +1012,7 @@ public void endPage(IPageContent page) { public void startImage(IImageContent image) { IStyle style = image.getComputedStyle(); InlineFlag inlineFlag = getInlineFlag(style); - String uri = image.getURI(); + String uri = this.verifyURI(image.getURI()); String mimeType = image.getMIMEType(); String extension = image.getExtension(); String altText = image.getAltText(); @@ -1494,4 +1501,25 @@ static class TocInfo { this.tocLevel = tocLevel; } } + + /** + * Check the URL to be valid and fall back try it like file-URL + */ + private String verifyURI(String uri) { + if (uri != null && !uri.toLowerCase().startsWith(URL_PROTOCOL_TYPE_DATA)) { + try { + new URL(uri).toURI(); + } catch (MalformedURLException | URISyntaxException excUrl) { + // invalid URI try it like "file:" + try { + String tmpUrl = URL_PROTOCOL_TYPE_FILE + "///" + uri; + new URL(tmpUrl).toURI(); + uri = tmpUrl; + } catch (MalformedURLException | URISyntaxException excFile) { + } + } + } + return uri; + } + } diff --git a/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/emitter/EmitterUtil.java b/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/emitter/EmitterUtil.java index c2fe3142f4..d0f9e50d7f 100644 --- a/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/emitter/EmitterUtil.java +++ b/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/emitter/EmitterUtil.java @@ -26,6 +26,8 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Base64; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; @@ -59,6 +61,12 @@ public class EmitterUtil { protected static Logger logger = Logger.getLogger(EmitterUtil.class.getName()); + private static final String URL_IMAGE_TYPE_SVG = "image/svg+xml"; + private static final String URL_PROTOCOL_TYPE_FILE = "file:"; + private static final String URL_PROTOCOL_TYPE_DATA = "data:"; + private static final String URL_PROTOCOL_TYPE_DATA_BASE = ";base64,"; + private static final String URL_PROTOCOL_TYPE_DATA_UTF8 = ";utf8,"; + public static OutputStream getOuputStream(IEmitterServices services, String defaultOutputFile) throws EngineException { OutputStream out = null; @@ -273,11 +281,19 @@ public static byte[] getImageData(String imageURI) throws IOException { private static byte[] getNonSVGImageData(String imageURI) throws IOException { InputStream imageStream = null; - byte[] imageData; + byte[] imageData = null; try { - URL url = new URL(imageURI); - imageStream = url.openStream(); - imageData = readData(imageStream); + if (imageURI.startsWith(URL_PROTOCOL_TYPE_DATA) && imageURI.contains(URL_PROTOCOL_TYPE_DATA_BASE)) { + String base64[] = imageURI.split(URL_PROTOCOL_TYPE_DATA_BASE); + if (base64.length >= 2) { + String encodedImg = base64[1]; + imageData = Base64.getDecoder().decode(encodedImg.getBytes(StandardCharsets.UTF_8)); + } + } else { + URL url = new URL(imageURI); + imageStream = url.openStream(); + imageData = readData(imageStream); + } } finally { if (imageStream != null) { try { @@ -333,7 +349,31 @@ public static org.eclipse.birt.report.engine.layout.emitter.Image parseImage(IIm if (uri != null) { if (SvgFile.isSvg(uri)) { try { - data = SvgFile.transSvgToArray(uri); + if (uri.toLowerCase().contains(URL_IMAGE_TYPE_SVG)) { + // url svg stream + String svgSplitter = "svg\\+xml,"; + if (uri.contains(URL_IMAGE_TYPE_SVG + URL_PROTOCOL_TYPE_DATA_UTF8)) { + svgSplitter = "svg\\+xml;utf8,"; + } else if (uri.contains(URL_IMAGE_TYPE_SVG + URL_PROTOCOL_TYPE_DATA_BASE)) { + svgSplitter = "svg\\+xml;base64,"; + } + String[] uriParts = uri.split(svgSplitter); + if (uriParts.length >= 2) { + String encodedImg = uriParts[1]; + String decodedImg = encodedImg; + if (uri.contains(URL_PROTOCOL_TYPE_DATA_BASE)) { + decodedImg = new String(Base64.getDecoder() + .decode(encodedImg.getBytes(StandardCharsets.UTF_8))); + } + decodedImg = java.net.URLDecoder.decode(decodedImg, StandardCharsets.UTF_8); + data = SvgFile + .transSvgToArray(new ByteArrayInputStream(decodedImg.getBytes())); + } + } else { + // url svg file load + data = SvgFile.transSvgToArray(uri); + } + } catch (Exception e) { logger.log(Level.WARNING, e.getMessage()); } diff --git a/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/emitter/ImageReader.java b/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/emitter/ImageReader.java index 9152145a7b..88a444a666 100644 --- a/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/emitter/ImageReader.java +++ b/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/emitter/ImageReader.java @@ -18,6 +18,8 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URISyntaxException; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.Base64; @@ -58,6 +60,9 @@ public class ImageReader { protected static Logger logger = Logger.getLogger(ImageReader.class.getName()); + private static final String URL_PROTOCOL_TYPE_DATA = "data:"; + private static final String URL_PROTOCOL_TYPE_FILE = "file:"; + public ImageReader(IImageContent content, String supportedImageFormats) { this.content = content; this.supportedImageFormats = supportedImageFormats; @@ -70,7 +75,7 @@ public ImageReader(IImageContent content, String supportedImageFormats) { public int read() { buffer = null; checkObjectType(content); - String uri = content.getURI(); + String uri = this.verifyURI(content.getURI()); try { switch (content.getImageSource()) { case IImageContent.IMAGE_FILE: @@ -178,10 +183,14 @@ private void readImage(String uri) throws IOException { byte[] bytes = null; if (Objects.equals(parseDataUrl.getEncoding(), "base64")) { //$NON-NLS-1$ bytes = Base64.getDecoder().decode(parseDataUrl.getData()); - } else if (parseDataUrl.getEncoding() == null) { + } else { /* The case of no encoding, the data is a string on the URL */ bytes = parseDataUrl.getData().getBytes(StandardCharsets.UTF_8); /* Charset of the SVG file */ } + if (this.objectType == TYPE_SVG_OBJECT) { + String decodedImg = java.net.URLDecoder.decode(new String(bytes), StandardCharsets.UTF_8); + bytes = decodedImg.getBytes(StandardCharsets.UTF_8); + } if (bytes != null) { readImage(bytes); @@ -254,4 +263,24 @@ private void readImage(byte[] data) throws IOException { status = UNSUPPORTED_OBJECTS; } } + + /** + * Check the URL to be valid and fall back try it like file-URL + */ + private String verifyURI(String uri) { + if (uri != null && !uri.toLowerCase().startsWith(URL_PROTOCOL_TYPE_DATA)) { + try { + new URL(uri).toURI(); + } catch (MalformedURLException | URISyntaxException excUrl) { + // invalid URI try it like "file:" + try { + String tmpUrl = URL_PROTOCOL_TYPE_FILE + "///" + uri; + new URL(tmpUrl).toURI(); + uri = tmpUrl; + } catch (MalformedURLException | URISyntaxException excFile) { + } + } + } + return uri; + } } diff --git a/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/presentation/LocalizedContentVisitor.java b/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/presentation/LocalizedContentVisitor.java index 2e8acb4bd2..5ad6db2096 100644 --- a/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/presentation/LocalizedContentVisitor.java +++ b/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/presentation/LocalizedContentVisitor.java @@ -111,6 +111,9 @@ public class LocalizedContentVisitor { 1, -34, 102, 31, 120, 0, 0, 0, 12, 73, 68, 65, 84, 24, 87, 99, -8, -1, -1, 63, 0, 5, -2, 2, -2, -89, 53, -127, -124, 0, 0, 0, 0, 73, 69, 78, 68, -82, 66, 96, -126 }; + private static final String FILE_PROTOCOL = ":/"; + private static final String URL_DATA_PROTOCOL = "data:"; + public LocalizedContentVisitor(ExecutionContext context) { this.context = context; this.locale = context.getLocale(); @@ -608,7 +611,7 @@ private IImageContent localizeImage(IImageContent image) { } } else if (image.getImageSource() == IImageContent.IMAGE_URL) { String uri = image.getURI(); - if (!uri.contains(":/")) { + if (!uri.contains(FILE_PROTOCOL) && !uri.contains(URL_DATA_PROTOCOL)) { IRenderOption option = context.getRenderOption(); if (option != null) { String appBaseUrl = option.getAppBaseURL(); diff --git a/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/util/SvgFile.java b/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/util/SvgFile.java index 868cb350f3..84acd417af 100644 --- a/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/util/SvgFile.java +++ b/engine/org.eclipse.birt.report.engine/src/org/eclipse/birt/report/engine/util/SvgFile.java @@ -14,9 +14,12 @@ package org.eclipse.birt.report.engine.util; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Base64; import java.util.logging.Logger; import org.apache.batik.transcoder.TranscoderInput; @@ -27,10 +30,15 @@ public class SvgFile { private static Logger logger = Logger.getLogger(SvgFile.class.getName()); + private static final String URL_IMAGE_TYPE_SVG = "image/svg+xml"; + private static final String URL_PROTOCOL_TYPE_DATA = "data:"; + private static final String URL_PROTOCOL_TYPE_DATA_BASE = ";base64,"; + private static final String URL_PROTOCOL_TYPE_DATA_UTF8 = ";utf8,"; + static boolean isSvg = false; public static boolean isSvg(String uri) { - if (uri != null && uri.endsWith(".svg")) { + if (uri != null && uri.endsWith(".svg") || uri.toLowerCase().contains(URL_IMAGE_TYPE_SVG)) { isSvg = true; } else { isSvg = false; @@ -39,17 +47,47 @@ public static boolean isSvg(String uri) { } public static boolean isSvg(String mimeType, String uri, String extension) { - isSvg = ((mimeType != null) && mimeType.equalsIgnoreCase("image/svg+xml")) //$NON-NLS-1$ - || ((uri != null) && uri.toLowerCase().endsWith(".svg")) //$NON-NLS-1$ + isSvg = ((mimeType != null) + && mimeType.equalsIgnoreCase(URL_IMAGE_TYPE_SVG)) // $NON-NLS-1$ + || ((uri != null) + && (uri.toLowerCase().endsWith(".svg") || uri.toLowerCase().contains(URL_IMAGE_TYPE_SVG))) //$NON-NLS-1$ || ((extension != null) && extension.toLowerCase().endsWith(".svg")); //$NON-NLS-1$ return isSvg; } public static byte[] transSvgToArray(String uri) throws Exception { - InputStream in = new URL(uri).openStream(); - try (in) { - return transSvgToArray(in); + byte[] data = null; + + if (uri != null && uri.toLowerCase().contains(URL_PROTOCOL_TYPE_DATA) + && uri.toLowerCase().contains(URL_IMAGE_TYPE_SVG)) { + String encodedImg = null; + String decodedImg = null; + if (uri.toLowerCase().contains(URL_IMAGE_TYPE_SVG)) { + String svgSplitter = "svg\\+xml,"; + if (uri.contains(URL_IMAGE_TYPE_SVG + URL_PROTOCOL_TYPE_DATA_UTF8)) { + svgSplitter = "svg\\+xml;utf8,"; + } else if (uri.contains(URL_IMAGE_TYPE_SVG + URL_PROTOCOL_TYPE_DATA_BASE)) { + svgSplitter = "svg\\+xml;base64,"; + } + String[] uriParts = uri.split(svgSplitter); + if (uriParts.length >= 2) { + encodedImg = uriParts[1]; + decodedImg = encodedImg; + if (uri.contains(URL_PROTOCOL_TYPE_DATA_BASE)) { + decodedImg = new String( + Base64.getDecoder().decode(encodedImg.getBytes(StandardCharsets.UTF_8))); + } + } + } + decodedImg = java.net.URLDecoder.decode(decodedImg, StandardCharsets.UTF_8); + data = SvgFile.transSvgToArray(new ByteArrayInputStream(decodedImg.getBytes())); + } else { + InputStream in = new URL(uri).openStream(); + try (in) { + data = transSvgToArray(in); + } } + return data; } public static byte[] transSvgToArray(InputStream inputStream) throws Exception { diff --git a/engine/uk.co.spudsoft.birt.emitters.excel/src/uk/co/spudsoft/birt/emitters/excel/handlers/CellContentHandler.java b/engine/uk.co.spudsoft.birt.emitters.excel/src/uk/co/spudsoft/birt/emitters/excel/handlers/CellContentHandler.java index 4b1a060cc7..9d35fd87aa 100644 --- a/engine/uk.co.spudsoft.birt.emitters.excel/src/uk/co/spudsoft/birt/emitters/excel/handlers/CellContentHandler.java +++ b/engine/uk.co.spudsoft.birt.emitters.excel/src/uk/co/spudsoft/birt/emitters/excel/handlers/CellContentHandler.java @@ -15,16 +15,20 @@ package uk.co.spudsoft.birt.emitters.excel.handlers; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.math.BigDecimal; +import java.net.MalformedURLException; +import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Base64; import java.util.Date; import java.util.Iterator; import java.util.List; -import org.apache.commons.codec.binary.Base64; import org.apache.poi.common.usermodel.HyperlinkType; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellStyle; @@ -48,6 +52,7 @@ import org.eclipse.birt.report.engine.ir.DimensionType; import org.eclipse.birt.report.engine.layout.emitter.Image; import org.eclipse.birt.report.engine.presentation.ContentEmitterVisitor; +import org.eclipse.birt.report.engine.util.SvgFile; import org.w3c.dom.css.CSSPrimitiveValue; import org.w3c.dom.css.CSSValue; @@ -123,9 +128,11 @@ public class CellContentHandler extends AbstractHandler { */ protected String hyperlinkBookmark; - private static String DATA_PROTOCOL = "data:"; - - private static String DATA_PROTOCOL_BASE = ";base64,"; + private static final String URL_IMAGE_TYPE_SVG = "image/svg+xml"; + private static final String URL_PROTOCOL_TYPE_FILE = "file:"; + private static final String URL_PROTOCOL_TYPE_DATA = "data:"; + private static final String URL_PROTOCOL_TYPE_DATA_BASE = ";base64,"; + private static final String URL_PROTOCOL_TYPE_DATA_UTF8 = ";utf8,"; /** * Constructor @@ -549,30 +556,73 @@ public void recordImage(HandlerState state, Coordinate location, IImageContent i StyleManagerUtils smu = state.getSmu(); Workbook wb = state.getWb(); String mimeType = image.getMIMEType(); - if ((data == null) && (image.getURI() != null)) { - String stringURI = image.getURI().toString().toLowerCase(); - if (stringURI.startsWith(DATA_PROTOCOL) && stringURI.contains(DATA_PROTOCOL_BASE)) { - String base64[] = image.getURI().toString().split(DATA_PROTOCOL_BASE); - if (base64.length >= 2) { - data = Base64.decodeBase64(base64[1]); - } - } else { - try { - URL imageUrl = new URL(image.getURI()); - URLConnection conn = imageUrl.openConnection(); - conn.connect(); - mimeType = conn.getContentType(); - int imageType = smu.poiImageTypeFromMimeType(mimeType, null); - if (imageType == 0) { - log.debug("Unrecognised/unhandled image MIME type: " + mimeType); - } else { + String stringURI = image.getURI(); + + if (stringURI != null + && (stringURI.toLowerCase().endsWith(".svg") || stringURI.toLowerCase().contains(URL_IMAGE_TYPE_SVG)) + || mimeType != null && mimeType.toLowerCase().equals(URL_IMAGE_TYPE_SVG)) { + + try { + String encodedImg = null; + String decodedImg = null; + if (stringURI != null && stringURI.toLowerCase().contains(URL_IMAGE_TYPE_SVG)) { + // svg: url stream image + String svgSplitter = "svg\\+xml,"; + if (stringURI.contains(URL_IMAGE_TYPE_SVG + URL_PROTOCOL_TYPE_DATA_UTF8)) { + svgSplitter = "svg\\+xml;utf8,"; + } else if (stringURI.contains(URL_IMAGE_TYPE_SVG + URL_PROTOCOL_TYPE_DATA_BASE)) { + svgSplitter = "svg\\+xml;base64,"; + } + String[] uriParts = stringURI.split(svgSplitter); + if (uriParts.length >= 2) { + encodedImg = uriParts[1]; + decodedImg = encodedImg; + if (stringURI.contains(URL_PROTOCOL_TYPE_DATA_BASE)) { + decodedImg = new String( + Base64.getDecoder().decode(encodedImg.getBytes(StandardCharsets.UTF_8))); + } + } + } else { + // svg: url file load + if (data == null && stringURI != null) { + String uri = this.verifyURI(stringURI); // URL(this.id); + URL imageUrl = new URL(uri); + URLConnection conn = imageUrl.openConnection(); + conn.connect(); data = smu.downloadImage(conn); image.setData(data); } - } catch (IOException ex) { - log.debug(ex.getClass(), ": ", ex.getMessage()); - ex.printStackTrace(); + decodedImg = new String(image.getData()); + } + decodedImg = java.net.URLDecoder.decode(decodedImg, StandardCharsets.UTF_8); + data = SvgFile.transSvgToArray(new ByteArrayInputStream(decodedImg.getBytes())); + + } catch (Exception e) { + // invalid svg image, default handling + } + + } else if (stringURI != null && stringURI.startsWith(URL_PROTOCOL_TYPE_DATA) + && stringURI.contains(URL_PROTOCOL_TYPE_DATA_BASE)) { + String base64[] = image.getURI().toString().split(URL_PROTOCOL_TYPE_DATA_BASE); + if (base64.length >= 2) { + data = Base64.getDecoder().decode(base64[1]); + } + } else if ((data == null) && (image.getURI() != null)) { + try { + URL imageUrl = new URL(this.verifyURI(image.getURI())); + URLConnection conn = imageUrl.openConnection(); + conn.connect(); + mimeType = conn.getContentType(); + int imageType = smu.poiImageTypeFromMimeType(mimeType, null); + if (imageType == 0) { + log.debug("Unrecognised/unhandled image MIME type: " + mimeType); + } else { + data = smu.downloadImage(conn); + image.setData(data); } + } catch (IOException ex) { + log.debug(ex.getClass(), ": ", ex.getMessage()); + ex.printStackTrace(); } } if (data != null) { @@ -630,4 +680,24 @@ protected void removeMergedCell(HandlerState state, int row, int col) { } } + /** + * Check the URL to be valid and fall back try it like file-URL + */ + private String verifyURI(String uri) { + if (uri != null && !uri.toLowerCase().startsWith(URL_PROTOCOL_TYPE_DATA)) { + try { + new URL(uri).toURI(); + } catch (MalformedURLException | URISyntaxException excUrl) { + // invalid URI try it like "file:///" + try { + String tmpUrl = URL_PROTOCOL_TYPE_FILE + "///" + uri; + new URL(tmpUrl).toURI(); + uri = tmpUrl; + } catch (MalformedURLException | URISyntaxException excFile) { + } + } + } + return uri; + } + }