IText 7 中的多行文本调整

Multi-Line Text Fitting in IText 7

这个问题是 的后续问题 在上一个 post 之后,我设法创建了以下适合段落中某些空格的文本的方法。

public static void getPlainFill2(String str, Document doc, PdfDocument document, Paragraph root,
    Paragraph space, boolean isCentred) {
// System.out.println("prevText: "+prev.getText());
float width = doc.getPageEffectiveArea(PageSize.A4).getWidth();
float height = doc.getPageEffectiveArea(PageSize.A4).getHeight();
if (str.isEmpty() || str.isBlank()) {
    str = "________";
}
IRenderer spaceRenderer = space.createRendererSubTree().setParent(doc.getRenderer());

LayoutResult spaceResult = spaceRenderer
    .layout(new LayoutContext(new LayoutArea(1, new Rectangle(width, height))));

Rectangle rectSpaceBox = ((ParagraphRenderer) spaceRenderer).getOccupiedArea().getBBox();

float writingWidth = rectSpaceBox.getWidth();
float writingHeight = rectSpaceBox.getHeight();

Rectangle remaining = doc.getRenderer().getCurrentArea().getBBox();
float yReal = remaining.getTop() + 2f;// orig 4f

float sizet = 0;
for (int i = 0; i < root.getChildren().size(); i++) {
    IElement e = root.getChildren().get(i);

    if (e.equals(space)) {

    break;
    }

    IRenderer ss = e.createRendererSubTree().setParent(doc.getRenderer());
    
    LayoutResult ss2 = ss.layout(new LayoutContext(new LayoutArea(1, new Rectangle(width, height))));

    sizet += ss.getOccupiedArea().getBBox().getWidth();

    System.out.println("width: " + width + " current: " + sizet);

}
float start =  sizet+doc.getLeftMargin();
 if(isCentred) 
     start = (width - getRealWidth(doc, root,width,height))/2+doc.getLeftMargin()+sizet;
 


Rectangle towr = new Rectangle(start, yReal, writingWidth, writingHeight);// sizet+doc.getLeftMargin()

PdfCanvas pdfcanvas = new PdfCanvas(document.getFirstPage());
Canvas canvas = new Canvas(pdfcanvas, towr);
canvas.setTextAlignment(TextAlignment.CENTER);
canvas.setHorizontalAlignment(HorizontalAlignment.CENTER);

Paragraph paragraph = new Paragraph(str).setTextAlignment(TextAlignment.CENTER).setBold();//.setMultipliedLeading(0.9f);
Div lineDiv = new Div();
lineDiv.setVerticalAlignment(VerticalAlignment.MIDDLE);
lineDiv.add(paragraph);

float fontSizeL = 1f;
float fontSizeR = 12;
int adjust = 0;
while (Math.abs(fontSizeL - fontSizeR) > 1e-1) {
    float curFontSize = (fontSizeL + fontSizeR) / 2;
    lineDiv.setFontSize(curFontSize);
    // It is important to set parent for the current element renderer to a root
    // renderer
    IRenderer renderer = lineDiv.createRendererSubTree().setParent(canvas.getRenderer());
    LayoutContext context = new LayoutContext(new LayoutArea(1, towr));
    if (renderer.layout(context).getStatus() == LayoutResult.FULL) {
    // we can fit all the text with curFontSize
    fontSizeL = curFontSize;
    } else {
    fontSizeR = curFontSize;
    }
    if(adjust>=2) {
    writingHeight -=1.3f;
    yReal += 1.4f;
    adjust= 0;
    }
}

lineDiv.setFontSize(fontSizeL);
canvas.add(lineDiv);
// border
// PdfCanvas(document.getFirstPage()).rectangle(towr).setStrokeColor(ColorConstants.BLACK).stroke();

canvas.close();

}




public static float getRealWidth (Document doc, Paragraph root,float width,float height) {
 float sizet = 0;
    
 for(int  i = 0;i<root.getChildren().size();i++) {
     IElement e =  root.getChildren().get(i);
     
    
        IRenderer ss = e.createRendererSubTree().setParent(doc.getRenderer());
    LayoutResult ss2 = ss.layout(new LayoutContext(new LayoutArea(1, new Rectangle(width,height))));

    sizet +=ss.getOccupiedArea().getBBox().getWidth();


        
    }
return sizet;}

现在这几乎可以正常工作了,当文本缩放到较小的尺寸时会出现一些小问题,如下所示: https://i.ibb.co/MkxfwjQ/Screenshot-from-2021-06-14-18-27-09.png(我无法 post 图片,因为我没有代表。) 但主要问题是您必须逐行编写段落才能工作。作为下一个示例:

