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

New vector font #1721

Closed
gfwilliams opened this issue Dec 10, 2019 · 38 comments
Closed

New vector font #1721

gfwilliams opened this issue Dec 10, 2019 · 38 comments

Comments

@gfwilliams
Copy link
Member

g.fillPoly now supports irregular polys, so there's no need for the regular polygons inside vector_font.h

A new font with irregular polygons should be significantly smaller

@MaBecker
Copy link
Contributor

MaBecker commented Dec 10, 2019

Do you like to share what’s on you mind?

@gfwilliams
Copy link
Member Author

The existing font generator (which I haven't actually published) leans on a bunch of stuff from another project and just mashes triangles together, so I can't just 'redo' the font.

So either:

  • A script to mash the regular polygons in the existing font together
  • A script to make a new vector font. I tried to see if I could do something quick, but no luck :) I think right now the best bet would be:
    • a script to make an SVG with all the characters in it
    • Open the SVG in Inkscape, do Object->Path and 'Flatten Beziers'
    • another script to read the flattened SVG and make a font file

@MaBecker
Copy link
Contributor

What about Hershey fonts you named in an earlier chat?

See #1702 (comment)

@gfwilliams
Copy link
Member Author

Hershey test code here: http://forum.espruino.com/conversations/321520/

I guess I could drop the vector font and use hershey fonts instead. My concern is really that I will get complaints if the font changes, but I guess it's not like the vector font is amazing at the moment anyway.

Also, the dumb way of doing wider lines for hershey would be to just draw each line as a separate polygon - but that's going to be really slow so I guess we need to make something that'll output a polygon (with smooth edges) from a standard polyline.

It'd be a neat thing to have, but it's easier said than done :)

@MaBecker
Copy link
Contributor

It'd be a neat thing to have, but it's easier said than done :)

I totally agree, spend a few hours with line thickness and trying to draw some Hershey characters .....

@MaBecker
Copy link
Contributor

MaBecker commented Jan 8, 2020

Have you thought about using bezier curves for drawing chars?

Any hints why g.fillPoly() is not filling this mirrored C as expected?

function quadraticBezier(x0,y0,x1,y1,x2,y2,drawCircle){
  var x,y,t,t2;
  var pp = [];
  var sn = 3;

if (drawCircle){
  g.drawCircle(x0,y0,3);
  g.drawCircle(x1,y1,3);
  g.drawCircle(x2,y2,3);
}
  pp.push(x0,y0);
  var s = Math.min(sn/Math.abs(x0-x2),sn/Math.abs(y0-y2));
  for( t = s; t <= 1; t += s) {
    t2 = t*t;
    tp2 = (1 - t) * (1 - t);
    x = ( x0 * tp2 +
        x1 * 2 * (1 - t) * t + 
        t2 * x2 + 0.5) | 0;
    y = ( y0 * tp2 +
        y1 * 2 * (1 - t) * t + 
        t2 * y2 + 0.5 ) | 0 ;
    pp.push(x,y);
  }
  pp.push(x2,y2);
  return pp;
}

var pp = [];
b = 20;
// outer-top-left
pp.push.apply(pp,quadraticBezier(20,60, 20,20, 60,20,true));
// outer-top-right
pp.push.apply(pp,quadraticBezier(60,20, 100,20, 100,60,true));
// outer-bottom-right
pp.push.apply(pp,quadraticBezier(100,100, 100,140, 60,140,true));
// outer-bottom-left
pp.push.apply(pp,quadraticBezier(60,140, 20,140, 20,100,true));

// outer-bottom-left
pp.push.apply(pp,quadraticBezier(20+b,100, 20+b,140-b,60,140-b,true));
// outer-bottom-right
pp.push.apply(pp,quadraticBezier(60,140-b, 100-b,140-b,100-b,100 ,true));
// outer-top-right
pp.push.apply(pp,quadraticBezier(100-b,60, 100-b,20+b, 60,20+b,true));
//inner-top-left
pp.push.apply(pp,quadraticBezier(60,20+b, 20+b,20+b,20+b,60 ,true));

