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

Better Bangle.js fonts #3109

Open
gfwilliams opened this issue Nov 20, 2023 · 24 comments
Open

Better Bangle.js fonts #3109

gfwilliams opened this issue Nov 20, 2023 · 24 comments
Labels
area-graphics Bangle2 Related to Bangle.js 2 help wanted Extra attention is needed question Further information is requested type-enhancement New feature or request

Comments

@gfwilliams
Copy link
Member

There have been discussions about this in the past (I'm afraid I can't find any good links at the moment - they're probably on the forum). But vaguely related issues are #1145 and #1311

Problem

  • Smaller fonts are too thin to be readable
  • Big/small font styles don't match
  • We don't support any characters outside ISO8859-1 0..255 codepage with current fonts
  • We don't have a nice way of choosing what font to use right now - every app implements its own solution, using hard-coded fonts

Where we are

  • There is an awesome free font in different sizes, specifically designed for the Bangle's display (well, pebble's): https://github.com/pebble-dev/renaissance/tree/master - it's in PBF format
  • Bangle.js now supports PBF loading and rendering
  • Bangle.js 2 also now supports Unicode so can render non-ASCII chars too
  • There's also GNU unifont which I've turned into a PBF file so we can even support full chinese/japanese/etc locally

So what's the issue?

It'd be nice if we had a global way of changing fonts (eg by installing an app/library via the App Loader) - to either change the look and feel, size of fonts, or to add native support for more character sets.

While we could have a library/function that handled simple font selection, eg require("font").getFont("big"/"medium"/"small") I don't think that's really useful enough.

I think we need something that'll choose a font based on some text and how much space there is. For example:

... = require("font").getFont({
  w : 176, h : 120, 
  wrap : true,
  text : "Hello this is a lot of text I want to display on the screen"
});

... = require("font").getFont({
  w : 60, h : 60, 
  wrap : false,
  text : "10m"
})

Any thoughts on this? I think the main things it needs to be able to handle are:

  • Blocks of text in messages
  • Small bits of text for labels or values (eg the 'run' app)
  • Ideally being aware of images in the text - no point using a tiny font when there are images that are 16px high anyway
  • Maybe returning the pre-wrapped, pre-cropped (with ...) text as well as the font just to save on computing things twice
  • Specifying a maximum/minimum font height?
  • Do we need to think about multiple text items? For example if we have a list (like in the Launcher) maybe we want to make everything in the list the same size?

I guess it might be good to think about existing apps that would benefit from this (messagegui, launcher, run, E.showMenu, E.showPrompt, etc) and what kind of features they would need?

Also, I guess this may need to be something that runs quite fast - so do we actually want a built-in function that just looks at a list of available fonts? Or maybe we get it working in JS first, then if it's slow we look at moving it inside the firmware.

@gfwilliams gfwilliams added type-enhancement New feature or request help wanted Extra attention is needed question Further information is requested Bangle2 Related to Bangle.js 2 labels Nov 20, 2023
@bobrippling
Copy link
Collaborator

Examples

Yes, I like the sound of this, currently I have widclk and widalarmeta both displaying slightly different 7-segment fonts in the widget bar and having this more globally configurable would be nice.

Plan/points

I think getFont({ w, h, ... }) sounds like good, and later down the line, the layout library would be able to lean on it too.

  • +1 for also returning the pre-wrapped/cropped text
  • For whether we have min/max font height, I suppose it depends how we implement getFont(), although thinking about it from what I've typed below, I think just a height parameter would work, as we'd probably handle it as a max height:
    • If we have min/max, and there's two fonts that satisfy, do we always go for larger? (probably yes)
    • If we just have a height passed instead, do we always go for the largest font under (or at) that height?
    • So it's the same behaviour really, I don't think minimum size comes into play (except for this next point)
  • For multiple text items, we could either have something in the API where we accept an array of max heights (of which we take the smallest), or we leave that logic up to the app itself - I'd vote for the latter, at least for the first implementation

I'd also like to add, for existing apps that would use this API / an initial implementation, I think it would be nice if we could let the user configure a single font for widgets, as mentioned at the start of my post

Initial approach

User config

  • Ability to install fonts from the apploader, add entry to settings to enable/disable?
    • Later implementation: permit configuring this via settings
  • Permit user to upload/specify a font for the widget bar

Test apps

