Full font

The conversion process was so nasty & ad hoc that I'm committing the
generated font file.
This commit is contained in:
Jeff Epler 2024-07-24 18:14:09 -05:00
parent a4c72dbc9a
commit 3554999f0a
11 changed files with 3930 additions and 22 deletions

1
.gitignore vendored
View file

@ -2,3 +2,4 @@ chargen
chargen.s
chargen.png
build
__pycache__

1
5x9.h Normal file

File diff suppressed because one or more lines are too long

View file

@ -146,26 +146,7 @@ fprintf(stderr, "\n");
[i+8*256] = CSWIZZLE(r9) \
uint16_t chargen[256*CHAR_Y] = {
CHAR('A',
0b00000,
0b01100,
0b10010,
0b10010,
0b11110,
0b10010,
0b10010,
0b10010,
0b00000),
CHAR('B',
0b00000,
0b11100,
0b10010,
0b10010,
0b11100,
0b10010,
0b10010,
0b11100,
0b00000),
#include "5x9.h"
};
int cx, cy, attr = 0x300;
@ -356,10 +337,19 @@ int main() {
#endif
if(0)
for (size_t i=0; i<count_of(chardata32); i++) { chardata32[i] = 0x08000800; }
attr = 0x300;
scrnprintf(
"An overclock to 140MHz is also very stable if you wanted 720 pixels Some classic text modes could\r\n"
"double the rightmost pixel of the 8-bit-wide font (typically, if the high bit of the character\r\n"
"number was set) instead of outputting 0, so you could get block graphics but they would be\r\n"
"slightly distorted worst with the \"shade\" characters but fine with almost everything else\n\r\n\r\n");
for (int x = 0; x < 0x4000; x += 0x100) {
attr = x;
scrnprintf("AA BB AB BA ABBA ", FB_WIDTH_CHAR, FB_HEIGHT_CHAR, CHAR_X, CHAR_Y);
scrnprintf("AA BB AB BA ABBA ", FB_WIDTH_CHAR, FB_HEIGHT_CHAR, CHAR_X, CHAR_Y);
scrnprintf("01234567890 wasd il -uwu_", FB_WIDTH_CHAR, FB_HEIGHT_CHAR, CHAR_X, CHAR_Y);
scrnprintf("AA BB AB BA gqgq ", FB_WIDTH_CHAR, FB_HEIGHT_CHAR, CHAR_X, CHAR_Y);
}
#if STANDALONE

2987
mkfont/5x9.bdf Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,39 @@
from dataclasses import dataclass
class Bitmap:
def __init__(self, width, height, color_count):
self.width = width
self.height = height
if color_count > 255:
raise ValueError("Cannot support that many colors")
self.values = bytearray(width * height)
def __setitem__(self, index, value):
if isinstance(index, tuple):
index = index[0] + index[1] * self.width
self.values[index] = value
def __getitem__(self, index):
if isinstance(index, tuple):
print(index[0], index[1], self.width, len(self))
index = index[0] + index[1] * self.width
print(index)
return self.values[index]
def __len__(self):
return self.width * self.height
def __repr__(self):
return f"<Bitmap {self.width}x{self.height}>"
@dataclass
class Glyph:
bitmap: Bitmap = None
unknown: int = 0
width: int = 0
height: int = 0
dx: int = 0
dy: int = 0
shift_x: int = 0
shift_y: int = 0

View file

@ -0,0 +1,233 @@
# SPDX-FileCopyrightText: 2019 Scott Shawcroft for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""
`adafruit_bitmap_font.bdf`
====================================================
Loads BDF format fonts.
* Author(s): Scott Shawcroft
Implementation Notes
--------------------
**Hardware:**
**Software and Dependencies:**
* Adafruit CircuitPython firmware for the supported boards:
https://github.com/adafruit/circuitpython/releases
"""
try:
from typing import Union, Optional, Tuple, Iterable
from io import FileIO
except ImportError:
pass
from . import Glyph, Bitmap
import gc
from .glyph_cache import GlyphCache
__version__ = "0.0.0+auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Bitmap_Font.git"
class BDF(GlyphCache):
"""Loads glyphs from a BDF file in the given bitmap_class."""
def __init__(self, f: FileIO, bitmap_class: Bitmap) -> None:
super().__init__()
self.file = f
self.name = f
self.file.seek(0)
self.bitmap_class = bitmap_class
line = self._readline_file()
if not line or not line.startswith("STARTFONT 2.1"):
raise ValueError("Unsupported file version")
self._verify_bounding_box()
self.point_size = None
self.x_resolution = None
self.y_resolution = None
self._ascent = None
self._descent = None
@property
def descent(self) -> Optional[int]:
"""The number of pixels below the baseline of a typical descender"""
if self._descent is None:
self.file.seek(0)
while True:
line = self.file.readline()
if not line:
break
if line.startswith(b"FONT_DESCENT "):
self._descent = int(line.split()[1])
break
return self._descent
@property
def ascent(self) -> Optional[int]:
"""The number of pixels above the baseline of a typical ascender"""
if self._ascent is None:
self.file.seek(0)
while True:
line = self._readline_file()
if not line:
break
if line.startswith("FONT_ASCENT "):
self._ascent = int(line.split()[1])
break
return self._ascent
def _verify_bounding_box(self) -> None:
"""Private function to verify FOUNTBOUNDINGBOX parameter
This function will parse the first 10 lines of the font source
file to verify the value or raise an exception in case is not found
"""
self.file.seek(0)
# Normally information about the FONT is in the first four lines.
# Exception is when font file have a comment. Comments are three lines
# 10 lines is a safe bet
for _ in range(11):
line = self._readline_file()
while line.startswith("COMMENT "):
line = self._readline_file()
if line.startswith("FONTBOUNDINGBOX "):
_, x, y, x_offset, y_offset = line.split()
self._boundingbox = (int(x), int(y), int(x_offset), int(y_offset))
try:
self._boundingbox
except AttributeError as error:
raise RuntimeError(
"Source file does not have the FOUNTBOUNDINGBOX parameter"
) from error
def _readline_file(self) -> str:
line = self.file.readline()
return str(line, "utf-8")
def get_bounding_box(self) -> Tuple[int, int, int, int]:
"""Return the maximum glyph size as a 4-tuple of: width, height, x_offset, y_offset"""
return self._boundingbox
def load_glyphs(self, code_points: Union[int, str, Iterable[int]]) -> None:
# pylint: disable=too-many-statements,too-many-branches,too-many-nested-blocks,too-many-locals
metadata = True
character = False
code_point = None
bytes_per_row = 1
desired_character = False
current_info = {}
current_y = 0
rounded_x = 1
if isinstance(code_points, int):
remaining = set()
remaining.add(code_points)
elif isinstance(code_points, str):
remaining = set(ord(c) for c in code_points)
elif isinstance(code_points, set):
remaining = code_points
else:
remaining = set(code_points)
for code_point in remaining.copy():
if code_point in self._glyphs and self._glyphs[code_point]:
remaining.remove(code_point)
if not remaining:
return
x, _, _, _ = self._boundingbox
self.file.seek(0)
while True:
line = self.file.readline()
if not line:
break
if line.startswith(b"CHARS "):
metadata = False
elif line.startswith(b"SIZE"):
_, self.point_size, self.x_resolution, self.y_resolution = line.split()
elif line.startswith(b"COMMENT"):
pass
elif line.startswith(b"STARTCHAR"):
character = True
elif line.startswith(b"ENDCHAR"):
character = False
if desired_character:
bounds = current_info["bounds"]
shift = current_info["shift"]
gc.collect()
self._glyphs[code_point] = Glyph(
current_info["bitmap"],
0,
bounds[0],
bounds[1],
bounds[2],
bounds[3],
shift[0],
shift[1],
)
remaining.remove(code_point)
if not remaining:
return
desired_character = False
elif line.startswith(b"BBX"):
if desired_character:
_, x, y, x_offset, y_offset = line.split()
x = int(x)
y = int(y)
x_offset = int(x_offset)
y_offset = int(y_offset)
current_info["bounds"] = (x, y, x_offset, y_offset)
current_info["bitmap"] = self.bitmap_class(x, y, 2)
elif line.startswith(b"BITMAP"):
if desired_character:
rounded_x = x // 8
if x % 8 > 0:
rounded_x += 1
bytes_per_row = rounded_x
if bytes_per_row % 4 > 0:
bytes_per_row += 4 - bytes_per_row % 4
current_y = 0
elif line.startswith(b"ENCODING"):
_, code_point = line.split()
code_point = int(code_point)
if code_point in remaining:
desired_character = True
current_info = {"bitmap": None, "bounds": None, "shift": None}
elif line.startswith(b"DWIDTH"):
if desired_character:
_, shift_x, shift_y = line.split()
shift_x = int(shift_x)
shift_y = int(shift_y)
current_info["shift"] = (shift_x, shift_y)
elif line.startswith(b"SWIDTH"):
pass
elif character:
if desired_character:
bits = int(line.strip(), 16)
width = current_info["bounds"][0]
start = current_y * width
x = 0
for i in range(rounded_x):
val = (bits >> ((rounded_x - i - 1) * 8)) & 0xFF
for j in range(7, -1, -1):
if x >= width:
break
bit = 0
if val & (1 << j) != 0:
bit = 1
current_info["bitmap"][start + x] = bit
x += 1
current_y += 1
elif metadata:
pass

View file

@ -0,0 +1,64 @@
# SPDX-FileCopyrightText: 2019 Scott Shawcroft for Adafruit Industries
#
# SPDX-License-Identifier: MIT
from __future__ import annotations
"""
`adafruit_bitmap_font.bitmap_font`
====================================================
Loads bitmap glyphs from a variety of font.
* Author(s): Scott Shawcroft
Implementation Notes
--------------------
**Hardware:**
**Software and Dependencies:**
* Adafruit CircuitPython firmware for the supported boards:
https://github.com/adafruit/circuitpython/releases
"""
try:
from typing import Optional, Union
from displayio import Bitmap
from . import bdf
from . import pcf
from . import ttf
except ImportError:
pass
__version__ = "0.0.0+auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Bitmap_Font.git"
def load_font(
filename: str, bitmap_class: Optional[Bitmap] = None
) -> Union[bdf.BDF, pcf.PCF, ttf.TTF]:
"""Loads a font file. Returns None if unsupported."""
# pylint: disable=import-outside-toplevel, redefined-outer-name, consider-using-with
if not bitmap_class:
from . import Bitmap
bitmap_class = Bitmap
font_file = open(filename, "rb")
first_four = font_file.read(4)
if filename.endswith("bdf") and first_four == b"STAR":
from . import bdf
return bdf.BDF(font_file, bitmap_class)
if filename.endswith("pcf") and first_four == b"\x01fcp":
from . import pcf
return pcf.PCF(font_file, bitmap_class)
if filename.endswith("ttf") and first_four == b"\x00\x01\x00\x00":
from . import ttf
return ttf.TTF(font_file, bitmap_class)
raise ValueError("Unknown magic number %r" % first_four)

View file

@ -0,0 +1,52 @@
# SPDX-FileCopyrightText: 2019 Scott Shawcroft for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""
`adafruit_bitmap_font.glyph_cache`
====================================================
Displays text using CircuitPython's displayio.
* Author(s): Scott Shawcroft
Implementation Notes
--------------------
**Hardware:**
**Software and Dependencies:**
* Adafruit CircuitPython firmware for the supported boards:
https://github.com/adafruit/circuitpython/releases
"""
from . import Glyph
from typing import Union, Iterable
import gc
__version__ = "0.0.0+auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Bitmap_Font.git"
class GlyphCache:
"""Caches glyphs loaded by a subclass."""
def __init__(self) -> None:
self._glyphs = {}
def load_glyphs(self, code_points: Union[int, str, Iterable[int]]) -> None:
"""Loads displayio.Glyph objects into the GlyphCache from the font."""
def get_glyph(self, code_point: int) -> Glyph:
"""Returns a displayio.Glyph for the given code point or None is unsupported."""
if code_point in self._glyphs:
return self._glyphs[code_point]
code_points = set()
code_points.add(code_point)
self._glyphs[code_point] = None
self.load_glyphs(code_points)
gc.collect()
return self._glyphs[code_point]

View file

@ -0,0 +1,412 @@
# SPDX-FileCopyrightText: 2020 Jeff Epler for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""
`adafruit_bitmap_font.pcf`
====================================================
Loads PCF format fonts.
* Author(s): Jeff Epler
Implementation Notes
--------------------
**Hardware:**
**Software and Dependencies:**
* Adafruit CircuitPython firmware for the supported boards:
https://github.com/adafruit/circuitpython/releases
"""
try:
from typing import Union, Tuple, Iterator, Iterable
from io import FileIO
from displayio import Bitmap as displayioBitmap
except ImportError:
pass
from collections import namedtuple
import gc
import struct
from micropython import const
from fontio import Glyph
from .glyph_cache import GlyphCache
try:
from bitmaptools import readinto as _bitmap_readinto
except ImportError:
_bitmap_readinto = None # pylint: disable=invalid-name
_PCF_PROPERTIES = const(1 << 0)
_PCF_ACCELERATORS = const(1 << 1)
_PCF_METRICS = const(1 << 2)
_PCF_BITMAPS = const(1 << 3)
_PCF_INK_METRICS = const(1 << 4)
_PCF_BDF_ENCODINGS = const(1 << 5)
_PCF_SWIDTHS = const(1 << 6)
_PCF_GLYPH_NAMES = const(1 << 7)
_PCF_BDF_ACCELERATORS = const(1 << 8)
_PCF_DEFAULT_FORMAT = const(0x00000000)
_PCF_ACCEL_W_INKBOUNDS = const(0x00000100)
_PCF_COMPRESSED_METRICS = const(0x00000100)
_PCF_GLYPH_PAD_MASK = const(3 << 0) # See the bitmap table for explanation */
_PCF_BYTE_MASK = const(1 << 2) # If set then Most Sig Byte First */
_PCF_BIT_MASK = const(1 << 3) # If set then Most Sig Bit First */
_PCF_SCAN_UNIT_MASK = const(3 << 4)
# https://fontforge.org/docs/techref/pcf-format.html
Table = namedtuple("Table", ("format", "size", "offset"))
Metrics = namedtuple(
"Metrics",
(
"left_side_bearing",
"right_side_bearing",
"character_width",
"character_ascent",
"character_descent",
"character_attributes",
),
)
Accelerators = namedtuple(
"Accelerators",
(
"no_overlap",
"constant_metrics",
"terminal_font",
"constant_width",
"ink_inside",
"ink_metrics",
"draw_direction",
"font_ascent",
"font_descent",
"max_overlap",
"minbounds",
"maxbounds",
"ink_minbounds",
"ink_maxbounds",
),
)
Encoding = namedtuple(
"Encoding", ("min_byte2", "max_byte2", "min_byte1", "max_byte1", "default_char")
)
Bitmap = namedtuple("Bitmap", ("glyph_count", "bitmap_sizes"))
class PCF(GlyphCache):
"""Loads glyphs from a PCF file in the given bitmap_class."""
def __init__(self, f: FileIO, bitmap_class: displayioBitmap) -> None:
super().__init__()
self.file = f
self.name = f
f.seek(0)
self.buffer = bytearray(1)
self.bitmap_class = bitmap_class
_, table_count = self._read("<4sI")
self.tables = {}
for _ in range(table_count):
type_, format_, size, offset = self._read("<IIII")
self.tables[type_] = Table(format_, size, offset)
bitmap_format = self.tables[_PCF_BITMAPS].format
if bitmap_format != 0xE:
raise NotImplementedError("Unsupported format %s" % bitmap_format)
self._accel = self._read_accelerator_tables()
self._encoding = self._read_encoding_table()
self._bitmaps = self._read_bitmap_table()
self._ascent = self._accel.font_ascent
self._descent = self._accel.font_descent
minbounds = self._accel.ink_minbounds
maxbounds = self._accel.ink_maxbounds
width = maxbounds.right_side_bearing - minbounds.left_side_bearing
height = maxbounds.character_ascent + maxbounds.character_descent
self._bounding_box = (
width,
height,
minbounds.left_side_bearing,
-maxbounds.character_descent,
)
@property
def ascent(self) -> int:
"""The number of pixels above the baseline of a typical ascender"""
return self._ascent
@property
def descent(self) -> int:
"""The number of pixels below the baseline of a typical descender"""
return self._descent
def get_bounding_box(self) -> Tuple[int, int, int, int]:
"""Return the maximum glyph size as a 4-tuple of: width, height, x_offset, y_offset"""
return self._bounding_box
def _read(self, format_: str) -> Tuple:
size = struct.calcsize(format_)
if size != len(self.buffer):
self.buffer = bytearray(size)
self.file.readinto(self.buffer)
return struct.unpack_from(format_, self.buffer)
def _seek_table(self, table: Table) -> int:
self.file.seek(table.offset)
(format_,) = self._read("<I")
if format_ & _PCF_BYTE_MASK == 0:
raise RuntimeError("Only big endian supported")
return format_
def _read_encoding_table(self) -> Encoding:
encoding = self.tables[_PCF_BDF_ENCODINGS]
self._seek_table(encoding)
return Encoding(*self._read(">hhhhh"))
def _read_bitmap_table(self) -> Bitmap:
bitmaps = self.tables[_PCF_BITMAPS]
format_ = self._seek_table(bitmaps)
(glyph_count,) = self._read(">I")
self.file.seek(bitmaps.offset + 8 + 4 * glyph_count)
bitmap_sizes = self._read(">4I")
return Bitmap(glyph_count, bitmap_sizes[format_ & 3])
def _read_metrics(self, compressed_metrics: bool) -> Metrics:
if compressed_metrics:
(
left_side_bearing,
right_side_bearing,
character_width,
character_ascent,
character_descent,
) = self._read("5B")
left_side_bearing -= 0x80
right_side_bearing -= 0x80
character_width -= 0x80
character_ascent -= 0x80
character_descent -= 0x80
attributes = 0
else:
(
left_side_bearing,
right_side_bearing,
character_width,
character_ascent,
character_descent,
attributes,
) = self._read(">5hH")
return Metrics(
left_side_bearing,
right_side_bearing,
character_width,
character_ascent,
character_descent,
attributes,
)
def _read_accelerator_tables(self) -> Accelerators:
# pylint: disable=too-many-locals
accelerators = self.tables.get(_PCF_BDF_ACCELERATORS)
if not accelerators:
accelerators = self.tables.get(_PCF_ACCELERATORS)
if not accelerators:
raise RuntimeError("Accelerator table missing")
format_ = self._seek_table(accelerators)
has_inkbounds = format_ & _PCF_ACCEL_W_INKBOUNDS
(
no_overlap,
constant_metrics,
terminal_font,
constant_width,
ink_inside,
ink_metrics,
draw_direction,
_,
font_ascent,
font_descent,
max_overlap,
) = self._read(">BBBBBBBBIII")
minbounds = self._read_metrics(False)
maxbounds = self._read_metrics(False)
if has_inkbounds:
ink_minbounds = self._read_metrics(False)
ink_maxbounds = self._read_metrics(False)
else:
ink_minbounds = minbounds
ink_maxbounds = maxbounds
return Accelerators(
no_overlap,
constant_metrics,
terminal_font,
constant_width,
ink_inside,
ink_metrics,
draw_direction,
font_ascent,
font_descent,
max_overlap,
minbounds,
maxbounds,
ink_minbounds,
ink_maxbounds,
)
def _read_properties(self) -> Iterator[Tuple[bytes, Union[bytes, int]]]:
property_table_offset = self.tables[_PCF_PROPERTIES]["offset"]
self.file.seek(property_table_offset)
(format_,) = self._read("<I")
if format_ & _PCF_BYTE_MASK == 0:
raise RuntimeError("Only big endian supported")
(nprops,) = self._read(">I")
self.file.seek(property_table_offset + 8 + 9 * nprops)
pos = self.file.tell()
if pos % 4 > 0:
self.file.read(4 - pos % 4)
(string_size,) = self._read(">I")
strings = self.file.read(string_size)
string_map = {}
i = 0
for value in strings.split(b"\x00"):
string_map[i] = value
i += len(value) + 1
self.file.seek(property_table_offset + 8)
for _ in range(nprops):
name_offset, is_string_prop, value = self._read(">IBI")
if is_string_prop:
yield (string_map[name_offset], string_map[value])
else:
yield (string_map[name_offset], value)
def load_glyphs(self, code_points: Union[int, str, Iterable[int]]) -> None:
# pylint: disable=too-many-statements,too-many-branches,too-many-nested-blocks,too-many-locals
if isinstance(code_points, int):
code_points = (code_points,)
elif isinstance(code_points, str):
code_points = [ord(c) for c in code_points]
code_points = sorted(
c for c in code_points if self._glyphs.get(c, None) is None
)
if not code_points:
return
indices_offset = self.tables[_PCF_BDF_ENCODINGS].offset + 14
bitmap_offset_offsets = self.tables[_PCF_BITMAPS].offset + 8
first_bitmap_offset = self.tables[_PCF_BITMAPS].offset + 4 * (
6 + self._bitmaps.glyph_count
)
metrics_compressed = self.tables[_PCF_METRICS].format & _PCF_COMPRESSED_METRICS
first_metric_offset = self.tables[_PCF_METRICS].offset + (
6 if metrics_compressed else 8
)
metrics_size = 5 if metrics_compressed else 12
# These will each _tend to be_ forward reads in the file, at least
# sometimes we'll benefit from oofatfs's 512 byte cache and avoid
# excess reads
indices = [None] * len(code_points)
for i, code_point in enumerate(code_points):
enc1 = (code_point >> 8) & 0xFF
enc2 = code_point & 0xFF
if enc1 < self._encoding.min_byte1 or enc1 > self._encoding.max_byte1:
continue
if enc2 < self._encoding.min_byte2 or enc2 > self._encoding.max_byte2:
continue
encoding_idx = (
(enc1 - self._encoding.min_byte1)
* (self._encoding.max_byte2 - self._encoding.min_byte2 + 1)
+ enc2
- self._encoding.min_byte2
)
self.file.seek(indices_offset + 2 * encoding_idx)
(glyph_idx,) = self._read(">H")
if glyph_idx != 65535:
indices[i] = glyph_idx
all_metrics = [None] * len(code_points)
for i, code_point in enumerate(code_points):
index = indices[i]
if index is None:
continue
self.file.seek(first_metric_offset + metrics_size * index)
all_metrics[i] = self._read_metrics(metrics_compressed)
bitmap_offsets = [None] * len(code_points)
for i, code_point in enumerate(code_points):
index = indices[i]
if index is None:
continue
self.file.seek(bitmap_offset_offsets + 4 * index)
(bitmap_offset,) = self._read(">I")
bitmap_offsets[i] = bitmap_offset
# Batch creation of glyphs and bitmaps so that we need only gc.collect
# once
gc.collect()
bitmaps = [None] * len(code_points)
for i in range(len(all_metrics)): # pylint: disable=consider-using-enumerate
metrics = all_metrics[i]
if metrics is not None:
width = metrics.right_side_bearing - metrics.left_side_bearing
height = metrics.character_ascent + metrics.character_descent
bitmap = bitmaps[i] = self.bitmap_class(width, height, 2)
self._glyphs[code_points[i]] = Glyph(
bitmap,
0,
width,
height,
metrics.left_side_bearing,
-metrics.character_descent,
metrics.character_width,
0,
)
for i, code_point in enumerate(code_points):
metrics = all_metrics[i]
if metrics is None:
continue
self.file.seek(first_bitmap_offset + bitmap_offsets[i])
width = metrics.right_side_bearing - metrics.left_side_bearing
height = metrics.character_ascent + metrics.character_descent
bitmap = bitmaps[i]
if _bitmap_readinto:
_bitmap_readinto(
bitmap,
self.file,
bits_per_pixel=1,
element_size=4,
reverse_pixels_in_element=True,
)
else:
words_per_row = (width + 31) // 32
buf = bytearray(4 * words_per_row)
start = 0
for _ in range(height):
self.file.readinto(buf)
for k in range(width):
if buf[k // 8] & (128 >> (k % 8)):
bitmap[start + k] = 1
start += width

View file

@ -0,0 +1,65 @@
# SPDX-FileCopyrightText: 2019 Scott Shawcroft for Adafruit Industries
#
# SPDX-License-Identifier: MIT
# pylint: skip-file
# Remove the above when TTF is actually supported.
try:
from typing import Tuple
from io import FileIO
from displayio import Bitmap
except ImportError:
pass
import struct
# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6glyf.html
class TTF:
def __init__(self, f: FileIO, bitmap: Bitmap) -> None:
f.seek(0)
self.file = f
self.characters = {}
def read(format: str) -> Tuple:
s = struct.calcsize(format)
return struct.unpack_from(format, f.read(s))
scalar_type = read(">I")
numTables, searchRange, entrySelector, rangeShift = read(">HHHH")
print(numTables)
table_info = {}
for _ in range(numTables):
tag, checkSum, offset, length = read(">4sIII")
print(tag.decode("utf-8"), hex(checkSum), offset, length)
table_info[tag] = (offset, length)
head_offset, head_length = table_info[b"head"]
f.seek(head_offset)
version, fontRevision, checkSumAdjustment, magicNumber = read(">IIII")
flags, unitsPerEm, created, modified = read(">HHQQ")
xMin, yMin, xMax, yMax = read(">hhhh")
print(xMin, yMin, xMax, yMax)
macStyle, lowestRecPPEM, fontDirectionHint = read(">HHh")
indexToLocFormat, glyphDataFormat = read(">hh")
glyf_offset, glyf_length = table_info[b"glyf"]
f.seek(glyf_offset)
while f.tell() < glyf_offset + glyf_length:
numberOfContours, xMin, yMin, xMax, yMax = read(">hhhhh")
if numberOfContours > 0: # Simple
print(numberOfContours)
ends = []
for _ in range(numberOfContours):
ends.append(read(">H"))
instructionLength = read(">h")[0]
instructions = read(">{}s".format(instructionLength))[0]
print(instructions)
break
else:
raise RuntimeError("Unsupported font")

64
mkfont/mkfont.py Normal file
View file

@ -0,0 +1,64 @@
import array
from dataclasses import dataclass
import sys
import click
from adafruit_bitmap_font import bitmap_font, Bitmap
def extract_deposit_bits(*positions):
data_out = 0
for p in positions:
if p[0]:
for dest in p[1:]:
data_out |= 1 << dest
return data_out
class OffsetBitmap:
def __init__(self, dx, dy, glyph):
self.dx = dx
self.dy = dy
self.glyph = glyph
def __getitem__(self, pos):
x, y = pos
x = x - self.glyph.dx
y = y - (7 - self.glyph.height) + self.glyph.dy
print(pos, x, y)
if 0 <= x < self.glyph.bitmap.width and 0 <= y < self.glyph.bitmap.height:
return self.glyph.bitmap[x,y]
return 0
@click.command
@click.argument("bdf", type=click.Path(exists=True))
@click.argument("header", type=click.File(mode='w'), default=sys.stdout)
def main(bdf, header):
font = bitmap_font.load_font(bdf, Bitmap)
width, height, dx, dy = font.get_bounding_box()
print(width, height, dx, dy)
# if width != 5 or height != 9:
# raise SystemExit("sorry, only 5x9 monospace fonts supported")
output_data = array.array('H', [0] * 9 * 256)
font.load_glyphs(range(256))
for i in range(256):
g = font.get_glyph(i)
if g is None:
continue
print(repr(chr(i)), g)
bitmap = OffsetBitmap(dx, dy, g)
for j in range(9):
d = extract_deposit_bits(
(bitmap[4, j], 0, 1),
(bitmap[3, j], 2, 3),
(bitmap[2, j], 4, 5),
(bitmap[1, j], 6, 7),
(bitmap[0, j], 8, 9))
output_data[j * 256 + i] = d << 2
print(", ".join(f"0x{x:04x}" for x in output_data), file=header)
if __name__ == '__main__':
main()