I am using iText7 to generate PDFs. I have to repeat a bit complex header on every page.
My complex header is three paragraphs with different formatting of each one, centered, something like this:
I tried to mimic an example with repeating Table as a header https://kb.itextpdf.com/home/it7kb/faq/how-to-add-a-table-as-a-header without success.
If I follow an example and replace Table with a Div created as (pseudo-code)
Paragraph myTitle = new Paragraph();
Paragraph title1, subtitle2, sub_subtitle3; // All initialised properly
myTitle.add(title1).add(new AreaBreak(NEXT_AREA)).add(subtitle2).add(sub_subtitle3);
Title appears, but all Title/Subtitle/Sub-subtitle are in one line. How can I insert line breaks between the paragraphs?
If I follow an example and replace Table with a Div created as (pseudo-code) Div.add(title1).add(new AreaBreak(NEXT_AREA)).add(subtitle2).add(sub_subtitle3);
nothing appears at all as a title.
Any ideas of how to achieve the desired effect?
I was able to solve the issue. It is fairly close to the idea explained on the IText knowledge base in the Table as a header example. The key is to update canvas position after each paragraph of the title is added:
canvas.add(titleElement.e).setFixedPosition(doc.getLeftMargin(), titleElement.h + doc.getTopMargin(), width);
Full code below is based on the TableHeader example from here.
In my example complex title is a list of Paragraph
, but can be any list if IBlockElement
I've used itext 7.1.13
package samples;
import com.itextpdf.kernel.events.Event;
import com.itextpdf.kernel.events.IEventHandler;
import com.itextpdf.kernel.events.PdfDocumentEvent;
import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.layout.Canvas;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.AreaBreak;
import com.itextpdf.layout.element.IBlockElement;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.element.Table;
import com.itextpdf.layout.layout.LayoutArea;
import com.itextpdf.layout.layout.LayoutContext;
import com.itextpdf.layout.layout.LayoutResult;
import com.itextpdf.layout.property.TextAlignment;
import com.itextpdf.layout.renderer.DocumentRenderer;
import com.itextpdf.layout.renderer.ParagraphRenderer;
import java.io.File;
import java.util.List;
import java.util.stream.Collectors;
public class ComplexHeader {
public static final String DEST = "./target/sandbox/events/table_header.pdf";
private static final PageSize pageSize = PageSize.A4;
public static void main(String[] args) throws Exception {
File file = new File(DEST);
file.getParentFile().mkdirs();
new ComplexHeader().manipulatePdf(DEST);
}
protected void manipulatePdf(String dest) throws Exception {
PdfDocument pdfDoc = new PdfDocument(new PdfWriter(dest));
Document doc = new Document(pdfDoc, pageSize);
String loremIpsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
List<IBlockElement> title = List.of(
titleWithProp("Title", 14, 5, 3, false),
titleWithProp("Subtitle", 12, 3, 2, true),
titleWithProp("Sub-Subtitle", 7, 0, 2, false));
ComplexHeaderEventHandler handler = new ComplexHeaderEventHandler(doc, title);
pdfDoc.addEventHandler(PdfDocumentEvent.END_PAGE, handler);
// Calculate top margin to be sure that the table will fit the margin.
float topMargin = 20 + handler.getTitleHeight();
doc.setMargins(topMargin, 36, 36, 36);
for (int i = 0; i < 10; i++) {
doc.add(new Paragraph(loremIpsum));
}
doc.add(new AreaBreak());
doc.add(new Paragraph(loremIpsum));
doc.add(new AreaBreak());
doc.add(new Paragraph(loremIpsum));
doc.close();
}
private static Paragraph titleWithProp(String text, float fontSize,
float topMargin, float bottomMargine, boolean isBold) {
Paragraph p = new Paragraph(text).setTextAlignment(TextAlignment.CENTER)
.setFontSize(fontSize).setMarginTop(topMargin).setMarginBottom(bottomMargine);
if (isBold) {
p.setBold();
}
return p;
}
private static class ComplexHeaderEventHandler implements IEventHandler {
private float titleHeight;
private Document doc;
private List<TitleComponentHolder> documentTitle;
private static class TitleComponentHolder {
IBlockElement e;
public float getH() {
return h;
}
float h;
public TitleComponentHolder(IBlockElement e, float h) {
this.e = e;
this.h = h;
}
}
public ComplexHeaderEventHandler(Document doc, List<IBlockElement> newTitle) {
this.doc = doc;
documentTitle = newTitle.stream().map(t -> new TitleComponentHolder(t, calculateElementHeight(t))).
collect(Collectors.toList());
titleHeight = documentTitle.stream().map(TitleComponentHolder::getH).reduce(0f, Float::sum);
}
@Override
public void handleEvent(Event currentEvent) {
PdfDocumentEvent docEvent = (PdfDocumentEvent) currentEvent;
PdfDocument pdfDoc = docEvent.getDocument();
PdfPage page = docEvent.getPage();
PdfCanvas pdfCanvas = new PdfCanvas(page.newContentStreamBefore(), page.getResources(), pdfDoc);
float coordX = pageSize.getX() + doc.getLeftMargin();
float coordY = pageSize.getTop() - doc.getTopMargin();
float width = pageSize.getWidth() - doc.getRightMargin() - doc.getLeftMargin();
Rectangle rect = new Rectangle(coordX, coordY, width, titleHeight);
try (Canvas canvas = new Canvas(pdfCanvas, rect)) {
documentTitle.forEach(title ->
canvas.add(title.e).setFixedPosition(doc.getLeftMargin(), title.h + doc.getTopMargin(), width));
}
}
public float getTitleHeight() {
return titleHeight;
}
private float calculateElementHeight(IBlockElement e) {
ParagraphRenderer renderer = (ParagraphRenderer) e.createRendererSubTree();
renderer.setParent(new DocumentRenderer(doc));
// Simulate the positioning of the renderer to find out how much space the header table will occupy.
LayoutResult result = renderer.layout(new LayoutContext(new LayoutArea(0, pageSize)));
return result.getOccupiedArea().getBBox().getHeight();
}
}
}