itext 7 在同一页面上混合全身和分栏布局

Itext 7 mixed full body and column layout on the same page

我正在使用 iText 7 并尝试在同一页面上创建具有混合布局的文档。

我需要在同一页面上先进行全身布局,然后再进行栏布局,并且这些栏可能会延伸到后续页面。然后我需要恢复到全身布局,然后再次是从与全身部分相同的页面开始的列,并且这些列可能再次流到后续页面。

但是,这似乎只适用于列从生成的文档的第 1 页开始的地方。如果不是,则列的文本似乎想要放置在布局不正确的先前页面上。我在这里试过这个例子:

不幸的是,它在我的场景中不起作用。这是 lorem ipsum 测试的结果:

有人可以帮忙吗?

更新: 我越来越接近解决方案。我已经更新了我的代码,列中的文本和牛体布局现在不重叠。但是,当在整个正文布局中溢出到后续页面的段落之后在同一页面上开始列呈现时,列布局的该部分中文本的起始部分将丢失。此外,呈现似乎是从新页面开始的,而不是与之前呈现的段落相同的页面,该段落溢出到完整布局中的后续页面。

我正在使用以下代码 (3 类):

package org.jsb.DocGen;

import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.renderer.DrawContext;
import com.itextpdf.layout.renderer.IRenderer;
import com.itextpdf.layout.renderer.ParagraphRenderer;

import lombok.Getter;

public final class CustomParagraphRenderer extends ParagraphRenderer {
    private @Getter float y = 0.0f;

    public CustomParagraphRenderer(Paragraph modelElement) {
        super(modelElement);
    }

    @Override
    public void drawBorder(DrawContext drawContext) {
        super.drawBorder(drawContext);
        this.y = getOccupiedAreaBBox().getBottom();
    }

    @Override
    public IRenderer getNextRenderer() {
        return new CustomParagraphRenderer((Paragraph) modelElement);
    }

}


package org.jsb.DocGen;

import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.layout.ColumnDocumentRenderer;
import com.itextpdf.layout.Document;

public class CustomColumnDocumentRenderer extends ColumnDocumentRenderer {

    private Rectangle [] columns2 = null;

    public CustomColumnDocumentRenderer(Document document, Rectangle [] columns1, Rectangle [] columns2) {
        super(document, columns1);
        this.columns2 = columns2;
    }

    @Override
    protected PageSize addNewPage(PageSize customPageSize) {
        PageSize size = super.addNewPage(customPageSize);
        super.columns = this.columns2;
        return size;
    }

}



package org.jsb.DocGen;

import java.awt.Desktop;
import java.io.File;
import java.io.IOException;

import org.junit.jupiter.api.Test;

