# A more accurate approach to calculating text width & text height

**URL:** <https://discourse.processing.org/t/a-more-accurate-approach-to-calculating-text-width-text-height/24879>\
**Category:** Gallery\
**Created:** [October 24, 2020, 10:02pm UTC](https://discourse.processing.org/t/a-more-accurate-approach-to-calculating-text-width-text-height/24879 "2020-10-24T22:02:16Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![micycle](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/micycle/32/201_2.png) [@micycle](https://discourse.processing.org/u/micycle)\
**Post date:** [October 24, 2020, 10:02pm UTC](https://discourse.processing.org/t/a-more-accurate-approach-to-calculating-text-width-text-height/24879/1 "2020-10-24T22:02:16Z")

</div>

_Originally posted on SO, but should be relevant here too._

> <https://stackoverflow.com/questions/64146977/how-do-you-find-out-the-height-and-width-of-a-pfont-string-in-processing-or-java/64518397#64518397>

Using the inbuilt functions `textWidth()`, `textAscent()`, and `textDescent()` are an easy way to get a _good_ approximate result for the height and width of a string, but they are not _exact_.

Why?

- `textAscent()` returns text height above the line **based on the letter 'd’**
- `textDescent()` returns text height below the line **based on the letter ‘p’**.
- `textWidth()` includes glyph whitespace (aka padding; ideally we want to ignore this for the first and last characters)

`textAscent() + textDescent()` therefore measures the **maximum height** of a string in a given font and font size, and not the height of a specific string. In other words, if your text doesn’t include both ‘d’ and ‘p’ characters, then using these methods to determine text height will overestimate the result (as we see in Kevin’s screenshot).

* * *

## Getting the exact height

We can use this approach to get an _exact_ result for height:

1. Get a vector representation of each character
2. Iterate over the vector’s vertices, finding:
  - The vertex with highest Y position
  - The vertex with lowest Y position

3. Subtract the highest Y position from the lowest Y position to return actual string height

### Code Example

Note you’ll need to explicitly create a `PFont` for this.

```auto
String string = "Hello world";

PFont font = createFont("Arial", 96, true); // arial, size 96
textFont(font);

float minY = Float.MAX_VALUE;
float maxY = Float.NEGATIVE_INFINITY;

for (Character c : string.toCharArray()) {
	PShape character = font.getShape(c); // create character vector
	for (int i = 0; i < character.getVertexCount(); i++) {
		minY = min(character.getVertex(i).y, minY);
		maxY = max(character.getVertex(i).y, maxY);
	}
}

final float textHeight = maxY - minY;

```

**Result**

_(Note we’re still using `textWidth()` for width here)_

```auto
text(string, mouseX, mouseY);
rect(mouseX, mouseY, textWidth("Hello world"), -textHeight);

```

[![enter image description here](https://i.stack.imgur.com/51PWC.png)](https://i.stack.imgur.com/51PWC.png)

## Getting the exact width

### Code Example

```auto
String string = "Hello world";

PFont font = createFont("Arial", 96, true); // arial, size 96
textFont(font);

float textWidth = textWidth(string); // call Processing method

whitespace = (font.width(string.charAt(string.length() - 1)) * font.getSize()
		- font.getGlyph(string.charAt(string.length() - 1)).width) / 2;
textWidth -= whitespace; // subtract whitespace of last character

whitespace = (font.width(string.charAt(0)) * font.getSize() - font.getGlyph(string.charAt(0)).width) / 2;
textWidth -= whitespace; // subtract whitespace of first character

```

**Result**

_(Putting the two together…)_

```auto
text(string, mouseX, mouseY);
rect(mouseX + whitespace, mouseY, textWidth, -textHeight);

```

[![enter image description here](https://i.stack.imgur.com/oQLa7.png)](https://i.stack.imgur.com/oQLa7.png)

### Y-Axis Alignment

A rectangle drawn around `"Hello world"` happens to be aligned because none of the glyphs descend below the _baseline_.

With a string like `@#'pdXW\`, both `@` and `p` descend below the baseline such that the rectangle, although it is the correct height, is out of alignment with the string on the y-axis, as below:

[![enter image description here](https://i.stack.imgur.com/IpUGS.png)](https://i.stack.imgur.com/IpUGS.png)

A programmatic way to determine the y-offset would be to find the Y-coordinate of the lowest _(although remember Processing’s y-axis extends downwards so we’re actually looking for the highest value)_ vertex . Fortunately, this was calculated as part of finding the exact height.

We can simply use the `maxY` value that was calculated there to offset the text bounding box.

**Result**

```auto
text(string, mouseX, mouseY);
rect(mouseX + whitespace, mouseY + maxY, textWidth, -textHeight);

```

[![enter image description here](https://i.stack.imgur.com/vaf2q.png)](https://i.stack.imgur.com/vaf2q.png)

---

<div class="post-metadata">

**Author:** ![micycle](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/micycle/32/201_2.png) [@micycle](https://discourse.processing.org/u/micycle)\
**Post date:** [October 24, 2020, 10:06pm UTC](https://discourse.processing.org/t/a-more-accurate-approach-to-calculating-text-width-text-height/24879/2 "2020-10-24T22:06:22Z")

</div>

To compare, the naive approach…

```auto
text(string, mouseX, mouseY);
rect(mouseX, mouseY, textWidth(string), -textAscent() + textDescent());

```

…looks like this:

![image](https://canada1.discourse-cdn.com/flex036/uploads/processingfoundation1/original/2X/0/055eefc8d846069bc2ff4cf7a5b705e3de0e956a.png)

---

<div class="post-metadata">

**Author:** ![everythingability](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/everythingability/32/937_2.png) [@everythingability](https://discourse.processing.org/u/everythingability)\
**Post date:** [October 27, 2020, 6:11pm UTC](https://discourse.processing.org/t/a-more-accurate-approach-to-calculating-text-width-text-height/24879/4 "2020-10-27T18:11:08Z")

</div>

Oh hang on. I tried the code and think I found a bug… in adapting it slightly it doesn’t work anymore.

```auto
String string = "Hello @world";
float whitespace;
float textWidth;
float textHeight;
float minY;
float maxY;

void setup() {
  size(800, 800);
  noFill();
  stroke(255, 0, 0);
  
  PFont font = createFont("Arial", 96, true); // arial, size 96
  textFont(font);  
  textWidth = textWidth(string); // call Processing method

  float minY = Float.MAX_VALUE;
  float maxY = Float.NEGATIVE_INFINITY;

  for (Character c : string.toCharArray()) {
    PShape character = font.getShape(c); // create character vector
    for (int i = 0; i < character.getVertexCount(); i++) {
      minY = min(character.getVertex(i).y, minY);
      maxY = max(character.getVertex(i).y, maxY);
    }
  }

  textHeight = maxY - minY;

  whitespace = (font.width(string.charAt(string.length() - 1)) * font.getSize()
    - font.getGlyph(string.charAt(string.length() - 1)).width) / 2;
  textWidth -= whitespace; // subtract whitespace of last character

  whitespace = (font.width(string.charAt(0)) * font.getSize() - font.getGlyph(string.charAt(0)).width) / 2;
  textWidth -= whitespace; // subtract whitespace of first character
  
  
}

void draw() {
  background(0);
  float theX = 100;
  float theY = 200;
  
  text(string, theX, theY);
  
  fill(#E5FA08);
  ellipse(theX, theY, 4, 4);
  
  noFill();
  stroke(#FA085D);
  rect(theX + whitespace, theY + maxY, textWidth, -textHeight);
}

```

---

<div class="post-metadata">

**Author:** ![micycle](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/micycle/32/201_2.png) [@micycle](https://discourse.processing.org/u/micycle)\
**Post date:** [October 27, 2020, 6:29pm UTC](https://discourse.processing.org/t/a-more-accurate-approach-to-calculating-text-width-text-height/24879/5 "2020-10-27T18:29:02Z")

</div>

> [@everythingability](#):
>
> `float maxY = Float.NEGATIVE_INFINITY;`

The bug is with your code. You’re assigning to a second `maxY` variable scoped within `setup()`, and not the one which `theY + maxY` references.

![image](https://canada1.discourse-cdn.com/flex036/uploads/processingfoundation1/original/2X/a/ae734b7df056a9781c2ff47bda0a36449a6a9ed9.png)

---

<div class="post-metadata">

**Author:** ![everythingability](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/everythingability/32/937_2.png) [@everythingability](https://discourse.processing.org/u/everythingability)\
**Post date:** [October 27, 2020, 6:59pm UTC](https://discourse.processing.org/t/a-more-accurate-approach-to-calculating-text-width-text-height/24879/6 "2020-10-27T18:59:11Z")

</div>

OH YES. DUR!!!

Thank you, thank you, thank you…

---

<div class="post-metadata">

**Author:** ![Tsskyx](https://avatars.discourse-cdn.com/v4/letter/t/90ced4/32.png) [@Tsskyx](https://discourse.processing.org/u/Tsskyx)\
**Post date:** [December 26, 2021, 2:49pm UTC](https://discourse.processing.org/t/a-more-accurate-approach-to-calculating-text-width-text-height/24879/7 "2021-12-26T14:49:57Z")

</div>

Hello, I seem to have come up with an even better version of doing this. As you can see in the image above, that final “d” has still a bit of whitespace to the right, which this method didn’t account for. Well, here’s my code:

```auto
PFont font;
String string = "";

void setup() {
  size(800, 600);
  font = createFont("Arial", 96, true);
  textFont(font);
}

void draw() {
  background(0);
  fill(255, 100);
  stroke(255, 0, 0);
  textWithBoxes(string, mouseX, mouseY);
}

public void textWithBoxes(String string, float x, float y) {
  float leftMost = Float.POSITIVE_INFINITY, rightMost = Float.NEGATIVE_INFINITY, topMost = Float.POSITIVE_INFINITY, bottomMost = Float.NEGATIVE_INFINITY;

  text(string, x, y);
  
  noFill();
  for (Character c : string.toCharArray()) {
    if (c != ' ') {
      PFont.Glyph glyph = font.getGlyph(c);

      float left = x + glyph.leftExtent;
      float top = y - glyph.topExtent;
      
      float right = left + glyph.width;
      float bottom = top + glyph.height;

      leftMost = left < leftMost ? left : leftMost;
      topMost = top < topMost ? top : topMost;
      rightMost = right > rightMost ? right : rightMost;
      bottomMost = bottom > bottomMost ? bottom : bottomMost;

      rect(left, top, glyph.width, glyph.height);
    }
    x += textWidth(c);
  }
  rect(leftMost, topMost, rightMost - leftMost, bottomMost - topMost);
}

```

I took this knowledge from the Processing source code, which uses these glyph class variables to position the letters when rendering them. This even has the advantage of being able to give you the exact bounding box of each character in the string.

There was also one slight issue with the bounding box of the space character, which was HUGE for some reason, so I omitted it. Any other inaccuracies are not my fault; it seems that some characters render off by like 1 pixel in whatever direction, requiring manual tweaking.
