/*-
* 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");
}
}