复杂 header 在每个 PDF 页面上重复

Complex header repeating on every PDF page

我正在使用 iText7 生成 PDF。我必须在每一页上重复有点复杂的 header。 我的复杂 header 是三个段落,每个段落都有不同的格式,居中,像这样:

我试图模仿一个例子,将 Table 重复为 header https://kb.itextpdf.com/home/it7kb/faq/how-to-add-a-table-as-a-header 但没有成功。

如果我按照示例将 Table 替换为创建为 (pseudo-code)

的 Div
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/Subtitle/Sub-subtitle 都在一行中。如何在段落之间插入换行符?

如果我按照示例将 Table 替换为创建为 (pseudo-code) Div.add(title1).add(new AreaBreak(NEXT_AREA)).add(subtitle2).add(sub_subtitle3); 的 Div 什么都没有显示为标题。

关于如何达到预期效果的任何想法?

我能够解决问题。它与 Table 中 IText 知识库中作为 header 示例解释的想法非常接近。关键是每段标题添加后更新canvas位置:

canvas.add(titleElement.e).setFixedPosition(doc.getLeftMargin(), titleElement.h + doc.getTopMargin(), width);

下面的完整代码基于 here 中的 Table 标头示例。 在我的示例中,复杂标题是 Paragraph 的列表,但如果 IBlockElement 则可以是任何列表 我用过 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();
        }
    }
}