g.drawPoly(pp,true);
//g.fillPoly(pp,true);

g.drawPoly(pp,true);

Bildschirmfoto 2020-01-08 um 16 28 10

g.fillPoly(pp,true);

Bildschirmfoto 2020-01-08 um 16 30 18

@MaBecker
Copy link
Contributor

MaBecker commented Jan 8, 2020

Ups I missed the warning:

WARNING: Maximum number of points (64) exceeded for fillPoly

this is the content of array pp:

[ 20, 60, 20, 54, 21, 49, 22, 44, 24, 40, 26, 36, 28, 32, 31, 29, 34, 26, 38, 24, 43, 23, 47, 21, 52, 20, 58, 20, 60, 20, 60, 20, 66, 20, 71, 21, 76, 22, 80, 24, 84, 26, 88, 28, 91, 31, 94, 34, 96, 38, 98, 43, 99, 47, 100, 52, 100, 58, 100, 60, 100, 100, 100, 106, 99, 111, 98, 116, 96, 120, 94, 124, 92, 128, 89, 131, 86, 134, 82, 136, 78, 138, 73, 139, 68, 140, 62, 140, 60, 140, 60, 140, 54, 140, 49, 139, 44, 138, 40, 136, 36, 134, 32, 132, 29, 129, 26, 126, 24, 122, 23, 118, 21, 113, 20, 108, 20, 102, 20, 100, 40, 100, 40, 106, 42, 110, 44, 114, 47, 117, 51, 119, 56, 120, 60, 120, 60, 120, 66, 120, 70, 118, 74, 116, 77, 113, 79, 109, 80, 104, 80, 100, 80, 60, 80, 54, 78, 50, 76, 46, 73, 43, 69, 41, 64, 40, 60, 40, 60, 40, 54, 40, 50, 42, 46, 44, 43, 47, 41, 51, 40, 56, 40, 60 ]

pp.length = 184

@MaBecker
Copy link
Contributor

MaBecker commented Jan 8, 2020

Bumping up the vars and reduced the bezier point helped. So lets draw chars with multi poly lines to keep heap small - right?

Bildschirmfoto 2020-01-08 um 17 33 10

@gfwilliams
Copy link
Member Author

Personally I would. I'm not sure the difference in size between a properly simplified curve and the bezier will be that different, and the more points in the poly the slower it renders.

That bezier function looks neat though - that might be something neat to add to Espruino? At the very least as a module.

@MaBecker
Copy link
Contributor

MaBecker commented Jan 8, 2020

I personally like to add it to the Espruino firmware. Will try to send a pr in the next days.

@gfwilliams
Copy link
Member Author

Thanks! Please can you do it with the SAVE_ON_FLASH ifdef on it though?

@MaBecker
Copy link
Contributor

MaBecker commented Jan 9, 2020

  • a script to make an SVG with all the characters in it
  • Open the SVG in Inkscape, do Object->Path and 'Flatten Beziers'
  • another script to read the flattened SVG and make a font file

Have you checked opentype.js for this job?

Just checking the glyph-inspector.

@gfwilliams
Copy link
Member Author

Thanks! That looks spot on - it'll make the job way easier. All that's needed it so shove in some of the code from https://github.com/gfwilliams/svgtoeagle and it should be reasonably easy to spit out the right data.

The only gotcha right now is because we don't support polygons with holes, for some chars (like O) we basically have to split the outside poly and add an inner one - but the code for svgtoeagle does that.

I'll take a look at this soon.

@MaBecker
Copy link
Contributor

first try.

  • create two function, this one and a internal one that can be user eg for drawing rounded rectangle
    • like graphicsSquaredBezier()
  • forced to use array to pass that for vertices, because only 5 are possible, can this be increased?
    • ERROR: getArgumentSpecifier: Too many arguments to fit in type specifier, Use JsVarArray
  • missing calculation of s value
  • is there a smarter way to access the elements?
  • not sure if I got the SAVE_ON_FLASH ifdef correctly
  • the JsvIterator looks strange, is a simple way to access the array elements available?
  • will run some more tests and like to get some input for optimization, before create a pr
