【问题标题】:A better solution for Itext 7 Text fittingItext 7 文本拟合的更好解决方案
【发布时间】:2021-07-30 10:04:47
【问题描述】:

我有一个使用 Itext 5 并按预期工作的项目。 程序必须将 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();
}

//stackoverflow 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() 空间大小?

【问题讨论】:

  • 问题 4 似乎与您当前代码的其他 3 个问题完全无关。提出一个单独的问题并正确描述您要实现的目标以及您当前拥有的代码是最有意义的(我在当前代码中看不到任何选项卡)

标签: optimization itext itext7


【解决方案1】:
  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();
}

【讨论】:

  • 太棒了,谢谢,希望我可以让它在我的整个多线项目中发挥作用。
  • 好的,这个解决方案适用于单一的、未居中的线路。它不适用于多段落行,因为没有断线检测,并且如果文本对齐居中,它也不起作用。你能提供一个多行段落且文本居中的例子吗?
  • 请提出一个单独的问题,其中包含更详细的描述和代码示例,说明什么不适用于您的其他情况
猜你喜欢
  • 1970-01-01
  • 2021-08-30
  • 2012-05-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-20
  • 2011-11-21
  • 1970-01-01
相关资源
最近更新 更多