Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
| """Braille-character canvas for high-resolution terminal graphics. | |
| Each terminal cell maps to a 2x4 dot grid using Unicode braille characters | |
| (U+2800βU+28FF), giving 2Γ horizontal and 4Γ vertical resolution. | |
| """ | |
| # Braille dot positions: (0,0) (1,0) dots 1,4 | |
| # (0,1) (1,1) dots 2,5 | |
| # (0,2) (1,2) dots 3,6 | |
| # (0,3) (1,3) dots 7,8 | |
| _DOT_MAP = ( | |
| (0x01, 0x08), | |
| (0x02, 0x10), | |
| (0x04, 0x20), | |
| (0x40, 0x80), | |
| ) | |
| class BrailleCanvas: | |
| """A pixel canvas that renders to braille characters.""" | |
| def __init__(self, term_width: int, term_height: int): | |
| self.term_width = term_width | |
| self.term_height = term_height | |
| self.pixel_width = term_width * 2 | |
| self.pixel_height = term_height * 4 | |
| self._buf = bytearray(term_width * term_height) | |
| def clear(self) -> None: | |
| for i in range(len(self._buf)): | |
| self._buf[i] = 0 | |
| def set_pixel(self, x: int, y: int) -> None: | |
| if 0 <= x < self.pixel_width and 0 <= y < self.pixel_height: | |
| cx, rx = divmod(x, 2) | |
| cy, ry = divmod(y, 4) | |
| self._buf[cy * self.term_width + cx] |= _DOT_MAP[ry][rx] | |
| def render(self) -> list[str]: | |
| lines = [] | |
| for row in range(self.term_height): | |
| offset = row * self.term_width | |
| line = "".join( | |
| chr(0x2800 + self._buf[offset + col]) | |
| for col in range(self.term_width) | |
| ) | |
| lines.append(line) | |
| return lines | |
| # ββ Bitmap font (5Γ7 uppercase + digits) ββββββββββββββββββββββββββββββ | |
| _FONT: dict[str, list[str]] = {} | |
| def _define_font() -> None: | |
| """Define a simple 5Γ7 bitmap font for uppercase ASCII.""" | |
| glyphs = { | |
| "A": [" ## ", "# #", "# #", "####", "# #", "# #", "# #"], | |
| "B": ["### ", "# #", "# #", "### ", "# #", "# #", "### "], | |
| "C": [" ## ", "# #", "# ", "# ", "# ", "# #", " ## "], | |
| "D": ["### ", "# #", "# #", "# #", "# #", "# #", "### "], | |
| "E": ["####", "# ", "# ", "### ", "# ", "# ", "####"], | |
| "F": ["####", "# ", "# ", "### ", "# ", "# ", "# "], | |
| "G": [" ## ", "# #", "# ", "# ##", "# #", "# #", " ###"], | |
| "H": ["# #", "# #", "# #", "####", "# #", "# #", "# #"], | |
| "I": ["###", " # ", " # ", " # ", " # ", " # ", "###"], | |
| "J": [" ##", " # ", " # ", " # ", " # ", "# # ", " # "], | |
| "K": ["# #", "# # ", "## ", "## ", "# # ", "# #", "# #"], | |
| "L": ["# ", "# ", "# ", "# ", "# ", "# ", "####"], | |
| "M": ["# #", "## ##", "# # #", "# # #", "# #", "# #", "# #"], | |
| "N": ["# #", "## #", "## #", "# ##", "# ##", "# #", "# #"], | |
| "O": [" ## ", "# #", "# #", "# #", "# #", "# #", " ## "], | |
| "P": ["### ", "# #", "# #", "### ", "# ", "# ", "# "], | |
| "Q": [" ## ", "# #", "# #", "# #", "# ##", "# #", " ## "], | |
| "R": ["### ", "# #", "# #", "### ", "# # ", "# #", "# #"], | |
| "S": [" ## ", "# #", "# ", " ## ", " #", "# #", " ## "], | |
| "T": ["#####", " # ", " # ", " # ", " # ", " # ", " # "], | |
| "U": ["# #", "# #", "# #", "# #", "# #", "# #", " ## "], | |
| "V": ["# #", "# #", "# #", " # # ", " # # ", " # ", " # "], | |
| "W": ["# #", "# #", "# #", "# # #", "# # #", "## ##", "# #"], | |
| "X": ["# #", "# #", " ## ", " ## ", " ## ", "# #", "# #"], | |
| "Y": ["# #", "# #", " # # ", " # ", " # ", " # ", " # "], | |
| "Z": ["####", " #", " # ", " # ", "# ", "# ", "####"], | |
| " ": [" ", " ", " ", " ", " ", " ", " "], | |
| "0": [" ## ", "# #", "# #", "# #", "# #", "# #", " ## "], | |
| "1": [" # ", "## ", " # ", " # ", " # ", " # ", "###"], | |
| "2": [" ## ", "# #", " #", " # ", " # ", "# ", "####"], | |
| "3": [" ## ", "# #", " #", " ## ", " #", "# #", " ## "], | |
| "4": ["# #", "# #", "# #", "####", " #", " #", " #"], | |
| "5": ["####", "# ", "### ", " #", " #", "# #", " ## "], | |
| "6": [" ## ", "# ", "### ", "# #", "# #", "# #", " ## "], | |
| "7": ["####", " #", " # ", " # ", " # ", " # ", " # "], | |
| "8": [" ## ", "# #", "# #", " ## ", "# #", "# #", " ## "], | |
| "9": [" ## ", "# #", "# #", " ###", " #", " #", " ## "], | |
| } | |
| _FONT.update(glyphs) | |
| _define_font() | |
| def text_to_pixels(text: str, scale: int = 1) -> list[tuple[int, int]]: | |
| """Convert text string to a list of (x, y) pixel positions using bitmap font.""" | |
| pixels = [] | |
| cursor_x = 0 | |
| for ch in text.upper(): | |
| glyph = _FONT.get(ch) | |
| if glyph is None: | |
| cursor_x += 4 * scale | |
| continue | |
| for row_idx, row in enumerate(glyph): | |
| for col_idx, cell in enumerate(row): | |
| if cell == "#": | |
| for sy in range(scale): | |
| for sx in range(scale): | |
| pixels.append((cursor_x + col_idx * scale + sx, | |
| row_idx * scale + sy)) | |
| glyph_width = max(len(r) for r in glyph) | |
| cursor_x += (glyph_width + 1) * scale | |
| return pixels | |