Cell cell3 = new Cell();
        LineCountingParagraph line3 = new LineCountingParagraph("");
        Text ch07 = new Text("Paragraph Prev ");
        line3.add(ch07);
        Paragraph nrZile =  getEmptySpace(15);
        line3.add(nrZile);
        Text ch08 = new Text("afterStr, textasdsadasdas ");
        line3.add(ch08);
        Paragraph data =  getEmptySpace(18);
        line3.add(data);
        Text ch09 = new Text(".\n");
        line3.add(ch09);
        line3.setTextAlignment(TextAlignment.CENTER);
        cell3.add(line3);
        doc.add(cell3);
        getPlainFill2("thisisalongstring", doc, document, line3, nrZile, true);
        getPlainFill2("1333", doc, document, line3, data, true);
         
           Cell cell4 =  new Cell();
            LineCountingParagraph line4 =  new LineCountingParagraph("");
            Paragraph loc2  = getEmptySpace(30);
            line4.add(loc2);
            Text pr32 = new Text(" aasdbsadasd ");
            line4.add(pr32);
            Paragraph nr2 = getEmptySpace(8);
            line4.add(nr2);
            Text pr33 =  new Text(" asdasdasdasd.\n");
            line4.add(pr33);
            line4.setTextAlignment(TextAlignment.CENTER);
            cell4.add(line4);
            doc.add(cell4);
         
            getPlainFill2("1333", doc, document, line4, nr2, true);

如果您需要更多代码,我会上传到某个地方。 现在有没有办法在多行的同一段落中插入文本?因为我似乎找不到在 IText 7.1.11 中检测换行符的方法。

完整代码:

package pdfFill;

import java.io.File;
import java.io.IOException;

import com.itextpdf.kernel.colors.ColorConstants;
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.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.layout.Canvas;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.Cell;
import com.itextpdf.layout.element.Div;
import com.itextpdf.layout.element.IElement;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.element.Text;
import com.itextpdf.layout.layout.LayoutArea;
import com.itextpdf.layout.layout.LayoutContext;
import com.itextpdf.layout.layout.LayoutResult;
import com.itextpdf.layout.property.HorizontalAlignment;
import com.itextpdf.layout.property.TextAlignment;
import com.itextpdf.layout.property.VerticalAlignment;
import com.itextpdf.layout.renderer.DrawContext;
import com.itextpdf.layout.renderer.IRenderer;
import com.itextpdf.layout.renderer.ParagraphRenderer;



public class Newway4 {

