Fonts and word-wrap on TFT screens.

Place code snippets and demo code here
Post Reply
bugs
Posts: 142
Joined: Mon Feb 08, 2021 10:10 pm
Location: Scotland
Has thanked: 47 times
Been thanked: 50 times

Fonts and word-wrap on TFT screens.

Post by bugs »

I built an internet radio and used an small OLED screen to show the shoutcast data stream (Title, artist, album etc) and had to write a routine to wrap the words sensibly. This was using a mono font with all characters taking the same space.
I have been considering to replace the OLED with a TFT screen and use a variable width font to allow more characters on the screen. This meant obtaining the width of each font character.
I cannot do this with the inbuilt fonts so have will be requesting an addition feature as I post this.
The Freefonts which can be added to font slots 10-> were more accessible so I present a pair of programs and some sample pictures using variable width font FreeSans9pt7b.bin.
First a comparison of word-wrapped and normal text on a 2.8" 320x240 TFT.
nonwrap_8x6.jpg
wrapped_8x6.jpg
And a sample screen from the web radio prototype on a 1.69" 280x240 TFT.
rad1_8x6.jpg
The first program extracts the character spaces (usually about 126 of them) from the font file and creates a much smaller binary file for use in the main program(s)

Code: [Local Link Removed for Guests]

CODE: xxxx.bas

--------------------------------------------------------------------------------
'Program to get character widths (x-advance) from a binary font file
'and create a binary file (usually only 128 bytes) for use in other programs
'/rdfontwidths.bas

 fontfile$ = "/FreeSans9pt7b.bin"
 Ret  =  FILE.EXISTS(fontfile$)
 if ret = 0 then wlog "no file" : end
 
 fl$ = word$(fontfile$, 1, ".")&".wid"
 siz = file.size(fontfile$)
 iobuff.dim(0, siz)
'load the basic info from beginning of font file
 FILE.READ_IOBUFF(0),  fontfile$ ,  0,  16
 segsizelo = iobuff.read(0, 0)
 segsizehi = iobuff.read(0, 1)
 charfirst = iobuff.read(0, 8)
 charlast = iobuff.read(0, 10)
 yadvance = iobuff.read(0, 12) 'not used here
 segsize = (segsizehi*256) + segsizelo
 offset = 16
 glyphaddr = segsize + offset

'Now load the individual character x-advance (width) information
FILE.READ_IOBUFF(0),  fontfile$ ,  glyphaddr,  siz  
iobuff.dim(1, charlast+1)
for j = 0 to charlast-charfirst
    wlog chr$(j + charfirst), 
    ptr = j*12 + 6  'there are 12 bytes per character and x-advance is the 6th byte
    v1 = iobuff.read(0, ptr)
    wlog v1
    iobuff.write(1, j + charfirst, v1)
next j

FILE.WRITE_IOBUFF(1),  fl$

'==============================================================

' just test the binary file looks ok
IOBUFF.DESTROY(0)
iobuff.dim(0, 128)
FILE.READ_IOBUFF(0),  fl$,  0,  127
wlog "______________________________"
for i = 32 to 126
  wlog chr$(i), iobuff.read(0, i)
next i

and a simple program which fits a long string to the specified screen width and height.

Code: [Local Link Removed for Guests]

CODE: xxxx.bas

--------------------------------------------------------------------------------
'program to get character widths (x-advance) from a previously created binary file (rdfont.bas)
'/wordwrap.bas

  fontfile$ = "/FreeSans9pt7b.bin" 

'set size of tft screen ( OR TEXT AREA )  
  maxwid = 320  ' number of tft pixels (width)
  maxht = 240  ' number of tft pixels (height)
  fontsize = 1 'mutiplier for text size
  
  fl$ = word$(fontfile$, 1, ".")
  fl$ = fl$ &  ".wid"
 