/*JSON{
  "type" : "method",
  "class" : "Graphics",
  "name" : "squaredBezier",
  "#if" : "!defined(SAVE_ON_FLASH) && !defined(ESPRUINOBOARD)",
  "generate" : "jswrap_graphics_squaredBezier",
  "params" : [
    ["arr","JsVar","An array of three vertices, six enties in form of ```[x0,y0,x1,y1,x2,y2]```"]
  ],
  "return" : ["JsVar", "Array with calculated points" ]
}
Calculate points between start and end points, max 10 points without start and end points
*/
JsVar *jswrap_graphics_squaredBezier( JsVar *parent, JsVar *arr ){

  JsVar *result = jsvNewEmptyArray();

  if (jsvGetArrayLength(arr) != 6) 
    return result; 

  float s,t,t2,tp2,tpt;
  int sn = 6;
  int x0,x1,x2,y0,y1,y2;

  JsvIterator it;
  jsvIteratorNew(&it, arr, JSIF_EVERY_ARRAY_ELEMENT);
  x0 = jsvIteratorGetIntegerValue(&it); jsvIteratorNext(&it);
  y0 = jsvIteratorGetIntegerValue(&it); jsvIteratorNext(&it);
  x1 = jsvIteratorGetIntegerValue(&it); jsvIteratorNext(&it);
  y1 = jsvIteratorGetIntegerValue(&it); jsvIteratorNext(&it);
  x2 = jsvIteratorGetIntegerValue(&it); jsvIteratorNext(&it);
  y2 = jsvIteratorGetIntegerValue(&it);
  jsvIteratorFree(&it);

//jsiConsolePrintf("%d,%d,%d,%d,%d,%d\n",x0,y0,x1,y1,x2,y2);

  s = 0.15;

  for ( t = s; t <= 1; t += s ) {
    t2 = t*t;
    tp2 = (1 - t) * (1 - t);
    tpt = 2 * (1 - t) * t;
    JsVar *x = jsvNewFromInteger(
                         x0 * tp2 +
                         x1 * tpt + 
                         x2 * t2 + 0.5);
    JsVar *y = jsvNewFromInteger(
                         y0 * tp2 +
                         y1 * tpt + 
                         y2 * t2 + 0.5 );
    jsvArrayPush(result, x);
    jsvArrayPush(result, y);
    jsvUnLock2(x,y);
  }
  return  result;
}

first test result:

>x = g.squaredBezier([10,10,10,50,50,50]);
=[ 11, 21, 14, 30, 18, 38, 24, 44, 33, 48, 42, 50 ]

@gfwilliams
Copy link
Member Author

Looks good...

I'd add if (!result) return 0; just after result = jsvNewEmptyArray(); in case it can't be allocated

forced to use array to pass that for vertices

Just do what you're doing and use ["arr","JsVarArray","An array... in the JSON. SHould be fine

is there a smarter way to access the elements?

There's jsvGetArrayItem (https://github.com/espruino/Espruino/blob/master/src/jsvar.h#L688) but your method is faster. It seems good. There are shortcuts for getting an array of bytes but getting ints is rare.

not sure if I got the SAVE_ON_FLASH ifdef correctly

looks good to me :)

the JsvIterator looks strange, is a simple way to access the array elements available?

As above. Your method is fine :)

will run some more tests and like to get some input for optimization, before create a pr

Looks good. All I'd suggest is doing:

jsvArrayPushAndUnLock(result, jsvNewFromInteger(...));

To avoid the separate unlock call and variables floating around, but that's pretty minor

@MaBecker
Copy link
Contributor

Thanks for your hints - just created pr #1743

@gfwilliams
Copy link
Member Author

Try this branch: https://github.com/espruino/Espruino/tree/vector_font_roboto

Not had a chance to test properly yet, but that's using a properly generated vector font using irregular polys.

Currently it's only a tiny bit smaller than the original (regular poly) one so I'm not sure if it's worth the effort. I doubt it'll look substantially better

@MaBecker
Copy link
Contributor

