provide an option to decouple fontSize & canvasSize
jcppman opened this issue · 13 comments
Hi fellows,
First, thanks for this useful lib!
It would be very useful if an option like options.font.size
or something is provided to use as font size while rendering text, as sometimes font-size
and the actual dimensions of the rendered text on the canvas are quite different (and would be clipped if the actual size is bigger). please see the example here
so an optional option is really helpful as users can tweak it by themselve if meet such scenario.
I'd love to provide a PR if you like
Thanks
Hi @jcppman, thanks for finding this issue and creating the JSFiddle!
It looks like this is naivety on my part when dealing with font sizing - I had a quick look earlier and hacked in something that seemed to work pretty well if the user defined the font size in em
s rather than px
s. I'm fairly certain we can switch it around so that we can continue to use px
and so that we don't need to add an additional option - I would much rather deal with this transparently so that the end user doesn't have to worry about fiddling around with the font size/canvas size.
I'll try and get something more concrete done this week and get back to you on it.
Thanks again!
Sounds awesome! Would you mind to leak more details you've found? Just curious :)
Sure - I forked your original fiddle: https://jsfiddle.net/3v5c82xw/5/. In the fork, I pasted the plugin's code and added a function (getFontSize
) to get the element's font size, which I borrowed and adapted from here: http://tzi.fr/js/convert-em-in-px. I multiplied the canvas size by the returned value from that function, then I changed the size
option to use em
s instead of px
s.
Having thought about it a little more, I'm not sure we will be able to fix this when the size
option is specified in px
s, as I think we'd need to know the em ratio of the font that's being used... I'm a little fuzzy on font sizing and stuff TBH....
That said, perhaps the best solution will be to have the plugin support em
/rem
units for the size
option, and then update the docs to recommend that users specify the size in those units to avoid this issue.
Hmm, after digging I got something.
The current solution doesn't always work and here's the jsfiddle to demo the fail case: https://jsfiddle.net/5oybj1rh/3/ and please open the web console to see debug info.
The reason is, in current solution:
- when using
getFontSize()
to get fontSize, what it get is the fontSize of the container, in this case.mat
div, on my Chrome the default value is16px
. - we set
options.size
to3em
and after parsing it is3
- so
canvasSize
is set to16px * 3 = 48 px
- when we set
context.font = '3em FontAwesome'
, what it refers to as1em
is the font size of his parent, in canvas it is the default value10px
Reference, so what we get is30px FontAwesome
- and
30px
happens to be smaller than48px
so no clipping
however, clipping might happen if the font size of parent container is close to, equal or smaller than default canvas font size (10px
).
the core issue is that there's no guarantee that when font-size is x
, the actual dimensions of rendered character will be smaller than x
, it all depends on font-icon designers.
I got an idea that might be able to solve this, please tell me what you think:
if there's no way to "predict" the actual size of rendered characters, fine, let's render it and "measure" it! Exactly like the way you get the unicode!
You can find the modified fiddle here: https://jsfiddle.net/pbLzhd55/4/
First I tried getComputedStyle
but because the actual character (the :before
) is display:inline
so both width
and height
are "auto"
, and although his wrapper (the <i>
) has width
and height
but the value is different to the actual dimensions (usually smaller, say if the character is 16 x 17 usually his wrapper is 16 x 16 or something).
so we need to use getBoundingClientRect
, and since we can't get pseudo elements as an element and use getBoundingClientRect
on it, I change the <i>
to have display: inline
and remove position: absolute
to make it exactly big as the content inside, then wrap it with another position: absolute;
wrapper to prevent it from affecting others.
now finally I got the exact size of the character!
obviously this method need to be improved a little bit because due to antialiasing or other reasons, when we draw a character with font-size: 18px
on canvas the result is slightly different (you can see the canvas version in the fiddle) than a font-size: 18px
character in DOM, it is blurred so might still be clipped a little bit.
this might be a solution: http://www.html5rocks.com/en/tutorials/canvas/hidpi/
Awesome work @jcppman, thanks! Everything you've written above makes sense, and the demo looks good too. I got it into my head yesterday that we'd have to somehow know the size of an M character for the font (which Font Awesome and other icon fonts obviously doesn't have), but measuring the actual character we want to use is a much better idea.
I couldn't see any issue with blurring/clipping with cursor in the demo however. The canvas in the topbar does look blurred though. There's an existing issue I'm aware of, where adding an outline to the cursor looks awful in Chrome under *nix OSes. That cropped up with a Chrome update a while back - it always used to look fine, and still does (or at least did last time I checked) on Chrome for Windows.
I'll try and get your changes into a branch today and run the tests, and make sure all the various options still work as expected.
Thanks again for the hard work you've put into this issue!
I'm glad that it helps! 😈
The blurring & clipping issue might have something to do with screen resolution, and clipping is a little bit critical than blurring I'd say. Try some "naughtier" icons like "fa-reddit-alien" or "fa-arrows-v", on my side they're very "clipped"
Those icons work fine for me with your changes - are they still clipped for you even with your changes?
I just tried it on a windows machine with normal resolution and it works perfectly. So it must be the canvas HiDPI supporting issue
FYI I found a solid solution, but only works on Chrome: combine window.devicePixelRate
with -webkit-image-set
: https://jsfiddle.net/pbLzhd55/8/, and here is the reference.
@jcppman Looks Firefox aren't in any particular hurry to implement the image-set
CSS, so I'm not sure where to go with this next. I'd love to hack around and try a few things but I don't have access to HiDPI screen :(
yeah I guess this one (HiDPI compatibility) is more trivial than the original clipping issue, let's wait for browsers to grow up a bit :)
OK good plan - let's get this one fixed first, and get a separate issue raised for the HiDPI screens issue. Do you want to go ahead and create a pull request with your original changes implemented, or do you want me to implement it?
Actually I do have a branch that contains the fix, but somehow I can't pass the test on both PhantomJs and real browsers, even without the fixing, on both my windows & osx machine.
I'd create the PR first anyway, and if you find that the way I did the fix is not proper, feel free to implement it by yourself & close it :)
Thank you!