Skip to content

Commit

Permalink
feat: Add basic text handling.
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewjw committed Dec 13, 2023
1 parent 6afeafc commit da99539
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 1 deletion.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ htmldocs/
build/
dist/
i75.egg-info/
i75/fontdata/*.py
6 changes: 6 additions & 0 deletions docs/text.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
==============
Text Rendering
==============

.. automodule:: i75.text
:members:
39 changes: 39 additions & 0 deletions examples/text/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/usr/bin/env micropython
# i75
# Copyright (C) 2023 Andrew Wilkinson
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import picographics

from i75 import I75, text


def main() -> None:
i75 = I75(
display_type=picographics.DISPLAY_INTERSTATE75_64X64,
rotate=0 if I75.is_emulated() else 90)

white = i75.display.create_pen(255, 255, 255)
i75.display.set_pen(white)

text(i75.display, "cg_pixel_3x5_5", 0, 0, "The Quick Brown Fox")

i75.display.update()

i75.sleep_ms(10000)


if __name__ == "__main__":
main()
1 change: 1 addition & 0 deletions i75/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@

from .colour import Colour # noqa
from .datetime import DateTime # noqa
from .text import text # noqa
52 changes: 52 additions & 0 deletions i75/text.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/usr/bin/env python3
# i75
# Copyright (C) 2023 Andrew Wilkinson
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

try:
from typing import Any, Dict
except ImportError:
pass

from .graphics import Graphics


def text(buffer: Graphics, font: str, x: int, y: int, text: str):
"""
Render the given text, at location (x,y) using font onto the scren buffer.
"""
globals: Dict[str, Any] = {}
locals: Dict[str, Any] = {}
exec(f"import i75.fontdata.{font}", globals, locals)
font_data = getattr(locals["i75"].fontdata, font)

for c in text:
if c == " ":
x += font_data.SPACE_WIDTH
continue
if c in font_data.DATA:
width, data = font_data.DATA[c]
else:
width, data = font_data.DATA["#"]

for cy in range(y, y+font_data.HEIGHT):
row = data[cy-y]
if row == b'\0':
continue
for cx in range(x, x+width):
if row & 1 == 1:
buffer.pixel(cx, cy)
row = row >> 1
x += width + 1
3 changes: 3 additions & 0 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@

ampy ${@:1} mkdir i75 --exists-okay
ampy ${@:1} mkdir i75/tz --exists-okay
ampy ${@:1} mkdir i75/fontdata --exists-okay
ampy ${@:1} put i75/__init__.py i75/__init__.py
ampy ${@:1} put i75/basei75.py i75/basei75.py
ampy ${@:1} put i75/colour.py i75/colour.py
ampy ${@:1} put i75/datetime.py i75/datetime.py
ampy ${@:1} put i75/graphics.py i75/graphics.py
ampy ${@:1} put i75/nativei75.py i75/nativei75.py
ampy ${@:1} put i75/text.py i75/text.py
ampy ${@:1} put i75/tz/__init__.py i75/tz/__init__.py
ampy ${@:1} put i75/tz/europe_london.py i75/tz/europe_london.py
ampy ${@:1} put i75/fontdata/Pixel_Millennium_8.py i75/fontdata/Pixel_Millennium_8.py
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
long_description_content_type="text/markdown",
url="https://github.com/andrewjw/i75",
scripts=["bin/i75"],
packages=setuptools.find_packages() + ["i75.emulated", "i75.tz"],
packages=setuptools.find_packages() + ["i75.emulated", "i75.tz", "i75.fontdata"],
include_package_data=True,
classifiers=[
"Programming Language :: Python :: 3.8",
Expand Down
88 changes: 88 additions & 0 deletions utils/generate_fonts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#!/usr/bin/env python3
# i75
# Copyright (C) 2023 Andrew Wilkinson
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import os.path
import json
import math
from typing import Tuple
import string
import sys

from PIL import Image, ImageDraw, ImageFont

character_set = string.digits + string.ascii_letters + string.punctuation

def get_max_size(font: ImageFont.FreeTypeFont) -> Tuple[int, int]:
width, height = 0, 0
for c in character_set:
cwidth, cheight = get_size(font, c)
width = max(width, cwidth)
height = max(height, cheight)
return width, height

def get_size(font: ImageFont.FreeTypeFont, c: str) -> Tuple[int, int]:
ascent, descent = font.getmetrics()
left, top, right, bottom = font.getmask(c, stroke_width=0).getbbox()
return right, bottom + max(0, ascent + descent)


def get_char_data(font: ImageFont.FreeTypeFont, height: int, c: str) -> bytes:
width, _ = get_size(font, c)
im = Image.new(mode="1", size=(width, height))
draw = ImageDraw.Draw(im)
draw.text((0, 0), c, font=font, fill=1, stroke_width=0)

data = bytes()
for y in range(height):
b = 0
for x in range(width):
b = b | im.getpixel((x, y)) << x
data += b.to_bytes(1, "little")

return width, data


def generate_font_data(fontfile: str, size: int) -> None:
fontname = os.path.basename(fontfile).split(".")[0].replace("-", "_").replace(" ", "_")
font = ImageFont.truetype(font=fontfile, size=size)

max_width, max_height = get_max_size(font)

font_data = {}
for c in character_set:
font_data[c] = repr(get_char_data(font, max_height, c))

try:
os.mkdir("i75/fontdata")
except FileExistsError:
pass
with open(f"i75/fontdata/{fontname}_{size}.py", "w") as fp:
fp.write(f"# This font data was generated from {fontfile} with size {size}.\n")

fp.write(f"HEIGHT = {max_height} \n")
fp.write(f"SPACE_WIDTH = {max_width} \n")
fp.write("DATA = {\n")
for c in font_data.keys():
fp.write(f" {repr(c)}: {font_data[c]},\n")
fp.write("}\n")

def main() -> None:
generate_font_data("data/cg-pixel-3x5/cg-pixel-3x5.ttf", 5)


if __name__ == "__main__":
main()

0 comments on commit da99539

Please sign in to comment.