Wow, thanks a lot for your effort, look's like you created a smart script to do the job!

just comparing some numbers
new
Bildschirmfoto 2020-01-13 um 20 23 48

old
Bildschirmfoto 2020-01-13 um 20 24 06

I doubt it'll look substantially better

Yes, I can see what you mean

All curved chars are much nicer and there is much more room for improvement!

  • Starting with function quadraticBezier(pp,x0,y0,x1,y1,x2,y2)

    var sn = 6;
    ....
    if ( s > 1) s = 0.5;
    if ( s < 0.1) s = 0.1;

  • maybe Roboto-Light.ttf or Roboto-Condensed.ttf is a better choice or even differen free font

Like run further tests with create_vector_font.js.

@gfwilliams
Copy link
Member Author

Yes, using the light font might be better. Also, if you found a font that was less curvy it'd almost certainly compress down a lot better!

Also, I'm not using quadraticBezier because for some reason it stopped the font from working when I tried it (might just be too many points again?) - so if you can get that working it'd probably really help the shape.

@MaBecker
Copy link
Contributor

What tricks did you used to get opentype.js working ?

npm -g install  opentype.js  

running the create_vector_font.js gives me Error: Cannot find module 'opentype.js'

@gfwilliams
Copy link
Member Author

Just npm install opentype.js when in the scripts dir - -g is for global

@MaBecker
Copy link
Contributor

Just npm install opentype.js when in the scripts dir - -g is for global

Ok , this is Mac Os X related ..... tried with and without -g, same result.

Also, I'm not using quadraticBezier because for some reason it stopped the font from working when I tried it (might just be too many points again?) - so if you can get that working it'd probably really help the shape.

Ok that explains why there are some pixel outside of the chars. Will fiddle around with the numbers of points quadraticBezier creates to make sure that it does not exceed the limit of 64 vertices.

@gfwilliams
Copy link
Member Author

it does not exceed the limit of 64 vertices.

We can always raise that limit, it's a bit artificial. The main thing is to ensure that we're not generating loads of extra points that aren't required for a good-looking font

@MaBecker
Copy link
Contributor

./create_vector_font.js now works on my side too.

Roboto-Condensed.ttf with additional 4 bezier points
Left: glyph-inspector
Right: 113 points

Bildschirmfoto 2020-01-15 um 22 48 49

Roboto-Light.ttf with additional 4 bezier points
Left: glyph-inspector
Right: 112 points

Bildschirmfoto 2020-01-15 um 22 37 17

  • optimize s in quadraticBezier
  • eliminate spikes
  • figure out why fillPoly() cant fill generated data correct
  • doubled the limit

/*
Char code 54 : 6
Roboto-Light.ttf
bezier: first plus three additional points
*/

pp54C3v2 = [35,7,
35,8,
29,9,
24,11,
19,14,
15,17,
11,22,
9,27,
7,33,
7,39,
7,66,
7,67,
7,74,
9,80,
11,86,
14,90,
18,94,
22,96,
27,97,
33,97,
38,97,
43,96,
47,94,
51,90,
54,86,
56,81,
57,76,
58,68,
58,69,
57,63,
56,58,
54,54,
52,49,
49,46,
45,43,
40,42,
35,41,
35,42,
33,42,
31,43,
28,44,
26,44,
24,45,
23,46,
21,48,
24,53,
26,52,
28,52,
30,51,
32,50,
32,51,
35,52,
38,53,
40,55,
42,56,
43,58,
44,62,
45,65,
45,68,
45,69,
45,74,
44,77,
43,81,
42,83,
40,86,
38,88,
35,89,
33,88,
33,89,
30,89,
27,88,
25,86,
23,83,
22,80,
20,76,
20,72,
19,66,
19,58,
19,59,
20,57,
21,56,
23,55,
24,53,
21,48,
19,49,
19,39,
19,40,
20,35,
21,31,
22,28,
24,24,
26,21,
28,19,
31,18,
35,17,
35,18,
37,18,
39,18,
40,19,
42,19,
43,19,
45,20,
47,20,
48,20,
51,11,
51,12,
49,11,
47,11,
45,10,
43,9,
41,9,
39,8,
37,8];

