Skip to content
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

Text metrics #356

Closed
scottgarner opened this issue Sep 5, 2014 · 22 comments
Closed

Text metrics #356

scottgarner opened this issue Sep 5, 2014 · 22 comments

Comments

@scottgarner
Copy link
Contributor

textWidth() seems to only return the correct value after calling text().

function setup() {

  background(0);
  fill(255);
  noStroke();

  textSize(20);
  s = "String.";

  // Returns 28.9013671875
  console.log(textWidth(s));

  text(s, 0, 50);
  // Returns 57.802734375

  console.log(textWidth(s)); 

}

I think it's just a matter of setting this.drawingContext.font before calling this.drawingContext.measureText(), but I'm not sure what would be the preferred approach.

I'm thinking the best plan is to take this out of text(), which is inefficient since there's no reason to set the styling for every call if it hasn't changed, and instead set this.drawingContext.font anytime a relavant property (size, style, font) is updated. This way everything is always up to date for text(), textWidth() and textHeight();

@scottgarner
Copy link
Contributor Author

Okay, so the above changes work, but I think there's a bigger problem. measureText() returns a TextMetrics object that doesn't actually include height. Maybe some browsers support it, but it isn't in the spec. I checked on a Mac in Safari, Chome and Firefox and doesn't look like textHeight() works at all in its current form?

@dhowe
Copy link
Contributor

dhowe commented Sep 5, 2014