Perhaps we use run and E.showMenu as trials - run for a full screen app with variable text, E.showMenu as an "app" where we want consistent display of text for all items?

And yes, +1 for initial implementation in JS, then performance checks

@gfwilliams
Copy link
Member Author

Thanks!

Ability to install fonts from the apploader, add entry to settings to enable/disable?

I'm not sure I understand about enable/disable? Surely you just disable by uninstalling the font app?

Permit user to upload/specify a font for the widget bar

Maybe getFont could have an optional use:"widget" field, so that you could manually specify fonts for specific types of things? I guess having use:"numeric" might be handy too so some fonts can choose to use 7 segment/etc.

While we can definitely add some settings to the font library (eg minimum font size, lock widget font, etc) I'm expecting that as long as we give enough info to getFont, other users might contribute different font apps with different fonts/behaviour.

I'm thinking at first maybe we have one lightweight font app that mirrors the existing behaviour (with built in fonts) which is the default that gets auto-installed, and then a second app using the Pebble fonts mentioned above. And when we're happy with the Pebble font one I think we just swap over.

@bobrippling
Copy link
Collaborator

I'm not sure I understand about enable/disable? Surely you just disable by uninstalling the font app?

Good point - yes the enable/disable is unnecessary

Maybe getFont could have an optional use:"widget" field, so that you could manually specify fonts for specific types of things? I guess having use:"numeric" might be handy too so some fonts can choose to use 7 segment/etc.

Excellent idea, yes I think that'll work nicely for the font settings app being able to present the user with these use-types.

While we can definitely add some settings to the font library (eg minimum font size, lock widget font, etc) I'm expecting that as long as we give enough info to getFont, other users might contribute different font apps with different fonts/behaviour.

I'm interested in what font apps will do, are they effectively the setup for the builtin code that will sit behind getFont() ? Or will they provide getFont() as a module, a bit like textinput or provides_modules?

@gfwilliams
Copy link
Member Author

I'm interested in what font apps will do

Right now I'm thinking they'll provide a font module that provides getFont - exactly as you say with provides_modules. That way everything is already there to auto-install and get rid of duplicates

@Mineinjava
Copy link
Contributor

The Renaissance font looks good. To what extent will a raster font be speedier to draw than a vector font, especially at high resolutions?

@gfwilliams
Copy link
Member Author

You'd have to do some testing but as a guess I'd say any font is faster as a raster unless it's over 40px high. Also for smaller fonts the final rendering is much better.

Basically any normal font that's not filling the screen is better as raster

@gfwilliams
Copy link
Member Author

Some work towards this now in 591c1f8

@gfwilliams
Copy link
Member Author

It appears to work! https://forum.espruino.com/conversations/394649/?offset=25#comment17323914

Just checked and you can even send messages from (the latest) Gadgetbridge too

@Mineinjava
Copy link
Contributor

The ț character (U+021B) still doesn't appear in notifications on iOS, will test later today.

I am using the extended fonts

@gfwilliams
Copy link
Member Author

So does g.clear(1).setFontIntl().drawString("\u021B") not do anything for you? What about other chars outside the 0..255 range?

I see it in 'all fonts' at least, and in extended fonts we do up to char code 1103 so 0x21B should be in there

@bobrippling
Copy link
Collaborator

It appears to work! https://forum.espruino.com/conversations/394649/?offset=25#comment17323914

Just checked and you can even send messages from (the latest) Gadgetbridge too

Great stuff! I'm quite keen on having consistent fonts across a few widgets I have, I'm quite short on time over the next few weeks but looking forward to incorporating this

@gfwilliams
Copy link
Member Author

Great! I should add that while there are now these font libraries available, the getFont function described in this issue isn't properly implemented (it just returns Intl for everything) - I just figured it was better to get the fonts in and usable in the messages app (even if it was a slight hack), rather than not having the functionality at all while I waited to try and implement the function nicely.

So ideally, before we start using the font all over the place we should probably implement getFont a bit better in these libraries and then try and use that, so we're not spreading slightly hacky code all over the place.

Also thanks for your time recently @bobrippling - I know you've been busy :)

@Chriz76
Copy link
Contributor

Chriz76 commented Jul 5, 2024

Perhaps we use run and E.showMenu as trials

Did a simple test with the run app and just swapped the two existing fonts with the same size.

The dithered text is almost unreadable and the other font is too broad for dist and time.