var scale = (sx, sy, p) => p.map((v, i) => v * ((i & 1) ? sy : sx));
g.drawPoly(scale(2.3, 2.3, pp54C4v2), true);
//g.drawPoly(scale(0.5, 0.5, pp54C4v2), true);

Bildschirmfoto 2020-01-15 um 23 10 04

function grid(gx,gy){
var XMAX = g.getWidth();
var YMAX = g.getHeight();
for (x = gx; x < XMAX; x += gx) g.drawLine(x,0,x,YMAX);
for (y = gy; y < YMAX; y += gy) g.drawLine(0,y,YMAX,y);
}
grid(25,25);
// 25px
g.drawPoly(scale(0.25, 0.25, pp54C4v2), true);
// 50px
g.drawPoly(translate(25,0,scale(0.5, 0.5, pp54C4v2)), true);
// 100px
g.drawPoly(translate(75,0,scale(1, 1, pp54C4v2)), true);

@MaBecker
Copy link
Contributor

Update:
[x] optimize s in quadraticBezier
[x] eliminate spikes

@MaBecker
Copy link
Contributor

What about require a vector font like it is possible for bitmap fonts?

@gfwilliams
Copy link
Member Author

Yes, that's definitely possible. You'd just need to make graphicsFillVectorChar and graphicsVectorCharWidth work with a pointer to the data. I guess you could make it work with iterators which would be more flexible (eg could draw from a string sitting in flash memory), but a bit slower (although the main delay right now will be in rendering, not getting the font vector data)

@MaBecker
Copy link
Contributor

could draw from a string sitting in flash memory

That sounds cool. Is the firmware read for this and if yes how should that look like.
Would be nice to see a snippet.

@gfwilliams
Copy link
Member Author

Not sure I understand? Just look at what jswrap_graphics_drawString does for custom bitmap fonts right now - just using iterators again

@MaBecker
Copy link
Contributor

Sorry, but I have no clue how to get this done and even it seams that I am not able to formulate a wish for a snippet to work on.

@MaBecker
Copy link
Contributor

So what I already did is rewriting drawString in JS for some test reasons, skip this for now and try to get a nice vector font.

Finding:

  • Small chars eg 25px do not look nice
    g.setFontVector(25).drawString("5",0,0).dump();

Bildschirmfoto 2020-01-17 um 06 54 26

@gfwilliams
Copy link
Member Author

I think for testing you could do a 'native' compile for your OS with make and then just do run something like: https://github.com/espruino/Espruino/blob/master/tests/test_graphics_fonts.js - You might find it a faster way to test.

Custom vector fonts would be a nice addition but I think it's probably not that high up the priority list for now. I guess maybe one flexible option would be to just have a 'custom JS' font where you supply the JS function to work out char widths and to draw chars? Not as fast but it could he a great way to add hershey fonts.

In that image, is that the exact same data, just drawn with drawPoly and fillPoly? If so it looks like maybe a bug in fillPoly? Might be worth extracting the exact data you supply to it and then filing a new bug for it?

@MaBecker
Copy link
Contributor

Font is HelveticaNarrow

Used the script to create vector.h and compiled a BOARD=LINUX.

So it’s comparing g.drawPoly() with g.setFontsize().drawString() using the same vertices.

@MaBecker
Copy link
Contributor

think it's probably not that high up the priority list for now

I totally agree.

Will try to fix fillPoly() in the next days

@gfwilliams
Copy link
Member Author

I'd like to look at this again - anything we can shave off the firmware size would be a huge bonus right now. For instance if we could use a 16px grid we could halve the size of this.

I think realistically it might be better off starting more or less from scratch and making a custom font - maybe design it as an SVG and then export that to Espruino?

@MaBecker
Copy link
Contributor

maybe design it as an SVG and then export that to Espruino?

Good idea, don't miss the fillPoly() issue.

@gfwilliams
Copy link
Member Author

@gfwilliams
Copy link
Member Author

Fixed now

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

No branches or pull requests

2 participants