    public static void main(String[] args) {
        PdfWriter writer;

        try {
            writer = new PdfWriter(new File("test4.pdf"));

            PdfDocument document = new PdfDocument(writer);
             document.getDocumentInfo().addCreationDate();
             document.getDocumentInfo().setAuthor("Piri");
             document.getDocumentInfo().setTitle("Test_Whosebug");
             document.setDefaultPageSize(PageSize.A4);
             Document doc =  new Document(document);
             doc.setFontSize(12);
          

            
            
        
            final Paragraph titlu = new Paragraph();
            final Text t1 = new Text("\n\n\n\nTest Whosebug\n\n\n").setBold().setUnderline();
            titlu.setHorizontalAlignment(HorizontalAlignment.CENTER);
            titlu.setTextAlignment(TextAlignment.CENTER);
            titlu.add(t1).setBold();
            doc.add(titlu);
            

            Cell cell1 = new Cell();
            LineCountingParagraph line1 = new LineCountingParagraph("");
            line1.add( addTab());
            Text ch01 = new Text("This is the 1st example ");
            line1.add(ch01);
            Paragraph name =  getEmptySpace(42);
            line1.add(name);// cnp new line
            Text ch02 = new Text(" that works ");
            line1.add(ch02);
            Paragraph domiciliu =  getEmptySpace(63);
            line1.add(domiciliu);
            /* Text ch03 = new Text("\njudet");
            line1.add(ch03);
            Paragraph judet =  getEmptySpace(12);
            line1.add(judet);*/
           Text ch031 = new Text("\n");
            line1.add(ch031);
            cell1.add(line1);
            doc.add(cell1);
             getPlainFill2("with insertion str", doc, document, line1, name, false);
             getPlainFill2("because is writtin line by line", doc, document, line1, domiciliu, false);
          


             
             
             Cell cell2 =  new Cell();
                LineCountingParagraph line2 =  new LineCountingParagraph("");
                Text p51 = new Text("as you can see in this");
                line2.add(p51);
                Paragraph localitatea = getEmptySpace(30);
                line2.add(localitatea);
                Text p7 = new Text(" and ");
                line2.add(p7);
                Paragraph nrCasa =getEmptySpace(8);
                line2.add(nrCasa);
                Text p09 = new Text(" of text scalling ");
                line2.add(p09);
                Paragraph telefon = getEmptySpace(22);
                line2.add(telefon);
                Text p11 =  new Text(".");
                line2.add(p11);
                line2.setTextAlignment(TextAlignment.CENTER);
                cell2.add(line2);
                doc.add(cell2);
                getPlainFill2("sentence", doc, document, line2, localitatea, true);
                getPlainFill2("example", doc, document, line2, nrCasa, true);
                getPlainFill2("text scalling bla bla", doc, document, line2, telefon, true);
             
             
             doc.add(new Paragraph("\n\n\n"));
             
             
             LineCountingParagraph paragraphTest =  new LineCountingParagraph("");
             paragraphTest.add(addTab());
             Text testch01 =  new Text("This is the 2nd example ");
             paragraphTest.add(testch01);
             Paragraph emptyTest01 =  getEmptySpace(42);
             paragraphTest.add(emptyTest01);
             Text testch02 =  new Text(" that doesn't work ");
             paragraphTest.add(testch02);
             Paragraph  emptyTest02 =  getEmptySpace(53);
             paragraphTest.add(emptyTest02);
             Text testch04 =  new Text(" this next goes to the next line but ");
             paragraphTest.add(testch04);
             Paragraph emptyTest03 =  getEmptySpace(42);
             paragraphTest.add(emptyTest03);
             Text testch05 =  new Text(" won't appear !!");
             paragraphTest.add(testch05);
             doc.add(paragraphTest);
             getPlainFill2("with insertion str", doc, document, paragraphTest, emptyTest01, false);
             getPlainFill2("because next text goes next line", doc, document, paragraphTest, emptyTest02, false);
             getPlainFill2("this text", doc, document, paragraphTest, emptyTest03, false);
             
            
             
             
        
            doc.close();
            writer.flush();


        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public static String getStrWithDots(final int dots, final String str) {
        final int strSize = str.length();
        final StringBuilder sb = new StringBuilder();
        int dotsRemained;
        if (strSize > dots) {
            dotsRemained = 0;
        } else {
            dotsRemained = dots - strSize;
        }
        for (int i = 0; i < dotsRemained; ++i) {
            if (i == dotsRemained / 2) {
            sb.append(str);
            }
            sb.append(".");
        }
        return sb.toString();
        }
    
      public static void getPlainFill2(String str, Document doc, PdfDocument document, Paragraph root,
                Paragraph space, boolean isCentred) {
            // System.out.println("prevText: "+prev.getText());
            float width = doc.getPageEffectiveArea(PageSize.A4).getWidth();
            float height = doc.getPageEffectiveArea(PageSize.A4).getHeight();
            if (str.isEmpty() || str.isBlank()) {
                str = "________";
            }
            IRenderer spaceRenderer = space.createRendererSubTree().setParent(doc.getRenderer());

            LayoutResult spaceResult = spaceRenderer
                .layout(new LayoutContext(new LayoutArea(1, new Rectangle(width, height))));

            Rectangle rectSpaceBox = ((ParagraphRenderer) spaceRenderer).getOccupiedArea().getBBox();

            float writingWidth = rectSpaceBox.getWidth();
            float writingHeight = rectSpaceBox.getHeight();

            Rectangle remaining = doc.getRenderer().getCurrentArea().getBBox();
            float yReal = remaining.getTop() + 2f;// orig 4f

            float sizet = 0;
            for (int i = 0; i < root.getChildren().size(); i++) {
                IElement e = root.getChildren().get(i);

                if (e.equals(space)) {

                break;
                }

                IRenderer ss = e.createRendererSubTree().setParent(doc.getRenderer());
                
                LayoutResult ss2 = ss.layout(new LayoutContext(new LayoutArea(1, new Rectangle(width, height))));

                sizet += ss.getOccupiedArea().getBBox().getWidth();


            }
            float start =  sizet+doc.getLeftMargin();
             if(isCentred) 
                 start = (width - getRealWidth(doc, root,width,height))/2+doc.getLeftMargin()+sizet;
             
            
            
            Rectangle towr = new Rectangle(start, yReal, writingWidth, writingHeight);// sizet+doc.getLeftMargin()

            PdfCanvas pdfcanvas = new PdfCanvas(document.getFirstPage());
            Canvas canvas = new Canvas(pdfcanvas, towr);
            canvas.setTextAlignment(TextAlignment.CENTER);
            canvas.setHorizontalAlignment(HorizontalAlignment.CENTER);

            Paragraph paragraph = new Paragraph(str).setTextAlignment(TextAlignment.CENTER).setBold();//.setMultipliedLeading(0.9f);//setbold oprtional
            Div lineDiv = new Div();
            lineDiv.setVerticalAlignment(VerticalAlignment.MIDDLE);
            lineDiv.add(paragraph);

            float fontSizeL = 0.0001f, fontSizeR= 10000;
            int adjust = 0;
         
            while (Math.abs(fontSizeL - fontSizeR) > 1e-1) {
                float curFontSize = (fontSizeL + fontSizeR) / 2;
                lineDiv.setFontSize(curFontSize);
                // It is important to set parent for the current element renderer to a root
                // renderer
                IRenderer renderer = lineDiv.createRendererSubTree().setParent(canvas.getRenderer());
                LayoutContext context = new LayoutContext(new LayoutArea(1, towr));
                if (renderer.layout(context).getStatus() == LayoutResult.FULL) {
                // we can fit all the text with curFontSize
                fontSizeL = curFontSize;
                
                   if (++adjust>1)
                       towr.setHeight(towr.getHeight()-0.90f);
                } else {
                fontSizeR = curFontSize;
                }
              
            }

            lineDiv.setFontSize(fontSizeL);
            canvas.add(lineDiv);
    
             new PdfCanvas(document.getFirstPage()).rectangle(towr).setStrokeColor(ColorConstants.BLACK).stroke();

            canvas.close();

            }
   
    public static Text addTab() {
        StringBuilder sb =  new StringBuilder();
        for(int i = 0;i<8;i++)
            sb.append("\u00a0");
        return new Text(sb.toString());
    }
    
    
    
    public static float getRealWidth (Document doc, Paragraph root,float width,float height) {
         float sizet = 0;
            
         for(int  i = 0;i<root.getChildren().size();i++) {
             IElement e =  root.getChildren().get(i);
             
            
                IRenderer ss = e.createRendererSubTree().setParent(doc.getRenderer());
            LayoutResult ss2 = ss.layout(new LayoutContext(new LayoutArea(1, new Rectangle(width,height))));
      
            sizet +=ss.getOccupiedArea().getBBox().getWidth();

        
                
            }
        return sizet;
    }
     
     
   
    

     
     private static Paragraph getEmptySpace(int size) {
          Paragraph space = new Paragraph();
            space.setMaxWidth(size);
            for(int i=0;i<size;i++) {
            //    par.add("\u00a0");
                space.add("\u00a0");
            }
            return space;
     }
     
     
     
     private static class LineCountingParagraph extends Paragraph {
            private int linesWritten = 0;

            public LineCountingParagraph(String text) {
                super(text);
            }

            public void addWrittenLines(int toAdd) {
                linesWritten += toAdd;
            }

            public int getNumberOfWrittenLines() {
                return linesWritten;
            }

            @Override
            protected IRenderer makeNewRenderer() {
                return new LineCountingParagraphRenderer(this);
            }
        }

        private static class LineCountingParagraphRenderer extends ParagraphRenderer {
            public LineCountingParagraphRenderer(LineCountingParagraph modelElement) {
                super(modelElement);
            }

            @Override
            public void drawChildren(DrawContext drawContext) {
                ((LineCountingParagraph)modelElement).addWrittenLines(lines.size());
                super.drawChildren(drawContext);
            }

            @Override
            public IRenderer getNextRenderer() {
                return new LineCountingParagraphRenderer((LineCountingParagraph) modelElement);
            }
        }

}

问题:在 PDF 的上半部分,您可以看到创建两个 LineCountingParagraph 实例的结果,每行一个。在 PDF 的下半部分,您可以看到仅创建一个 LineCountingParagraph 实例时的结果。因此,在段落内容换行到下一行的情况下,使文本适合框内效果不佳。

你把事情搞得不必要的复杂,以至于我们不得不从头开始:)

所以目标是能够创建具有固定宽度的框内插入的段落,我们需要在其中copy-fit(放置)一些文本,确保选择的字体大小应使文本适合该框。

结果应该类似于这张图片:

我们的想法是将固定宽度的段落添加到我们的主要段落中(iText 允许添加块元素,段落是块元素 - 到段落中)。固定宽度将由我们段落的内容保证 - 它只包含 non-breakable spaces。我们的段落实际上将得到另一个段落的支持,其中包含我们想要放入包装段落的实际内容。在包装段落的布局期间,我们将知道它的有效边界,我们将使用该区域使用二进制搜索算法为我们的内容段落确定正确的字体大小。一旦确定了正确的字体大小,我们将确保包含内容的段落紧挨着我们的包装段落绘制。

包装段落的代码非常简单。它只是期望具有真实内容的底层段落作为参数。与 iText 布局一样,我们应该自定义自动缩放段落的渲染器:

private static class AutoScalingParagraph extends Paragraph {
    Paragraph innerParagraph;