The latter could be solved by specifying a max width. But I think an other option would be to further categories fonts like width-height, boldness and maybe if it is a fixed width font or not. Then we could request a font according to these characteristics and get the most matching one with the result looking better.

Edit: I checked the code. Height and width (if it is fixed) are already stored. Should also be no problem to calculate the maximum/average width from the widths array for non fixed fonts.

image

@Chriz76
Copy link
Contributor

Chriz76 commented Jul 6, 2024

The bangle js 2 menu uses one main fixed font. With a second smaller font as fallback for the items if the text doesn't fit in one line. And then cut them if it still doesn't fit.

To mimic the current behaviour an approach with requesting a font in a specified height would work. With an additional option for the item to use a smaller size or more condensed font if needed and additional cutting if needed.

A more advanced solution would be a min font size like it is used in the message app. But this would require more changes like adjusting the surrounding rect hight to the font height. Then make it a global setting and use it for settings, menu, the launcher and every app that wants to respect it.

image

@Chriz76
Copy link
Contributor

Chriz76 commented Jul 7, 2024

Next I integrated the best fitting pebble renaissance font sizes into the run app. On the left the original. On the right the pebble fonts:

image

I replaced 6x8:2 with renaissance_18_bold.pbf and 6x15:2 with renaissance_28.pbf.

It looks ok even though 6x8 is fixed width and renaissance_18 isn't.

But both pebble fonts look smaller. The reason ist that there is a lot of spacing above the glyphs. See for example "G". It has a spacing of 7 above: https://github.com/pebble-dev/renaissance/blob/66df6992283adf97ba4689004dde7c51b5d03225/files/renaissance_18_bold.pbff#L485

https://github.com/pebble-dev/renaissance/wiki/PBFF-Format-description/

I am not sure. But this makes the fonts much less useful for us or what do you think?

Also one question: What is the reason the pbf format was included in espruino instead of converting the pbf fonts to the espruino format?

@gfwilliams
Copy link
Member Author

Thanks for that testing... apart from alignment, the 18_bold font looks good.

In terms of how we ask for the fonts, it seems like something like:

... = require("font").getFont({
  w : 60, h : 60, 
  wrap : true, minHeight: 10,
  text : "10m"
})

could work? The font lib could always check what the current app is and provide tweaks if needed.

What is the reason the pbf format was included in espruino instead of converting the pbf fonts to the espruino format?

The Espruino format assumes that all characters are in one range (eg 32..255), but PBF allows us to have Unicode characters to support different languages, and we can have for instance Korean, but with ASCII too, and not have to have all the characters inbetween.

The height thing is annoying, but actually that could be changed by modifying the font files - we can just adjust the reported height and maybe crop down/reposition the few chars that go out of range.

@gfwilliams
Copy link
Member Author

I just saw your forum posts as well: https://forum.espruino.com/conversations/383349/#comment17446938

Continuing on here...

I had assumed that for 36px we might just pixel-double the 18px for now as Bangle.js can just do that internally - but there are algorithms for pixel doubling so perhaps one could be built into the font converter tool: https://github.com/espruino/EspruinoWebTools/blob/master/fontconverter.js

@Chriz76
Copy link
Contributor

Chriz76 commented Jul 8, 2024

the 18_bold font looks good

Yes, I like the look of the run app with the pebble fonts.

require("font").getFont ... could work?

Yes, I think it will work. I would give it a try with the run app and the menu: Depending on the systemwide configured fonts always the best one is chosen by getFont(). Then I try it with the current default fonts and compare it to only the pebble fonts and see how it works and looks. But a bold option for getFont() is neccesary otherwise the labels of the run app won't work.

The height thing is annoying [...] could be changed by modifying the font files

Yes, it should be possible to automate this.

We might just pixel-double the 18px.

The pebble fonts look nice because they are adjusted for each size. I attached the glyph "q" as an example in 18px and 28p. We won't get the nice rounding of the 28px when doubling the 18px.
But if we really want to change to the pebble fonts then we can ask the author. Maybe he even has it vectorized. Or I can do the adjusments myself (already tried it with a few glyphs).

image

@Chriz76
Copy link
Contributor

Chriz76 commented Jul 11, 2024

This code globally maps many of the system fonts to my version of the pebble fonts:

https://gist.github.com/Chriz76/75de579af0a844d126df94b1b303657b

This enables testing it in the existing apps without modifying them.

