mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-04-10 13:02:59 +00:00
switching to TTF font
This commit is contained in:
BIN
assets/fonts/dot_digital-7.ttf
Normal file
BIN
assets/fonts/dot_digital-7.ttf
Normal file
Binary file not shown.
@@ -8,7 +8,6 @@ from rgbmatrix import graphics
|
|||||||
import pytz
|
import pytz
|
||||||
from src.config_manager import ConfigManager
|
from src.config_manager import ConfigManager
|
||||||
import time
|
import time
|
||||||
import freetype
|
|
||||||
|
|
||||||
# Configure logger for this module
|
# Configure logger for this module
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -37,10 +36,10 @@ 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 using PIL
|
||||||
font_dir = os.path.join(os.path.dirname(__file__), '..', 'assets', 'fonts')
|
font_dir = os.path.join(os.path.dirname(__file__), '..', 'assets', 'fonts')
|
||||||
self.title_face = freetype.Face(os.path.join(font_dir, 'ic8x8u.bdf'))
|
self.title_font = ImageFont.truetype(os.path.join(font_dir, '5by7.regular.ttf'), 7)
|
||||||
self.body_face = freetype.Face(os.path.join(font_dir, 'cozette.bdf'))
|
self.body_font = ImageFont.truetype(os.path.join(font_dir, 'dot_digital-7.ttf'), 7)
|
||||||
|
|
||||||
# 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', {})
|
||||||
@@ -165,25 +164,6 @@ class OfTheDayManager:
|
|||||||
logger.debug("OfTheDayManager update interval reached")
|
logger.debug("OfTheDayManager update interval reached")
|
||||||
self.last_update = current_time
|
self.last_update = current_time
|
||||||
|
|
||||||
def _draw_bdf_text(self, draw, face, text, x, y, color=(255,255,255)):
|
|
||||||
"""Draw text using a BDF font loaded with freetype."""
|
|
||||||
orig_x = x
|
|
||||||
# Calculate baseline offset for proper text alignment
|
|
||||||
baseline_offset = face.size.height - face.size.ascender
|
|
||||||
for char in text:
|
|
||||||
face.load_char(char)
|
|
||||||
bitmap = face.glyph.bitmap
|
|
||||||
# Position text using baseline alignment
|
|
||||||
for i in range(bitmap.rows):
|
|
||||||
for j in range(bitmap.width):
|
|
||||||
byte_index = i * bitmap.pitch + (j // 8)
|
|
||||||
if byte_index < len(bitmap.buffer):
|
|
||||||
byte = bitmap.buffer[byte_index]
|
|
||||||
if byte & (1 << (7 - (j % 8))):
|
|
||||||
draw.point((x + j, y + baseline_offset + i), fill=color)
|
|
||||||
x += face.glyph.advance.x >> 6
|
|
||||||
return x - orig_x
|
|
||||||
|
|
||||||
def draw_item(self, category_name, item):
|
def draw_item(self, category_name, item):
|
||||||
try:
|
try:
|
||||||
title = item.get('title', 'No Title')
|
title = item.get('title', 'No Title')
|
||||||
@@ -192,60 +172,49 @@ class OfTheDayManager:
|
|||||||
draw = ImageDraw.Draw(self.display_manager.image)
|
draw = ImageDraw.Draw(self.display_manager.image)
|
||||||
matrix_width = self.display_manager.matrix.width
|
matrix_width = self.display_manager.matrix.width
|
||||||
matrix_height = self.display_manager.matrix.height
|
matrix_height = self.display_manager.matrix.height
|
||||||
title_face = self.title_face
|
title_font = self.title_font
|
||||||
body_face = self.body_face
|
body_font = self.body_font
|
||||||
|
|
||||||
# Get font heights and set consistent positioning
|
# Get font heights
|
||||||
title_height = title_face.height
|
title_height = title_font.getsize('A')[1]
|
||||||
body_height = body_face.height
|
body_height = body_font.getsize('A')[1]
|
||||||
|
|
||||||
# --- Draw Title (always at top, ic8x8u.bdf) ---
|
# --- Draw Title (always at top, 5by7.regular.ttf) ---
|
||||||
title_y = 0 # Start at top
|
title_y = 0 # Start at top
|
||||||
self._draw_bdf_text(draw, title_face, title, 1, title_y, color=self.title_color)
|
draw.text((1, title_y), title, font=title_font, fill=self.title_color)
|
||||||
|
|
||||||
# Calculate title width and actual height for proper spacing
|
# Calculate title width for underline
|
||||||
title_width = 0
|
title_width = title_font.getsize(title)[0]
|
||||||
max_title_height = 0
|
|
||||||
baseline_offset = title_face.size.height - title_face.size.ascender
|
|
||||||
for c in title:
|
|
||||||
title_face.load_char(c)
|
|
||||||
title_width += title_face.glyph.advance.x
|
|
||||||
# Track the maximum height including baseline offset
|
|
||||||
bitmap = title_face.glyph.bitmap
|
|
||||||
actual_height = baseline_offset + bitmap.rows
|
|
||||||
if actual_height > max_title_height:
|
|
||||||
max_title_height = actual_height
|
|
||||||
title_width = title_width // 64
|
|
||||||
|
|
||||||
# Underline below title using actual title height
|
# Underline below title
|
||||||
underline_y = max_title_height + 1 # Just below the actual title
|
underline_y = title_height + 1 # Just below the title
|
||||||
draw.line([(1, underline_y), (1 + title_width, underline_y)], fill=self.title_color, width=1)
|
draw.line([(1, underline_y), (1 + title_width, underline_y)], fill=self.title_color, width=1)
|
||||||
|
|
||||||
# --- Draw Subtitle or Description (rotating, cozette.bdf) ---
|
# --- Draw Subtitle or Description (rotating, dot_digital-7.ttf) ---
|
||||||
# Start subtitle/description below the title with proper spacing
|
# Start subtitle/description below the title with proper spacing
|
||||||
y_start = max_title_height + 3 # Leave space between title and subtitle
|
y_start = title_height + 3 # Leave space between title and subtitle
|
||||||
available_height = matrix_height - y_start
|
available_height = matrix_height - y_start
|
||||||
available_width = matrix_width - 2
|
available_width = matrix_width - 2
|
||||||
|
|
||||||
if self.rotation_state == 0 and subtitle:
|
if self.rotation_state == 0 and subtitle:
|
||||||
# Show subtitle
|
# Show subtitle
|
||||||
wrapped = self._wrap_text(subtitle, available_width, body_face, max_lines=3, line_height=body_height, max_height=available_height)
|
wrapped = self._wrap_text(subtitle, available_width, body_font, max_lines=3, line_height=body_height, max_height=available_height)
|
||||||
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
|
||||||
self._draw_bdf_text(draw, body_face, line, 1, y_start + i * body_height, color=self.subtitle_color)
|
draw.text((1, y_start + i * body_height), line, font=body_font, fill=self.subtitle_color)
|
||||||
elif self.rotation_state == 1 and description:
|
elif self.rotation_state == 1 and description:
|
||||||
# Show description
|
# Show description
|
||||||
wrapped = self._wrap_text(description, available_width, body_face, max_lines=3, line_height=body_height, max_height=available_height)
|
wrapped = self._wrap_text(description, available_width, body_font, max_lines=3, line_height=body_height, max_height=available_height)
|
||||||
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
|
||||||
self._draw_bdf_text(draw, body_face, line, 1, y_start + i * body_height, color=self.subtitle_color)
|
draw.text((1, y_start + i * body_height), line, font=body_font, fill=self.subtitle_color)
|
||||||
# else: nothing to show
|
# else: nothing to show
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error drawing 'of the day' item: {e}", exc_info=True)
|
logger.error(f"Error drawing 'of the day' item: {e}", exc_info=True)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _wrap_text(self, text, max_width, face, max_lines=3, line_height=8, max_height=24):
|
def _wrap_text(self, text, max_width, font, max_lines=3, line_height=8, max_height=24):
|
||||||
if not text:
|
if not text:
|
||||||
return [""]
|
return [""]
|
||||||
lines = []
|
lines = []
|
||||||
@@ -253,11 +222,7 @@ 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
|
text_width = font.getsize(test_line)[0]
|
||||||
for c in test_line:
|
|
||||||
face.load_char(c)
|
|
||||||
text_width += face.glyph.advance.x
|
|
||||||
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:
|
||||||
@@ -267,11 +232,7 @@ class OfTheDayManager:
|
|||||||
else:
|
else:
|
||||||
truncated = word
|
truncated = word
|
||||||
while len(truncated) > 0:
|
while len(truncated) > 0:
|
||||||
test_width = 0
|
test_width = font.getsize(truncated + "...")[0]
|
||||||
for c in (truncated + "..."):
|
|
||||||
face.load_char(c)
|
|
||||||
test_width += face.glyph.advance.x
|
|
||||||
test_width = test_width // 64
|
|
||||||
if test_width <= max_width:
|
if test_width <= max_width:
|
||||||
lines.append(truncated + "...")
|
lines.append(truncated + "...")
|
||||||
break
|
break
|
||||||
@@ -285,7 +246,7 @@ class OfTheDayManager:
|
|||||||
while len(lines) < max_lines:
|
while len(lines) < max_lines:
|
||||||
lines.append("")
|
lines.append("")
|
||||||
return lines[:max_lines]
|
return lines[:max_lines]
|
||||||
|
|
||||||
def display(self, force_clear=False):
|
def display(self, force_clear=False):
|
||||||
if not self.enabled:
|
if not self.enabled:
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user