Itext 7 Text fitting 更好的解决方案

A better solution for Itext 7 Text fitting

我有一个使用 Itext 5 并按预期工作的项目。 程序必须将 userInput 放在某些 'Chunks' 段落内。段落每行有不可移动的(块)字,并且 userInput 应该缩放 space 为段落内的 userInput 保留。 旧项目有以下代码(作为示例制作)

public class Oldway {
static final transient Font bold2  = FontFactory.getFont("Times-Roman", 10.0f, 1);
public static void main(String[] args) {
    
    Document document = new Document();
    document.setPageSize(PageSize.A4);
    try {
        PdfWriter writer =  PdfWriter.getInstance(document,  new FileOutputStream(new File("itext5.pdf")));
        document.open();
        
        Paragraph title = new Paragraph("Title of doc");
        title.setAlignment(1);
        document.add(title);
        
    Paragraph dec= new Paragraph();
        Chunk ch01 = new Chunk("Prev text ");
        dec.add(ch01);
        Chunk ch02 = new Chunk(getEmptySpace(42));
        dec.add(ch02);
        Chunk  ch03 =  new Chunk(" next Text");
        dec.add(ch03);
        document.add(dec);
        
        float y = writer.getVerticalPosition(false);
        
        float x2 = document.left() + ch01.getWidthPoint();
        float x3 = x2 + ch02.getWidthPoint();
        getPlainFillTest("Text to insert", document, y, x3, x2, writer, false);
        
        document.close();
        writer.flush();
        
    } catch (FileNotFoundException | DocumentException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

}
public static Chunk getEmptySpace(int size) {
    Chunk ch =  new Chunk();
    for(int i = 0;i<=size;i++) {
        ch.append("\u00a0");
    }
    
    return new Chunk(ch);
}


public static void getPlainFillTest(String str,Document document,float y, float x1pos,
        float x2pos, PdfWriter writer,boolean withTab) {
    
        if(str.isEmpty() || str.isBlank()) {
            str =  "________";
        }

        Rectangle rec2 = null;
        if(!withTab)
            rec2 = new Rectangle(x2pos, y, x1pos-2,y+10);
        else {
            rec2 = new Rectangle(x2pos+35, y, x1pos+33,y+10);
        }
        BaseFont bf = bold2.getBaseFont();
        PdfContentByte cb = writer.getDirectContent();
        float fontSize = getMaxFontSize(bf, str,(int)rec2.getWidth(), (int)rec2.getHeight());
        Phrase phrase = new Phrase(str,  new Font(bf,  fontSize));
        ColumnText.showTextAligned(cb, Element.ALIGN_CENTER, phrase,
                // center horizontally
                (rec2.getLeft() + rec2.getRight()) / 2,
                // shift baseline based on descent
                rec2.getBottom() - bf.getDescentPoint(str, fontSize),0);
        cb.saveState();//patrulaterul albastru
        cb.setColorStroke(Color.BLUE);
        cb.rectangle(rec2.getLeft(), rec2.getBottom(), rec2.getWidth(), rec2.getHeight());
        cb.stroke();
        cb.restoreState();
}

//Whosebug solution
  private static float getMaxFontSize(BaseFont bf, String text, int width, int height){
       // avoid infinite loop when text is empty
        if(text.isEmpty()){
            return 0.0f;
        }


        float fontSize = 0.1f;
        while(bf.getWidthPoint(text, fontSize) < width){
            fontSize += 0.1f;
        }

        float maxHeight = measureHeight(bf, text, fontSize);
        while(maxHeight > height){
            fontSize -= 0.1f;
            maxHeight = measureHeight(bf, text, fontSize);
        };

        return fontSize;
    }

    public static  float measureHeight(BaseFont baseFont, String text, float fontSize) 
    { 
        float ascend = baseFont.getAscentPoint(text, fontSize); 
        float descend = baseFont.getDescentPoint(text, fontSize); 
        return ascend - descend; 
    }}

现在我正尝试在 IText 7 中做同样的事情,但...并不那么容易! 我设法创建了一个工作代码,但它很乱,有些东西没有得到正确的坐标。 Itext7代码(作为例子制作):

public class Newway {

public static void main(String[] args) {
    PdfWriter writer;
    try {
        writer = new PdfWriter(new File("test2.pdf"));
    
    PdfDocument document = new PdfDocument(writer);
     document.getDocumentInfo().addCreationDate();
     document.getDocumentInfo().setTitle("Title");
     document.setDefaultPageSize(PageSize.A4);
     Document doc =  new Document(document);
     doc.setFontSize(12);
     
     
     Paragraph par =  new Paragraph();
     Text ch01 = new Text("Prev Text ");    
     par.add(ch01);
     Paragraph space = new Paragraph();
     space.setMaxWidth(40);
     for(int i=0;i<40;i++) {
         par.add("\u00a0");
         space.add("\u00a0");
     }
     Text ch02 =  new Text(" next text");
     par.add(ch02);
     doc.add(par);
     
     
     Paragraph linePara = new Paragraph().add("Test from UserInput")
                .setTextAlignment(TextAlignment.CENTER).setBorder(new DottedBorder(1));
     
    float width = doc.getPageEffectiveArea(PageSize.A4).getWidth();
    float height = doc.getPageEffectiveArea(PageSize.A4).getHeight();
     
     IRenderer primul = ch01.createRendererSubTree().setParent(doc.getRenderer());  
     IRenderer spaceR = space.createRendererSubTree().setParent(doc.getRenderer());     
     LayoutResult primulResult = primul.layout(new LayoutContext(new LayoutArea(1, new Rectangle(width,height))));
     LayoutResult layoutResult = spaceR.layout(new LayoutContext(new LayoutArea(1, new Rectangle(width,height))));
     Rectangle primulBox  = ((TextRenderer) primul).getInnerAreaBBox();
     Rectangle rect  = ((ParagraphRenderer) spaceR).getInnerAreaBBox();
     float rwidth =  rect.getWidth();
     float rheight =  rect.getHeight();
     
     float x = primulBox.getWidth()+ doc.getLeftMargin();
     float y =  rect.getY()+(rheight*2.05f);//rect.getY() is never accurate, is always below the paragraph. WHY ??
     
     
     Rectangle towr =  new Rectangle(x, y, rwidth, rheight*1.12f);//rheight on default is way too small
     
     PdfCanvas pdfcanvas =  new PdfCanvas(document.getFirstPage());
     Canvas canvas =  new Canvas(pdfcanvas, towr);
     //from theinternet
     float fontSizeL = 1; 
     float fontSizeR = 14;
     while (Math.abs(fontSizeL - fontSizeR) > 1e-1) {
            float curFontSize = (fontSizeL + fontSizeR) / 2;
            linePara.setFontSize(curFontSize);
            // It is important to set parent for the current element renderer to a root renderer
            IRenderer renderer = linePara.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;
            }
        }
        canvas.add(linePara);
        new PdfCanvas(document.getFirstPage()).rectangle(towr).setStrokeColor(ColorConstants.BLACK).stroke();
        canvas.close();
        doc.close();
        writer.flush();
     
     
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}}

问题是