The fonts are adjusted to have less top offset and I also added a 36px version with nice rounded edges for the ascii glyphs:
https://github.com/Chriz76/renaissance/tree/master/fonts

This is how the regular mapping looks:

Herunterladen (16)
Herunterladen (15)
Herunterladen (14)

And this uses an increased size mapping for better readability:

Herunterladen (13)
Herunterladen (12)
Herunterladen (11)

The fonts would still need some tweaking but if this looks promising to you, then I could make the fonts and the global replacement an app so everyone can try it.

@gfwilliams
Copy link
Member Author

This is really cool - and it looks like a massive improvement! I like the hack of just changing setFont for now!

I'm off this coming week so I wouldn't be able to look at any big changes, but if you wanted you make this available in the app loader for testing I think that'd be great - and then maybe we could move towards also providing a require("font").getFont function and slowly translating apps over to using that.

@Chriz76
Copy link
Contributor

Chriz76 commented Jul 14, 2024

Created the app and made it available on my fork. It is highly experimental so I don't know if I should create a PR for it or if testers want to try it on my fork?

https://chriz76.github.io/BangleApps/?c=&q=scale

https://github.com/Chriz76/BangleApps/tree/master/apps/scalefonts

Next I will take a look at getFont. Got some nice insperation from the conversation here and also took a look how other wear OS solve it.

@thyttan
Copy link
Collaborator

thyttan commented Jul 15, 2024

@Chriz76

I installed it on my watch and it really gives a boost to the visual presentation!

Some notes:

  • It felt like menus were a little slower to render, being choppier when I installed scalefonts (together with kineticscroll). Maybe that would go away if it was to be included in the firmware?
  • I really liked the feature to fill the available space as to help with legibility!
  • I'm not sure, but maybe for really small sizes the font was not as legible as some other fonts. I got that feeling on e.g. quicklaunchs extension screen. But it may also just be a matter of being used to the other font.
  • Overall I dig this!

@Chriz76
Copy link
Contributor

Chriz76 commented Jul 16, 2024

Thank you @thyttan for testing and your feedback!

I installed it on my watch and it really gives a boost to the visual presentation!

That's great news.

  • It felt like menus were a little slower to render, being choppier when I installed scalefonts (together with kineticscroll). Maybe that would go away if it was to be included in the firmware?

I am not sure if it will go away when it is build in the firmware. setFont doesn't do much so it is maybe the rendering of the pbf font that slows thing down, but this is already implemented in c code.

  • I'm not sure, but maybe for really small sizes the font was not as legible as some other fonts. I got that feeling on e.g. quicklaunchs extension screen. But it may also just be a matter of being used to the other font.

I am using "14" as smallest of the pebble fonts and then fallback to the defined font. The reason is, that the pebble fonts are much smaller in reality than their name suggests. The letter "A" in size 14 is only 9 pixels high resulting in size 11 with one pixel at the bottom and one at the top. That is a bit frustrating and we might have to create more larger sizes.

So even my self created largest font size 36 is in reality much smaller. Here are the real sizes (already including space at the top and bottom) to compare it to the existing fonts:

	{name: "renaissance36", size: 26},
	{name: "renaissance28", size: 22},
	{name: "renaissance24", size: 20},
	{name: "renaissance18", size: 14},
	{name: "renaissance14", size: 11},
	{name: "renaissance9", size: 9}

@Chriz76
Copy link
Contributor

Chriz76 commented Jul 16, 2024

@thyttan

  • I really liked the feature to fill the available space as to help with legibility!

I developed a method like discussed before to achive this and would also enable a global font size setting and make apps scale to it.

function getFont(string, maxSize, bold, maxWidth, maxHeight, minSize, shortenToFit, wrap) {...}

test("Very long text made fit to screen. It is really long! And therefore get's cut as soon as it reaches 8px. Lorem ipsum. De bello galicco.", 36, false, 80, 80, 8, true, true);
test("Long text wrapped to fit to rectangle", 36, false, 80, 80, 8, true, true);
test("Long text made to fit one line",36, false, 80, 20, 8, true, false);
test("Fit one line", 36, false, 80, 20, 8, true, false);
test("Fit", 36, false, 80, 28, 8, true, false);

https://gist.github.com/Chriz76/5e69de3e56c8399b59a6364e15713209

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-graphics Bangle2 Related to Bangle.js 2 help wanted Extra attention is needed question Further information is requested type-enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

5 participants