Strip emoji from calendar event titles before rendering
Build and push server image / build-and-push (push) Successful in 49s
Build and push server image / build-and-push (push) Successful in 49s
ImageFont.load_default() has no emoji glyphs, and PIL/FreeType don't skip an unsupported codepoint -- they substitute a visible ".notdef" tofu box, which read as a rendering glitch rather than "not supported." Stripped instead (covers the standard emoji Unicode blocks, skin-tone modifiers, and the ZWJ used to combine them into one glyph), with surrounding whitespace collapsed.
This commit is contained in:
@@ -11,6 +11,7 @@ from __future__ import annotations
|
||||
|
||||
import calendar as calendar_module
|
||||
import io
|
||||
import re
|
||||
from datetime import date, datetime, timedelta
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
@@ -102,6 +103,40 @@ def _fmt_time(dt: datetime) -> str:
|
||||
return text if text else "12:00 AM"
|
||||
|
||||
|
||||
# ImageFont.load_default() (used everywhere in this module -- see the
|
||||
# module docstring) has no emoji glyphs. PIL/FreeType don't just skip an
|
||||
# unsupported codepoint, they substitute a ".notdef" tofu box (a visible
|
||||
# filled rectangle), which reads as a rendering glitch rather than "emoji
|
||||
# not supported" on an actual panel. Stripped before drawing rather than
|
||||
# left to render as tofu. Covers the standard Unicode emoji blocks (plus
|
||||
# skin-tone modifiers, the zero-width joiner used to combine them into
|
||||
# one glyph, and the variation selector that forces emoji presentation)
|
||||
# -- these block ranges are stable even as new individual emoji get
|
||||
# added within them, so this doesn't need updating as emoji sets grow.
|
||||
_EMOJI_PATTERN = re.compile(
|
||||
"["
|
||||
"\U0001F1E6-\U0001F1FF" # regional indicator symbols (flag emoji)
|
||||
"\U0001F300-\U0001F5FF" # misc symbols & pictographs
|
||||
"\U0001F600-\U0001F64F" # emoticons
|
||||
"\U0001F680-\U0001F6FF" # transport & map symbols
|
||||
"\U0001F900-\U0001F9FF" # supplemental symbols & pictographs
|
||||
"\U0001FA70-\U0001FAFF" # symbols & pictographs extended-A
|
||||
"\U0001F3FB-\U0001F3FF" # skin-tone modifiers
|
||||
"\U00002600-\U000026FF" # misc symbols (☀☂☕ etc.)
|
||||
"\U00002700-\U000027BF" # dingbats (✂✈✉ etc.)
|
||||
"\U0000FE0F" # variation selector-16 (emoji presentation)
|
||||
"\U0000200D" # zero-width joiner
|
||||
"]+"
|
||||
)
|
||||
|
||||
|
||||
def _strip_emoji(text: str) -> str:
|
||||
"""Removes emoji (see _EMOJI_PATTERN above) and collapses whatever
|
||||
whitespace that leaves behind -- "\U0001F389 Birthday party" becomes
|
||||
"Birthday party", not " Birthday party"."""
|
||||
return re.sub(r"\s+", " ", _EMOJI_PATTERN.sub("", text)).strip()
|
||||
|
||||
|
||||
def _truncate_to_width(draw: ImageDraw.ImageDraw, text: str, font: ImageFont.ImageFont, max_width: int) -> str:
|
||||
"""Pixel-width-aware truncation (unlike device.py's char-count
|
||||
_truncate, tuned for a fixed firmware font at a fixed size) -- this
|
||||
@@ -313,7 +348,7 @@ def _draw_agenda_day(img: Image.Image, draw: ImageDraw.ImageDraw, day: date, eve
|
||||
color = _event_color(event, owners_seen, palette_rgb)
|
||||
draw.rounded_rectangle([text_x0, y + 2, text_x0 + 10, y + row_h - 7], radius=3, fill=color)
|
||||
time_str = "All day" if event["all_day"] else _fmt_time(_event_start(event, tz))
|
||||
line = f"{time_str} {event['summary']}"
|
||||
line = f"{time_str} {_strip_emoji(event['summary'])}"
|
||||
draw_text(img, (text_x0 + 18, y), _truncate_to_width(draw, line, body_font, text_w - 18), body_font)
|
||||
y += row_h
|
||||
|
||||
@@ -428,7 +463,8 @@ def _build_week(events: list[dict], browse_offset: int, orientation: str, tz: Zo
|
||||
break
|
||||
color = _event_color(event, owners_seen, palette_rgb)
|
||||
draw.rounded_rectangle([x0 + 4, y + 1, x0 + 11, y + row_h - 5], radius=2, fill=color)
|
||||
text = event["summary"] if event["all_day"] else f"{_fmt_time(_event_start(event, tz))[:-3]} {event['summary']}"
|
||||
summary = _strip_emoji(event["summary"])
|
||||
text = summary if event["all_day"] else f"{_fmt_time(_event_start(event, tz))[:-3]} {summary}"
|
||||
draw_text(img, (x0 + 16, y), _truncate_to_width(draw, text, chip_font, col_w - 20), chip_font)
|
||||
y += row_h
|
||||
|
||||
|
||||
Reference in New Issue
Block a user