  1. 有没有更好、更优雅的方法来做到这一点?
  2. 为什么rect.getY()总是在段落下面?以及如何让 Y 与段落实际 Y 坐标相匹配?
  3. 为什么默认值 'rheight' 总是太小?但是 (rheight*1.1f) 有效吗?
  4. (可选)如何在 IText 7 中设置 tab() space 大小?
  1. 这种方式非常好,因为它考虑了所有可能的模型元素设置和布局过程的影响。您的 iText 5 替代方案对于基于拉丁语的文本的基本情况已经足够好了,而无需在视觉方面进行任何修改。你拥有的 iText 7 代码更加灵活,如果你使用更复杂的布局设置、复杂的脚本等,它仍然可以工作。我还看到 iText 5 代码在你的示例中是 105 行,而 iText 7 代码是 80 行。

  2. 你在这里添加了一些魔法 +(rheight*2.05f); 而实际上你在这里缺少的是当你通过 Canvas 绘制时你没有定义你的边距,所以你真正需要的而不是 rect.getY()+(rheight*2.05f);rect.getY() + doc.getBottomMargin()

  3. 问题出在您将 rheight 计算为 renderer.getInnerAreaBBox() 而此计算未考虑应用于段落的默认页边距。边距包含在占用区域中,但不包含在内部区域 bbox 中。要解决此问题,请改用 renderer.getOccupiedArea().getBBox()。在这种情况下,不再需要将 rheight 乘以系数。

现在的视觉效果略有不同,但不再有魔术常量。根据您真正想要实现的目标,您可以进一步调整代码(在这里和那里添加一些边距等)。但是代码很好地适应了用户文本的变化。

之前的视觉结果:

之后的视觉效果:

结果代码:

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

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


    Paragraph par =  new Paragraph();
    Text ch01 = new Text("Prev Text ");
    par.add(ch01);
    Paragraph space = new Paragraph();
    space.setMaxWidth(40);
    for(int i=0;i<40;i++) {
        par.add("\u00a0");
        space.add("\u00a0");
    }
    Text ch02 =  new Text(" next text");
    par.add(ch02);
    doc.add(par);


    Paragraph linePara = new Paragraph().add("Test from UserInput")
            .setTextAlignment(TextAlignment.CENTER).setBorder(new DottedBorder(1));

    float width = doc.getPageEffectiveArea(PageSize.A4).getWidth();
    float height = doc.getPageEffectiveArea(PageSize.A4).getHeight();

    IRenderer primul = ch01.createRendererSubTree().setParent(doc.getRenderer());
    IRenderer spaceR = space.createRendererSubTree().setParent(doc.getRenderer());
    LayoutResult primulResult = primul.layout(new LayoutContext(new LayoutArea(1, new Rectangle(width,height))));
    LayoutResult layoutResult = spaceR.layout(new LayoutContext(new LayoutArea(1, new Rectangle(width,height))));
    Rectangle primulBox  = ((TextRenderer) primul).getInnerAreaBBox();
    Rectangle rect  = ((ParagraphRenderer) spaceR).getOccupiedArea().getBBox();
    float rwidth =  rect.getWidth();
    float rheight =  rect.getHeight();

    float x = primulBox.getWidth()+ doc.getLeftMargin();
    float y =  rect.getY() + doc.getBottomMargin();


    Rectangle towr =  new Rectangle(x, y, rwidth, rheight);

    PdfCanvas pdfcanvas =  new PdfCanvas(document.getFirstPage());
    Canvas canvas =  new Canvas(pdfcanvas, towr);
    //from theinternet
    float fontSizeL = 1;
    float fontSizeR = 14;
    while (Math.abs(fontSizeL - fontSizeR) > 1e-1) {
        float curFontSize = (fontSizeL + fontSizeR) / 2;
        linePara.setFontSize(curFontSize);
        // It is important to set parent for the current element renderer to a root renderer
        IRenderer renderer = linePara.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;
        }
    }
    canvas.add(linePara);
    new PdfCanvas(document.getFirstPage()).rectangle(towr).setStrokeColor(ColorConstants.BLACK).stroke();
    canvas.close();
    doc.close();
    writer.flush();


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