# Yet another approach to text rendering (WIP)

**URL:** <https://forum.electromage.com/t/yet-another-approach-to-text-rendering-wip/1897>\
**Category:** Patterns and Code\
**Created:** [January 4, 2022, 2:49am UTC](https://forum.electromage.com/t/yet-another-approach-to-text-rendering-wip/1897 "2022-01-04T02:49:58Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![zranger1](https://forum.electromage.com/user_avatar/forum.electromage.com/zranger1/32/301_2.png) [@zranger1](https://forum.electromage.com/u/zranger1)\
**Post date:** [January 4, 2022, 2:49am UTC](https://forum.electromage.com/t/yet-another-approach-to-text-rendering-wip/1897/1 "2022-01-04T02:49:58Z")

</div>

Here’s a quick demo of a new text renderer I’m working on. This is an early preview – it’s a long way from optimized, and needs to have more animation tooling added. I’m posting it now because I just got to a good place: the anti-aliased font rendering works, and the morphing just looks cool!

It’s set up for a 16x16 matrix. Should work on any 2D display though – you might have to use the sliders to adjust line width and size for best results on 8x8.

(Edit: 4pm, 1/4/2022 - updated code)

> **SDF Font Demo (WIP)**
>
> ```auto
> // SDF Font Demo with Morphing Characters
> // (Work-in-Progress v2)
> // 1/04/2022 ZRanger1
> 
> // UI control variables
> export var objectSize = 0.85;
> export var lineWidth = 0.085;
> export var speed = 500;
> 
> // shape function selection - your message here!
> 
> //var shapeSdf = [_A,_B,_C,_D,_E,_F,_G,_H,_I,_J,_K,_L,_M,_N,_O,_P,_Q,_R,_S,_T,_U,_V,_W,_X,_Y,_Z];
> var shapeSdf = [_P,_I,_X,_E,_L,_B,_L,_A,_Z,_E];
> 
> // animation control
> var shape = 0;
> var nextShape = 1;
> var hue = 0;
> var morphClock = 0;
> var wait = 0; 
> 
> var lerpPct = 0;
> var t1, timebase,t2;
> 
> // move coordinate origin to center.  
> translate(-0.5,-0.5);
> 
> // comment/uncomment this line to flip charaters on x axis as necessary
> // thanks @pixie, for the reminder!  
> scale(-1,1);
> 
> // UI
> 
> export function sliderSpeed(v) {
> speed = 100+(v*1900);
> }
> 
> export function sliderSize(v) {
> objectSize = v;
> }
> 
> export function sliderLineWidth(v){
> lineWidth = 0.25 * v * v;
> }
> 
> export function beforeRender(delta) {
> timebase = (timebase + delta/1000) % 1000;
> t1 = timebase * 10;
> t2 = time(0.04);  
> morphClock += delta
> 
> // morph to a new shape every other second...
> if (morphClock > speed) {
> if (!wait) {
> shape = nextShape; // set to next shape
> nextShape = (nextShape+1) % shapeSdf.length;  
> }
> morphClock = 0;    
> wait = !wait;
> }
> 
> lerpPct = morphClock / speed;
> }
> 
> export function render2D(index,x,y) {
> if (wait) {
> d = shapeSdf[shape](x,y);
> } else {
> d = shapeSdf[shape](x,y) * (1-lerpPct) + shapeSdf[nextShape](x,y) * lerpPct;
> }  
> 
> v = (d <= lineWidth) ? 1-d/lineWidth : 0;
> 
> hsv((x+y+t2), 1, v)
> }
> 
> // SDF function for line segment
> function line(x,y,x1,y1,x2,y2) {
> x1 *= objectSize; y1 *= objectSize;
> x2 *= objectSize; y2 *= objectSize;
>   
> ax = x - x1; ay = y - y1;
> bx = x2 - x1; by = y2 - y1;
> h = clamp((ax * bx + ay * by)/(bx * bx + by * by),0,1);
> return hypot(ax - bx * h,ay - by * h);
> }
> 
> // original sdf text demo shader: https://www.shadertoy.com/view/lsXXRs
> // font from https://dl.dropboxusercontent.com/u/14645664/files/glsl-text.txt , which no longer
> // seems to exist.
> // TODO -- need to rescale the segments to save calculation
> 
> function _A(px,py) {
> var d = 1;
> d=min(d,line(px,py,-0.28571,0.5,-0.28571,-0.42857));
> d=min(d,line(px,py,-0.28571,-0.42857,0.28571,-0.42857));
> d=min(d,line(px,py,0.28571,-0.42857,0.28571,0.07143));
> d=min(d,line(px,py,0.28571,0.07143,-0.28571,0.07143));
> d=min(d,line(px,py,-0.28571,0.07143,0.28571,0.07143));
> d=min(d,line(px,py,0.28571,0.07143,0.28571,0.5));
> return d;
> }
> function _B(px,py) {
> var d = 1;
> d=min(d,line(px,py,0.14286,0.07143,0.14286,-0.42857));
> d=min(d,line(px,py,0.14286,-0.42857,-0.28571,-0.42857));
> d=min(d,line(px,py,-0.28571,-0.42857,-0.28571,0.5));
> d=min(d,line(px,py,-0.28571,0.5,0.28571,0.5));
> d=min(d,line(px,py,0.28571,0.5,0.28571,0.07143));
> d=min(d,line(px,py,0.28571,0.07143,-0.28571,0.07143));
> return d;
> }
> function _C(px,py) {
> var d = 1;
> d=min(d,line(px,py,0.28571,-0.42857,-0.28571,-0.42857));
> d=min(d,line(px,py,-0.28571,-0.42857,-0.28571,0.5));
> d=min(d,line(px,py,-0.28571,0.5,0.28571,0.5));
> return d;
> }
> function _D(px,py) {
> var d = 1;
> d=min(d,line(px,py,-0.28571,0.5,0.14286,0.5));
> d=min(d,line(px,py,0.14286,0.5,0.21429,0.42857));
> d=min(d,line(px,py,0.21429,0.42857,0.28571,0.25));
> d=min(d,line(px,py,0.28571,0.25,0.28571,-0.10714));
> d=min(d,line(px,py,0.28571,-0.10714,0.21429,-0.35714));
> d=min(d,line(px,py,0.21429,-0.35714,0.14286,-0.42857));
> d=min(d,line(px,py,0.14286,-0.42857,-0.28571,-0.42857));
> d=min(d,line(px,py,-0.28571,-0.42857,-0.28571,0.5));
> return d;
> }
> function _E(px,py) {
> var d = 1;
> d=min(d,line(px,py,0.28571,-0.42857,-0.28571,-0.42857));
> d=min(d,line(px,py,-0.28571,-0.42857,-0.28571,0.07143));
> d=min(d,line(px,py,-0.28571,0.07143,0,0.07143));
> d=min(d,line(px,py,0,0.07143,-0.28571,0.07143));
> d=min(d,line(px,py,-0.28571,0.07143,-0.28571,0.5));
> d=min(d,line(px,py,-0.28571,0.5,0.28571,0.5));
> return d;
> }
> function _F(px,py) {
> var d = 1;
> d=min(d,line(px,py,0.28571,-0.42857,-0.28571,-0.42857));
> d=min(d,line(px,py,-0.28571,-0.42857,-0.28571,0.07143));
> d=min(d,line(px,py,-0.28571,0.07143,0,0.07143));
> d=min(d,line(px,py,0,0.07143,-0.28571,0.07143));
> d=min(d,line(px,py,-0.28571,0.07143,-0.28571,0.5));
> return d;
> }
> function _G(px,py) {
> var d = 1;
> d=min(d,line(px,py,0.28571,-0.28571,0.28571,-0.42857));
> d=min(d,line(px,py,0.28571,-0.42857,-0.28571,-0.42857));
> d=min(d,line(px,py,-0.28571,-0.42857,-0.28571,0.5));
> d=min(d,line(px,py,-0.28571,0.5,0.28571,0.5));
> d=min(d,line(px,py,0.28571,0.5,0.28571,0.07143));
> d=min(d,line(px,py,0.28571,0.07143,0.07143,0.07143));
> return d;
> }
> function _H(px,py) {
> var d = 1;
> d=min(d,line(px,py,-0.28571,-0.42857,-0.28571,0.5));
> d=min(d,line(px,py,-0.28571,0.5,-0.28571,0.07143));
> d=min(d,line(px,py,-0.28571,0.07143,0.28571,0.07143));
> d=min(d,line(px,py,0.28571,0.07143,0.28571,-0.42857));
> d=min(d,line(px,py,0.28571,-0.42857,0.28571,0.5));
> return d;
> }
> function _I(px,py) {
> var d = 1;
> d=min(d,line(px,py,-0.21429,-0.42857,0.21429,-0.42857));
> d=min(d,line(px,py,0.21429,-0.42857,0,-0.42857));
> d=min(d,line(px,py,0,-0.42857,0,0.5));
> d=min(d,line(px,py,0,0.5,-0.21429,0.5));
> d=min(d,line(px,py,-0.21429,0.5,0.21429,0.5));
> return d;
> }
> function _J(px,py) {
> var d = 1;
> d=min(d,line(px,py,-0.21429,0.5,0,0.5));
> d=min(d,line(px,py,0,0.5,0.14286,0.35714));
> d=min(d,line(px,py,0.14286,0.35714,0.14286,-0.42857));
> d=min(d,line(px,py,0.14286,-0.42857,-0.21429,-0.42857));
> return d;
> }
> function _K(px,py) {
> var d = 1;
> d=min(d,line(px,py,-0.28571,-0.42857,-0.28571,0.5));
> d=min(d,line(px,py,-0.28571,0.5,-0.28571,0.07143));
> d=min(d,line(px,py,-0.28571,0.07143,-0.07143,0.07143));
> d=min(d,line(px,py,-0.07143,0.07143,0.28571,-0.42857));
> d=min(d,line(px,py,0.28571,-0.42857,-0.07143,0.07143));
> d=min(d,line(px,py,-0.07143,0.07143,0.28571,0.5));
> return d;
> }
> function _L(px,py) {
> var d = 1;
> d=min(d,line(px,py,-0.28571,-0.42857,-0.28571,0.5));
> d=min(d,line(px,py,-0.28571,0.5,0.28571,0.5));
> return d;
> }
> function _M(px,py) {
> var d = 1;
> d=min(d,line(px,py,-0.28571,0.5,-0.28571,-0.42857));
> d=min(d,line(px,py,-0.28571,-0.42857,0,-0.07143));
> d=min(d,line(px,py,0,-0.07143,0.28571,-0.42857));
> d=min(d,line(px,py,0.28571,-0.42857,0.28571,0.5));
> return d;
> }
> function _N(px,py) {
> var d = 1;
> d=min(d,line(px,py,-0.28571,0.5,-0.28571,-0.42857));
> d=min(d,line(px,py,-0.28571,-0.42857,0.28571,0.5));
> d=min(d,line(px,py,0.28571,0.5,0.28571,-0.42857));
> return d;
> }
> function _O(px,py) {
> var d = 1;
> d=min(d,line(px,py,0.28571,-0.42857,-0.28571,-0.42857));
> d=min(d,line(px,py,-0.28571,-0.42857,-0.28571,0.5));
> d=min(d,line(px,py,-0.28571,0.5,0.28571,0.5));
> d=min(d,line(px,py,0.28571,0.5,0.28571,-0.42857));
> return d;
> }
> function _P(px,py) {
> var d = 1;
> d=min(d,line(px,py,-0.28571,0.5,-0.28571,-0.42857));
> d=min(d,line(px,py,-0.28571,-0.42857,0.28571,-0.42857));
> d=min(d,line(px,py,0.28571,-0.42857,0.28571,0.07143));
> d=min(d,line(px,py,0.28571,0.07143,-0.28571,0.07143));
> return d;
> }
> function _Q(px,py) {
> var d = 1;
> d=min(d,line(px,py,0.28571,0.5,0.28571,-0.42857));
> d=min(d,line(px,py,0.28571,-0.42857,-0.28571,-0.42857));
> d=min(d,line(px,py,-0.28571,-0.42857,-0.28571,0.5));
> d=min(d,line(px,py,-0.28571,0.5,0.28571,0.5));
> d=min(d,line(px,py,0.28571,0.5,0.07143,0.28571));
> return d;
> }
> function _R(px,py) {
> var d = 1;
> d=min(d,line(px,py,-0.28571,0.5,-0.28571,-0.42857));
> d=min(d,line(px,py,-0.28571,-0.42857,0.28571,-0.42857));
> d=min(d,line(px,py,0.28571,-0.42857,0.28571,0.07143));
> d=min(d,line(px,py,0.28571,0.07143,-0.28571,0.07143));
> d=min(d,line(px,py,-0.28571,0.07143,0.07143,0.07143));
> d=min(d,line(px,py,0.07143,0.07143,0.28571,0.5));
> return d;
> }
> function _S(px,py) {
> var d = 1;
> d=min(d,line(px,py,0.28571,-0.42857,-0.28571,-0.42857));
> d=min(d,line(px,py,-0.28571,-0.42857,-0.28571,0.07143));
> d=min(d,line(px,py,-0.28571,0.07143,0.28571,0.07143));
> d=min(d,line(px,py,0.28571,0.07143,0.28571,0.5));
> d=min(d,line(px,py,0.28571,0.5,-0.28571,0.5));
> return d;
> }
> function _T(px,py) {
> var d = 1;
> d=min(d,line(px,py,0,0.5,0,-0.42857));
> d=min(d,line(px,py,0,-0.42857,-0.28571,-0.42857));
> d=min(d,line(px,py,-0.28571,-0.42857,0.28571,-0.42857));
> return d;
> }
> function _U(px,py) {
> var d = 1;
> d=min(d,line(px,py,-0.28571,-0.42857,-0.28571,0.5));
> d=min(d,line(px,py,-0.28571,0.5,0.28571,0.5));
> d=min(d,line(px,py,0.28571,0.5,0.28571,-0.42857));
> return d;
> }
> function _V(px,py) {
> var d = 1;
> d=min(d,line(px,py,-0.28571,-0.42857,0,0.5));
> d=min(d,line(px,py,0,0.5,0.28571,-0.42857));
> return d;
> }
> function _W(px,py) {
> var d = 1;
> d=min(d,line(px,py,-0.28571,-0.42857,-0.28571,0.5));
> d=min(d,line(px,py,-0.28571,0.5,0,0.21429));
> d=min(d,line(px,py,0,0.21429,0.28571,0.5));
> d=min(d,line(px,py,0.28571,0.5,0.28571,-0.42857));
> return d;
> }
> function _X(px,py) {
> var d = 1;
> d=min(d,line(px,py,-0.28571,-0.42857,0.28571,0.5));
> d=min(d,line(px,py,0.28571,0.5,0,0.03571));
> d=min(d,line(px,py,0,0.03571,0.28571,-0.42857));
> d=min(d,line(px,py,0.28571,-0.42857,-0.28571,0.5));
> return d;
> }
> function _Y(px,py) {
> var d = 1;
> d=min(d,line(px,py,-0.28571,-0.42857,0,0.07143));
> d=min(d,line(px,py,0,0.07143,0,0.5));
> d=min(d,line(px,py,0,0.5,0,0.07143));
> d=min(d,line(px,py,0,0.07143,0.28571,-0.42857));
> return d;
> }
> function _Z(px,py) {
> var d = 1;
> d=min(d,line(px,py,-0.28571,-0.42857,0.28571,-0.42857));
> d=min(d,line(px,py,0.28571,-0.42857,0,0.07143));
> d=min(d,line(px,py,0,0.07143,-0.21429,0.07143));
> d=min(d,line(px,py,-0.21429,0.07143,0.21429,0.07143));
> d=min(d,line(px,py,0.21429,0.07143,0,0.07143));
> d=min(d,line(px,py,0,0.07143,-0.28571,0.5));
> d=min(d,line(px,py,-0.28571,0.5,0.28571,0.5));
> return d;
> }
> 
> ```

---

<div class="post-metadata">

**Author:** ![jeff](https://forum.electromage.com/user_avatar/forum.electromage.com/jeff/32/3473_2.png) [@jeff](https://forum.electromage.com/u/jeff)\
**Post date:** [January 4, 2022, 3:19am UTC](https://forum.electromage.com/t/yet-another-approach-to-text-rendering-wip/1897/2 "2022-01-04T03:19:23Z")

</div>

Super cool, John! Hey, what’s with underscore E? Is E a reserved symbol?

---

<div class="post-metadata">

**Author:** ![zranger1](https://forum.electromage.com/user_avatar/forum.electromage.com/zranger1/32/301_2.png) [@zranger1](https://forum.electromage.com/u/zranger1)\
**Post date:** [January 4, 2022, 3:35am UTC](https://forum.electromage.com/t/yet-another-approach-to-text-rendering-wip/1897/3 "2022-01-04T03:35:33Z")

</div>

yeah, E is reserved. I’ll probably convert everything to \_A, \_B, etc, just to be consistent.

---

<div class="post-metadata">

**Author:** ![Mebejedi](https://forum.electromage.com/letter_avatar_proxy/v4/letter/m/bb73d2/32.png) [@Mebejedi](https://forum.electromage.com/u/Mebejedi)\
**Post date:** [January 4, 2022, 4:21am UTC](https://forum.electromage.com/t/yet-another-approach-to-text-rendering-wip/1897/4 "2022-01-04T04:21:50Z")

</div>

Is this one of those “Not for V2” patterns? I’m locked out of mine, now. I can find it on discovery, but the webpage won’t load, and I can’t get it into AP mode. ☹

[EDIT] I was eventually able to get back into it.

---

<div class="post-metadata">

**Author:** ![pixie](https://forum.electromage.com/letter_avatar_proxy/v4/letter/p/7bcc69/32.png) [@pixie](https://forum.electromage.com/u/pixie)\
**Post date:** [January 4, 2022, 9:25am UTC](https://forum.electromage.com/t/yet-another-approach-to-text-rendering-wip/1897/5 "2022-01-04T09:25:11Z")

</div>

Neato!

But how is your pixelmap function oriented? On my matrices, which are defined with (0,0) at the upper left and (1,1) at the lower right, the letters appear flipped in the X direction.

---

<div class="post-metadata">

**Author:** ![zranger1](https://forum.electromage.com/user_avatar/forum.electromage.com/zranger1/32/301_2.png) [@zranger1](https://forum.electromage.com/u/zranger1)\
**Post date:** [January 4, 2022, 3:26pm UTC](https://forum.electromage.com/t/yet-another-approach-to-text-rendering-wip/1897/6 "2022-01-04T15:26:15Z")

</div>

@mebejedi, there’s nothing in there that would hurt a v2. It uses the new array initialization features, so firmware has to be up to date, and that’s about it. It does use a _lot_ of CPU though - not lightning fast yet, even on a v3 - so it may be dragging the v2 to a slow crawl. I’m setting one up to test today – backing it up and preparing to reflash if I kill it! Hopefully not though.

(Edit: It doesn’t seem to kill v2s. Frame rate drops to a screamin’ 6 or 7 fps at 16x16 though, and that might reduce UI responsiveness.)

@pixie, the short answer is, my matrix (a BTF-Lighting 16x16) has x flipped the wrong way. I use the default PB matrix map, and the way the board is wired and most easily mounted, that’s just how it comes out. It’d be great if everybody stuck to one standard orientation, but it’s kind of hard to insist. Look at any thread on text display, and you’ll find multiple complaints about characters being flipped.

For release, I’ll set this up with a single flag that lets you easily set x orientation. For now, prior to the coming “Great Font Vector Renormalization of 2022”, to flip x to the correct orientation, substitute this version of the line function for the original (starting at around line 20).

```auto
// SDF function for line segment
function line(x,y,x1,y1,x2,y2) {
  x1 = -0.75 + (x1 / 4); y1 = -1.2 + (y1 / 4)
  x2 = -0.75 + (x2 / 4); y2 = -1.2 + (y2 / 4)  
  
  x1 *= objectSize; y1 *= objectSize;
  x2 *= objectSize; y2 *= objectSize;
  
  ax = x - x1; ay = y - y1;
  bx = x2 - x1; by = y2 - y1;
  h = clamp((ax * bx + ay * by)/(bx * bx + by * by),0,1);
  return hypot(ax - bx * h,ay - by * h);
}

```

---

<div class="post-metadata">

**Author:** ![Mebejedi](https://forum.electromage.com/letter_avatar_proxy/v4/letter/m/bb73d2/32.png) [@Mebejedi](https://forum.electromage.com/u/Mebejedi)\
**Post date:** [January 4, 2022, 5:22pm UTC](https://forum.electromage.com/t/yet-another-approach-to-text-rendering-wip/1897/7 "2022-01-04T17:22:54Z")

</div>

> [@zranger1](#):
>
> (Edit: It doesn’t seem to kill v2s. Frame rate drops to a screamin’ 6 or 7 fps at 16x16 though, and that might reduce UI responsiveness.)

Yeah, that’s what happened. I’ll wait 'til my V3 comes in. 🙂

And I thought this was weird for AP mode…

 ![Screenshot_20220104-092336_Photos](https://forum.electromage.com/uploads/default/original/2X/b/b9f9e1f324610573bcc0cdc0273c661ef5e9dfa5.jpeg)

---

<div class="post-metadata">

**Author:** ![pixie](https://forum.electromage.com/letter_avatar_proxy/v4/letter/p/7bcc69/32.png) [@pixie](https://forum.electromage.com/u/pixie)\
**Post date:** [January 4, 2022, 5:39pm UTC](https://forum.electromage.com/t/yet-another-approach-to-text-rendering-wip/1897/8 "2022-01-04T17:39:09Z")

</div>

@zranger1, I took the easier way out and put `scale(-1,1);` into `beforeRender()`.

---

<div class="post-metadata">

**Author:** ![zranger1](https://forum.electromage.com/user_avatar/forum.electromage.com/zranger1/32/301_2.png) [@zranger1](https://forum.electromage.com/u/zranger1)\
**Post date:** [January 4, 2022, 7:20pm UTC](https://forum.electromage.com/t/yet-another-approach-to-text-rendering-wip/1897/9 "2022-01-04T19:20:46Z")

</div>

> @zranger1, I took the easier way out and put `scale(-1,1);` into `beforeRender()`.

What?! You mean I climbed inch by inch, up this icy, treacherous mountain, reached the summit and… there’s an escalator on the other side?

---

<div class="post-metadata">

**Author:** ![Scruffynerf](https://forum.electromage.com/letter_avatar_proxy/v4/letter/s/34f0e0/32.png) [@Scruffynerf](https://forum.electromage.com/u/Scruffynerf)\
**Post date:** [January 4, 2022, 9:44pm UTC](https://forum.electromage.com/t/yet-another-approach-to-text-rendering-wip/1897/10 "2022-01-04T21:44:34Z")

</div>

So many good things above. SDF fonts (ok, ok not really SDF fonts which are a real thing, this is just a good hack toward one). Scale reversals.

We keep pushing these to do something new. It’s so much fun.

---

<div class="post-metadata">

**Author:** ![gmcmicken](https://forum.electromage.com/user_avatar/forum.electromage.com/gmcmicken/32/1637_2.png) [@gmcmicken](https://forum.electromage.com/u/gmcmicken)\
**Post date:** [January 4, 2022, 10:55pm UTC](https://forum.electromage.com/t/yet-another-approach-to-text-rendering-wip/1897/11 "2022-01-04T22:55:59Z")

</div>

> [@zranger1](#):
>
> What?! You mean I climbed inch by inch, up this icy, treacherous mountain, reached the summit and… there’s an escalator on the other side?

🤣

> [@pixie](#):
>
> I took the easier way out and put `scale(-1,1);` into `beforeRender()`.

I was thinking about this from another thread but never tried it. Good to know!

---

<div class="post-metadata">

**Author:** ![zranger1](https://forum.electromage.com/user_avatar/forum.electromage.com/zranger1/32/301_2.png) [@zranger1](https://forum.electromage.com/u/zranger1)\
**Post date:** [January 4, 2022, 11:06pm UTC](https://forum.electromage.com/t/yet-another-approach-to-text-rendering-wip/1897/12 "2022-01-04T23:06:34Z")

</div>

Just updated the code in the first post with the results of the Great Vector Renormalization, plus some other minor things.

Lots more to do before it’s library worthy, but this one’s about twice as fast, looks slightly better, and makes it easier to spell things!

---

<div class="post-metadata">

**Author:** ![GodmanchesterGoblin](https://forum.electromage.com/letter_avatar_proxy/v4/letter/g/65b543/32.png) [@GodmanchesterGoblin](https://forum.electromage.com/u/GodmanchesterGoblin)\
**Post date:** [January 5, 2022, 1:27am UTC](https://forum.electromage.com/t/yet-another-approach-to-text-rendering-wip/1897/13 "2022-01-05T01:27:37Z")

</div>

> [@zranger1](#):
>
> …and makes it easier to spell things!

But I’ve been converting text characters to hexadecimal ASCII code and making up character bitmaps in my head for the last 40 years (literally!). 😆

Oh well, this looks like a really worthwhile improvement. I shall play with it tomorrow.

---

<div class="post-metadata">

**Author:** ![sorceror](https://forum.electromage.com/user_avatar/forum.electromage.com/sorceror/32/735_2.png) [@sorceror](https://forum.electromage.com/u/sorceror)\
**Post date:** [January 5, 2022, 10:36pm UTC](https://forum.electromage.com/t/yet-another-approach-to-text-rendering-wip/1897/14 "2022-01-05T22:36:46Z")

</div>

I can steal those vertex coordinates for an antialiased-line font, right?

---

<div class="post-metadata">

**Author:** ![zranger1](https://forum.electromage.com/user_avatar/forum.electromage.com/zranger1/32/301_2.png) [@zranger1](https://forum.electromage.com/u/zranger1)\
**Post date:** [January 5, 2022, 11:48pm UTC](https://forum.electromage.com/t/yet-another-approach-to-text-rendering-wip/1897/15 "2022-01-05T23:48:24Z")

</div>

By all means, please do! If you get around to expanding it with numbers and punctuation, I’ll steal it back from you! A good vector font would be a really useful resource.

---

<div class="post-metadata">

**Author:** ![Scruffynerf](https://forum.electromage.com/letter_avatar_proxy/v4/letter/s/34f0e0/32.png) [@Scruffynerf](https://forum.electromage.com/u/Scruffynerf)\
**Post date:** [January 6, 2022, 12:16am UTC](https://forum.electromage.com/t/yet-another-approach-to-text-rendering-wip/1897/16 "2022-01-06T00:16:14Z")

</div>

A true SDF font approach would be interesting too. Or even a version of the above using circles and lines.

---

<div class="post-metadata">

**Author:** ![zranger1](https://forum.electromage.com/user_avatar/forum.electromage.com/zranger1/32/301_2.png) [@zranger1](https://forum.electromage.com/u/zranger1)\
**Post date:** [January 6, 2022, 12:43am UTC](https://forum.electromage.com/t/yet-another-approach-to-text-rendering-wip/1897/17 "2022-01-06T00:43:08Z")

</div>

The “real” sdf fonts that are popular in game engines lately are just textures containing distance fields generated from vector fonts by an algorithm similar to what’s in this pattern. The rendering is done at super high resolution and is then resampled and filtered by the GPU hardware for rendering at whatever size the current frame requires. It does look really good, and is way faster than drawing with TrueType or whatever.

We don’t have the enormous memory of a modern GPU, or the hardware texture compression/decompression mechanisms, so I figured it was reasonable to burn some CPU cycles generating the distance field at frame time instead.

**(…and now, the part where I attempt to explain why you’d even want to use this crazy algorithm in the first place… Skip if you already know! 🙂 )**

The interesting thing about this is that it’s still just a vector font, in this case drawn with an an algorithm that would be insanely inefficient if drawn by a single CPU on a large display. @sorcerer’s version, which will draw the vectors in a frame buffer using a “normal” line drawing algorithm would be tremendously faster. The SDF algorithm asks, for every pixel, “is this pixel inside or outside my shape”. If the number of pixels per processor is large, it’ll be slower than an algorithm that calculates and draws only the required pixels.

There’s context though – the SDF algorithm has some fun and useful features.

- even with a single core and rendering thread, LED displays are low enough resolution that the pixels/processor ratio is still small enough to make SDFs practical.
- Texturing the resulting shapes is very simple. The whole shape is treated as one “thing” – no polygons to worry about.
- manipulating the geometry - collision detection, distortion, mirroring, repeating, constructive geometry, morphing between shapes, etc. - is very nearly trivial.
- you get high quality anti-aliasing as an inherent feature of the algorithm.
- and importantly, since it treats each pixel independently, it is intrinsically scalable to any number of processors. This is why it’s so widely used in the GPU world. (And eventually, our LED controllers will have more cores and threads too, and things written this way will simply go faster, without any software changes.)

---

<div class="post-metadata">

**Author:** ![ChrisNZ](https://forum.electromage.com/letter_avatar_proxy/v4/letter/c/7feea3/32.png) [@ChrisNZ](https://forum.electromage.com/u/ChrisNZ)\
**Post date:** [January 6, 2022, 11:10am UTC](https://forum.electromage.com/t/yet-another-approach-to-text-rendering-wip/1897/18 "2022-01-06T11:10:38Z")

</div>

I’m not sure that it is relevant for fonts on the PB, but thought some of you might appreciate this clever approach to drawing fonts with a GPU:

> **[Easy Scalable Text Rendering on the GPU](https://medium.com/@evanwallace/easy-scalable-text-rendering-on-the-gpu-c3f4d782c5ac)**
>
> This post describes the text rendering algorithm I used on thetamath.com, a GPU-powered 2D equation renderer I wrote for fun last December…

---

<div class="post-metadata">

**Author:** ![sorceror](https://forum.electromage.com/user_avatar/forum.electromage.com/sorceror/32/735_2.png) [@sorceror](https://forum.electromage.com/u/sorceror)\
**Post date:** [January 13, 2022, 8:36pm UTC](https://forum.electromage.com/t/yet-another-approach-to-text-rendering-wip/1897/19 "2022-01-13T20:36:30Z")

</div>

Wow! Here’s a font editor in which each character is a list of lists of coordinates. Each list of N coordinates defines N-1 line(s): [https://brutalita.com/](https://brutalita.com/)

For example the right-pointing arrow glyph is the point of the arrow (2 lines) followed by the shaft:

`{"→":[[[1,1],[2,2],[1,3]],[[0,2],[2,2]]], ...}`

I’m thinking that to make a text scroller, on your Real Computer you could take only the used characters and put them in a list, and then the various strings you want to display are lists of indexes into that array.

I’m not sure I like it more than graph paper, but someone has already drawn an entire font so I’m going to make a renderer!

---

<div class="post-metadata">

**Author:** ![sorceror](https://forum.electromage.com/user_avatar/forum.electromage.com/sorceror/32/735_2.png) [@sorceror](https://forum.electromage.com/u/sorceror)\
**Post date:** [January 13, 2022, 9:37pm UTC](https://forum.electromage.com/t/yet-another-approach-to-text-rendering-wip/1897/20 "2022-01-13T21:37:49Z")

</div>

Hey presto, here it is! Renderer coming up next!

( _minor edit: the space character is no segments, and [] is not ok, so it gets magic number -1_ )

```javascript
$ ./packstring.rb < brutalita-1642105672773.json 'PixelBlaze Salamanders'
chars = [[[[0,2.5],[2,2.5],[2,1],[1,0],[0,0],[0,4]]],[[[1,0]],[[0.5,1],[1,1],[1,4],[0.5,4],[1.5,4]]],[[[0,1],[2,4]],[[2,1],[0,4]]],[[[0,2.5],[2,2.5],[2,1.5],[1.5,1],[0.5,1],[0,1.5],[0,3.5],[0.5,4],[1.5,4],[2,3.5]]],[[[0.5,0],[1,0],[1,4],[0.5,4],[1.5,4]]],[[[0,0],[1,0],[2,1],[2,2],[1,2],[2,3],[2,4],[0,4],[0,0]]],[[[0.5,1],[1.5,1],[2,1.5],[2,3],[1,4],[0.5,4],[0,3.5],[0,1.5],[0.5,1]],[[2,1],[2,4]]],[[[0,1],[2,1],[0,4],[2,4]]],[[[2,0],[0.5,0],[0,0.5],[0,1.5],[0.5,2],[1.5,2],[2,2.5],[2,3.5],[1.5,4],[0,4]]],[[[0,1],[0,4]],[[0,1.5],[0.5,1],[1,1.5],[1,3],[1,1.5],[1.5,1],[2,1.5],[2,4]]],[[[0,1],[0,4]],[[0,1.5],[0.5,1],[1.5,1],[2,1.5],[2,4]]],[[[2,0],[2,1.5],[1.5,1],[0.5,1],[0,1.5],[0,3.5],[0.5,4],[1.5,4],[2,3.5],[2,1.5],[2,4]]],[[[0,1],[0,4]],[[0,2],[1,1],[2,1]]],[[[2,1],[0.5,1],[0,1.5],[0,2],[0.5,2.5],[1.5,2.5],[2,3],[2,3.5],[1.5,4],[0,4]]]]
texts = [[0,1,2,3,4,5,4,6,7,3,-1,8,6,4,6,9,6,10,11,3,12,13]]

```

```ruby
#!/usr/bin/env ruby

require 'multi_json'

# Output variables
chars = []
texts = []

# Character -> chars[] index tracker
cnums = { ' ' => -1 }
cnum = 0

font = MultiJson.load(STDIN.read)

ARGV.each do |text|
  tcnums = []
  text.chars.each do |char|
    if cnums.key?(char)
      tcnums.append(cnums[char])
    elsif font.key?(char)
      chars[cnum] = font[char]
      cnums[char] = cnum
      tcnums.append(cnum)
      cnum += 1
    else
      warn('Character not in font: ' + char)
    end
  end
  texts.append(tcnums)
end

puts 'chars = ' + MultiJson.dump(chars)
puts 'texts = ' + MultiJson.dump(texts)

```

[Next page](https://forum.electromage.com/t/yet-another-approach-to-text-rendering-wip/1897.md?page=2)
