3.3 Zobrazovanie textu v neproporcionálnom fonte
Neproporcionálny font
Objekt ST7789 ponúka viaceré funkcie pre jednoduché zobrazovanie textu (bez automatického zalamovania riadkov a podobných vymožeností). Umožňujú zobraziť zadaný text na zadanej pozícii displeja, pričom sa použije zvolený font. Jednotlivé funkcie sa líšia predovšetkým formátom fontu a možnosťou zvoliť si farby a veľkosť písma.
V tejto kapitole sa pozrieme na dva spôsoby zobrazovania bitmapových fontov, ktoré sú neproporcionálne (konzolové, monospace). Keďže sú bitmapové, majú pevnú veľkosť a nemôžeme ju zvoliť pri výpise. Fonty sú vyjadrené bitovou mapou, každý pixel zaberá 1 bit, teda existuje len farba písma a farba pozadia. Kvôli tomu nie je možné „vyhladiť“ hrany písma miešanými odtieňmi a zobrazený text pri blízkom pohľade pôsobí drsne, na hranách vidno „schody“ z pixelov. Displej M5Stick je však malý a tieto neduhy budú len sotva badateľné.
Niektoré fonty sú implementované priamo do firmvéru. Tieto „zabudované“ fonty nemíňajú operačnú pamäť a čítajú sa priamo z EEPROM / flash. Ide o takzvané „frozen“ objekty, detaily sa dozviete v dokumentácii MicroPython.
Jednoduché VGA fonty a funkcia „text“
Súbor s fontom je modul, ktorý obsahuje samotné obrazové údaje (FONT) pre znaky v súvislej postupnosti, napríklad pre ASCII ide o znaky s kódom 32 až 127, plný rozsah sú kódy 0 až 255. Nepodporuje viac znakov. Modul okrem bitmapy obsahuje aj rozmery znakov (WIDTH a HEIGHT) a ich rozsah (kód FIRST a LAST), neobsahuje farby.
Jednoduché fonty z čias prvých PC je možné nájsť v GitHub priečinku st7789_mpy/fonts/fn_text. Sú predkompilované (frozen) v našom firmvéri a dostupné aj bez nahrávania - stačí ich importovať príkazom:
import {názov fontu} as {alias}
import vga2_8x14 as font
print(f"Font {font.__name__} má rozmery {font.WIDTH} × {font.HEIGHT} px "
f"a znaky hodnôt {font.FIRST} až {font.LAST}.")
Alias nie je nutný, no uľahčuje prácu a umožňuje font dodatočne na jednom mieste zmeniť. Konkrétne sú k dispozícii tieto „zabudované“ fonty:
- rozmer 8 × 8 px: vga2_8x8;
- rozmer 8 × 14 px: vga2_8x14;
- rozmer 8 × 16 px: vga2_8x16, tosh1_vga2_8x16, tosh2_vga2_8x16;
- rozmer 16 × 16 px: vga2_16x16, vga2_bold_16x16;
- rozmer 16 × 32 px: vga2_16x32, vga2_bold_16x32.
V spomínanom GitHub priečinku sú tieto fonty aj vo verzii vga1 - tie obsahujú len základné ASCII znaky.
Verzia vga2 je plne 8-bitová, teda obsahuje aj ďalšie znaky v prehistorickom kódovaní CP437, ktoré sa používalo v dobách prvých IBM PC a MS-DOS. To v praxi znamená, že ich využitie nebude priamočiaro fungovať zo zadaného textu v UTF-8 (a ani neobsahuje slovenské písmená), ale je nutné použiť číselné kódy bajtov alebo prevodovú tabuľku. Znaky však umožňujú vytvoriť pseudo-grafické rozhranie okien, známe z textových režimov, napríklad kedysi slávny Norton Commander v MS-DOS, dnes Midnight Commander v Linuxe.
Predpony tosh1 a tosh2 sú len iné varianty fontov zo starých počítačov Toshiba.
V prípade potreby si môžeme pripraviť aj ďalšie historické konzolové fonty z GitHub projektu romfont - ich konverziu z BIN prevedie nástroj font_from_romfont.py.
Z estetického hľadiska sú pre texty vhodnejšie fonty s dvojnásobnou výškou oproti šírke, teda rozmerov 8 × 16 a 16 × 32 px. To nám na displeji 240 × 135 px umožňuje zobrazovať text v rozlíšení 30 × 8 alebo 15 × 4 znakov. Zaujímavým kompromisom je font úsporných rozmerov 8 × 14 px, ktorý ešte stále vyzerá dobre a umožní vypísať o jeden riadok viac (30 × 9 znakov na displej).
Vzhľadom na malé rozmery displeja sú fonty šírky 8 px vhodné len pre mladé oči a pre starších ľudí predstavujú problém.
Pamäťová náročnosť externých fontov je daná počtom znakov a rozmermi - každý pixel zaberá 1 bit, čo pre bežný font 8 × 16 px dáva vo variante ASCII 1,5 KiB, v plnom variante 4 KiB. Pamäť sa obsadí hneď po importovaní fontu.
Výpis textu
O výpis textu sa postará funkcia text():
- text({font}, {text}, {x}, {y}, {farba textu}, {farba pozadia})
Táto objektová funkcia umožňuje zvoliť farby, text vypíše na zadanú pozíciu, no nerieši zalamovanie riadkov a zobrazí len tú časť, ktorá sa vojde od zadanej pozície po okraj obrazovky.
import M5Stick
from time import sleep
displej = M5Stick.lcd
displej.rotation(3)
displej.init()
if hasattr(M5Stick, "pmu"): M5Stick.pmu.lcd_on()
import vga2_8x14 as font14
farba_pozadie, farba_text, farba_text2 = 65535, 0, 31
displej.fill(farba_pozadie)
displej.text(font14, "font VGA, velkost 8x14:", 0, 0, farba_text2, farba_pozadie)
displej.text(font14, " abcdefghijklmnopqrstuvwxyz", 0, 14, farba_text, farba_pozadie)
displej.text(font14, " ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0, 28, farba_text, farba_pozadie)
displej.text(font14, " 0123456789", 0, 42, farba_text, farba_pozadie)
sleep(5)
# výpis všetkých znakov
displej.fill(farba_pozadie)
y = 0
for od in range(0, 256, 30):
# vytvoríme postupnosť bajtov (znakov)
znaky = bytes(range(od, min(od + 30, 256)))
displej.text(font14, znaky, 0, y, farba_text, farba_pozadie)
y += 15
Univerzálne bitmapové fonty a funkcia „bitmap“
V tomto prípade je fontom modul bitmapy, ktorý obsahuje samotné obrazové údaje (BITMAP) v definovanej bitovej hĺbke (BPP) a tiež špecifikuje rozmer jedného obrázku / znaku (WIDTH a HEIGHT). Z toho vyplýva, že pokiaľ v tomto formáte chceme používať font, musí mať každý znak rovnaké rozmery, a teda opäť ide o neproporcionálny font.
Priložená je aj paleta farieb (zoznam PALETTE), nie je však v 16-bitovom formáte color565 a poradie bajtov treba vymeniť. Farby v palete je možné meniť priebežne podľa potreby. Keďže je v module definovaná i bitová hĺbka, teoreticky podporuje aj „vyhladené“ fonty, no nemáme ich k dispozícii a dostupné sú len 1-bitové fonty.
Pre zobrazovanie znakov je nevyhnutný zoznam znakov (MAP) - v podstate ide o textový reťazec obsahujúci práve tie znaky, ktoré sú v obrazových údajoch, a to v rovnakom poradí. Môžeme mať teda aj font obsahujúci len špecifické znaky, ktoré potrebujeme, napríklad len číslice. Font môže obsahovať aj ľubovoľné UTF-8 znaky, nielen ASCII. Vďaka zoznamu znakov môžeme príslušný znak nájsť v obrazových údajoch cez jeho index. V obrazových údajoch samozrejme nemusíme mať len textové znaky, ale aj rôzne ikonky a symboly - tie môžeme nájsť priamym zadaním poradového čísla. V objekte fontu je uvedený aj počet znakov (BITMAPS).
V našom firmvéri máme „zabudované“ vybrané fonty Inconsolata - stačí ich importovať rovnako, ako VGA fonty:
import {názov fontu} as {alias}
import inconsolata_SK_32 as font
print(f"Font {font.__name__} má rozmery {font.WIDTH} × {font.HEIGHT} px "
f"a obsahuje {font.BITMAPS} znakov v {font.BPP}-bitovej farebnej hĺbke:")
print(font.MAP)
Konkrétne sú k dispozícii tieto fonty:
- veľkosť 22 (11 × 23 px): inconsolata_SK_22;
- veľkosť 32 (16 × 34 px): inconsolata_SK_32;
- veľkosť 48 (24 × 51 px): inconsolata_SK_48;
- veľkosť 60 (30 × 63 px): inconsolata_SK_60;
- veľkosť 80 (40 × 83 px): inconsolata_SK_80.
Tieto fonty obsahujú okrem ASCII znakov aj znaky €„“©°±₂²µ×…•» a všetky slovenské a české písmená (dokopy 149 znakov). Je ich možné nájsť v Github priečinku st7789_mpy/fonts/fn_bitmap. Sú tam aj ich podmnožiny (napríklad len numerické znaky a symboly) pre individuálne použitie.
Môžeme si tiež pripraviť akékoľvek vlastné fonty z bežného formátu TTF pomocou nástroja monofont2bitmap.py.
Pamäťová náročnosť externých fontov je daná počtom znakov a rozmermi, každý pixel zaberá 1 bit, čo napríklad pre font 16 × 34 px (veľkosť 32) vychádza na 68 B/znak, teda uvedená SK sada minie cca 10 KiB operačnej pamäte. Pamäť sa obsadí hneď po importovaní fontu. Najväčší font 40 × 83 px (veľkosť 80) vychádza na 415 B/znak, celý font až 60 KiB. Preto je priam nevyhnutné fonty používať v podobe frozen objektov (zabudované do firmvéru) alebo len v podobe malej sady znakov.
Výpis textu
O výpis jedného textového znaku sa postará funkcia bitmap():
- bitmap({font}, {x}, {y}, {index znaku})
Táto objektová funkcia nie je primárne určená na výpis textu, ale na zobrazenie časti bitmapového obrazu vo vyššie uvedenom formáte. Šikovný programátor však nemá problém pripraviť si jednoduchú funkciu na výpis textu, napríklad:
def Text(displej, font, text, x = None, y = None, farby = None):
if x is None:
# centrovanie textu na stred obrazovky
x = (displej.width() - len(text) * font.WIDTH) // 2
if y is None:
y = (displej.height() - font.HEIGHT) // 2
if farby:
# prehodenie bajtov pre farby a zápis do palety fontu
for i in (0, 1):
font.PALETTE[i] = farby[i] << 8 & 0xff00 | farby[i] >> 8
for znak in text:
try: displej.bitmap(font, x, y, font.MAP.index(znak)) # nájdenie indexu znaku a vykreslenie
except ValueError: pass # znak sa vo fonte nenachádza
x += font.WIDTH
return text
import inconsolata_SK_60 as font60
import inconsolata_SK_22 as font22
Text(displej, font60, "UKÁŽKA", y = 0, farby = (farba_text2, farba_pozadie))
Text(displej, font22, "Môžeme vypísať rôzne", y = 64, farby = (farba_text, farba_pozadie))
Text(displej, font22, "slová - aj maľované", y = 88, farby = (farba_text, farba_pozadie))
Text(displej, font22, "jabĺčko, či broskyňu!", y = 112, farby = (farba_text, farba_pozadie))