|
| 1 | +package org.eclipse.birt.report.engine.emitter.pdf; |
| 2 | + |
| 3 | +import java.io.ByteArrayOutputStream; |
| 4 | +import java.io.ByteArrayInputStream; |
| 5 | +import java.nio.charset.StandardCharsets; |
| 6 | + |
| 7 | +import org.apache.batik.transcoder.SVGAbstractTranscoder; |
| 8 | +import org.apache.batik.transcoder.TranscoderInput; |
| 9 | +import org.apache.batik.transcoder.print.PrintTranscoder; |
| 10 | +import org.openpdf.text.Document; |
| 11 | +import org.openpdf.text.PageSize; |
| 12 | +import org.openpdf.text.pdf.PdfContentByte; |
| 13 | +import org.openpdf.text.pdf.PdfTemplate; |
| 14 | +import org.openpdf.text.pdf.PdfWriter; |
| 15 | + |
| 16 | +import junit.framework.TestCase; |
| 17 | + |
| 18 | +/******************************************************************************* |
| 19 | + * Copyright (c) 2026 Contributors to the Eclipse Foundation |
| 20 | + * |
| 21 | + * This program and the accompanying materials are made available under the |
| 22 | + * terms of the Eclipse Public License 2.0 which is available at |
| 23 | + * https://www.eclipse.org/legal/epl-2.0/. |
| 24 | + * |
| 25 | + * SPDX-License-Identifier: EPL-2.0 |
| 26 | + * |
| 27 | + * Contributors: |
| 28 | + * See git history |
| 29 | + *******************************************************************************/ |
| 30 | +public class PDFSvgEmbeddingTest extends TestCase { |
| 31 | + |
| 32 | + private static final String SIMPLE_SVG = |
| 33 | + "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"100\" height=\"100\">" + |
| 34 | + "<rect x=\"10\" y=\"10\" width=\"80\" height=\"80\" fill=\"red\"/>" + |
| 35 | + "</svg>"; |
| 36 | + |
| 37 | + private static final String SVG_WITH_PATH = |
| 38 | + "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"200\" height=\"200\">" + |
| 39 | + "<path d=\"M10,10 L190,10 L190,190 L10,190 Z\" fill=\"blue\" stroke=\"black\" stroke-width=\"2\"/>" + |
| 40 | + "</svg>"; |
| 41 | + |
| 42 | + private static final String SVG_WITH_CIRCLE = |
| 43 | + "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"50\" height=\"50\">" + |
| 44 | + "<circle cx=\"25\" cy=\"25\" r=\"20\" fill=\"green\"/>" + |
| 45 | + "</svg>"; |
| 46 | + |
| 47 | + private static final String SVG_WITH_TEXT = |
| 48 | + "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"300\" height=\"50\">" + |
| 49 | + "<text x=\"10\" y=\"30\" font-family=\"Arial\" font-size=\"20\">Hello World</text>" + |
| 50 | + "</svg>"; |
| 51 | + |
| 52 | + public void testTranscodeSvgByteArrayToPdfTemplate() throws Exception { |
| 53 | + byte[] svgData = SIMPLE_SVG.getBytes(StandardCharsets.UTF_8); |
| 54 | + float width = 100, height = 100; |
| 55 | + |
| 56 | + PdfTemplate template = transSvgToTemplate(svgData, width, height); |
| 57 | + |
| 58 | + assertNotNull("PDF template must not be null", template); |
| 59 | + assertTrue("Template width must be positive", template.getWidth() > 0); |
| 60 | + assertTrue("Template height must be positive", template.getHeight() > 0); |
| 61 | + } |
| 62 | + |
| 63 | + public void testTranscodeSvgWithPath() throws Exception { |
| 64 | + byte[] svgData = SVG_WITH_PATH.getBytes(StandardCharsets.UTF_8); |
| 65 | + float width = 200, height = 200; |
| 66 | + |
| 67 | + PdfTemplate template = transSvgToTemplate(svgData, width, height); |
| 68 | + |
| 69 | + assertNotNull("PDF template must not be null", template); |
| 70 | + assertEquals("Template width must match", width, template.getWidth()); |
| 71 | + assertEquals("Template height must match", height, template.getHeight()); |
| 72 | + } |
| 73 | + |
| 74 | + public void testTranscodeSvgWithCircle() throws Exception { |
| 75 | + byte[] svgData = SVG_WITH_CIRCLE.getBytes(StandardCharsets.UTF_8); |
| 76 | + PdfTemplate template = transSvgToTemplate(svgData, 50, 50); |
| 77 | + assertNotNull("PDF template for circle SVG must not be null", template); |
| 78 | + } |
| 79 | + |
| 80 | + public void testTranscodeSvgWithText() throws Exception { |
| 81 | + byte[] svgData = SVG_WITH_TEXT.getBytes(StandardCharsets.UTF_8); |
| 82 | + PdfTemplate template = transSvgToTemplate(svgData, 300, 50); |
| 83 | + assertNotNull("PDF template for text SVG must not be null", template); |
| 84 | + } |
| 85 | + |
| 86 | + public void testGeneratedPdfIsReadableAndContainsSvgContent() throws Exception { |
| 87 | + byte[] svgData = SVG_WITH_PATH.getBytes(StandardCharsets.UTF_8); |
| 88 | + |
| 89 | + Document document = new Document(PageSize.A4); |
| 90 | + ByteArrayOutputStream pdfOut = new ByteArrayOutputStream(); |
| 91 | + PdfWriter writer = PdfWriter.getInstance(document, pdfOut); |
| 92 | + |
| 93 | + document.open(); |
| 94 | + |
| 95 | + PdfContentByte cb = writer.getDirectContent(); |
| 96 | + PdfTemplate svgTemplate = transSvgToTemplate(svgData, 200, 200); |
| 97 | + |
| 98 | + assertNotNull("SVG template must not be null", svgTemplate); |
| 99 | + cb.addTemplate(svgTemplate, 50, 500); |
| 100 | + |
| 101 | + document.newPage(); |
| 102 | + document.close(); |
| 103 | + |
| 104 | + byte[] pdfBytes = pdfOut.toByteArray(); |
| 105 | + assertTrue("PDF must be non-empty", pdfBytes.length > 0); |
| 106 | + assertTrue("PDF must start with %PDF-", |
| 107 | + new String(pdfBytes, 0, Math.min(pdfBytes.length, 5), StandardCharsets.ISO_8859_1).startsWith("%PDF-")); |
| 108 | + } |
| 109 | + |
| 110 | + public void testSvgEmbeddedAsVectorNotRasterized() throws Exception { |
| 111 | + byte[] svgData = SVG_WITH_PATH.getBytes(StandardCharsets.UTF_8); |
| 112 | + |
| 113 | + Document document = new Document(PageSize.A4); |
| 114 | + ByteArrayOutputStream pdfOut = new ByteArrayOutputStream(); |
| 115 | + PdfWriter writer = PdfWriter.getInstance(document, pdfOut); |
| 116 | + |
| 117 | + document.open(); |
| 118 | + |
| 119 | + PdfContentByte cb = writer.getDirectContent(); |
| 120 | + PdfTemplate svgTemplate = transSvgToTemplate(svgData, 200, 200); |
| 121 | + cb.addTemplate(svgTemplate, 50, 500); |
| 122 | + |
| 123 | + document.close(); |
| 124 | + |
| 125 | + byte[] pdfBytes = pdfOut.toByteArray(); |
| 126 | + String pdfContent = new String(pdfBytes, StandardCharsets.ISO_8859_1); |
| 127 | + |
| 128 | + assertTrue("PDF must contain vector path commands", |
| 129 | + pdfContent.contains(" m ") || pdfContent.contains(" l ")); |
| 130 | + |
| 131 | + assertFalse("PDF must NOT contain JPEG raster stream (DCTDecode)", |
| 132 | + pdfContent.contains("/DCTDecode")); |
| 133 | + } |
| 134 | + |
| 135 | + public void testSvgWithNamespaceEmbeddedCorrectly() throws Exception { |
| 136 | + String svgWithNs = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + |
| 137 | + "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" " + |
| 138 | + "width=\"100\" height=\"100\">" + |
| 139 | + "<rect x=\"0\" y=\"0\" width=\"100\" height=\"100\" fill=\"yellow\"/>" + |
| 140 | + "</svg>"; |
| 141 | + byte[] svgData = svgWithNs.getBytes(StandardCharsets.UTF_8); |
| 142 | + |
| 143 | + PdfTemplate template = transSvgToTemplate(svgData, 100, 100); |
| 144 | + assertNotNull("SVG with namespace must transcode to non-null template", template); |
| 145 | + } |
| 146 | + |
| 147 | + public void testEmptySvgDataThrowsException() throws Exception { |
| 148 | + try { |
| 149 | + transSvgToTemplate(new byte[0], 100, 100); |
| 150 | + fail("Empty SVG data should cause an exception"); |
| 151 | + } catch (Exception e) { |
| 152 | + } |
| 153 | + } |
| 154 | + |
| 155 | + public void testNullSvgDataDoesNotFail() throws Exception { |
| 156 | + PdfTemplate template = transSvgToTemplate(null, 100, 100); |
| 157 | + assertNotNull("Null SVG data should still produce a template (empty)", template); |
| 158 | + } |
| 159 | + |
| 160 | + private PdfTemplate transSvgToTemplate(byte[] svgData, float width, float height) throws Exception { |
| 161 | + ByteArrayOutputStream pdfOut = new ByteArrayOutputStream(); |
| 162 | + Document document = new Document(new org.openpdf.text.Rectangle(width + 20, height + 20)); |
| 163 | + PdfWriter writer = PdfWriter.getInstance(document, pdfOut); |
| 164 | + |
| 165 | + document.open(); |
| 166 | + PdfContentByte cb = writer.getDirectContent(); |
| 167 | + |
| 168 | + PdfTemplate template = cb.createTemplate(width, height); |
| 169 | + java.awt.Graphics2D g2D = template.createGraphics(width, height); |
| 170 | + |
| 171 | + PrintTranscoder transcoder = new PrintTranscoder(); |
| 172 | + if (svgData != null && svgData.length > 0) { |
| 173 | + transcoder.transcode(new TranscoderInput(new ByteArrayInputStream(svgData)), null); |
| 174 | + } |
| 175 | + transcoder.addTranscodingHint(SVGAbstractTranscoder.KEY_ALLOW_EXTERNAL_RESOURCES, Boolean.TRUE); |
| 176 | + |
| 177 | + java.awt.print.PageFormat pg = new java.awt.print.PageFormat(); |
| 178 | + java.awt.print.Paper p = new java.awt.print.Paper(); |
| 179 | + p.setSize(width, height); |
| 180 | + p.setImageableArea(0, 0, width, height); |
| 181 | + pg.setPaper(p); |
| 182 | + transcoder.print(g2D, pg, 0); |
| 183 | + |
| 184 | + g2D.dispose(); |
| 185 | + document.close(); |
| 186 | + |
| 187 | + return template; |
| 188 | + } |
| 189 | +} |
0 commit comments