Skip to content

Commit

Permalink
Merge pull request #597 from syjer/596-generalize-data-uri-support
Browse files Browse the repository at this point in the history
Fixes #596 - generalize data uri support
  • Loading branch information
danfickle authored Nov 13, 2020
2 parents 1e7e5d6 + 1531a16 commit de6f0ee
Show file tree
Hide file tree
Showing 9 changed files with 155 additions and 169 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ public abstract static class BaseRendererBuilderState {
protected BaseRendererBuilder(TBaseRendererBuilderState state) {
this.state = state;
this.useProtocolsStreamImplementation(new NaiveUserAgent.DefaultHttpStreamFactory(), "http", "https");
this.useProtocolsStreamImplementation(new NaiveUserAgent.DataUriFactory(), "data");
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,13 @@
package com.openhtmltopdf.swing;

import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
Expand Down Expand Up @@ -111,11 +107,50 @@ public FSStream getUrl(String uri) {
return new DefaultHttpStream(is);
}
}

private static class ByteStream implements FSStream {

ByteStream(byte[] input) {
this.input = input;
}

private final byte[] input;

@Override
public InputStream getStream() {
return new ByteArrayInputStream(input);
}

@Override
public Reader getReader() {
return new InputStreamReader(getStream(), StandardCharsets.UTF_8);
}
}

public static class DataUriFactory implements FSStreamFactory {

@Override
public FSStream getUrl(String url) {
int idxSeparator;
if (url != null && url.startsWith("data:") && (idxSeparator = url.indexOf(',')) > 0) {
String data = url.substring(idxSeparator+1);
byte[] res;
if (url.indexOf("base64,") == idxSeparator - 6 /* 6 = "base64,".length */) {
res = Base64.getMimeDecoder().decode(data);
} else {
res = data.getBytes(StandardCharsets.UTF_8);
}
return new ByteStream(res);
}
return null;
}
}

public NaiveUserAgent() {
FSStreamFactory factory = new DefaultHttpStreamFactory();
this._protocolsStreamFactory.put("http", factory);
this._protocolsStreamFactory.put("https", factory);
this._protocolsStreamFactory.put("data", new DataUriFactory());
}

public void setProtocolsStreamFactory(Map<String, FSStreamFactory> protocolsStreamFactory) {
Expand Down Expand Up @@ -146,20 +181,27 @@ protected boolean hasProtocolFactory(String protocol) {
return _protocolsStreamFactory.containsKey(protocol);
}

protected String extractProtocol(String uri) throws URISyntaxException {
int idxSeparator;
if (uri != null && (idxSeparator = uri.indexOf(':')) > 0) {
return uri.substring(0, idxSeparator);
} else {
throw new URISyntaxException(uri, "missing protocol for URI");
}
}

/**
* Gets a InputStream for the resource identified by a resolved URI.
*/
protected InputStream openStream(String uri) {
java.io.InputStream is = null;

try {
URI urlObj = new URI(uri);
String protocol = urlObj.getScheme();
String protocol = extractProtocol(uri);

if (hasProtocolFactory(protocol)) {
return getProtocolFactory(protocol).getUrl(uri).getStream();
}
else {
} else {
try {
is = new URL(uri).openStream();
} catch (java.net.MalformedURLException e) {
Expand All @@ -184,13 +226,11 @@ protected Reader openReader(String uri) {
InputStream is = null;

try {
URI urlObj = new URI(uri);
String protocol = urlObj.getScheme();
String protocol = extractProtocol(uri);

if (hasProtocolFactory(protocol)) {
return getProtocolFactory(protocol).getUrl(uri).getReader();
}
else {
} else {
try {
is = new URL(uri).openStream();
} catch (java.net.MalformedURLException e) {
Expand Down Expand Up @@ -250,54 +290,51 @@ public CSSResource getCSSResource(String uri) {
public ImageResource getImageResource(String uri) {
ImageResource ir;

if (ImageUtil.isEmbeddedBase64Image(uri)) {
BufferedImage image = ImageUtil.loadEmbeddedBase64Image(uri);
return new ImageResource(null, AWTFSImage.createImage(image));
} else {
String resolved = _resolver.resolveURI(this._baseUri, uri);

if (resolved == null) {
XRLog.log(Level.INFO, LogMessageId.LogMessageId2Param.LOAD_URI_RESOLVER_REJECTED_LOADING_AT_URI, "image resource", uri);
return null;
}

// First, we check the internal per run cache.
ir = _imageCache.get(resolved);
if (ir != null) {
return ir;
}

// Finally we fetch from the network or file, etc.
InputStream is = openStream(resolved);

if (is != null) {
try {
BufferedImage img = ImageIO.read(is);
if (img == null) {
throw new IOException("ImageIO.read() returned null");
}

AWTFSImage fsImage2 = (AWTFSImage) AWTFSImage.createImage(img);

ir = new ImageResource(resolved, fsImage2);
_imageCache.put(resolved, ir);

return ir;
} catch (FileNotFoundException e) {
XRLog.log(Level.WARNING, LogMessageId.LogMessageId1Param.EXCEPTION_CANT_READ_IMAGE_FILE_FOR_URI_NOT_FOUND, resolved);
} catch (IOException e) {
XRLog.log(Level.WARNING, LogMessageId.LogMessageId1Param.EXCEPTION_CANT_READ_IMAGE_FILE_FOR_URI, uri, e);
} finally {
try {
is.close();
} catch (IOException e) {
// ignore
}
}
}

return new ImageResource(resolved, null);
}
String resolved = _resolver.resolveURI(this._baseUri, uri);

if (resolved == null) {
XRLog.log(Level.INFO, LogMessageId.LogMessageId2Param.LOAD_URI_RESOLVER_REJECTED_LOADING_AT_URI, "image resource", uri);
return null;
}

// First, we check the internal per run cache.
ir = _imageCache.get(resolved);
if (ir != null) {
return ir;
}

// Finally we fetch from the network or file, etc.
InputStream is = openStream(resolved);

if (is != null) {
try {
BufferedImage img = ImageIO.read(is);
if (img == null) {
throw new IOException("ImageIO.read() returned null");
}

AWTFSImage fsImage2 = (AWTFSImage) AWTFSImage.createImage(img);

ir = new ImageResource(resolved, fsImage2);
_imageCache.put(resolved, ir);

return ir;
} catch (FileNotFoundException e) {
XRLog.log(Level.WARNING, LogMessageId.LogMessageId1Param.EXCEPTION_CANT_READ_IMAGE_FILE_FOR_URI_NOT_FOUND, resolved);
} catch (IOException e) {
XRLog.log(Level.WARNING, LogMessageId.LogMessageId1Param.EXCEPTION_CANT_READ_IMAGE_FILE_FOR_URI, uri, e);
} finally {
try {
is.close();
} catch (IOException e) {
// ignore
}
}
}

return new ImageResource(resolved, null);

}

/**
Expand Down Expand Up @@ -328,10 +365,6 @@ public XMLResource getXMLResource(String uri) {

@Override
public byte[] getBinaryResource(String uri) {
if (ImageUtil.isDataUri(uri)) {
return ImageUtil.getEmbeddedDataUri(uri);
}

String resolved = _resolver.resolveURI(this._baseUri, uri);

if (resolved == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,20 +220,6 @@ public static BufferedImage createTransparentImage(int width, int height) {
return bi;
}

/**
* Detect if an URI represents an embedded base 64 image.
*
* @param uri URI of the image
* @return A boolean
*/
public static boolean isEmbeddedBase64Image(String uri) {
return (uri != null && uri.startsWith("data:image/"));
}

public static boolean isDataUri(String uri) {
return uri != null && uri.startsWith("data:");
}

/**
* Get the binary content of an embedded base 64 image.
*
Expand All @@ -251,28 +237,6 @@ public static byte[] getEmbeddedBase64Image(String imageDataUri) {
return null;
}

public static byte[] getEmbeddedDataUri(String dataUri) {
return getEmbeddedBase64Image(dataUri);
}

/**
* Get the BufferedImage of an embedded base 64 image.
*
* @param imageDataUri URI of the embedded image
* @return The BufferedImage
*/
public static BufferedImage loadEmbeddedBase64Image(String imageDataUri) {
try {
byte[] buffer = getEmbeddedBase64Image(imageDataUri);
if (buffer != null) {
return ImageIO.read(new ByteArrayInputStream(buffer));
}
} catch (IOException ex) {
XRLog.log(Level.WARNING, LogMessageId.LogMessageId0Param.EXCEPTION_CANT_READ_XHTML_EMBEDDED_IMAGE, ex);
}
return null;
}

interface Scaler {
/**
* Convenience method that returns a scaled instance of the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ enum LogMessageId0Param implements LogMessageId {
EXCEPTION_MATHML_COULD_NOT_REGISTER_FONT(XRLog.EXCEPTION, "Could not register font correctly"),
EXCEPTION_JAVA2D_COULD_NOT_LOAD_FONT(XRLog.EXCEPTION, "Couldn't load font. Please check that it is a valid truetype font."),
EXCEPTION_PROBLEM_TRYING_TO_READ_INPUT_XHTML_FILE(XRLog.EXCEPTION, "Problem trying to read input XHTML file"),
EXCEPTION_CANT_READ_XHTML_EMBEDDED_IMAGE(XRLog.EXCEPTION, "Can't read XHTML embedded image."),
EXCEPTION_COULD_NOT_LOAD_FONT_METRICS(XRLog.EXCEPTION, "Couldn't load font metrics."),
EXCEPTION_UNABLE_TO_PARSE_PAGE_OF_IMG_TAG_WITH_PDF(XRLog.EXCEPTION, "Unable to parse page of img tag with PDF!"),
EXCEPTION_TRIED_TO_OPEN_A_PASSWORD_PROTECTED_DOCUMENT_AS_SRC_FOR_IMG(XRLog.EXCEPTION, "Tried to open a password protected document as src for an img!"),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package com.openhtmltopdf.util;

import java.awt.image.BufferedImage;
import static org.hamcrest.CoreMatchers.notNullValue;

import org.hamcrest.CoreMatchers;
import org.junit.Test;
import static org.hamcrest.MatcherAssert.assertThat;

Expand All @@ -15,13 +13,4 @@ public void testGetEmbeddedBase64Image() {
byte[] result = ImageUtil.getEmbeddedBase64Image(onePixel);
assertThat(result, notNullValue());
}

@Test
public void testLoadEmbeddedBase64Image() {
String onePixel = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAACXBIWXMAAC4jAAAuIwF4pT92AAAADElEQVQI12P4//8/AAX+Av7czFnnAAAAAElFTkSuQmCC";
BufferedImage result = ImageUtil.loadEmbeddedBase64Image(onePixel);
assertThat(result.getHeight(), CoreMatchers.is(1));
assertThat(result.getWidth(), CoreMatchers.is(1));
}

}
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<html>
<head>
<style>
@import url("data:text/css;base64,cCB7Y29sb3I6cmVkfQ==");
@page {
size: 50px 50px;
margin: 0;
}
</style>
</head>
<body>
<p>test</p>
</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -1276,6 +1276,11 @@ public void testIssue553TableNegativeMargin() throws IOException {
assertTrue(vt.runTest("issue-553-table-negative-margin"));
}

@Test
public void testIssue596CSSDataUriInImportUrl() throws IOException {
assertTrue(vt.runTest("issue-596-generalize-data-uri-support"));
}

// TODO:
// + Elements that appear just on generated overflow pages.
// + content property (page counters, etc)
Expand Down
Loading

0 comments on commit de6f0ee

Please sign in to comment.