processing / p5.js

p5.js is a client-side JS platform that empowers artists, designers, students, and anyone to learn to code and express themselves creatively on the web. It is based on the core principles of Processing. http://twitter.com/p5xjs —
http://p5js.org/
GNU Lesser General Public License v2.1
21.67k stars 3.33k forks source link

Bring back textHeight() #3320

Open devinhalladay opened 5 years ago

devinhalladay commented 5 years ago

Nature of issue?

Most appropriate sub-area of p5.js?

Which platform were you using when you encountered this?

New feature details:

Working with typography in some basic ways is currently very difficult in P5; currently, I'm really struggling with multiple multi-line text boxes that need to be positioned in relation to each other. I see that P5 used to have a textHeight() function that got the height of a text block (though I'm not sure this handled multi-line text) and it was removed in PR #368 because Processing doesn't have an equivalent.

Is there the possibility of bringing this back into P5 so that the library can handle typographic basics on its own without the use of P5.dom elements?

(To clarify, I would like to use this to get the height of a multi-line text block.)

TanviKumar commented 5 years ago

Will be happy to take this up if the feature is to be brought back!

dhowe commented 5 years ago

We might also want to support multi-line metrics with p5.Font.textBounds

devinhalladay commented 5 years ago

Any updates on this? I'd love to contribute but not sure my skill level is high enough to contribute any meaningful code. @dhowe what's the best process for me to help get multi-line metrics moving with p5.Font.textBounds?

dhowe commented 5 years ago

This is not totally straightforward. There are two ways to make multiline text in p5.js: a) to include line breaks (\n) in your text-string, or b) to pass x,y,w,h to the text() command, which automatically breaks lines according to the implied rectangle.

For case a) one could easily enough split the string on line breaks into some number of substrings, then compute the bounds for each substring (using the current p5.Font.textBounds()), then return the smallest bounding box containing each of those single line bounding boxes.

For case b) if the user supplies a bounding box, then this is, or course, a bounding box for the supplied text, however, it is not a tight bounding box (In most cases there will be additional space along the edges). To get tight bounds, one would again need to compute the bounds for each line separately, then return the aggregate bounds for the set. If one had implemented case a), then it could be called with the text from case b) with line breaks specified as \n.

The 2nd issue with case b) is that the signature of p5.Font.textBounds() would have to change to accommodate these additional parameters (width/height), and its not fully clear (to me at least) how this would best work.

image

Note that the optional fontSize parameter is fully redundant now, as textBounds will use the current textSize (bbox1 and bbox2 will be identical below).

image

So one option would be to allow the 3rd and 4th optional parameters to be width and height of the text box, as follows:

p5.Font.prototype.textBounds = function(str, x, y, [fontSizeOrWidth], [height], [options]);

but this would involve either removing fontSize from the API (a breaking change), or the confusing double-use of the 3rd parameter, where if there is no 4th parameter it means size, but if there is a 4th, it then means width. I imagine there are other options as well (such as including width/height in the options object) but none seem appealing.

Spongman commented 5 years ago

i wonder, does it make sense for the user to supply the height of a bounding box? isn't the height a calculated result?

dhowe commented 5 years ago

The basic use-case here is, I think, to layout text on a 'page' or other rectangular space, where one wants to use as much of a long string of text as can fit within that space. One can also specify [x,y,width] only, without a height, for cases where you want the height to vary based on the text and font metrics (though I notice a bug with this: see #3355).

The more I think about this, the more I lean toward text() and p5.Font.textBounds() accepting the same arguments (with the additional optional 'options' argument for the latter)...

GodWoold commented 5 years ago

Solved ✓ https://editor.p5js.org/ThatsTrue/sketches/QmHcXTd_o (works for p5.js up to u to convert it to processing)

munusshih commented 1 year ago

https://editor.p5js.org/munusshih/sketches/4BwL7q2Fe

p5.prototype.textHeight = function (...args) {

  const tL = this._renderer.textLeading();
  const tS = this._renderer.textSize();

  let lineNum = 0;
  if (typeof args[0] !== undefined) {
    lineNum = args[0].split(/\r?\n|\r|\n/g).length;
  }
  const textH = tL * (lineNum - 1) + tS;

  return textH;
};

I made this sketch here to propose an idea of using textLeading and textSize to calculate textHeight() But I'm not sure if this math is general enough to cover all cases.

limzykenneth commented 1 year ago

@munusshih That I think will give the maximum height possible but not necessarily the exact height, ie. it includes the ascenders of the first line (if any) and descenders space of the last line (if any) even if the line itself don't have anything that goes to the top or bottom of the bounding box, it will still be calculated.

For example

textHeight("xxxxxxxxx");
textHeight("AAAAAAAAAA");

would have the same height.

It is simpler this way but it would differ from value returned from textBounds(), so whether this is ok or not depending on the possible use case of textHeight().

dhowe commented 1 day ago

In v2.0 this is easily handled via textBounds(), for a tight, text-aware bounding box, or fontBounds(), for a loose, font-specific bounds, then accessing the width or height property