Skip to content

Commit 7920e0e

Browse files
committed
WIP
1 parent 03d935b commit 7920e0e

File tree

2 files changed

+155
-1
lines changed

2 files changed

+155
-1
lines changed

adafruit_bitmap_font/bitmap_font.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from . import bdf
2929
from . import pcf
3030
from . import ttf
31+
from . import lvfontbin
3132
except ImportError:
3233
pass
3334

@@ -37,7 +38,7 @@
3738

3839
def load_font(
3940
filename: str, bitmap: Optional[Bitmap] = None
40-
) -> Union[bdf.BDF, pcf.PCF, ttf.TTF]:
41+
) -> Union[bdf.BDF, pcf.PCF, ttf.TTF, lvfontbin.BIN]:
4142
"""Loads a font file. Returns None if unsupported."""
4243
# pylint: disable=import-outside-toplevel, redefined-outer-name, consider-using-with
4344
if not bitmap:

adafruit_bitmap_font/lvfontbin.py

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
from io import FileIO
2+
from typing import Union, Iterable
3+
4+
class LVGLFont:
5+
def __init__(self, f: FileIO):
6+
self.file = f
7+
while True:
8+
section_size = int.from_bytes(f.read(4), byteorder='little')
9+
if section_size == 0:
10+
break
11+
table_marker = f.read(4)
12+
print(table_marker, section_size)
13+
section_start = f.tell()
14+
remaining_section = f.read(section_size - 8)
15+
if table_marker == b'head':
16+
self._load_head(remaining_section)
17+
elif table_marker == b'cmap':
18+
self._load_cmap(remaining_section)
19+
elif table_marker == b'loca':
20+
self._max_cid = int.from_bytes(remaining_section[0:4], byteorder='little')
21+
self._loca_start = section_start + 4
22+
elif table_marker == b'glyf':
23+
self._glyf_start = section_start - 8
24+
25+
def _load_head(self, data):
26+
self._version = int.from_bytes(data[0:4], byteorder='little')
27+
self._font_size = int.from_bytes(data[6:8], byteorder='little')
28+
self._ascent = int.from_bytes(data[8:10], byteorder='little')
29+
self._descent = int.from_bytes(data[10:12], byteorder='little', signed=True)
30+
self._typo_ascent = int.from_bytes(data[12:14], byteorder='little')
31+
self._typo_descent = int.from_bytes(data[14:16], byteorder='little', signed=True)
32+
self._line_gap = int.from_bytes(data[16:18], byteorder='little')
33+
self._min_y = int.from_bytes(data[18:20], byteorder='little')
34+
self._max_y = int.from_bytes(data[20:22], byteorder='little')
35+
self._default_advance_width = int.from_bytes(data[22:24], byteorder='little')
36+
self._kerning_scale = int.from_bytes(data[24:26], byteorder='little')
37+
self._index_to_loc_format = data[26]
38+
self._glyph_id_format = data[27]
39+
self._advance_format = data[28]
40+
self._bits_per_pixel = data[29]
41+
self._glyph_bbox_xy_bits = data[30]
42+
self._glyph_bbox_wh_bits = data[31]
43+
self._glyph_advance_bits = data[32]
44+
self._glyph_header_bits = self._glyph_advance_bits + 2 * self._glyph_bbox_xy_bits + 2 * self._glyph_bbox_wh_bits
45+
self._glyph_header_bytes = (self._glyph_header_bits + 7) // 8
46+
self._compression_alg = data[33]
47+
self._subpixel_rendering = data[34]
48+
49+
for n in dir(self):
50+
if n.startswith('_') and not n.startswith('__'):
51+
print(n, getattr(self, n))
52+
53+
def _load_cmap(self, data):
54+
data = memoryview(data)
55+
subtable_count = int.from_bytes(data[0:4], byteorder='little')
56+
self._cmap_tiny = []
57+
for i in range(subtable_count):
58+
subtable_header = data[4 + 16 * i:4 + 16 * (i + 1)]
59+
data_offset = int.from_bytes(subtable_header[0:4], byteorder='little')
60+
range_start = int.from_bytes(subtable_header[4:8], byteorder='little')
61+
range_length = int.from_bytes(subtable_header[8:10], byteorder='little')
62+
glyph_offset = int.from_bytes(subtable_header[10:12], byteorder='little')
63+
data_entries_count = int.from_bytes(subtable_header[12:14], byteorder='little')
64+
format_type = subtable_header[14]
65+
# print(f"Subtable {i}: data_offset {data_offset:x} {range_start:x} {range_length:x} glyph_offset {glyph_offset:x} data_entries_count {data_entries_count} format_type {format_type}")
66+
67+
if format_type != 2:
68+
raise RuntimeError(f"Unsupported cmap format {format_type}")
69+
70+
self._cmap_tiny.append((range_start, range_start + range_length, glyph_offset))
71+
72+
@property
73+
def ascent(self) -> int:
74+
"""The number of pixels above the baseline of a typical ascender"""
75+
return self._ascent
76+
77+
@property
78+
def descent(self) -> int:
79+
"""The number of pixels below the baseline of a typical descender"""
80+
return self._descent
81+
82+
def get_bounding_box(self) -> tuple[int, int, int, int]:
83+
"""Return the maximum glyph size as a 4-tuple of: width, height, x_offset, y_offset"""
84+
return self._bounding_box
85+
86+
def _seek(self, offset):
87+
self.file.seek(offset)
88+
self._byte = 0
89+
self._remaining_bits = 0
90+
91+
def _read_bits(self, num_bits):
92+
result = 0
93+
needed_bits = num_bits
94+
while needed_bits > 0:
95+
if self._remaining_bits == 0:
96+
self._byte = self.file.read(1)[0]
97+
self._remaining_bits = 8
98+
available_bits = min(needed_bits, self._remaining_bits)
99+
result = (result << available_bits) | (self._byte >> (8 - available_bits))
100+
self._byte <<= available_bits
101+
self._byte &= 0xff
102+
self._remaining_bits -= available_bits
103+
needed_bits -= available_bits
104+
return result
105+
106+
def load_glyphs(self, code_points: Union[int, str, Iterable[int]]) -> None:
107+
# pylint: disable=too-many-statements,too-many-branches,too-many-nested-blocks,too-many-locals
108+
if isinstance(code_points, int):
109+
code_points = (code_points,)
110+
elif isinstance(code_points, str):
111+
code_points = [ord(c) for c in code_points]
112+
113+
for code_point in code_points:
114+
cid = None
115+
for start, end, offset in self._cmap_tiny:
116+
if start <= code_point < end:
117+
cid = offset + (code_point - start)
118+
break
119+
120+
if cid is None or cid >= self._max_cid:
121+
print(f"Code point {code_point:x} not found")
122+
continue
123+
offset_length = 4 if self._index_to_loc_format == 1 else 2
124+
125+
self._seek(self._loca_start + cid * offset_length)
126+
glyph_offset = int.from_bytes(self.file.read(offset_length), byteorder='little')
127+
print(chr(code_point), code_point, hex(code_point), "cid", cid, "glyph_offset", glyph_offset)
128+
129+
self._seek(self._glyf_start + glyph_offset)
130+
131+
glyph_advance = self._read_bits(self._glyph_advance_bits)
132+
bbox_x = self._read_bits(self._glyph_bbox_xy_bits)
133+
bbox_y = self._read_bits(self._glyph_bbox_xy_bits)
134+
bbox_w = self._read_bits(self._glyph_bbox_wh_bits)
135+
bbox_h = self._read_bits(self._glyph_bbox_wh_bits)
136+
print(f"Advance: {glyph_advance}, BBox: {bbox_x},{bbox_y} {bbox_w}x{bbox_h}")
137+
for _ in range(bbox_h):
138+
for _ in range(bbox_w):
139+
bitmap = self._read_bits(self._bits_per_pixel)
140+
print(' #'[bitmap], end='')
141+
print()
142+
print()
143+
144+
145+
if __name__ == '__main__':
146+
import sys
147+
if len(sys.argv) < 2:
148+
print('Usage: lvgl_bin.py <font_file>')
149+
sys.exit(1)
150+
151+
font = LVGLFont(open(sys.argv[1], "rb"))
152+
153+
font.load_glyphs(' 19🖮⌨🖱🎉🐍')

0 commit comments

Comments
 (0)