    public AutoScalingParagraph(Paragraph innerParagraph) {
        this.innerParagraph = innerParagraph;
    }

    @Override
    protected IRenderer makeNewRenderer() {
        return new AutoScalingParagraphRenderer(this);
    }
}

private static class AutoScalingParagraphRenderer extends ParagraphRenderer {
    private IRenderer innerRenderer;

    public AutoScalingParagraphRenderer(AutoScalingParagraph modelElement) {
        super(modelElement);
    }

    @Override
    public LayoutResult layout(LayoutContext layoutContext) {
        LayoutResult baseResult = super.layout(layoutContext);
        this.innerRenderer = ((AutoScalingParagraph)modelElement).innerParagraph.createRendererSubTree().setParent(this);
        if (baseResult.getStatus() == LayoutResult.FULL) {
            float fontSizeL = 0.0001f, fontSizeR= 10000;

            while (Math.abs(fontSizeL - fontSizeR) > 1e-1) {
                float curFontSize = (fontSizeL + fontSizeR) / 2;
                this.innerRenderer.setProperty(Property.FONT_SIZE, UnitValue.createPointValue(curFontSize));

                if (this.innerRenderer.layout(new LayoutContext(getOccupiedArea().clone())).getStatus() == LayoutResult.FULL) {
                    // we can fit all the text with curFontSize
                    fontSizeL = curFontSize;
                } else {
                    fontSizeR = curFontSize;
                }
            }
            this.innerRenderer.setProperty(Property.FONT_SIZE, UnitValue.createPointValue(fontSizeL));

            this.innerRenderer.layout(new LayoutContext(getOccupiedArea().clone()));
        }
        return baseResult;
    }

