Skip to content

Commit

Permalink
enhancement to improve the svg-image-handling and the usage of data-u…
Browse files Browse the repository at this point in the history
…rl (eclipse-birt#1387)

Enhancement to improve the svg-image-handling and the usage of data-url
  • Loading branch information
speckyspooky authored Aug 11, 2023
1 parent b4c2cc9 commit 59c1d03
Show file tree
Hide file tree
Showing 9 changed files with 406 additions and 78 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,21 @@
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;

import org.apache.batik.transcoder.TranscoderException;
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;
Expand All @@ -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();

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand All @@ -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 {
Expand All @@ -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];
Expand Down Expand Up @@ -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));
}
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
*/
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
*
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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;
}

}
Loading

0 comments on commit 59c1d03

Please sign in to comment.