diff --git a/pom.xml b/pom.xml index 37692a70..7df23694 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.metanorma.fop mn2pdf - 1.13 + 1.14 Metanorma XML to PDF converter jar https://www.metanorma.com diff --git a/src/main/java/com/metanorma/fop/Util.java b/src/main/java/com/metanorma/fop/Util.java index ae0271a7..f808cae5 100644 --- a/src/main/java/com/metanorma/fop/Util.java +++ b/src/main/java/com/metanorma/fop/Util.java @@ -11,6 +11,7 @@ import java.nio.channels.ReadableByteChannel; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Base64; import java.util.Enumeration; import java.util.jar.Attributes; import java.util.jar.Manifest; @@ -124,4 +125,15 @@ public static String getAppVersion() { return version; } + + public static String getDecodedBase64SVGnode(String encodedString) { //throws SAXException, IOException, ParserConfigurationException { + byte[] decodedBytes = Base64.getDecoder().decode(encodedString); + String decodedString = new String(decodedBytes); + return decodedString; + /*if (decodedString.startsWith("") + 2); + } else { + return decodedString; + }*/ + } } diff --git a/src/main/java/com/metanorma/fop/mn2pdf.java b/src/main/java/com/metanorma/fop/mn2pdf.java index 5e7e3ff1..3b7e0967 100644 --- a/src/main/java/com/metanorma/fop/mn2pdf.java +++ b/src/main/java/com/metanorma/fop/mn2pdf.java @@ -3,16 +3,25 @@ import java.io.BufferedOutputStream; import java.io.BufferedWriter; import java.io.File; +import java.io.FileInputStream; import java.io.FileOutputStream; -import java.io.FileWriter; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; -import java.io.StringReader; import java.io.StringWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.security.CodeSource; import java.text.MessageFormat; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParserFactory; import javax.xml.transform.ErrorListener; @@ -22,6 +31,7 @@ import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; import javax.xml.transform.sax.SAXResult; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; @@ -42,6 +52,11 @@ import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; import org.xml.sax.SAXException; @@ -120,6 +135,10 @@ public class mn2pdf { static final int ERROR_EXIT_CODE = -1; + static final String TMPDIR = System.getProperty("java.io.tmpdir"); + + final Path tmpfilepath = Paths.get(TMPDIR, UUID.randomUUID().toString()); + /** * Converts an XML file to a PDF file using FOP * @@ -130,13 +149,19 @@ public class mn2pdf { * @throws IOException In case of an I/O problem * @throws FOPException, SAXException In case of a FOP problem */ - public void convertmn2pdf(String fontPath, File xml, File xsl, File pdf) throws IOException, FOPException, SAXException, TransformerException, TransformerConfigurationException, TransformerConfigurationException { + public void convertmn2pdf(String fontPath, File xml, File xsl, File pdf) throws IOException, FOPException, SAXException, TransformerException, TransformerConfigurationException, TransformerConfigurationException, ParserConfigurationException { + + + String imagesxml = getImageFilePath(xml); + try { //Setup XSLT TransformerFactory factory = TransformerFactory.newInstance(); Transformer transformer = factory.newTransformer(new StreamSource(xsl)); + transformer.setParameter("svg_images", imagesxml); + //Setup input for XSLT transformation Source src = new StreamSource(xml); @@ -174,11 +199,22 @@ public void convertmn2pdf(String fontPath, File xml, File xsl, File pdf) throws fontcfg.setPDFUAmode("DISABLED"); runFOP(fontcfg, src, pdf, transformer); } - } catch (Exception e) { e.printStackTrace(System.err); System.exit(ERROR_EXIT_CODE); } + // flush temporary folder + if (!DEBUG) { + //Files.deleteIfExists(tmpfilepath); + try { + Files.walk(tmpfilepath) + .sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete); + } catch (Exception ex) { + ex.printStackTrace(); + } + } } private void runFOP (fontConfig fontcfg, Source src, File pdf, Transformer transformer) throws IOException, FOPException, SAXException, TransformerException, TransformerConfigurationException, TransformerConfigurationException { @@ -237,7 +273,7 @@ public void error(TransformerException exc) public void fatalError(TransformerException exc) throws TransformerException { String excstr=exc.toString(); - if (excstr.contains("PDFConformanceException") && excstr.contains("all fonts, even the base 14 fonts, have to be embedded") && !PDFUA_error) { + if (excstr.contains("PDFConformanceException") && excstr.contains("PDF/UA-1") && !PDFUA_error) { // excstr.contains("all fonts, even the base 14 fonts, have to be embedded") System.err.println(exc.toString()); PDFUA_error = true; } else { @@ -347,4 +383,72 @@ private static String getJaxpImplementationInfo(String componentName, Class comp componentClass.getName(), source == null ? "Java Runtime" : source.getLocation()); } + + private String getImageFilePath(File xml) { + try { + DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); + InputStream xmlstream = new FileInputStream(xml); + Document sourceXML = dBuilder.parse(xmlstream); + NodeList images = sourceXML.getElementsByTagName("image"); + + HashMap svgmap = new HashMap<>(); + for (int i = 0; i < images.getLength(); i++) { + Node image = images.item(i); + Node mimetype = image.getAttributes().getNamedItem("mimetype"); + if (mimetype != null && mimetype.getTextContent().equals("image/svg+xml")) { + // decode base64 svg into external tmp file + Node svg_src = image.getAttributes().getNamedItem("src"); + Node svg_id = image.getAttributes().getNamedItem("id"); + if (svg_src != null && svg_id != null && svg_src.getTextContent().startsWith("data:image")) { + String base64svg = svg_src.getTextContent().substring(svg_src.getTextContent().indexOf("base64,")+7); + String xmlsvg = Util.getDecodedBase64SVGnode(base64svg); + try { + Files.createDirectories(tmpfilepath); + String id = svg_id.getTextContent(); + Path svgpath = Paths.get(tmpfilepath.toString(), id + ".svg"); + try (BufferedWriter bw = Files.newBufferedWriter(svgpath)) { + bw.write(xmlsvg); + } + svgmap.put(id, svgpath.toFile().toURI().toURL().toString()); + } catch (IOException ex) { + System.err.println("Can't save svg file into a temporary directory " + tmpfilepath.toString()); + ex.printStackTrace();; + } + } + } + } + if (!svgmap.isEmpty()) { + // crate map file for svg images + DocumentBuilderFactory documentFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder documentBuilder = documentFactory.newDocumentBuilder(); + Document document = documentBuilder.newDocument(); + Element root = document.createElement("images"); + document.appendChild(root); + for (Map.Entry item : svgmap.entrySet()) { + Element image = document.createElement("image"); + root.appendChild(image); + Attr attr_id = document.createAttribute("id"); + attr_id.setValue(item.getKey()); + image.setAttributeNode(attr_id); + Attr attr_path = document.createAttribute("src"); + attr_path.setValue(item.getValue()); + image.setAttributeNode(attr_path); + } + // save xml 'images.xml' with svg links to temporary folder + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + Transformer transformer = transformerFactory.newTransformer(); + DOMSource domSource = new DOMSource(document); + Path outputPath = Paths.get(tmpfilepath.toString(), "images.xml"); + StreamResult streamResult = new StreamResult(outputPath.toFile()); + transformer.transform(domSource, streamResult); + + return outputPath.toString(); + } + } catch (Exception ex) { + System.err.println("Can't save images.xml into temporary folder"); + ex.printStackTrace(); + } + return ""; + } }