import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.AreaBreak;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.property.AreaBreakType;
import com.itextpdf.layout.renderer.DocumentRenderer;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public final class ColumnTest {

    @Test
    public final void test() {
        Document document = null;
        String dest = String.format("%s/Desktop/mixedrenderers.pdf", System.getProperty("user.home")).replace("\", "/").replace("//", "/");
        File file = new File(dest);
        if(file.exists()) {
            file.delete();
        }
        try(
                PdfWriter pdfWriter = new PdfWriter(dest);
                PdfDocument pdf = new PdfDocument(pdfWriter);
        ) {
            String [] loremipsums = {
                "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam interdum sollicitudin velit nec semper. Aliquam porta venenatis tortor, et viverra nisl accumsan non. Sed euismod tincidunt ex et porttitor. Duis lacinia efficitur auctor. Quisque eros quam, maximus et suscipit quis, tempor fringilla lorem. Donec hendrerit hendrerit vehicula. Integer vulputate fermentum arcu in tincidunt. Fusce euismod sapien id iaculis efficitur. Suspendisse potenti.",
                "Proin condimentum lorem a enim cursus tincidunt. Proin dui ex, faucibus semper tincidunt vitae, lobortis ut urna. Nullam iaculis neque accumsan urna consectetur accumsan. Mauris quis est nunc. Pellentesque vitae urna congue, dignissim lacus quis, volutpat ipsum. Duis arcu neque, convallis et nunc aliquet, sollicitudin finibus sem. Donec malesuada commodo purus. Quisque imperdiet elementum suscipit. Cras fringilla dolor a nunc placerat porta. In id consequat justo, eget dictum mauris. Sed felis est, tristique vulputate nunc non, bibendum consequat nibh. Ut imperdiet sit amet lectus sed bibendum. Sed vitae blandit nibh, at tincidunt nisi. Nunc vulputate mi in ipsum egestas posuere eget ac arcu. Duis at sagittis sapien.",
                "Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean odio lorem, porttitor id ante id, dapibus blandit orci. Donec molestie luctus neque sit amet fermentum. Aliquam nec tempus nulla. Aenean nec auctor metus. Curabitur non ultrices enim. In nec orci efficitur, vestibulum sem ut, molestie metus. Aenean sit amet purus finibus, tempor nibh et, ultrices orci. Fusce elementum fringilla eros, vel facilisis justo placerat et. Proin sagittis, nunc vitae rutrum porttitor, libero risus vulputate ipsum, quis dignissim sapien orci non quam. Cras eu dolor volutpat, blandit lacus vitae, venenatis felis. Sed laoreet mi non turpis feugiat pharetra. Fusce sem est, condimentum at elit a, consequat condimentum mauris. Vestibulum est est, tincidunt sed varius ac, gravida eget purus. Pellentesque sit amet nibh sit amet mi tincidunt tempor non vitae quam.",
                "Sed a augue nunc. Suspendisse potenti. Praesent hendrerit sem lacus, sodales bibendum nunc pretium vel. Proin tincidunt, orci porttitor suscipit consectetur, neque dui fringilla neque, vitae dapibus orci libero quis leo. Nunc velit arcu, accumsan et felis ut, sodales varius libero. Quisque vitae iaculis ante. Suspendisse potenti. Vivamus fringilla sollicitudin mollis. Etiam nulla dolor, placerat at molestie a, lobortis et diam. Phasellus egestas aliquet pellentesque. Etiam pretium sapien sed nunc vehicula, in fermentum quam euismod. Proin auctor leo eu urna tempus, quis auctor felis bibendum. Quisque sollicitudin lacinia urna a ultricies. Ut volutpat eros tristique tempor imperdiet. Sed sit amet nulla non elit sollicitudin rutrum. In suscipit mollis purus, non efficitur justo molestie tempus.",
                "Quisque sed est odio. Ut et sodales nulla, ornare mollis nunc. Curabitur nec bibendum nunc. Phasellus lobortis auctor faucibus. Praesent quis metus at diam mollis laoreet. Nulla viverra risus in blandit interdum. Praesent sed tortor id felis tincidunt luctus nec vel dolor. Vivamus hendrerit, enim vel sollicitudin consequat, dui augue tincidunt metus, quis pellentesque dui ante non leo. Mauris ultricies elit id tempus vehicula. Nunc mauris arcu, accumsan quis lorem quis, pharetra tincidunt sem. Donec ut lacus molestie dolor convallis elementum tincidunt vitae sem. Fusce viverra tortor libero, vitae ultricies lectus hendrerit interdum. In hac habitasse platea dictumst. Fusce ante eros, pretium at pellentesque id, auctor et mi. Nam ut accumsan dolor, ac cursus elit. Nunc nec sapien blandit, volutpat tortor eget, aliquet lacus.",
            };
            Paragraph para = null;
            CustomParagraphRenderer paragraphRenderer = null;

            document = new Document(pdf, PageSize.A4);
            document.setRenderer(new DocumentRenderer(document));

            // One full body paragraph that does not flow onto subsequent pages ...
            para = new Paragraph().add(loremipsums[0]);
            paragraphRenderer = new CustomParagraphRenderer(para);
            para.setNextRenderer(paragraphRenderer);
            document.add(para);

            // ... followed by column layout starting on the same page and flowing onto subsequent pages.
            float y = paragraphRenderer.getY();
            float offSet = 36; // margins
            float gutter = 23; // column gap
            float columnWidth = (PageSize.A4.getWidth() - offSet * 2) / 2 - gutter;
            float columnHeight1 = y - offSet * 2;
            Rectangle[] columns1 = {
                new Rectangle(offSet, offSet, columnWidth, columnHeight1),
                new Rectangle(offSet + columnWidth + gutter, offSet, columnWidth, columnHeight1)
            };
            float columnHeight2 = PageSize.A4.getHeight() - offSet * 2;
            Rectangle[] columns2 = {
                new Rectangle(offSet, offSet, columnWidth, columnHeight2),
                new Rectangle(offSet + columnWidth + gutter, offSet, columnWidth, columnHeight2)};
            document.setRenderer(new CustomColumnDocumentRenderer(document, columns1, columns2));  
            for(int i = 0; i < 5; i++) {
                for(String loremipsum : loremipsums) {
                    document.add(new Paragraph(loremipsum));
                }
            }

            // Then followed by full body paragraphs stating on the page after the column layout and flowing onto subsequent pages ...
            document.setRenderer(new DocumentRenderer(document));
            document.add(new AreaBreak(AreaBreakType.NEXT_PAGE));
            document.add(new AreaBreak(AreaBreakType.NEXT_PAGE));
            for(int i = 0; i < 5; i++) {
                for(String loremipsum : loremipsums) {
                    para = new Paragraph().add(loremipsum);
                    paragraphRenderer = new CustomParagraphRenderer(para);
                    para.setNextRenderer(paragraphRenderer);
                    document.add(para);
                }
            }

            // ... finally followed by column layout starting on the same page as the last paragraph as the previous full body layout, and flowing onto subsequent pages.
            // BUG: However - this starts rendering at the top of a new page. It does NOT begin rendering on the same page as the last full body paragraph previously rendered.
            // BUG: And some of the text in gets lost at the start of rendering this section.
            y = ((CustomParagraphRenderer)para.getRenderer()).getY();
            offSet = 36; // margins
            gutter = 23; // column gap
            columnWidth = (PageSize.A4.getWidth() - offSet * 2) / 2 - gutter;
            columnHeight1 = y - offSet * 2;
            columns1 = new Rectangle[2];
            columns1[0] = new Rectangle(offSet, offSet, columnWidth, columnHeight1);
            columns1[1] = new Rectangle(offSet + columnWidth + gutter, offSet, columnWidth, columnHeight1);
            columnHeight2 = PageSize.A4.getHeight() - offSet * 2;
            columns2 = new Rectangle[2];
            columns2[0] = new Rectangle(offSet, offSet, columnWidth, columnHeight2);
            columns2[1] = new Rectangle(offSet + columnWidth + gutter, offSet, columnWidth, columnHeight2);
            document.setRenderer(new CustomColumnDocumentRenderer(document, columns1, columns2));
            for(int i = 0; i < 5; i++) {
                for(String loremipsum : loremipsums) {
                    document.add(new Paragraph(loremipsum));
                }
            }

            document.flush();
            document.close();
        } catch(Exception e) {
            log.error(null, e);
        } finally {
            if(document != null) {
                document.close();
            }
        }
        if(file.exists()) {
            try {
                Desktop.getDesktop().open(file);
            } catch (IOException e) {
                log.error(null, e);
            }
        }
    }

}

有人有想法吗?我将不胜感激。谢谢。

通过两处更改,您更新后的代码创建了所需的输出。不幸的是,我不明白其中第二个更改的必要性,也许 iText 开发人员应该解释它是否有意义或提出更好的建议。

CustomParagraphRenderer

一个问题是由于您确定 y 坐标的方式:

y = ((CustomParagraphRenderer)para.getRenderer()).getY();

根据布局过程的方式,该段落的当前渲染器可能根本没有被使用,因此其 y 值可能仍为初始化时的 0。这反过来会使你的 columns1 成为一个负高度的矩形,它会吞噬一些文本而不明显地呈现这些文本。

您可以通过将 y 变量设为静态来解决此问题。

private static float y = 0.0f;

(我没有使用你使用的 @Getter 注释,所以我不知道它是否也可以处理静态变量。如果不能,只需创建一个显式的 getter.. .)

现在任何未使用的 CustomParagraphRenderer 实例都不能再引入 0 值。

现在的结果:所有文本都在那里,即不再丢失任何文本。但不幸的是,两列呈现在页面上开始,然后再次导致重叠文本。解决这个问题:

另外 NEXT_PAGE AreaBreak

这个重叠文本问题可以通过在设置新渲染器之后添加内容之前添加 new AreaBreak(AreaBreakType.NEXT_PAGE) 来解决:

...
columns2 = new Rectangle[2];
columns2[0] = new Rectangle(offSet, offSet, columnWidth, columnHeight2);
columns2[1] = new Rectangle(offSet + columnWidth + gutter, offSet, columnWidth, columnHeight2);
document.setRenderer(new CustomColumnDocumentRenderer(document, columns1, columns2));
document.add(new AreaBreak(AreaBreakType.NEXT_PAGE)); // <---
for(int i = 0; i < 5; i++) {
    for(String loremipsum : loremipsums) {
        document.add(new Paragraph(loremipsum));
    }
}
...

结果:现在双栏文本在单栏布局的溢出段落部分之后开始,正如所愿。

但我不明白为什么这是必要的。我看到您使用了相同的技术来防止在以前的渲染器更改时重叠文本,但 IMO 应该没有必要。不幸的是,我对此还不够深入解释,更不用说为此提供更合理的解决方案了。


完整的测试代码可以在这个MixedColumnLayout单元测试中找到class。