    @Override
    public void drawChildren(DrawContext drawContext) {
        super.drawChildren(drawContext);
        innerRenderer.draw(drawContext);
    }

    @Override
    public IRenderer getNextRenderer() {
        return new AutoScalingParagraphRenderer((AutoScalingParagraph) modelElement);
    }
}

现在我们添加辅助函数来创建我们的包装段落,它只接受 space 中所需的段落宽度和我们想要适应的基础内容 space:

private static Paragraph createAdjustableParagraph(int widthInSpaces, Paragraph innerContent) {
    AutoScalingParagraph paragraph = new AutoScalingParagraph(innerContent);
    paragraph.setBorder(new SolidBorder(1));

    StringBuilder sb = new StringBuilder();
    for(int i=0;i<widthInSpaces;i++) {
        sb.append("\u00a0");
    }
    paragraph.add(sb.toString());
    return paragraph;
}

最后是主要代码:

PdfWriter writer = new PdfWriter(new File("test4.pdf"));

PdfDocument document = new PdfDocument(writer);
Document doc = new Document(document);

Paragraph paragraphTest = new Paragraph();
Text testch01 =  new Text("This is the 2nd example ");
paragraphTest.add(testch01);
paragraphTest.add(createAdjustableParagraph(42, new Paragraph("with insertion str")));
Text testch02 =  new Text(" that doesn't work ");
paragraphTest.add(testch02);
paragraphTest.add(createAdjustableParagraph(53, new Paragraph("because next text goes next line")));
Text testch04 =  new Text(" this next goes to the next line but ");
paragraphTest.add(testch04);
paragraphTest.add(createAdjustableParagraph(42, new Paragraph("this text")));
Text testch05 =  new Text(" won't appear !!");
paragraphTest.add(testch05);
doc.add(paragraphTest);

doc.close();

这给了我们以下结果:

所以我们只有一个包含一些内容的主要段落和我们的段落包装器,它们又具有我们想要适合的基础内容。

提示:文本居中非常简单,不需要计算坐标等,只需将右边的属性设置为内容所在的段落即可你喂给你的包装段落:

paragraphTest.add(createAdjustableParagraph(42, new Paragraph("this text").setTextAlignment(TextAlignment.CENTER)));

你得到的结果是: