How to make a String of any length fit within text box?

edited October 2015 in Programming Questions

I am using the following text() signature:

text(str, x1, y1, x2, y2);

where: x1 and y2 are cordinates of the text box and x2 and y2 are width and height of text box

I would like the font to have the highest possible size for which it still fits entirely into the text box specified by x2 and y2. I cannot use trial and error to determine the right font size - the String length varies, so the font size must be adapted each time the String length is altered.

The width and height of the box are constant - so the font size depends on them, not the other way around.

I was wondering if there was a simple method to achieve this?

I tried finding a formula for the font size but didn't succeed since the text size in this context depends on many factors:

  • the box width
  • the box height
  • the font itself,
  • the ascend,
  • the descend,
  • the structure of the string, that is, how long are each individual words?
  • maybe the textAlign

Do you know the solution to this problem? Maybe there is a built in Processing method that does that?

Answers

  • Hey,

    you can use textWidth() wo get the width of a string with the current text-size. Adding textAscent() textDescent() will give you its height.

    The default text-size is 12, so you can calculate the rest from there.

    Example:

    String str = "ABjgjhlkjlhlkhkl";
    // position and dimensions of the text-box
    int x =20;
    int y =20;
    int w = 200;
    int h = 60;
    
    void setup() {
      size(300, 150);
      // calculate minimum size to fit width
      float minSizeW = 12/textWidth(str) *w;
      // calculate minimum size to fit height
      float minSizeH = 12/(textDescent()+textAscent()) *h;
    
      textSize(min(minSizeW, minSizeH));
    
      noFill();
      rect(x, y, w, h);
    
      fill(0);
      text(str, x, y+h-textDescent());
    }
    
  • Thanks for your answer.

    However, your code seems to put the entire string into one line. What I wanted to achieve, is that the entre string (which is a sentence, so it has several words) populates the entire box. That is, the text size could be bigger because it could be distributed among several lines if the the text height allows for it.

    Do you know a solution to that?

  • I need to think about it but
    1) the number of lines of text depend on the font size
    2) changing the font size alters the text width
    3) changing the text width can change the number lines ....back to (1)

    @benja that was a nice solution I will be very impressed if you come up with a multi-line solution that uses word boundary line breaks.

  • Uh... multiline... hmm, that is more complicated, but not impossible. Starting to think about it.

  • Thanks for your replies.

    My very rough estimate was the following:

    Assumtion: - each char is a square box with side equal to textSize (in pixels). [This assumption is wrong, but, given a long enough String the average value of side of such a box is probably around textSize.]

    The area of the entire text box is widthheight. [A = wh;] The number of chars in the String is str.length(); [wlength = str.length();]

    The average area per char is the entire area divided by number of chars [a = A/wlength]

    The average area per char is equal to textSize^2.

    Therefore:

    textSize = sqrt((w * h)/wlength);

    Now, this "analysis" did not take into account a number of other factors, such as the ones I enumerated originally. Therefore, I introduce a constant c1 that will more or less make up for this. This constant is determined by experiment for each font and should be good enough so that when the string length changes, the string still fits into the box while having the greatest possible font size.

    The result is:

        String theString = "Note that Processing now lets you"+
              "call text() without first specifying a PFont with"+
              " textFont(). In that case, a generic sans-serif"+
             " font will be used instead. ";
    
        // position and dimensions of the text-box
        int centerx = 200;
        int centery = 200;
        int w = 350;
        int h = 100;
    
        float c1 = (float) 1;
    
        int wlength = theString.length();
        float tSize = c1*sqrt((w * h)/wlength);
    
        void setup() {
          rectMode(CENTER);
          background(0,0,0);
          size(500,300) ;
          noFill();
          stroke(255,255,255);
          strokeWeight(1/2);
    
    
          rect(centerx, centery, w, h);          
          textSize(tSize);
          textAlign(LEFT, CENTER);
          text(theString,centerx, centery, w, h);
        }
    

    If you change the string length, the width or height, you will see that the text size adapts and should fit into the square. You might have to alter the value of the constant originally.

  • Answer ✓

    Sadly, this seems inaccurate and would give different results depending on the word-lengths and line-breaks.

    I think the only way to do this is to split the text into words and recreate the lines word by word and test when the text will exceed the boundaries of the box. Here is a draft, open for discussion, imporovements and ideas:

    String str ="Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.";
    int x = 20;
    int y = 20;
    int w = 250;
    int h = 290;
    
    String[] words;
    float currentSize = 5;
    float bestSize = 5;
    float sizeIncrement = 0.5;
    
    void setup() {
      size(400, 400);  
    
      words = str.split(" ");
    
      boolean searching = true;  
      while(searching){
        if(testFontSize(currentSize)){
          bestSize = currentSize;
          currentSize += sizeIncrement;
        }else{
          searching = false;
        }
      }
      println("Best size: "+ bestSize);
    
      textSize(bestSize);
      fill(0);
      text(str, x, y, w, h);
      noFill();
      rect(x, y, w, h);
    }
    
    boolean testFontSize(float s) {
      textSize(s);
      // calculate max lines
      int currentLine = 1;
      int maxLines = floor( h / g.textLeading);
      boolean fitHeight = true;
      int nextWord = 0;
    
      while (fitHeight) {
        if (currentLine > maxLines) {
          fitHeight = false;
        } else {
          String temp = words[nextWord];
          // check if single word is already too wide
          if (textWidth(temp)>w)
            return false;
    
          boolean fitWidth = true;
          // add words until string is too wide  
          while (fitWidth) {
    
            if (textWidth(temp) > w) {
              currentLine++;
              fitWidth = false;
            } else {
              if (nextWord < words.length -1) {
                nextWord++;
                temp += " "+words[nextWord];
              } else
                return true;
            }
          }
        }
      }
    
      return false;
    } 
    
  • This works great, thanks!

    Plus the search takes only about 0.0001s for short Strings to about 0.005s for long sentences to execute, so it can be used without slowing down the program.

    I will try and see if there is any potential for improvement but the code works great for now.

    I think such a method should be added to the Processing library as it is extremely useful.

  • I have tried that also, but apparently I made things over complicated :)

    The code is posted in this related trhead:

    http://forum.processing.org/two/discussion/11654/how-to-count-line-feeds#Comment_47074

Sign In or Register to comment.