Skip to content

[p5.js 2.0 Beta Bug Report]: Document difference between textWidth and fontWidth with respect to whitespace #7745

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
1 of 17 tasks
davepagurek opened this issue Apr 17, 2025 · 12 comments

Comments

@davepagurek
Copy link
Contributor

davepagurek commented Apr 17, 2025

Most appropriate sub-area of p5.js?

  • Accessibility
  • Color
  • Core/Environment/Rendering
  • Data
  • DOM
  • Events
  • Image
  • IO
  • Math
  • Typography
  • Utilities
  • WebGL
  • Build process
  • Unit testing
  • Internationalization
  • Friendly errors
  • Other (specify if possible)

p5.js version

Latest dev-2.0

Web browser and version

Firefox

Operating system

MacOS

Steps to reproduce this

This logs 0 in 2.0 but something around 5 in 1.x:

function setup() {
  createCanvas(200, 200)
  textSize(20)
  console.log(textWidth(' '))
}

Live: https://editor.p5js.org/davepagurek/sketches/xlqZOPgIR

@ksen0
Copy link
Member

ksen0 commented Apr 17, 2025

Looks like maybe a conditional logic issue, with the empty string not getting evaluated? If the space is between characters, it is counted:

With 2.0:

function setup() {
  createCanvas(200, 200)
  textSize(20)
  console.log(textWidth(' ')) // 0
  console.log(textWidth('x')) // 9.58984375
  console.log(textWidth('xy')) // 19.658203125
  console.log(textWidth('x y')) // 25.21484375
}

Volunteers welcome to work on fixing this issue! Specifically, textWidth(' ') should not be 0, rather something around 5.

@HughJacks
Copy link

Hi! I would like to be assigned to this issue.

@davepagurek
Copy link
Contributor Author

Thanks @HughJacks ! I'll assign this to you.

@HughJacks
Copy link

I looked into the issue with textWidth(' ') returning 0. From what I can see the issue has to do with the fact that we calculate text width using actualBoundingBoxLeft and actualBoundingBoxRight from the TextMetrics object.

Current behavior:

let metrics = this.textDrawingContext().measureText(s);
let abl = metrics.actualBoundingBoxLeft;
let abr = metrics.actualBoundingBoxRight;
return abr + abl;

For a space character, both actualBoundingBoxLeft and actualBoundingBoxRight are 0, resulting in a total width of 0. However, the space character does have a visual width that should be measured.

Looking at the TextMetrics object for a space:

Metrics: TextMetrics {
  width: 5.556640625,           // This is the actual advance width
  actualBoundingBoxLeft: 0,     // No left extension
  actualBoundingBoxRight: 0,    // No right extension
  fontBoundingBoxAscent: 1.6875,
  fontBoundingBoxDescent: 21.3125,
  ...
}

Below is a screenshot showing how we handle other cases.
I notice that ' x y ' has the same width as 'x y'.
Image

I'm not sure if the issue is within _textWidthSingle or the conditional logic within _processLines.

What are the desired outputs of calling textWidth on each of these cases?

@davepagurek
Copy link
Contributor Author

Maybe @dhowe has some more info on what the difference between fontWidth and textWidth should be in general? fontWidth uses the width property while textWidth measures using actualBoundingBoxLeft/Right.

Some possible options I could see here:

  • If leading/trailing whitespace are the only differences in general between textWidth/fontWidth, we could document that textWidth measures just the text content, ignoring leading/trailing spaces, and that you should use fontWidth if you need them to be significant.
  • If there are other differences and leading/trailing spaces are supposed to count, maybe we could match /^\s*/ and /\s*$/ for each line (the leading and trailing whitespace, respectively) and add the .width property for each of those to the total width?
  • If leading/trailing whitespace are the only differences in general between textWidth/fontWidth and we think whitespace should always be significant, maybe we should merge those functions?

@dhowe
Copy link
Contributor

dhowe commented Apr 22, 2025

fontWidth() gives the width of the text via measureText(string).width assuming that each character in the string occupies the same space as the widest character in the font

textWidth() gives the width of the smallest bounding box containing the specified text, by returning the width of that bounding box

@davepagurek
Copy link
Contributor Author

It sounds like we do want space to be significant in that case? Since a single space character would still have a bounding box. So would it sound right to you to take the width returned by the current implementation of textWidth and add to that the width due to leading and trailing whitespace?

@dhowe
Copy link
Contributor

dhowe commented Apr 22, 2025

Here is the current behavior (which is consistent) for the two varieties of xWidth and xBounds, noting that textBounds() uses the same metrics as textWidth(), and fontBounds() uses the same metrics as fontWidth().

textBounds() gives the visual bounding box for the drawn text (with textWidth() giving the width of that box), while fontBounds() uses the browser's measureText(string).width

So it may be that we only need to make this clear in the reference (?)

Image

Image

@davepagurek
Copy link
Contributor Author

ok, I think that makes sense! In probably the references for both text and font width, we could briefly explain that one is tight and one is loose, and possibly show a comparison example on both references too (could be the same one on both.)

@davepagurek davepagurek changed the title [p5.js 2.0 Beta Bug Report]: textWidth returns 0 for a space [p5.js 2.0 Beta Bug Report]: Document difference between textWidth and fontWidth with respect to whitespace Apr 22, 2025
@ksen0
Copy link
Member

ksen0 commented Apr 23, 2025

Thank you so much for the explanation and investigation @HughJacks @dhowe and @davepagurek!

@HughJacks so the current behavior is actually the expected/correct behavior, but it the documentation + examples could really benefit from updates as suggested above:

  1. explain that one is tight and one is loose
  2. possibly show a comparison example on both references too (could be the same example on both)

Are you still interested in addressing this issue? If so, please feel free to go ahead and put together a PR updating the documentation for these 2 functions.

@HughJacks
Copy link

Hey @ksen0 sorry for slightly delayed response. Yes I would be interested in updating the documentation and examples. I will get on that soon.

Thanks to everyone for their help.

@ksen0
Copy link
Member

ksen0 commented Apr 23, 2025

No worries and no rush @HughJacks ! Thanks for taking this on

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: In Progress
Development

No branches or pull requests

4 participants