Skip to content

Commit 3a60d6b

Browse files
committed
Works!
1 parent 5c341c5 commit 3a60d6b

File tree

5 files changed

+90
-102
lines changed

5 files changed

+90
-102
lines changed

adafruit_bitmap_font/lvfontbin.py

Lines changed: 52 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
try:
2828
from io import FileIO
2929
from typing import Iterable, Union
30+
3031
from displayio import Bitmap
3132
except ImportError:
3233
pass
@@ -47,7 +48,7 @@ class LVGLFont(GlyphCache):
4748

4849
def __init__(self, f: FileIO, bitmap_class=None):
4950
"""Initialize LVGL font.
50-
51+
5152
Args:
5253
f: File object containing the LVGL font data
5354
bitmap_class: Optional bitmap class to use for glyphs. Defaults to displayio.Bitmap.
@@ -76,6 +77,7 @@ def __init__(self, f: FileIO, bitmap_class=None):
7677
table_marker = f.read(4)
7778
section_start = f.tell()
7879
remaining_section = f.read(section_size - 8)
80+
print(f"Table marker @ {section_start}: {table_marker} ({section_size} bytes)")
7981
if table_marker == b"head":
8082
self._load_head(remaining_section)
8183
# Set bounding box based on font metrics from head section
@@ -84,7 +86,7 @@ def __init__(self, f: FileIO, bitmap_class=None):
8486
self._x_offset = 0
8587
self._y_offset = self._descent
8688
elif table_marker == b"cmap":
87-
self._load_cmap(remaining_section)
89+
self._load_cmap(remaining_section, section_start)
8890
elif table_marker == b"loca":
8991
self._max_cid = struct.unpack("<I", remaining_section[0:4])[0]
9092
self._loca_start = section_start + 4
@@ -119,12 +121,10 @@ def _load_head(self, data):
119121
self._compression_alg = data[33]
120122
self._subpixel_rendering = data[34]
121123

122-
def _load_cmap(self, data):
124+
def _load_cmap(self, data, section_start):
123125
data = memoryview(data)
124126
subtable_count = struct.unpack("<I", data[0:4])[0]
125-
self._cmap_tiny = []
126-
self._cmap_sparse = []
127-
self._cmap_continuous = []
127+
self._cmap_subtables = []
128128
data_offset = 4
129129

130130
for i in range(subtable_count):
@@ -139,35 +139,22 @@ def _load_cmap(self, data):
139139
# 1 byte - padding
140140
if len(subtable_header) < 16:
141141
raise RuntimeError("Invalid cmap subtable header size")
142-
143-
(data_offset_val, range_start, range_length, glyph_offset, entries_count) = struct.unpack(
144-
"<IIHHH", subtable_header[:14]
142+
143+
(data_offset_val, range_start, range_length, glyph_offset, entries_count) = (
144+
struct.unpack("<IIHHH", subtable_header[:14])
145145
)
146146
format_type = subtable_header[14]
147147

148-
if format_type == 0: # Format 0 - continuous
149-
if data_offset_val == 0:
150-
raise RuntimeError("Format 0 requires data section")
151-
subtable_data = data[data_offset_val:data_offset_val + range_length]
152-
if len(subtable_data) < range_length:
153-
raise RuntimeError("Invalid cmap data length")
154-
glyph_ids = [gid + glyph_offset for gid in subtable_data]
155-
self._cmap_continuous.append((range_start, range_start + range_length, glyph_ids))
156-
elif format_type == 2: # Format 0 tiny
157-
self._cmap_tiny.append((range_start, range_start + range_length, glyph_offset))
158-
elif format_type == 3: # Format sparse tiny
159-
if data_offset_val == 0:
160-
raise RuntimeError("Format 3 requires data section")
161-
subtable_data = data[data_offset_val:data_offset_val + entries_count * 2]
162-
if len(subtable_data) < entries_count * 2:
163-
raise RuntimeError("Invalid cmap data length")
164-
codepoints = []
165-
for j in range(entries_count):
166-
cp_offset = struct.unpack("<H", subtable_data[j * 2:(j + 1) * 2])[0]
167-
codepoints.append(range_start + cp_offset)
168-
self._cmap_sparse.append((codepoints, glyph_offset))
169-
else:
170-
raise RuntimeError(f"Unsupported cmap format {format_type}")
148+
# Store subtable header info
149+
subtable_info = {
150+
"format": format_type,
151+
"data_offset": data_offset_val + section_start - 8,
152+
"range_start": range_start,
153+
"range_length": range_length,
154+
"glyph_offset": glyph_offset,
155+
"entries_count": entries_count,
156+
}
157+
self._cmap_subtables.append(subtable_info)
171158

172159
@property
173160
def ascent(self) -> int:
@@ -218,25 +205,41 @@ def load_glyphs(self, code_points: Union[int, str, Iterable[int]]) -> None:
218205
for code_point in code_points:
219206
# Find character ID in the cmap table
220207
cid = None
221-
# Check format 0 (continuous)
222-
for start, end, glyph_ids in self._cmap_continuous:
223-
if start <= code_point < end:
224-
cid = glyph_ids[code_point - start]
225-
break
226-
227-
# Check format 2 (format 0 tiny)
228-
if cid is None:
229-
for start, end, offset in self._cmap_tiny:
230-
if start <= code_point < end:
231-
cid = offset + (code_point - start)
208+
209+
# Search through all subtables
210+
for subtable in self._cmap_subtables:
211+
format_type = subtable["format"]
212+
range_start = subtable["range_start"]
213+
range_length = subtable["range_length"]
214+
glyph_offset = subtable["glyph_offset"]
215+
entries_count = subtable["entries_count"]
216+
data_offset = subtable["data_offset"]
217+
218+
if range_start <= code_point < range_start + range_length:
219+
if format_type == 0: # Continuous
220+
# Read the glyph IDs from the data section (single bytes)
221+
self.file.seek(data_offset)
222+
subtable_data = self.file.read(entries_count)
223+
glyph_id = subtable_data[code_point - range_start]
224+
if glyph_id == 0:
225+
print(" Glyph ID 0: not found")
226+
continue
227+
cid = glyph_id + glyph_offset
232228
break
233-
234-
# Check format 3 (sparse tiny)
235-
if cid is None:
236-
for codepoints, offset in self._cmap_sparse:
237-
if code_point in codepoints:
238-
cid = offset + codepoints.index(code_point)
229+
elif format_type == 2: # Format 0 tiny
230+
cid = glyph_offset + (code_point - range_start)
239231
break
232+
elif format_type == 3: # Sparse tiny
233+
# Read the codepoint offsets from the data section
234+
self.file.seek(data_offset)
235+
subtable_data = self.file.read(entries_count * 2)
236+
for i in range(entries_count):
237+
cp_offset = struct.unpack("<H", subtable_data[i * 2 : (i + 1) * 2])[0]
238+
if cp_offset + range_start == code_point:
239+
cid = glyph_offset + i
240+
break
241+
if cid is not None:
242+
break
240243

241244
if cid is None or cid >= self._max_cid:
242245
self._glyphs[code_point] = None

examples/bitmap_font_lvgl_simpletest.py

Lines changed: 31 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -9,58 +9,36 @@
99

1010
from adafruit_bitmap_font import lvfontbin
1111

12-
# Use the unifont bin file that includes ASCII and emoji characters
13-
font_file = "fonts/unifont-16.0.02-ascii-emoji.bin"
12+
# Use the Japanese font file
13+
font_file = "examples/fonts/unifont-16.0.02-ja.bin"
1414

15-
try:
16-
with open(font_file, "rb") as f:
17-
font = lvfontbin.LVGLFont(f)
18-
print("Successfully loaded LVGL font")
19-
print("Font metrics:")
20-
print(f" Ascent: {font.ascent}")
21-
print(f" Descent: {font.descent}")
22-
bbox = font.get_bounding_box()
23-
print(f" Bounding box: width={bbox[0]}, height={bbox[1]}, x_offset={bbox[2]}, y_offset={bbox[3]}")
24-
25-
# Test ASCII characters (likely using format 0 or 2)
26-
test_ascii = "Hello, LVGL!"
27-
print(f"\nTesting ASCII characters: {test_ascii}")
28-
font.load_glyphs(test_ascii)
29-
for c in test_ascii:
30-
glyph = font.get_glyph(ord(c))
31-
if glyph:
32-
print(f" Character '{c}' (U+{ord(c):04X}):")
33-
# Print ASCII art representation of the glyph
34-
for y in range(glyph.height):
35-
pixels = []
36-
for x in range(glyph.width):
37-
value = glyph.bitmap[x, y]
38-
pixel = "#" if value > 0 else " "
39-
pixels.append(pixel)
40-
print(" " + "".join(pixels))
41-
else:
42-
print(f" Character '{c}' (U+{ord(c):04X}) not found in font")
43-
44-
# Test emoji characters (likely using format 3 - sparse)
45-
test_emoji = "😀🎉🚀" # Emoji: grinning face, party popper, rocket
46-
print(f"\nTesting emoji characters:")
47-
font.load_glyphs(test_emoji)
48-
for c in test_emoji:
49-
glyph = font.get_glyph(ord(c))
50-
if glyph:
51-
print(f" Emoji U+{ord(c):04X}:")
52-
# Print ASCII art representation of the glyph
53-
for y in range(glyph.height):
54-
pixels = []
55-
for x in range(glyph.width):
56-
value = glyph.bitmap[x, y]
57-
pixel = "#" if value > 0 else " "
58-
pixels.append(pixel)
59-
print(" " + "".join(pixels))
60-
else:
61-
print(f" Emoji U+{ord(c):04X} not found in font")
15+
with open(font_file, "rb") as f:
16+
font = lvfontbin.LVGLFont(f)
17+
print("Successfully loaded LVGL font")
18+
print("Font metrics:")
19+
print(f" Ascent: {font.ascent}")
20+
print(f" Descent: {font.descent}")
21+
bbox = font.get_bounding_box()
22+
print(
23+
f" Bounding box: width={bbox[0]}, height={bbox[1]}, x_offset={bbox[2]}, y_offset={bbox[3]}"
24+
)
6225

63-
except FileNotFoundError:
64-
print(f"Font file not found: {font_file}")
65-
except Exception as e:
66-
print(f"Error loading font: {e}")
26+
# Test Japanese characters
27+
test_japanese = "こんにちは世界!" # Hello World in Japanese (according to Claude)
28+
print(f"\nTesting Japanese characters: {test_japanese}")
29+
font.load_glyphs(test_japanese)
30+
for c in test_japanese:
31+
glyph = font.get_glyph(ord(c))
32+
if glyph:
33+
print(
34+
f" Character '{c}' (U+{ord(c):04X}):"
35+
) # Print ASCII art representation of the glyph
36+
for y in range(glyph.height):
37+
pixels = []
38+
for x in range(glyph.width):
39+
value = glyph.bitmap[x, y]
40+
pixel = "#" if value > 0 else " "
41+
pixels.append(pixel)
42+
print(" " + "".join(pixels))
43+
else:
44+
print(f" Character '{c}' (U+{ord(c):04X}) not found in font")
0 Bytes
Binary file not shown.

examples/fonts/unifont-16.0.02-ja.bin

14.3 KB
Binary file not shown.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# SPDX-FileCopyrightText: 2024 GNU Unifont Contributors
2+
#
3+
# SPDX-License-Identifier: OFL-1.1
4+
5+
# Unifont version 16.0.02 is licensed under the SIL Open Font License 1.1 (OFL-1.1).
6+
7+
# Original Unifont converted to LVGL binary format for use with CircuitPython.

0 commit comments

Comments
 (0)