'initialise with 128 byte file previously created from Freesans9pt7b font
   Ret  =  FILE.EXISTS(fl$)
  if ret = 0 then wlog "No file":end
  
'read character info from the font file
  iobuff.dim(0, 16)
  FILE.READ_IOBUFF(0),  fontfile$ ,  0,  16
  charfirst = iobuff.read(0, 8)
  charlast = iobuff.read(0, 10)
  yadvance = iobuff.read(0, 12) *  fontsize 
 
 ' now load the individual character widths to a buffer
  loadwidths fl$
  Ret  =  FILE.EXISTS(fl$) : if ret = 0 then wlog "No file" : end
 
  xadv = 0 : py = 0
'  
'get width of space character
  ch = asc(" ") :  getcharwid ch, xadv
 spwid = xadv 
  
'get width of widest characte  =  "W"
  ch = asc("W") :  getcharwid ch, xadv
  maxcharwid = xadv 

'initialise the tft  
  tft.init 1
  TFT.LOADFONT fontfile$, 2
  tft.text.font 11
  tft.text.size fontsize
  tft.fill tft.color(black)
  tft.text.color white
 
'set some dummy text string 
  Aa$ = "The quick brown fox jumps over the lazy dog. "
  bb$= "Now is the time for all good men to come to the aid of the party."
  cc$ = aa$ + " " + bb$ + " " + aa$ + " " + bb$ + " " + aa$ + " " + bb$ + " " + aa$ + " " + bb$
'write the text to screen with word wrap  
  wraptft cc$
 
 end
'=============================================================
 
 '--------------------------- the wrap routine --------------
 
sub wraptft(a$) 
  p$ = "" : st = millis
  py = yadvance - 6 ' account for the font character offset
  tft.text.pos 0, py  
  'check total string width in case it would fit
  strlen = len(a$) :  totwid = strlen * maxcharwid 
  if totwid< = maxwid then 
    wlog "It fits!"  : tft.print a$
  else
    totwid = 0 : wordwid=0 : wordcount = word.count(a$)
    for j = 1 to wordcount
      w$ = word$(a$, j) : getwordwid w$ ,wordwid
      totwid = totwid + spwid + wordwid
      if totwid>maxwid then 'have printed the current words so continue      
        totwid = wordwid + spwid
        tft.print p$ : p$ = w$       
        py = py + yadvance:  tft.text.pos 0, py
        if py> = maxht then
           p$ = "" :  exit for
        endif
      else  'just continue adding to the string to print              
        p$ = p$  &  " "  &  w$
      endif
    next j
    tft.print p$
  endif 
  wlog millis-st 
end sub
  
  '________________________________________________
  
  '------------- subroutines for character widths-------------------
  
sub getwordwid(w$,wordwid)  'get the width of a single word
    wordwid = 0
    for i = 1 to len(w$)
        ch = asc(mid$(w$, i, 1))
        getcharwid ch, xadv
        wordwid = wordwid + xadv
    next i
end sub
  
sub getcharwid(ch, xadv)  ' get the pixel width of a single character
    xadv = IOBUFF.READ(1,  ch) 
    xadv = xadv * fontsize
end sub
  
sub loadwidths(f$)  'load buffer from 128 byte width file
    iobuff.dim(1, charlast)
    FILE.READ_IOBUFF(1),  f$,  0,  charlast
end sub
  

Note that although the simple program fits the text to the screen boundaries, it is possible to constrain the text to a smaller area.

Feature request going in now... :)
You do not have the required permissions to view the files attached to this post.
bugs
Posts: 142
Joined: Mon Feb 08, 2021 10:10 pm
Location: Scotland
Has thanked: 47 times
Been thanked: 50 times

Re: Fonts and word-wrap on TFT screens.

Post by bugs »

Just to show the text constraints do not have to be just the screen boundaries (although requiring a more complex program):-
frames3.jpg
Sorry for poor phone picture.
You do not have the required permissions to view the files attached to this post.
Post Reply