mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-04-10 21:03:01 +00:00
more robust font loading for OTD manager
This commit is contained in:
@@ -37,14 +37,40 @@ class OfTheDayManager:
|
|||||||
self.last_rotation_time = time.time()
|
self.last_rotation_time = time.time()
|
||||||
self.last_category_rotation_time = time.time()
|
self.last_category_rotation_time = time.time()
|
||||||
|
|
||||||
# Load fonts using freetype
|
# Load fonts with robust path resolution and fallbacks
|
||||||
font_dir = os.path.join(os.path.dirname(__file__), '..', 'assets', 'fonts')
|
try:
|
||||||
self.title_font = freetype.Face(os.path.join(font_dir, 'ic8x8u.bdf'))
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
self.body_font = freetype.Face(os.path.join(font_dir, 'MatrixLight6.bdf'))
|
font_dir = os.path.abspath(os.path.join(script_dir, '..', 'assets', 'fonts'))
|
||||||
|
|
||||||
# Log font properties for debugging
|
def _safe_load_bdf_font(filename):
|
||||||
logger.debug(f"Title font properties: height={self.title_font.size.height}, ascender={self.title_font.size.ascender}, descender={self.title_font.size.descender}")
|
try:
|
||||||
logger.debug(f"Body font properties: height={self.body_font.size.height}, ascender={self.body_font.size.ascender}, descender={self.body_font.size.descender}")
|
font_path = os.path.abspath(os.path.join(font_dir, filename))
|
||||||
|
if not os.path.exists(font_path):
|
||||||
|
raise FileNotFoundError(f"Font file not found: {font_path}")
|
||||||
|
return freetype.Face(font_path)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to load BDF font '{filename}': {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
self.title_font = _safe_load_bdf_font('ic8x8u.bdf')
|
||||||
|
self.body_font = _safe_load_bdf_font('MatrixLight6.bdf')
|
||||||
|
|
||||||
|
# Fallbacks if BDF fonts aren't available
|
||||||
|
if self.title_font is None:
|
||||||
|
self.title_font = getattr(self.display_manager, 'bdf_5x7_font', None) or getattr(self.display_manager, 'small_font', ImageFont.load_default())
|
||||||
|
logger.warning("Using fallback font for title in OfTheDayManager")
|
||||||
|
if self.body_font is None:
|
||||||
|
self.body_font = getattr(self.display_manager, 'bdf_5x7_font', None) or getattr(self.display_manager, 'small_font', ImageFont.load_default())
|
||||||
|
logger.warning("Using fallback font for body in OfTheDayManager")
|
||||||
|
|
||||||
|
# Log font types for debugging
|
||||||
|
logger.debug(f"Title font type: {type(self.title_font).__name__}")
|
||||||
|
logger.debug(f"Body font type: {type(self.body_font).__name__}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Unexpected error during font initialization: {e}")
|
||||||
|
# Last-resort fallback
|
||||||
|
self.title_font = ImageFont.load_default()
|
||||||
|
self.body_font = ImageFont.load_default()
|
||||||
|
|
||||||
# Load categories and their data
|
# Load categories and their data
|
||||||
self.categories = self.of_the_day_config.get('categories', {})
|
self.categories = self.of_the_day_config.get('categories', {})
|
||||||
@@ -170,31 +196,34 @@ class OfTheDayManager:
|
|||||||
self.last_update = current_time
|
self.last_update = current_time
|
||||||
|
|
||||||
def _draw_bdf_text(self, draw, face, text, x, y, color=(255,255,255)):
|
def _draw_bdf_text(self, draw, face, text, x, y, color=(255,255,255)):
|
||||||
"""Draw text using a BDF font loaded with freetype."""
|
"""Draw text for both BDF (FreeType Face) and PIL TTF fonts."""
|
||||||
for char in text:
|
try:
|
||||||
# Load the glyph
|
# If we have a PIL font, use native text rendering
|
||||||
face.load_char(char)
|
if not isinstance(face, freetype.Face):
|
||||||
bitmap = face.glyph.bitmap
|
draw.text((x, y), text, fill=color, font=face)
|
||||||
|
return
|
||||||
# Draw the glyph
|
|
||||||
for i in range(bitmap.rows):
|
# Otherwise, render BDF glyphs manually
|
||||||
for j in range(bitmap.width):
|
for char in text:
|
||||||
try:
|
face.load_char(char)
|
||||||
# Get the byte containing the pixel
|
bitmap = face.glyph.bitmap
|
||||||
byte_index = i * bitmap.pitch + (j // 8)
|
|
||||||
if byte_index < len(bitmap.buffer):
|
for i in range(bitmap.rows):
|
||||||
byte = bitmap.buffer[byte_index]
|
for j in range(bitmap.width):
|
||||||
# Check if the specific bit is set
|
try:
|
||||||
if byte & (1 << (7 - (j % 8))):
|
byte_index = i * bitmap.pitch + (j // 8)
|
||||||
draw_y = y - face.glyph.bitmap_top + i
|
if byte_index < len(bitmap.buffer):
|
||||||
draw_x = x + face.glyph.bitmap_left + j
|
byte = bitmap.buffer[byte_index]
|
||||||
draw.point((draw_x, draw_y), fill=color)
|
if byte & (1 << (7 - (j % 8))):
|
||||||
except IndexError:
|
draw_y = y - face.glyph.bitmap_top + i
|
||||||
logger.warning(f"Index out of range for char '{char}' at position ({i}, {j})")
|
draw_x = x + face.glyph.bitmap_left + j
|
||||||
continue
|
draw.point((draw_x, draw_y), fill=color)
|
||||||
|
except IndexError:
|
||||||
# Move to next character position
|
logger.warning(f"Index out of range for char '{char}' at position ({i}, {j})")
|
||||||
x += face.glyph.advance.x >> 6
|
continue
|
||||||
|
x += face.glyph.advance.x >> 6
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in _draw_bdf_text: {e}", exc_info=True)
|
||||||
|
|
||||||
def draw_item(self, category_name, item):
|
def draw_item(self, category_name, item):
|
||||||
try:
|
try:
|
||||||
@@ -207,21 +236,24 @@ class OfTheDayManager:
|
|||||||
title_font = self.title_font
|
title_font = self.title_font
|
||||||
body_font = self.body_font
|
body_font = self.body_font
|
||||||
|
|
||||||
# Get font heights - simplified
|
# Get font heights using DisplayManager helpers (handles BDF and PIL fonts)
|
||||||
title_font.load_char('A')
|
try:
|
||||||
title_height = title_font.glyph.bitmap.rows
|
title_height = self.display_manager.get_font_height(title_font)
|
||||||
body_font.load_char('A')
|
except Exception:
|
||||||
body_height = body_font.glyph.bitmap.rows
|
title_height = 8
|
||||||
|
try:
|
||||||
|
body_height = self.display_manager.get_font_height(body_font)
|
||||||
|
except Exception:
|
||||||
|
body_height = 8
|
||||||
|
|
||||||
# --- Draw Title (always at top) ---
|
# --- Draw Title (always at top) ---
|
||||||
title_y = title_height # Position title so its bottom is at title_height
|
title_y = title_height # Position title so its bottom is at title_height
|
||||||
|
|
||||||
# Calculate title width for centering
|
# Calculate title width for centering (robust to font type)
|
||||||
title_width = 0
|
try:
|
||||||
for c in title:
|
title_width = self.display_manager.get_text_width(title, title_font)
|
||||||
title_font.load_char(c)
|
except Exception:
|
||||||
title_width += title_font.glyph.advance.x
|
title_width = len(title) * 6
|
||||||
title_width = title_width // 64
|
|
||||||
|
|
||||||
# Center the title
|
# Center the title
|
||||||
title_x = (matrix_width - title_width) // 2
|
title_x = (matrix_width - title_width) // 2
|
||||||
@@ -246,11 +278,10 @@ class OfTheDayManager:
|
|||||||
for i, line in enumerate(wrapped):
|
for i, line in enumerate(wrapped):
|
||||||
if line.strip(): # Only draw non-empty lines
|
if line.strip(): # Only draw non-empty lines
|
||||||
# Center each line of body text
|
# Center each line of body text
|
||||||
line_width = 0
|
try:
|
||||||
for c in line:
|
line_width = self.display_manager.get_text_width(line, body_font)
|
||||||
body_font.load_char(c)
|
except Exception:
|
||||||
line_width += body_font.glyph.advance.x
|
line_width = len(line) * 6
|
||||||
line_width = line_width // 64
|
|
||||||
line_x = (matrix_width - line_width) // 2
|
line_x = (matrix_width - line_width) // 2
|
||||||
# Add one pixel buffer between lines
|
# Add one pixel buffer between lines
|
||||||
line_y = y_start + i * (body_height + 1)
|
line_y = y_start + i * (body_height + 1)
|
||||||
@@ -261,11 +292,10 @@ class OfTheDayManager:
|
|||||||
for i, line in enumerate(wrapped):
|
for i, line in enumerate(wrapped):
|
||||||
if line.strip(): # Only draw non-empty lines
|
if line.strip(): # Only draw non-empty lines
|
||||||
# Center each line of body text
|
# Center each line of body text
|
||||||
line_width = 0
|
try:
|
||||||
for c in line:
|
line_width = self.display_manager.get_text_width(line, body_font)
|
||||||
body_font.load_char(c)
|
except Exception:
|
||||||
line_width += body_font.glyph.advance.x
|
line_width = len(line) * 6
|
||||||
line_width = line_width // 64
|
|
||||||
line_x = (matrix_width - line_width) // 2
|
line_x = (matrix_width - line_width) // 2
|
||||||
# Add one pixel buffer between lines
|
# Add one pixel buffer between lines
|
||||||
line_y = y_start + i * (body_height + 1)
|
line_y = y_start + i * (body_height + 1)
|
||||||
@@ -284,11 +314,10 @@ class OfTheDayManager:
|
|||||||
words = text.split()
|
words = text.split()
|
||||||
for word in words:
|
for word in words:
|
||||||
test_line = ' '.join(current_line + [word]) if current_line else word
|
test_line = ' '.join(current_line + [word]) if current_line else word
|
||||||
text_width = 0
|
try:
|
||||||
for c in test_line:
|
text_width = self.display_manager.get_text_width(test_line, face)
|
||||||
face.load_char(c)
|
except Exception:
|
||||||
text_width += face.glyph.advance.x
|
text_width = len(test_line) * 6
|
||||||
text_width = text_width // 64
|
|
||||||
if text_width <= max_width:
|
if text_width <= max_width:
|
||||||
current_line.append(word)
|
current_line.append(word)
|
||||||
else:
|
else:
|
||||||
@@ -298,11 +327,10 @@ class OfTheDayManager:
|
|||||||
else:
|
else:
|
||||||
truncated = word
|
truncated = word
|
||||||
while len(truncated) > 0:
|
while len(truncated) > 0:
|
||||||
test_width = 0
|
try:
|
||||||
for c in (truncated + "..."):
|
test_width = self.display_manager.get_text_width(truncated + "...", face)
|
||||||
face.load_char(c)
|
except Exception:
|
||||||
test_width += face.glyph.advance.x
|
test_width = len(truncated) * 6
|
||||||
test_width = test_width // 64
|
|
||||||
if test_width <= max_width:
|
if test_width <= max_width:
|
||||||
lines.append(truncated + "...")
|
lines.append(truncated + "...")
|
||||||
break
|
break
|
||||||
|
|||||||
Reference in New Issue
Block a user