
com.vectorprint.report.itext.EventHelper Maven / Gradle / Ivy
package com.vectorprint.report.itext;
/*
* #%L
* VectorPrintReport4.0
* %%
* Copyright (C) 2012 - 2013 VectorPrint
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
* #L%
*/
//~--- non-JDK imports --------------------------------------------------------
import com.itextpdf.text.BadElementException;
import com.itextpdf.text.Chunk;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Element;
import com.itextpdf.text.Font;
import com.itextpdf.text.Image;
import com.itextpdf.text.Phrase;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.exceptions.IllegalPdfSyntaxException;
import com.itextpdf.text.pdf.ColumnText;
import com.itextpdf.text.pdf.PdfAction;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfName;
import com.itextpdf.text.pdf.PdfPCell;
import com.itextpdf.text.pdf.PdfPTable;
import com.itextpdf.text.pdf.PdfPageEventHelper;
import com.itextpdf.text.pdf.PdfTemplate;
import com.itextpdf.text.pdf.PdfWriter;
import com.vectorprint.VectorPrintException;
import com.vectorprint.VectorPrintRuntimeException;
import com.vectorprint.configuration.EnhancedMap;
import com.vectorprint.configuration.annotation.Setting;
import com.vectorprint.report.ReportConstants;
import static com.vectorprint.report.ReportConstants.DEBUG;
import com.vectorprint.report.data.ReportDataAware;
import com.vectorprint.report.data.ReportDataHolder;
import com.vectorprint.report.data.types.ValueHelper;
import com.vectorprint.report.itext.debug.DebugHelper;
import com.vectorprint.report.itext.debug.DebugStyler;
import com.vectorprint.report.itext.style.BaseStyler;
import com.vectorprint.report.itext.style.DefaultStylerFactory;
import com.vectorprint.report.itext.style.DocumentStyler;
import com.vectorprint.report.itext.style.StylerFactory;
import com.vectorprint.report.itext.style.stylers.Advanced;
import java.awt.Color;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
//~--- JDK imports ------------------------------------------------------------
/**
* Responsible for printing headers, footers, debugging and failure information. Note that this handler uses a
* {@link PdfTemplate} with the dimensions of the first page of the document to afterwards write information
* (pagenumber, failure header) to pages, hence for now positioning may fail when using different page sizes.
*
* @author Eduard Drenth at VectorPrint.nl
* @param RD the type of data this helper can deal with
*/
public class EventHelper extends PdfPageEventHelper
implements StylerFactory, ReportDataAware {
public static final String UPTO = "upto";
private static final Logger log = Logger.getLogger(EventHelper.class.getName());
private ElementProducer elementProducer;
private RD reportData;
private PdfTemplate template;
private StylerFactory stylerFactory;
private boolean failuresHereAfter = false, debugHereAfter = false;
private int lastPage;
private final java.util.List doForAllPages = new ArrayList<>(1);
private final Map> doOnGenericTag = new HashMap<>(10);
private final Map imageChunks = new HashMap<>(10);
private final Map rectangles = new HashMap<>(10);
private ItextHelper itextHelper;
protected ElementProducer getElementProducer() {
return elementProducer;
}
void setElementProvider(ElementProducer elementProvider) {
this.elementProducer = elementProvider;
if (elementProvider instanceof DefaultElementProducer) {
((DefaultElementProducer) elementProvider).setPh(this);
}
}
/**
* key for looking up stylers in the settings that will be used for styling the footer
*/
public static final String PAGEFOOTERSTYLEKEY = "pagefooter";
/**
* the default stylers used to style the footer
*/
public static final String[] PAGEFOOTERSTYLE = new String[]{"Font(size=12)","Border(position=top,borderwidth=1)","Padding(padding=0)"};
/**
* key for looking up stylers in the settings that will be used for styling the footer table. The default value for
* this will be calculated based on document measures
*/
public static final String PAGEFOOTERTABLEKEY = "pagefootertable";
void setItextStylerFactory(StylerFactory itextStylerFactory) {
this.stylerFactory = itextStylerFactory;
itextHelper = new ItextHelper();
}
/**
* prepares template for printing header and footer
*
* @param writer
* @param document
*/
@Override
public final void onOpenDocument(PdfWriter writer, Document document) {
super.onOpenDocument(writer, document);
template = writer.getDirectContent().createTemplate(document.getPageSize().getWidth(), document.getPageSize().getHeight());
if (!getSettings().containsKey(PAGEFOOTERSTYLEKEY)) {
getSettings().put(PAGEFOOTERSTYLEKEY, PAGEFOOTERSTYLE);
}
if (!getSettings().containsKey(PAGEFOOTERTABLEKEY)) {
float tot = ItextHelper.ptsToMm(document.getPageSize().getWidth() - document.leftMargin() - document.rightMargin());
getSettings().put(PAGEFOOTERTABLEKEY, new StringBuilder("Table(columns=3,widths=")
.append(Math.round(tot * getSettings().getFloatProperty(0.85f, "footerleftwidthpercentage"))).append('|')
.append(Math.round(tot * getSettings().getFloatProperty(0.14f, "footermiddlewidthpercentage"))).append('|')
.append(Math.round(tot * getSettings().getFloatProperty(0.01f, "footerrightwidthpercentage"))).append(')').toString()
);
}
}
/**
* prints debug info when property debug is true, calls renderHeader and renderFooter and
* {@link Advanced#draw(com.itextpdf.text.Rectangle, java.lang.String) } with {@link Document#getPageSize() }
* and null for {@link DefaultStylerFactory#PAGESTYLERS}.
*
* @param writer
* @param document
*/
@Override
public final void onEndPage(PdfWriter writer, Document document) {
super.onEndPage(writer, document);
sanitize(writer);
try {
if (failuresHereAfter || debugHereAfter) {
PdfContentByte bg = writer.getDirectContentUnder();
Rectangle rect = writer.getPageSize();
rect.setBackgroundColor(itextHelper.fromColor(getSettings().getColorProperty(new Color(240, 240, 240), "legendbackground")));
bg.rectangle(rect);
bg.closePathFillStroke();
} else {
for (Advanced a : doForAllPages) {
try {
if (a.shouldDraw(null)) {
a.draw(document.getPageSize(), null);
}
} catch (VectorPrintException ex) {
throw new VectorPrintRuntimeException(ex);
}
}
}
if (!debugHereAfter && getSettings().getBooleanProperty(false, DEBUG)) {
PdfContentByte canvas = writer.getDirectContent();
Rectangle rect = new Rectangle(document.leftMargin(),
document.bottomMargin(),
document.right() - document.rightMargin(),
document.top() - document.topMargin());
DebugHelper.debugRect(canvas, rect, new float[]{10, 2}, 0.3f, getSettings(), stylerFactory.getLayerManager());
}
renderHeader(writer, document);
maxTagForGenericTagOnPage = ((DefaultElementProducer) elementProducer).getAdvancedTag();
if (getSettings().getBooleanProperty(Boolean.FALSE, ReportConstants.PRINTFOOTER)) {
renderFooter(writer, document);
} else {
log.warning("not printing footer, if you want page footers set " + ReportConstants.PRINTFOOTER + " to true");
}
maxTagForGenericTagOnPage = Integer.MAX_VALUE;
} catch (VectorPrintException | DocumentException | InstantiationException | IllegalAccessException e) {
throw new VectorPrintRuntimeException("failed to create the report header or footer: ", e);
}
}
public static void sanitize(PdfWriter writer) {
while (true) {
try {
writer.getDirectContent().sanityCheck();
break;
} catch (IllegalPdfSyntaxException e) {
if (e.getMessage().toLowerCase().contains("layer")) {
writer.getDirectContent().endLayer();
} else if (e.getMessage().toLowerCase().contains("state")) {
writer.getDirectContent().restoreState();
} else {
break;
}
}
}
while (true) {
try {
writer.getDirectContentUnder().sanityCheck();
break;
} catch (IllegalPdfSyntaxException e) {
if (e.getMessage().toLowerCase().contains("layer")) {
writer.getDirectContentUnder().endLayer();
} else if (e.getMessage().toLowerCase().contains("state")) {
writer.getDirectContentUnder().restoreState();
} else {
break;
}
}
}
}
/**
* when failure information is appended to the report, a header on each page will be printed refering to this
* information.
*
* @param template
* @param x
* @param y
*/
protected void printFailureHeader(PdfTemplate template, float x, float y) {
Font f = DebugHelper.debugFontLink(template, getSettings());
Chunk c = new Chunk(getSettings().getProperty("failures in report, see end of report", "failureheader"), f);
ColumnText.showTextAligned(template, Element.ALIGN_LEFT, new Phrase(c), x, y, 0);
}
/**
* When the setting {@link ReportConstants#PRINTFOOTER} is true prints the total number of pages on each page when
* the document is closed. Note that
*
* @param template
* @see #PAGEFOOTERSTYLEKEY
* @param x
* @param y
*/
protected void printTotalPages(PdfTemplate template, float x, float y) throws VectorPrintException, InstantiationException, IllegalAccessException {
if (getSettings().getBooleanProperty(Boolean.FALSE, ReportConstants.PRINTFOOTER)) {
Phrase p = elementProducer.createElement(String.valueOf(lastPage), Phrase.class, stylerFactory.getStylers(PAGEFOOTERSTYLEKEY));
ColumnText.showTextAligned(template, Element.ALIGN_LEFT, p, x, y, 0);
}
}
/**
* prints a failure and / or a debug header when applicable.
*
* @see #getTemplateImage(com.itextpdf.text.pdf.PdfTemplate)
* @param writer
* @param document
* @throws DocumentException
* @throws VectorPrintException
*/
private final void renderHeader(PdfWriter writer, Document document) throws DocumentException, VectorPrintException {
if ((!debugHereAfter && getSettings().getBooleanProperty(false, DEBUG))
|| (!failuresHereAfter && !getSettings().getBooleanProperty(false, DEBUG))) {
writer.getDirectContent().addImage(getTemplateImage(template));
if (getSettings().getBooleanProperty(false, DEBUG)) {
ArrayList a = new ArrayList(2);
a.add(PdfName.TOGGLE);
a.add(elementProducer.initLayerGroup(DEBUG, writer.getDirectContent()));
PdfAction act = PdfAction.setOCGstate(a, true);
Chunk h = new Chunk("toggle debug info", DebugHelper.debugFontLink(writer.getDirectContent(), getSettings())).setAction(act);
ColumnText.showTextAligned(writer.getDirectContent(), Element.ALIGN_LEFT, new Phrase(h), 10, document.top() - 15, 0);
Font f = DebugHelper.debugFontLink(writer.getDirectContent(), getSettings());
// act = PdfAction.gotoLocalPage("debugpage", true);
elementProducer.startLayerInGroup(DEBUG, writer.getDirectContent());
h = new Chunk(getSettings().getProperty("go to debug legend", "debugheader"), f).setLocalGoto(BaseReportGenerator.DEBUGPAGE);
ColumnText.showTextAligned(writer.getDirectContent(), Element.ALIGN_LEFT, new Phrase(h), 10, document.top() - 3, 0);
writer.getDirectContent().endLayer();
}
}
}
/**
* calls {@link #printTotalPages(com.itextpdf.text.pdf.PdfTemplate, float, float) }
* with {@link #PAGEFOOTERSTYLE a font from setup}, document.right() and the calculated bottom of the footertable.
* Clears the layermanager. When applicable calls {@link #printFailureHeader(com.itextpdf.text.pdf.PdfTemplate, float, float)
* }
*
* @param writer
* @param document
*/
@Override
public void onCloseDocument(PdfWriter writer, Document document) {
super.onCloseDocument(writer, document);
if (getSettings().getBooleanProperty(Boolean.FALSE, ReportConstants.PRINTFOOTER)) {
try {
printTotalPages(template, document.right(), footerBottom);
} catch (VectorPrintException | InstantiationException | IllegalAccessException ex) {
throw new VectorPrintRuntimeException(ex);
}
}
if (failuresHereAfter) {
printFailureHeader(template, 10, document.top() - 10);
}
}
/**
* creates a footer cell using an instance of {@link com.vectorprint.report.itext.styleAndAddToReport.stylers.Font}
*
* @see #PAGEFOOTERSTYLEKEY
* @see StylerFactory#getStylers(java.lang.String...)
* @param val
* @return
* @throws VectorPrintException
*/
private PdfPCell createFooterCell(Object val) throws VectorPrintException {
try {
return elementProducer.createElement(val, PdfPCell.class, getStylers(PAGEFOOTERSTYLEKEY));
} catch (InstantiationException | IllegalAccessException ex) {
throw new VectorPrintException(ex);
}
}
private int maxTagForGenericTagOnPage = Integer.MAX_VALUE;
/**
* prints a footer table with a line at the top, a date and page numbering, second cell will be right aligned
*
* @see #PAGEFOOTERTABLEKEY
* @see #PAGEFOOTERSTYLEKEY
*
* @param writer
* @param document
* @throws DocumentException
* @throws VectorPrintException
*/
protected void renderFooter(PdfWriter writer, Document document) throws DocumentException, VectorPrintException, InstantiationException, IllegalAccessException {
if (!debugHereAfter && !failuresHereAfter) {
PdfPTable footerTable = elementProducer.createElement(null, PdfPTable.class, getStylers(PAGEFOOTERTABLEKEY));
footerTable.addCell(createFooterCell(ValueHelper.createDate(new Date())));
String pageText = writer.getPageNumber() + getSettings().getProperty(" of ", UPTO);
PdfPCell c = createFooterCell(pageText);
c.setHorizontalAlignment(Element.ALIGN_RIGHT);
footerTable.addCell(c);
footerTable.addCell(createFooterCell(new Chunk()));
footerTable.writeSelectedRows(0, -1, document.getPageSize().getLeft() + document.leftMargin(),
document.getPageSize().getBottom(document.bottomMargin()), writer.getDirectContentUnder());
footerBottom = document.bottom() - footerTable.getTotalHeight();
}
}
private float footerBottom = 0;
private Image templateImage;
/**
* this image will be used for painting the total number of pages and for a failure header when failures are printed
* inside the report.
*
* @see #printFailureHeader(com.itextpdf.text.pdf.PdfTemplate, float, float)
* @see #printTotalPages(com.itextpdf.text.pdf.PdfTemplate, float, float)
* @param template
* @return
* @throws BadElementException
*/
protected Image getTemplateImage(PdfTemplate template) throws BadElementException {
if (templateImage == null) {
templateImage = Image.getInstance(template);
templateImage.setAbsolutePosition(0, 0);
}
return templateImage;
}
@Override
public void setReportDataHolder(RD reportData) {
this.reportData = reportData;
}
@Override
public RD getReportDataHolder() {
return this.reportData;
}
/**
* Calls the super and {@link Advanced#draw(com.itextpdf.text.Rectangle, java.lang.String) } for each Advanced styler
* registered. Adds a debugging link for images when in debug mode.
*
* @param writer
* @param document
* @param rect
* @param genericTag
* @see #addDelayedStyler(java.lang.String, java.util.Collection, com.itextpdf.text.Chunk)
* @see Advanced#addDelayedData(java.lang.String, com.itextpdf.text.Chunk)
* @see VectorPrintDocument
*/
@Override
public final void onGenericTag(PdfWriter writer, Document document, final Rectangle rect, String genericTag) {
// if (log.isLoggable(Level.FINE)) {
// Collection av = doOnGenericTag.get(genericTag);
// String data = null;
// if (av!=null) {
// for (Advanced a : av) {
// data += a.getDelayed(genericTag).getDataPart();
// break;
// }
// }
// System.out.println("wrapped: " + carriageReturns.toString() + ", " + genericTag + " " + data + " " + rect.toString() + ", x=" + rect.getLeft());
// }
if (doOnGenericTag.get(genericTag) != null
&& !genericTag.startsWith(VectorPrintDocument.DRAWNEAR)
&& !genericTag.startsWith(VectorPrintDocument.DRAWSHADOW)) {
int i = -1;
for (Advanced a : doOnGenericTag.get(genericTag)) {
++i;
if (genericTag.startsWith(DefaultElementProducer.ADV)
&& Integer.parseInt(genericTag.replace(DefaultElementProducer.ADV, "")) > maxTagForGenericTagOnPage) {
continue;
}
try {
if (a.shouldDraw(a.getDelayed(genericTag).getData())) {
if (a instanceof DebugStyler && imageChunks.containsKey(genericTag)) {
Chunk wrapper = imageChunks.get(genericTag);
Object[] atts = (Object[]) wrapper.getAttributes().get(Chunk.IMAGE);
Rectangle shifted = new Rectangle(rect);
shifted.setLeft(shifted.getLeft() + (Float) atts[1]);
shifted.setRight(shifted.getRight() + (Float) atts[1]);
shifted.setTop(shifted.getTop() + (Float) atts[2]);
shifted.setBottom(shifted.getBottom() + (Float) atts[2]);
a.draw(shifted, genericTag);
} else if (!genericTag.startsWith(VectorPrintDocument.IMG_DEBUG)) {
a.draw(rect, genericTag);
}
}
} catch (VectorPrintException ex) {
throw new VectorPrintRuntimeException(ex);
}
}
}
// images
if (genericTag.startsWith(VectorPrintDocument.IMG_DEBUG) && getSettings().getBooleanProperty(false, DEBUG)) {
// only now we can define a goto action, we know the position of the image
if (rectangles.containsKey(genericTag)) {
Rectangle rectangle = imageRectFromChunk(genericTag, rect);
DebugHelper.debugAnnotation(rectangle,
genericTag.replaceFirst(VectorPrintDocument.IMG_DEBUG, ""), writer);
} else {
DebugHelper.debugAnnotation(rect, genericTag.replaceFirst(VectorPrintDocument.IMG_DEBUG, ""), writer);
}
}
if (genericTag.startsWith(VectorPrintDocument.DRAWNEAR)) {
Rectangle rectangle = imageRectFromChunk(genericTag, rect);
com.vectorprint.report.itext.style.stylers.Image image = (com.vectorprint.report.itext.style.stylers.Image) doOnGenericTag.get(genericTag).iterator().next();
short i = -1;
for (Advanced a : doOnGenericTag.get(genericTag)) {
try {
if (++i > 0 && a.shouldDraw(a.getDelayed(genericTag).getData())) {
if (getSettings().getBooleanProperty(false, DEBUG)) {
DebugHelper.styleLink(writer.getDirectContent(), a.getStyleClass(), "draw near", rectangle.getLeft(), rectangle.getTop(), getSettings(), elementProducer);
}
a.draw(rectangle, genericTag);
}
} catch (VectorPrintException ex) {
throw new VectorPrintRuntimeException(ex);
}
}
}
if (genericTag.startsWith(VectorPrintDocument.DRAWSHADOW)) {
// we know the position of the image
Rectangle r = imageRectFromChunk(genericTag, rect);
com.vectorprint.report.itext.style.stylers.Image image = (com.vectorprint.report.itext.style.stylers.Image) doOnGenericTag.get(genericTag).iterator().next();
try {
image.drawShadow(r.getLeft(), r.getBottom(), r.getWidth(), r.getHeight(),genericTag.replaceFirst(VectorPrintDocument.DRAWSHADOW, ""));
} catch (VectorPrintException ex) {
throw new VectorPrintRuntimeException(ex);
}
}
}
private Rectangle imageRectFromChunk(String genericTag, Rectangle rect) {
Image r = rectangles.get(genericTag);
return new Rectangle(rect.getLeft(),
rect.getTop() - r.getScaledHeight() + Y_POSITION_FIX,
rect.getLeft() + r.getScaledWidth(),
rect.getTop() + Y_POSITION_FIX);
}
@Setting(keys = "yPositionImageFix")
private Integer Y_POSITION_FIX = 2;
@Override
public EnhancedMap getSettings() {
return stylerFactory.getSettings();
}
public boolean isFailuresHereAfter() {
return failuresHereAfter;
}
public void setFailuresHereAfter(boolean failuresHereAfter) {
this.failuresHereAfter = failuresHereAfter;
}
public boolean isDebugHereAfter() {
return debugHereAfter;
}
public void setDebugHereAfter(boolean debugHereAfter) {
this.debugHereAfter = debugHereAfter;
}
public int getLastPage() {
return lastPage;
}
public void setLastPage(int lastPage) {
this.lastPage = lastPage;
}
@Override
public List getStylers(String... styleClasses) throws VectorPrintException {
return stylerFactory.getStylers(styleClasses);
}
@Override
public DocumentStyler getDocumentStyler() throws VectorPrintException {
return stylerFactory.getDocumentStyler();
}
@Override
public Map getStylerSetup() {
return stylerFactory.getStylerSetup();
}
@Override
public void setImageLoader(ImageLoader imageLoader) {
}
public void addStylerForEachPage(Advanced advanced) {
doForAllPages.add(advanced);
}
/**
* add a styler that will do its styling in {@link #onGenericTag(com.itextpdf.text.pdf.PdfWriter, com.itextpdf.text.Document, com.itextpdf.text.Rectangle, java.lang.String)
* }.
*
* @param tag
* @param advanced
* @param chunk used when debugging, for placing debug info at the right position in the pdf
* @see Chunk#Chunk(com.itextpdf.text.Image, float, float, boolean)
*/
public void addDelayedStyler(String tag, Collection advanced, Chunk chunk) {
addDelayedStyler(tag, advanced, chunk, null);
}
/**
* add a styler that will do its styling in
* {@link #onGenericTag(com.itextpdf.text.pdf.PdfWriter, com.itextpdf.text.Document, com.itextpdf.text.Rectangle, java.lang.String)}.
*
* @param tag
* @param advanced
* @param chunk used when debugging, for placing debug info at the right position in the pdf
* @param img the value of rect
* @see Chunk#Chunk(com.itextpdf.text.Image, float, float, boolean)
*/
public void addDelayedStyler(String tag, Collection advanced, Chunk chunk, Image img) {
doOnGenericTag.put(tag, advanced);
if (chunk != null && chunk.getImage() != null) {
imageChunks.put(tag, chunk);
}
if (img != null) {
rectangles.put(tag, img);
}
}
@Override
public void setLayerManager(LayerManager layerManager) {
}
@Override
public List getBaseStylersFromCache(String... styleClasses) throws VectorPrintException {
return stylerFactory.getBaseStylersFromCache(styleClasses);
}
@Override
public void setDocument(Document document, PdfWriter writer) {
}
@Override
public Document getDocument() {
return stylerFactory.getDocument();
}
@Override
public PdfWriter getWriter() {
return stylerFactory.getWriter();
}
@Override
public LayerManager getLayerManager() {
return stylerFactory.getLayerManager();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy