p5.js icon indicating copy to clipboard operation
p5.js copied to clipboard

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

Open davepagurek opened this issue 8 months ago • 22 comments

Most appropriate sub-area of p5.js?

  • [ ] Accessibility
  • [ ] Color
  • [ ] Core/Environment/Rendering
  • [ ] Data
  • [ ] DOM
  • [ ] Events
  • [ ] Image
  • [ ] IO
  • [ ] Math
  • [x] 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

davepagurek avatar Apr 17 '25 00:04 davepagurek

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.

ksen0 avatar Apr 17 '25 07:04 ksen0

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

HughJacks avatar Apr 17 '25 21:04 HughJacks

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

davepagurek avatar Apr 17 '25 22:04 davepagurek

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?

HughJacks avatar Apr 21 '25 19:04 HughJacks

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?

davepagurek avatar Apr 21 '25 20:04 davepagurek

fontWidth() gives the width of the text via measureText(string).width including leading/trailing invisibles (spaces, etc.)

textWidth() gives the width of the smallest bounding box containing the visible text, by returning the width of that bounding box (not including invisible characters)

dhowe avatar Apr 22 '25 19:04 dhowe

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?

davepagurek avatar Apr 22 '25 19:04 davepagurek

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

dhowe avatar Apr 22 '25 19:04 dhowe

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 avatar Apr 22 '25 23:04 davepagurek

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.

ksen0 avatar Apr 23 '25 06:04 ksen0

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.

HughJacks avatar Apr 23 '25 21:04 HughJacks

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

ksen0 avatar Apr 23 '25 21:04 ksen0

Hi @HughJacks ! Are you currently working on this / still interested in working on it?

ksen0 avatar Jun 07 '25 08:06 ksen0

Hello, my apologies. I am no longer able to work on it. Best!

HughJacks avatar Jun 08 '25 22:06 HughJacks

hello , I want to work on this issue

Sushma-1706 avatar Sep 11 '25 08:09 Sushma-1706

Hi @Sushma-1706 are you still interested in working on this issue? If so, go ahead!

ksen0 avatar Sep 18 '25 07:09 ksen0

yes, i am still intrested thank you

Sushma-1706 avatar Sep 18 '25 08:09 Sushma-1706

It's yours @Sushma-1706 ! Let us know (here or on Discord in the contribute-to-p5 channel) if you have any questions !

ksen0 avatar Sep 18 '25 20:09 ksen0

Hey @Sushma-1706 are you still working on this issue? if no, then I would like to work on it thank you

hxrshxz avatar Oct 18 '25 18:10 hxrshxz

@hxrshxz hey , I'm not working on it anymore you go ahead

Sushma-1706 avatar Oct 18 '25 18:10 Sushma-1706

Hey @ksen0 Can you assign it to me thank you

hxrshxz avatar Oct 18 '25 18:10 hxrshxz

Hey @ksen0 I would like working on this issue. Can you assign it to me.

kishanchaubey23 avatar Nov 15 '25 19:11 kishanchaubey23