-
Notifications
You must be signed in to change notification settings - Fork 144
Add native PPF pixel font rendering to simulator #92
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -11,6 +11,7 @@ | |||||
| import json | ||||||
| import math | ||||||
| import os | ||||||
| import struct | ||||||
| import sys | ||||||
| import traceback | ||||||
| from types import ModuleType | ||||||
|
|
@@ -587,6 +588,64 @@ def xor(r, g=None, b=None, a=255) -> tuple: | |||||
| return brushes.color(r, g, b, a) | ||||||
|
|
||||||
|
|
||||||
| class _PPFFont: | ||||||
| """Native PPF pixel font renderer.""" | ||||||
| def __init__(self, path): | ||||||
| with open(path, "rb") as f: | ||||||
| self._data = f.read() | ||||||
| self._glyph_count = struct.unpack_from(">I", self._data, 6)[0] | ||||||
| self._bbox_w = struct.unpack_from(">H", self._data, 10)[0] | ||||||
| self._bbox_h = struct.unpack_from(">H", self._data, 12)[0] | ||||||
| self._row_bytes = (self._bbox_w + 7) // 8 | ||||||
| self._glyph_data_size = self._row_bytes * self._bbox_h | ||||||
| self._table_start = 46 | ||||||
| self._bitmap_start = self._table_start + self._glyph_count * 6 | ||||||
| self._glyphs = {} | ||||||
| for i in range(self._glyph_count): | ||||||
| off = self._table_start + i * 6 | ||||||
| cp = struct.unpack_from(">I", self._data, off)[0] | ||||||
| w = struct.unpack_from(">H", self._data, off + 4)[0] | ||||||
| self._glyphs[cp] = (i, w) | ||||||
|
|
||||||
| def get_height(self): | ||||||
| return self._bbox_h | ||||||
|
|
||||||
| def size(self, text): | ||||||
| text = str(text) | ||||||
| w = 0 | ||||||
| space_w = self._bbox_w // 3 | ||||||
| for ch in text: | ||||||
| cp = ord(ch) | ||||||
| if cp == 32: | ||||||
| w += space_w + 1 | ||||||
| elif cp in self._glyphs: | ||||||
| w += self._glyphs[cp][1] + 1 | ||||||
| return (max(0, w - 1), self._bbox_h) | ||||||
|
Comment on lines
+615
to
+623
|
||||||
|
|
||||||
| def render(self, text, antialias, color, *args, **kwargs): | ||||||
| text = str(text) | ||||||
| tw, th = self.size(text) | ||||||
| surf = pygame.Surface((max(1, tw), th), pygame.SRCALPHA) | ||||||
|
||||||
| surf = pygame.Surface((max(1, tw), th), pygame.SRCALPHA) | |
| surf = pygame.Surface((max(1, tw), max(1, th)), pygame.SRCALPHA) |
Copilot
AI
Mar 31, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For characters not present in the glyph table, both size() and render() currently skip without advancing x/w. That causes subsequent glyphs to overlap and size() to under-report width for strings containing unknown codepoints. Consider advancing by a fallback width (e.g., space width or bbox_w) and/or rendering a replacement glyph (like '?') to preserve layout.
Copilot
AI
Mar 31, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_PPFFont.render() uses nested loops with surf.set_at() per pixel; in pygame this is very slow and can become a bottleneck when drawing lots of text each frame. At minimum, lock the surface during pixel writes (surf.lock()/unlock()), or switch to a faster bulk approach (PixelArray/surfarray) to keep simulator FPS stable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_PPFFont.init parses fixed offsets (glyph_count/bbox/table_start) without validating the file signature or ensuring the computed table/bitmap ranges are within the file length. If a .ppf is truncated/corrupted, it may initialize successfully but later crash in render() with IndexError. Consider validating the magic (e.g., b"ppf!") and bounds-checking (len(_data) >= bitmap_start + glyph_count * glyph_data_size) and raising a clear exception so PixelFont.load() can safely fall back.