I just noticed this (separately) as well. textHeight() returns undefined in all my tests. Seems that also textAscent() and textDescent() are missing from Typography/attributes.js. I'm not aware of a simple fix for this, so I'm wondering if we might use Font.js (https://github.com/Pomax/Font.js) to grab these values (its MIT-expat licensed). Thoughts?

@scottgarner
Copy link
Contributor Author

I've looked on SO for a good solution and found a few articles. Looks like the only way to get the true pixel height of a given string is to manually calculate it by drawing to a canvas and examining the pixels. Bummer! That's what Font.js seems to do to get the ascent and descent, which it adds to get the height.

Some reading:

http://videlais.com/2014/03/16/the-many-and-varied-problems-with-measuring-font-height-for-html5-canvas/
http://mudcu.be/journal/2011/01/html5-typographic-metrics/#baselineCanvas

@lmccart
Copy link
Member

lmccart commented Sep 5, 2014

thanks for looking into this @scottgarner and @dhowe.

font.js looks light enough and seems to do what we want. I'd propose trying to wrap this in. alternatively, we leave textHeight etc out of the API and refer users to adding font.js or similar themselves. but I think it'd be nice to take the burden off and support these methods if possible.

@scottgarner
Copy link
Contributor Author

Thoughts on handling the wrapping? At a minimum, at startup and whenever textFont() is called a new Font object would need to be created. Kind of a bummer since it creates a giant canvas even if you don't use any text. Maybe this should all live in a typography addon?

We could also try to build our own version with a similar technique, perhaps only creating the test canvas when the height, ascent or descent are checked.

@dhowe
Copy link
Contributor

dhowe commented Sep 6, 2014

One idea might be to detect font-face rules in the css -- if one or more is found, then it creates the offscreen canvas, does the measurements, and creates one or more font objects (we might also add the loading into the preload routines)...

So we would only be creating the extra canvas when fonts are used -- or, since in the p5.js case, as we already have a canvas, we may be able to use it for the measurements rather than creating an additional one (though if the text is larger than the user-created canvas, the measurements may not be correct).

@scottgarner
Copy link
Contributor Author

Size would definitely be a concern using the default canvas. Font.js goes with 1000x1000. It has placeholder logic for slicing up longer segments, but it isn't implemented. I might also worry about state? If you were in the middle of a draw loop and added some text, you'd have some time where your canvas was taken over to be solid white with black text. Since there's lots of looping through pixels, it might even last a couple of frames?

Looking for font-face is an interesting idea, but we'd want it to work with system fonts, too, and even generics like "serif" and "sans-serif".

I experimented with some techniques from this SO post that use spans instead of a canvas, but the results aren't quite what I'd expect:

http://stackoverflow.com/questions/1134586/how-can-you-find-the-height-of-text-on-an-html-canvas/25355178#25355178

@dhowe
Copy link
Contributor

dhowe commented Sep 6, 2014

I think we are only talking about (max) ascent + descent (which can be computed once in preload), no? If we scale these values to the font-size, we get a (max) height measurement for any text string. This will not be a tight bound (taking into account descenders, etc.), but this is also the case in the Processing API -- it is only textWidth() that takes a string argument, textAscent() and textDescent() are constant for a given font/size. So I'm not clear on the problem mentioned above in the draw() loop...

And I'm not sure we can get measurement data for system fonts -- or at least not according to Font.js: 'system font files cannot be inspected, as they do not have an associated font.metrics object...'

But the span option looks interesting -- what kind of results did you see @scottgarner ?

@scottgarner
Copy link
Contributor Author

Oh, right. I see what you mean. I guess my expectations for textHeight would be the actual pixel height for a given string. And actually, textHeight() doesn't seem to exist in Processing proper? I'm remembering now just adding the ascent and decent on a project years ago.

In that case, the span trick might be the right solution for ascent and decent? It also looks like the HTML "living standard" has a much more robust treatment of text metrics, including bounding boxes, that we might consider down the line.

http://www.whatwg.org/specs/web-apps/current-work/multipage/scripting.html#textmetrics

@dhowe
Copy link
Contributor

dhowe commented Sep 6, 2014

Yes, I think whatever we do is probably a stop-gap until better support in the browsers... I also just tested the span solution and it seems OK, at least for system fonts. Also, and perhaps this is (or should be) a separate issue, but is there a method for loading non-system fonts (font-face fonts, etc., and also making sure they are preloaded before drawing starts)?

@scottgarner
Copy link
Contributor Author

I think the idea is that PFont would handle that? It's mentioned in #60.

@lmccart
Copy link
Member

lmccart commented Sep 7, 2014

yeah the whole pfont thing needs some rethinking, and I think we could feel free to change up the API with this to suit the web. at the minimum it'd be nice to have an easy way to hook in a font without having to touch css. with the text metrics. if some end up just being more hassle than they're worth we could probably cut things for now, considering support will likely change and improve in the future.

it sounds like both of you have more thoughts and experience with text so I'm happy to go with whatever you conclude.

@scottgarner
Copy link
Contributor Author

I'll take a stab at ascent and descent next week based on the span method,
which seems fairly reliable. I'd vote we drop textHeight() to avoid
confusion and since it isn't in Processing.

One question of approach: Do you think it would be better to calculate the
measurements whenever font properties were changed, or to calculate on the
fly when textAscent() or textDescent() are called?

On Sat, Sep 6, 2014 at 6:43 PM, Lauren McCarthy notifications@github.com
wrote:

yeah the whole pfont thing needs some rethinking, and I think we could
feel free to change up the API with this to suit the web. at the minimum
it'd be nice to have an easy way to hook in a font without having to touch
css. with the text metrics. if some end up just being more hassle than
they're worth we could probably cut things for now, considering support
will likely change and improve in the future.

it sounds like both of you have more thoughts and experience with text so
I'm happy to go with whatever you conclude.


Reply to this email directly or view it on GitHub
https://github.com/lmccart/p5.js/issues/356#issuecomment-54734489.

@lmccart
Copy link
Member

lmccart commented Sep 7, 2014

maybe calculate once when textAscent or textDescent is called if font properties have changed since last calculation?

@dhowe
Copy link
Contributor

dhowe commented Sep 7, 2014

Seems possible we calculate ascent and descent once (for each font) on load, then store these data as part of PFont, then scale based on font-size when textAscent/Descent are called... (this way we don't have to worry about creating DOM elements in the draw loop)

@scottgarner scottgarner changed the title textWidth Text metrics Sep 12, 2014
@scottgarner
Copy link
Contributor Author

Okay, I took a stab at this, but I'm not sure if the approach is ideal.

Because it's such a weird, hacky solution, I only use it if textDescent() or textAscent() are explicitly called. Calling one stores the other property as well and the values are essentially cached until the context font properties are changed. Right now, that just clears them instead of recalculating because I don't want to go through the weirdness unless someone is definitely using these functions, though I'm probably being overly cautious.

This could be done in a more refined way, like @dhowe's idea of storing a ratio that would work at different sizes for the same font, but I thought that might be better as part of the PFont implementation instead of reworking everything here.

@scottgarner
Copy link
Contributor Author

One other thing here. Because the font properties aren't applied at the beginning of text() like they were originally, the defaults aren't applied unless a property is explicitly changed.

@lmccart, what would be the appropriate place to initialize font properties for the main canvas? Basically, _applyTextProperties() just needs to be called somewhere during startup.

@lmccart
Copy link
Member

lmccart commented Sep 14, 2014

great stuff! you could call _applyTextProperties just after createCanvas in _start or somewhere in _setup, whichever makes more sense

lmccart pushed a commit that referenced this issue Sep 14, 2014
Removed textHeight() and add textAscent() and textDescent() per #356.
@scottgarner
Copy link
Contributor Author

Oh, a comment in _start() made me realize that the default canvas will very likely be replaced. Maybe it's best to do it in createCanvas so all canvases are covered?

I suppose it would also need to be called in createGraphics? A little worried I'm missing something here.

@lmccart
Copy link
Member

lmccart commented Sep 14, 2014

ah I see. maybe better to do it in the p5.Graphics constructor then?

@scottgarner
Copy link
Contributor Author

Hmm. That's the right place, I think. The problem is that we wouldn't want to apply tweaked settings to a p5.Graphics because they should aways start pristine (like with default stroke, fill, font etc.). So there should probably be a default set of font attributes that always get applied on canvas and graphics creation.

The quick fix would be to just set this.drawingContext.font in p5.Graphics with the defaults hardcoded, but that would mean the values in typography/attributes.js wouldn't necessarily be the defaults if someone made changes there.

That might be the best option for now? Once we get into PFont, we could have a default p5.Font created on start up that is always applied to new canvases/graphics.

lmccart pushed a commit that referenced this issue Sep 16, 2014
Setting font defaults for new canvases/graphics per #356
@dhowe
Copy link
Contributor

dhowe commented May 30, 2015

p5.Font now in place -- this should be closed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants