/*- * Copyright 2020 Beuth Hochschule für Technik Berlin, Hochschule für Technik Stuttgart * * This file is part of CityDoctor2. * * CityDoctor2 is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * CityDoctor2 is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with CityDoctor2. If not, see . */ package de.hft.stuttgart.citydoctor2.reporting.pdf; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.net.URI; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; import java.util.StringJoiner; import javax.xml.XMLConstants; import javax.xml.transform.Result; import javax.xml.transform.Source; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.sax.SAXResult; import org.apache.fop.apps.FOPException; import org.apache.fop.apps.FOUserAgent; import org.apache.fop.apps.Fop; import org.apache.fop.apps.FopFactory; import org.apache.fop.events.EventFormatter; import org.apache.fop.events.model.EventSeverity; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.xmlgraphics.util.MimeConstants; import org.jdom2.Document; import org.jdom2.Element; import org.jdom2.JDOMException; import org.jdom2.Namespace; import org.jdom2.output.DOMOutputter; import de.hft.stuttgart.citydoctor2.checkresult.utility.CheckReportWriteException; /** * * @author Matthias Betz * */ public class PdfReport { private static final Logger logger = LogManager.getLogger(PdfReport.class); private static final String FRONT_PAGE = "front-page"; private static final String REGION_BODY = "region-body"; private static final String MASTER_NAME = "master-name"; private static final String SIMPLE_PAGE_MASTER = "simple-page-master"; private static final String MARGIN_BOTTOM = "margin-bottom"; private static final String FONT_SIZE = "font-size"; private static final String CENTER = "center"; private static final String XSL_REGION_BODY = "xsl-region-body"; private static final String TEXT_ALIGN = "text-align"; private static final String BLOCK = "block"; private static final String FLOW_NAME = "flow-name"; private static final String MASTER_REFERENCE = "master-reference"; private static final String PAGE_SEQUENCE = "page-sequence"; private static final FopFactory FOP_FACTORY; private static final String HEADLINE_FONT_SIZE = "24pt"; private static final String SUB_HEADLINE_FONT_SIZE = "18pt"; static final Namespace FO_NS = Namespace.getNamespace("fo", "http://www.w3.org/1999/XSL/Format"); static final Namespace SVG_NS = Namespace.getNamespace("svg", "http://www.w3.org/2000/svg"); private static final String COPY_RIGHT = "Copyright (c) 2020 HFT Stuttgart. All rights reserved." + " HFT Stuttgart and its licensors retain all intellectual property" + " and proprietary rights in and to this software and related documentation." + " Any use, reproduction, disclosure, or distribution of this software and" + " related documentation without an express license agreement from HFT Stuttgart is strictly prohibited."; private Document doc; private Element fileElement; private Element tocFlow; private Element pageFlow; private Element headerBlock; private List
sections; static { URI uri = new File(".").toURI(); FOP_FACTORY = FopFactory.newInstance(uri); } public PdfReport(String logoPath) { doc = new Document(); sections = new ArrayList<>(); Element root = new Element("root", FO_NS); doc.setRootElement(root); root.addNamespaceDeclaration(FO_NS); createLayout(root); writeFrontPage(root, logoPath); writeTableOfContent(root); createHeaderAndFooter(root); } public void writeSourceFileName(String sourceFile) { sourceFile = split(sourceFile, 35); fileElement.addContent("For File: " + sourceFile); headerBlock.addContent(sourceFile); } /** * Split string in multiple lines * * @param s the string to be split * @param size the number of characters per line * @return the split string concatenated with \n */ private String split(String s, int size) { if (s.length() <= 35) { return s; } StringJoiner joiner = new StringJoiner("\n"); for (int start = 0; start < s.length(); start += size) { joiner.add(s.substring(start, Math.min(s.length(), start + size))); } return joiner.toString(); } public Section createSection(String headline) { return createSection(headline, false); } public Section createSection(String headline, boolean newPage) { Section s = new Section(headline, 0, newPage); sections.add(s); pageFlow.addContent(s.getContentBlock()); return s; } public void save(OutputStream outFile) throws CheckReportWriteException { // finish writing the table of content, 3 layers for (Section s : sections) { tocFlow.addContent(s.getTocBlock()); for (Section ss : s.getSubSections()) { tocFlow.addContent(ss.getTocBlock()); for (Section sss : ss.getSubSections()) { tocFlow.addContent(sss.getTocBlock()); } } } try { FOUserAgent userAgent = FOP_FACTORY.newFOUserAgent(); userAgent.getEventBroadcaster().addEventListener(event -> { EventSeverity severity = event.getSeverity(); String msg = EventFormatter.format(event); if (severity == EventSeverity.ERROR) { logger.error(msg); } else if (severity == EventSeverity.FATAL) { logger.fatal(msg); } else if (severity == EventSeverity.INFO) { logger.info(msg); } else if (severity == EventSeverity.WARN) { logger.warn(msg); } }); Fop fop = FOP_FACTORY.newFop(MimeConstants.MIME_PDF, userAgent, outFile); TransformerFactory factory = TransformerFactory.newInstance(); // deactivate external dtds because of security issues factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); // identity transformer Transformer transformer = factory.newTransformer(); DOMOutputter domOutputter = new DOMOutputter(); Source src = new DOMSource(domOutputter.output(doc)); Result res = new SAXResult(fop.getDefaultHandler()); transformer.transform(src, res); outFile.flush(); } catch (FOPException | JDOMException | TransformerException | IOException e) { throw new CheckReportWriteException(e); } } private void createHeaderAndFooter(Element root) { Element pageSequence = new Element(PAGE_SEQUENCE, FO_NS); root.addContent(pageSequence); pageSequence.setAttribute("initial-page-number", "1"); pageSequence.setAttribute(MASTER_REFERENCE, "content"); Element headerContent = new Element("static-content", FO_NS); pageSequence.addContent(headerContent); headerContent.setAttribute(FLOW_NAME, "header"); headerBlock = new Element(BLOCK, FO_NS); applyFont(headerBlock); headerBlock.setAttribute(FONT_SIZE, "8pt"); headerBlock.setAttribute("text-align-last", "justify"); Element inlineHeader = new Element("inline", FO_NS); inlineHeader.addContent("Check Report"); headerBlock.addContent(inlineHeader); headerBlock.addContent(new Element("leader", FO_NS)); headerContent.addContent(headerBlock); Element footerContent = new Element("static-content", FO_NS); pageSequence.addContent(footerContent); footerContent.setAttribute(FLOW_NAME, "footer"); Element pageNumberBlock = new Element(BLOCK, FO_NS); footerContent.addContent(pageNumberBlock); pageNumberBlock.setAttribute(TEXT_ALIGN, "end"); pageNumberBlock.addContent(new Element("page-number", FO_NS)); pageFlow = new Element("flow", FO_NS); pageFlow.setAttribute(FLOW_NAME, XSL_REGION_BODY); pageSequence.addContent(pageFlow); } private void writeTableOfContent(Element root) { Element pageSequence = new Element(PAGE_SEQUENCE, FO_NS); root.addContent(pageSequence); pageSequence.setAttribute(MASTER_REFERENCE, "toc"); tocFlow = new Element("flow", FO_NS); pageSequence.addContent(tocFlow); tocFlow.setAttribute(FLOW_NAME, XSL_REGION_BODY); Element title = new Element(BLOCK, FO_NS); tocFlow.addContent(title); applyHeadlineAttributes(title); title.addContent("Table of Contents"); } private void writeFrontPage(Element root, String logoPath) { Element pageSequence = new Element(PAGE_SEQUENCE, FO_NS); root.addContent(pageSequence); pageSequence.setAttribute(MASTER_REFERENCE, FRONT_PAGE); Element frontPageFlow = new Element("flow", FO_NS); pageSequence.addContent(frontPageFlow); frontPageFlow.setAttribute(FLOW_NAME, XSL_REGION_BODY); Element title = new Element(BLOCK, FO_NS); frontPageFlow.addContent(title); title.setAttribute(TEXT_ALIGN, CENTER); applyFont(title); title.setAttribute(FONT_SIZE, "34pt"); title.setAttribute("font-weight", "bold"); title.addContent("Check Report"); Element blockPicture = new Element(BLOCK, FO_NS); frontPageFlow.addContent(blockPicture); blockPicture.setAttribute(TEXT_ALIGN, CENTER); Element externalPicture = new Element("external-graphic", FO_NS); blockPicture.addContent(externalPicture); externalPicture.setAttribute("src", logoPath); fileElement = new Element(BLOCK, FO_NS); frontPageFlow.addContent(fileElement); fileElement.setAttribute(TEXT_ALIGN, CENTER); applyFont(fileElement); fileElement.setAttribute(FONT_SIZE, "24pt"); String date = ZonedDateTime.now().format(DateTimeFormatter.RFC_1123_DATE_TIME); Element createdOn = new Element(BLOCK, FO_NS); frontPageFlow.addContent(createdOn); createdOn.setAttribute(TEXT_ALIGN, CENTER); createdOn.setAttribute("space-before", "10mm"); applyFont(createdOn); createdOn.addContent("Created on " + date); Element copyRightText = createTextElement(COPY_RIGHT); frontPageFlow.addContent(copyRightText); copyRightText.setAttribute("space-before", "100mm"); copyRightText.setAttribute(FONT_SIZE, "8pt"); } static void applyHeadlineAttributes(Element headline) { applyFont(headline); headline.setAttribute(FONT_SIZE, HEADLINE_FONT_SIZE); headline.setAttribute("font-weight", "bold"); headline.setAttribute("margin-top", "10mm"); headline.setAttribute(MARGIN_BOTTOM, "10mm"); } static void applySubHeadlineAttributes(Element subHeadline) { applyFont(subHeadline); subHeadline.setAttribute(FONT_SIZE, SUB_HEADLINE_FONT_SIZE); subHeadline.setAttribute(MARGIN_BOTTOM, "10mm"); } static void applySubSubHeadlineAttributes(Element h) { applyFont(h); h.setAttribute(FONT_SIZE, "14pt"); h.setAttribute(MARGIN_BOTTOM, "5mm"); } static void applyFont(Element e) { e.setAttribute("font-family", "Helvetica"); } static void applySolidBorder(Element e) { e.setAttribute("border-style", "solid"); e.setAttribute("border-width", "1pt"); } private void createLayout(Element root) { Element layoutMaster = new Element("layout-master-set", FO_NS); root.addContent(layoutMaster); Element firstPageMaster = new Element(SIMPLE_PAGE_MASTER, FO_NS); layoutMaster.addContent(firstPageMaster); firstPageMaster.setAttribute(MASTER_NAME, FRONT_PAGE); applyPageSize(firstPageMaster); Element firstPageRegionBody = new Element(REGION_BODY, FO_NS); firstPageMaster.addContent(firstPageRegionBody); Element tableOfContentLayout = new Element(SIMPLE_PAGE_MASTER, FO_NS); layoutMaster.addContent(tableOfContentLayout); tableOfContentLayout.setAttribute(MASTER_NAME, "toc"); applyPageSize(tableOfContentLayout); Element tocRegionBody = new Element(REGION_BODY, FO_NS); tableOfContentLayout.addContent(tocRegionBody); Element contentLayout = new Element(SIMPLE_PAGE_MASTER, FO_NS); layoutMaster.addContent(contentLayout); contentLayout.setAttribute(MASTER_NAME, "content"); applyPageSize(contentLayout); Element contentRegionBody = new Element(REGION_BODY, FO_NS); contentRegionBody.setAttribute("margin", "5mm"); contentLayout.addContent(contentRegionBody); Element contentRegionBefore = new Element("region-before", FO_NS); contentLayout.addContent(contentRegionBefore); contentRegionBefore.setAttribute("region-name", "header"); contentRegionBefore.setAttribute("extent", "10mm"); Element contentRegionAfter = new Element("region-after", FO_NS); contentLayout.addContent(contentRegionAfter); contentRegionAfter.setAttribute("region-name", "footer"); contentRegionAfter.setAttribute("extent", "10mm"); contentRegionAfter.setAttribute("display-align", "after"); } static Element createTextElement(String text) { Element textElement = new Element(BLOCK, FO_NS); textElement.setAttribute("space-after", "6pt"); applyFont(textElement); textElement.addContent(text); return textElement; } private void applyPageSize(Element pageMaster) { pageMaster.setAttribute("margin-left", "15mm"); pageMaster.setAttribute("margin-right", "15mm"); pageMaster.setAttribute("margin-top", "10mm"); pageMaster.setAttribute(MARGIN_BOTTOM, "10mm"); pageMaster.setAttribute("page-height", "297mm"); pageMaster.setAttribute("page-width", "210mm"); } }