Skip to content

Commit c22318c

Browse files
author
Artyom Yanchevsky
committed
Add support to utf-8 image format in svg
DEVSIX-3847
1 parent 1cd1799 commit c22318c

File tree

7 files changed

+149
-10
lines changed

7 files changed

+149
-10
lines changed

src/main/java/com/itextpdf/html2pdf/resolver/resource/HtmlResourceResolver.java

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,16 @@ This file is part of the iText (R) project.
6060
import java.io.InputStream;
6161

6262
import java.net.URL;
63+
import java.nio.charset.StandardCharsets;
64+
import java.util.regex.Pattern;
6365

6466
/**
6567
* Extends {@link ResourceResolver} to also support SVG images
6668
*/
6769
public class HtmlResourceResolver extends ResourceResolver {
6870

69-
private static final String SVG_BASE64_PREFIX = "data:image/svg+xml";
71+
private static final String SVG_PREFIX = "data:image/svg+xml";
72+
private static final Pattern SVG_IDENTIFIER_PATTERN = Pattern.compile(",[\\s]*(<svg )");
7073

7174
private ProcessorContext context;
7275

@@ -104,14 +107,30 @@ public HtmlResourceResolver(String baseUri, ProcessorContext context, IResourceR
104107
this.context = context;
105108
}
106109

110+
@Override
111+
public PdfXObject retrieveImageExtended(String src) {
112+
if (src != null && src.trim().startsWith(SVG_PREFIX) && SVG_IDENTIFIER_PATTERN.matcher(src).find()) {
113+
PdfXObject imageXObject = tryResolveSvgImageSource(src);
114+
if (imageXObject != null) {
115+
return imageXObject;
116+
}
117+
}
118+
return super.retrieveImageExtended(src);
119+
}
120+
121+
/**
122+
* Retrieve image as either {@link com.itextpdf.kernel.pdf.xobject.PdfImageXObject}, or {@link PdfFormXObject}.
123+
*
124+
* @param src either link to file or base64 encoded stream
125+
* @return PdfXObject on success, otherwise null
126+
*/
107127
@Override
108128
protected PdfXObject tryResolveBase64ImageSource(String src) {
109129
String fixedSrc = src.replaceAll("\\s", "");
110-
if (fixedSrc.startsWith(SVG_BASE64_PREFIX)) {
111-
fixedSrc = fixedSrc.substring(fixedSrc.indexOf(BASE64IDENTIFIER) + 7);
112-
try {
113-
PdfXObject xObject = HtmlResourceResolver.processAsSvg(
114-
new ByteArrayInputStream(Base64.decode(fixedSrc)), context, null);
130+
if (fixedSrc.startsWith(SVG_PREFIX)) {
131+
fixedSrc = fixedSrc.substring(fixedSrc.indexOf(BASE64_IDENTIFIER) + BASE64_IDENTIFIER.length() + 1);
132+
try (ByteArrayInputStream stream = new ByteArrayInputStream(Base64.decode(fixedSrc))) {
133+
PdfFormXObject xObject = processAsSvg(stream, context, null);
115134
if (xObject != null) {
116135
return xObject;
117136
}
@@ -132,6 +151,18 @@ protected PdfXObject createImageByUrl(URL url) throws Exception {
132151
}
133152
}
134153

154+
private PdfXObject tryResolveSvgImageSource(String src) {
155+
try (ByteArrayInputStream stream = new ByteArrayInputStream(src.getBytes(StandardCharsets.UTF_8))) {
156+
PdfFormXObject xObject = processAsSvg(stream, context, null);
157+
if (xObject != null) {
158+
return xObject;
159+
}
160+
} catch (Exception ignored) {
161+
//Logs an error in a higher-level method if null is returned
162+
}
163+
return null;
164+
}
165+
135166
private static PdfFormXObject processAsSvg(InputStream stream, ProcessorContext context, String parentDir) throws IOException {
136167
SvgConverterProperties svgConverterProperties = ContextMappingHelper.mapToSvgConverterProperties(context);
137168
if (parentDir != null) {

src/test/java/com/itextpdf/html2pdf/Html2PdfTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,14 @@ This file is part of the iText (R) project.
6060
import com.itextpdf.test.annotations.LogMessage;
6161
import com.itextpdf.test.annotations.LogMessages;
6262
import com.itextpdf.test.annotations.type.IntegrationTest;
63+
64+
import java.io.FileInputStream;
6365
import org.junit.Assert;
6466
import org.junit.BeforeClass;
6567
import org.junit.Test;
6668
import org.junit.experimental.categories.Category;
6769

6870
import java.io.File;
69-
import java.io.FileInputStream;
7071
import java.io.IOException;
7172

7273
@Category(IntegrationTest.class)
@@ -161,7 +162,6 @@ public void htmlObjectAltTextTest() throws IOException, InterruptedException {
161162
public void htmlObjectNestedObjectTest() throws IOException, InterruptedException {
162163
HtmlConverter.convertToPdf(new File(sourceFolder + "objectTag_nestedTag.html"), new File(destinationFolder + "objectTag_nestedTag.pdf"));
163164
Assert.assertNull(new CompareTool().compareByContent(destinationFolder + "objectTag_nestedTag.pdf", sourceFolder + "cmp_objectTag_nestedTag.pdf", destinationFolder, "diff01_"));
164-
165165
}
166166

167167
@Test

src/test/java/com/itextpdf/html2pdf/resolver/resource/HtmlResourceResolverTest.java

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -359,13 +359,49 @@ public void resourceResolverSvgWithImageObjectTest() throws IOException, Interru
359359
try (FileInputStream fileInputStream = new FileInputStream(
360360
sourceFolder + "resourceResolverSvgWithImageObject.html");
361361
FileOutputStream fileOutputStream = new FileOutputStream(outPdf)) {
362-
HtmlConverter
363-
.convertToPdf(fileInputStream, fileOutputStream, new ConverterProperties().setBaseUri(sourceFolder));
362+
HtmlConverter.convertToPdf(fileInputStream, fileOutputStream,
363+
new ConverterProperties().setBaseUri(sourceFolder));
364364
}
365365

366366
Assert.assertNull(new CompareTool().compareByContent(outPdf, cmpPdf, destinationFolder));
367367
}
368368

369+
@Test
370+
@LogMessages(messages = {
371+
@LogMessage(messageTemplate = com.itextpdf.styledxmlparser.LogMessageConstant.UNABLE_TO_RETRIEVE_IMAGE_WITH_GIVEN_DATA_URI, count = 3),
372+
@LogMessage(messageTemplate = LogMessageConstant.WORKER_UNABLE_TO_PROCESS_OTHER_WORKER, count = 3)
373+
})
374+
public void resourceResolverSvgDifferentFormatsTest() throws IOException, InterruptedException {
375+
String html = sourceFolder + "resourceResolverSvgDifferentFormats.html";
376+
String outPdf = destinationFolder + "resourceResolverSvgDifferentFormats.pdf";
377+
String cmpPdf = sourceFolder + "cmp_resourceResolverSvgDifferentFormats.pdf";
378+
try (
379+
FileInputStream htmlInput = new FileInputStream(html);
380+
FileOutputStream pdfOutput = new FileOutputStream(outPdf)
381+
) {
382+
HtmlConverter.convertToPdf(htmlInput, pdfOutput, new ConverterProperties().setBaseUri(sourceFolder));
383+
}
384+
Assert.assertNull(new CompareTool().compareByContent(outPdf, cmpPdf, destinationFolder, "diffCorruptedSvg_"));
385+
}
386+
387+
@Test
388+
@LogMessages(messages = {
389+
@LogMessage(messageTemplate = com.itextpdf.styledxmlparser.LogMessageConstant.UNABLE_TO_RETRIEVE_IMAGE_WITH_GIVEN_DATA_URI),
390+
@LogMessage(messageTemplate = LogMessageConstant.WORKER_UNABLE_TO_PROCESS_OTHER_WORKER)
391+
})
392+
public void resourceResolverNotValidInlineSvgTest() throws IOException, InterruptedException {
393+
String html = sourceFolder + "resourceResolverNotValidInlineSvg.html";
394+
String outPdf = destinationFolder + "resourceResolverNotValidInlineSvg.pdf";
395+
String cmpPdf = sourceFolder + "cmp_resourceResolverNotValidInlineSvg.pdf";
396+
try (
397+
FileInputStream htmlInput = new FileInputStream(html);
398+
FileOutputStream pdfOutput = new FileOutputStream(outPdf)
399+
) {
400+
HtmlConverter.convertToPdf(htmlInput, pdfOutput, new ConverterProperties().setBaseUri(sourceFolder));
401+
}
402+
Assert.assertNull(new CompareTool().compareByContent(outPdf, cmpPdf, destinationFolder, "diffCorruptedSvg_"));
403+
}
404+
369405
@Test
370406
@LogMessages(messages = {@LogMessage(messageTemplate = SvgLogMessageConstant.NOROOT),
371407
@LogMessage(messageTemplate = LogMessageConstant.WORKER_UNABLE_TO_PROCESS_OTHER_WORKER)})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<html>
2+
<body>
3+
<p>Document with not valid svg</p>
4+
<img type='image/svg+xml' src='data:image/svg+xml, xmlns="http://www.w3.org/2000/svg"
5+
xmlns:xlink="http://www.w3.org/1999/xlink" width="300" height="200">
6+
<polygon points="100,10 40,198 190,78 10,78 160,198"
7+
style="fill:lime;stroke:purple;stroke-width:5;fill-rule:evenodd;"'>
8+
</body>
9+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<html>
2+
<body>
3+
<style>
4+
.svgimg {
5+
width: 200px;
6+
height: 200px;
7+
background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="300" height="200"> <polygon points="100,10 40,198 190,78 10,78 160,198" style="fill:lime;stroke:purple;stroke-width:5;fill-rule:evenodd;" /></svg>');
8+
}
9+
</style>
10+
<p>Document with svg images. Some of them are corrupted</p>
11+
<p>+ style</p>
12+
<div class="svgimg"></div>
13+
<p>+</p>
14+
<img type='image/svg+xml' src='data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciICAgICB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgd2lkdGg9IjUwMCIgaGVpZ2h0PSI1MDAiPiAgICA8bGluZSB4MT0iMCIgeTE9IjEwMCIgeDI9IjUwMCIgeTI9IjEwMCIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLXdpZHRoPSIyIiAgLz4gICAgPGxpbmUgeDE9IjAiIHkxPSIyMDAiIHgyPSI1MDAiIHkyPSIyMDAiIHN0cm9rZT0iYmxhY2siIHN0cm9rZS13aWR0aD0iMiIgIC8+ICAgIDxsaW5lIHgxPSIwIiB5MT0iMzAwIiB4Mj0iNTAwIiB5Mj0iMzAwIiBzdHJva2U9ImJsYWNrIiBzdHJva2Utd2lkdGg9IjIiICAvPiAgICA8bGluZSB4MT0iMCIgeTE9IjQwMCIgeDI9IjUwMCIgeTI9IjQwMCIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLXdpZHRoPSIyIiAgLz4gICAgPGxpbmUgeDE9IjEwMCIgeTE9IjAiIHgyPSIxMDAiIHkyPSI1MDAiIHN0cm9rZT0iYmxhY2siIHN0cm9rZS13aWR0aD0iMiIgIC8+ICAgIDxsaW5lIHgxPSIyMDAiIHkxPSIwIiB4Mj0iMjAwIiB5Mj0iNTAwIiBzdHJva2U9ImJsYWNrIiBzdHJva2Utd2lkdGg9IjIiICAvPiAgICA8bGluZSB4MT0iMzAwIiB5MT0iMCIgeDI9IjMwMCIgeTI9IjUwMCIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLXdpZHRoPSIyIiAgLz4gICAgPGxpbmUgeDE9IjQwMCIgeTE9IjAiIHgyPSI0MDAiIHkyPSI1MDAiIHN0cm9rZT0iYmxhY2siIHN0cm9rZS13aWR0aD0iMiIgIC8+PC9zdmc+'/>
15+
<p>- Corrupted</p>
16+
<img type='image/svg+xml' src='data:image/svg+xml;utf8,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciICAgICB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5v'>
17+
<p>+;utf8,</p>
18+
<img type='image/svg+xml' src='data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg"
19+
xmlns:xlink="http://www.w3.org/1999/xlink" width="300" height="200">
20+
<polygon points="100,10 40,198 190,78 10,78 160,198"
21+
style="fill:lime;stroke:purple;stroke-width:5;fill-rule:evenodd;" /></svg>'>
22+
<p>+</p>
23+
<img type='image/svg+xml' src='data:image/svg+xml;UTF-8,<svg xmlns="http://www.w3.org/2000/svg"
24+
xmlns:xlink="http://www.w3.org/1999/xlink" width="300" height="200">
25+
<polygon points="100,10 40,198 190,78 10,78 160,198"
26+
style="fill:lime;stroke:purple;stroke-width:5;fill-rule:evenodd;" /></svg>'>
27+
<p>+ utf8</p>
28+
<img type='image/svg+xml' src='data:image/svg+xml ; utf8 , <svg xmlns="http://www.w3.org/2000/svg"
29+
xmlns:xlink="http://www.w3.org/1999/xlink" width="300" height="200">
30+
<polygon points="100,10 40,198 190,78 10,78 160,198"
31+
style="fill:lime;stroke:purple;stroke-width:5;fill-rule:evenodd;" /></svg>'>
32+
<p>+;,</p>
33+
<img type='image/svg+xml' src=' data:image/svg+xml;,<svg xmlns="http://www.w3.org/2000/svg"
34+
xmlns:xlink="http://www.w3.org/1999/xlink" width="300" height="200">
35+
<polygon points="100,10 40,198 190,78 10,78 160,198"
36+
style="fill:lime;stroke:purple;stroke-width:5;fill-rule:evenodd;" /></svg>'>
37+
<p>+ fake base64</p>
38+
<img type='image/svg+xml' src='data:image/svg+xml; fakebase64 ,<svg xmlns="http://www.w3.org/2000/svg"
39+
xmlns:xlink="http://www.w3.org/1999/xlink" width="300" height="200">
40+
<polygon points="100,10 40,198 190,78 10,78 160,198"
41+
style="fill:lime;stroke:purple;stroke-width:5;fill-rule:evenodd;" /></svg>'>
42+
<p>- base 64</p>
43+
<img type='image/svg+xml' src='data:image/svg+xml; base64 ,<svg xmlns="http://www.w3.org/2000/svg"
44+
xmlns:xlink="http://www.w3.org/1999/xlink" width="300" height="200">
45+
<polygon points="100,10 40,198 190,78 10,78 160,198"
46+
style="fill:lime;stroke:purple;stroke-width:5;fill-rule:evenodd;" /></svg>'>
47+
<p>-e</p>
48+
<img type='image/svg+xml' src='data:image/svg+xml; , e <svg xmlns="http://www.w3.org/2000/svg"
49+
xmlns:xlink="http://www.w3.org/1999/xlink" width="300" height="200">
50+
<polygon points="100,10 40,198 190,78 10,78 160,198"
51+
style="fill:lime;stroke:purple;stroke-width:5;fill-rule:evenodd;" /></svg>'>
52+
<p>+,</p>
53+
<img type='image/svg+xml' src='data:image/svg+xml, <svg xmlns="http://www.w3.org/2000/svg"
54+
xmlns:xlink="http://www.w3.org/1999/xlink" width="300" height="200">
55+
<polygon points="100,10 40,198 190,78 10,78 160,198"
56+
style="fill:lime;stroke:purple;stroke-width:5;fill-rule:evenodd;" /></svg>'>
57+
<p>-;</p>
58+
<img type='image/svg+xml' src='data:image/svg+xml; <svg xmlns="http://www.w3.org/2000/svg"
59+
xmlns:xlink="http://www.w3.org/1999/xlink" width="300" height="200">
60+
<polygon points="100,10 40,198 190,78 10,78 160,198"
61+
style="fill:lime;stroke:purple;stroke-width:5;fill-rule:evenodd;" /></svg>'>
62+
</body>
63+
</html>

0 commit comments

Comments
 (0)