7

I want to add text into a rectangle(x,y,w,h). Text should be fitted size of rectangle (mean it has a maximum size but it still contains in rectangle).
I tried to measure the text size base on BaseFont.getWidthPoint() The problem is the final text size can't fit the rect. It looks like this:
enter image description here

Here is my try:

  PdfContentByte cb = writer.getDirectContent();
    cb.saveState();
    ColumnText ct = new ColumnText(writer.getDirectContent());
    Font font = new Font(BaseFont.createFont());
    int rectWidth = 80;
    float maxFontSize = getMaxFontSize(BaseFont.createFont(), "text", rectWidth );
    font.setSize(maxFontSize);
    ct.setText(new Phrase("test", font));
    ct.setSimpleColumn(10, 10, rectWidth , 70);
    ct.go();        

    // draw the rect
    cb.setColorStroke(BaseColor.BLUE);
    cb.rectangle(10, 10, rectWidth , 70);
    cb.stroke();
    cb.restoreState();

   // get max font size base on rect width
  private static float getMaxFontSize(BaseFont bf, String text, int width){
    float measureWidth = 1;
    float fontSize = 0.1f;
    float oldSize = 0.1f;
    while(measureWidth < width){
        measureWidth = bf.getWidthPoint(text, fontSize);     
        oldSize = fontSize;
        fontSize += 0.1f;
    }

    return oldSize;
}

Could you please tell me where I am wrong?

Another problem, I want to measure for both width and height, which text completely contains in rectangle and has the maximum font size. Is there any way to do this?

Update: here is the complete source code that worked for me:

   private static float getMaxFontSize(BaseFont bf, String text, int width, int height){
   // avoid infinite loop when text is empty
    if(TextUtils.isEmpty(text)){
        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; 
}
ductran
  • 10,043
  • 19
  • 82
  • 165
  • *I want to measure for both width and height, which text completely contains in rectangle and has the maximum font size.* - do you mean as a one liner (which is where your current code is headed) or do you also want to allow line breaks to best fill the rectangle? – mkl Dec 25 '13 at 21:00
  • I just need one line text – ductran Dec 26 '13 at 02:56
  • In that case apply the fixes I mentioned in my answer and additionally don't allow higher font sizes than rectangle highs. – mkl Dec 26 '13 at 10:21

3 Answers3

6

The main issue

The main issue is that you use the wrong arguments in ct.setSimpleColumn:

ct.setSimpleColumn(10, 10, rectWidth , 70);

In contrast to the later cb.rectangle call

cb.rectangle(10, 10, rectWidth , 70);

which has arguments float x, float y, float w, float h (w and h being width and height) the method ct.setSimpleColumn has arguments float llx, float lly, float urx, float ury (ll being lower left and ur being upper right). Thus your ct.setSimpleColumn should look like this:

ct.setSimpleColumn(10, 10, 10 + rectWidth, 10 + 70);

A side issue

In addition to the main issue your result font size is 0.1 too large; essentially this is an error already pointed out by @David.

Your main loop in your getMaxFontSize method is this:

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

This essentially results in oldSize (which eventually is returned) being the first font size which does not fit. You could fix this by instead using

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

Even better would be an approach only calculating the string width once, not using a loop at all, e.g.

private static float getMaxFontSize(BaseFont bf, String text, int width)
{
    int textWidth = bf.getWidth(text);

    return (1000 * width) / textWidth;
}

(This method uses integer arithmetic. If you insist on an exact fit, switch to float or double arithmetic.)

mkl
  • 90,588
  • 15
  • 125
  • 265
1

There are two bugs here, both in

while(measureWidth < width){
    measureWidth = bf.getWidthPoint(text, fontSize++);          
}
  1. You're staying in the loop until measureWidth >= width - in other words, by the time you escape from the while loop, measureWidth is already too big for the rectangle.
  2. You're doing fontSize++, which means that after you've used fontSize to calculate measureWidth, you're increasing it. When you do get round to returning it, it's one more than the value you just tested. So the return value from the method will be one more than the last value that you tested (which, due to point 1., was already too big).
Dawood ibn Kareem
  • 77,785
  • 15
  • 98
  • 110
  • I has updated my answer. for the first one, because I don't need to get the measure width, so there is no problem when escape while loop. The second, I try to get the last max size, but it doesn't work, the last `t` character still drop in new line. – ductran Dec 25 '13 at 16:15
  • 1
    Right. Looking at your new code - you've fixed issue (2) but not issue (1). You are breaking out of that loop with a value of `oldSize` for which `bf.getWidthPoint(text,oldSize)` is already bigger than `width` - that is, it's already too big. – Dawood ibn Kareem Dec 25 '13 at 20:11
  • 3
    And you shouldn't change the code in the question - you should append any new code to the end. Otherwise, it makes the question meaningless, and useless to any future users of the site. – Dawood ibn Kareem Dec 25 '13 at 20:12
0

I've spent quite a while to implement such functionality using binary search method to find rectangle fitting font size. And today i stumbled upon an interesting method in iText...

It is quite a new method in iText ColumnText;

public float fitText(Font font, String text, Rectangle rect, float maxFontSize, int runDirection)
//Fits the text to some rectangle adjusting the font size as needed.

In my version of iText it is static. See details: http://developers.itextpdf.com/reference/com.itextpdf.text.pdf.ColumnText

Dmitrii
  • 340
  • 2
  • 6