Full font
The conversion process was so nasty & ad hoc that I'm committing the generated font file.
This commit is contained in:
parent
a4c72dbc9a
commit
3554999f0a
11 changed files with 3930 additions and 22 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -2,3 +2,4 @@ chargen
|
|||
chargen.s
|
||||
chargen.png
|
||||
build
|
||||
__pycache__
|
||||
|
|
|
|||
1
5x9.h
Normal file
1
5x9.h
Normal file
File diff suppressed because one or more lines are too long
34
chargen.c
34
chargen.c
|
|
@ -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
2987
mkfont/5x9.bdf
Normal file
File diff suppressed because it is too large
Load diff
39
mkfont/adafruit_bitmap_font/__init__.py
Normal file
39
mkfont/adafruit_bitmap_font/__init__.py
Normal 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
|
||||
233
mkfont/adafruit_bitmap_font/bdf.py
Normal file
233
mkfont/adafruit_bitmap_font/bdf.py
Normal 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
|
||||
64
mkfont/adafruit_bitmap_font/bitmap_font.py
Normal file
64
mkfont/adafruit_bitmap_font/bitmap_font.py
Normal 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)
|
||||
52
mkfont/adafruit_bitmap_font/glyph_cache.py
Normal file
52
mkfont/adafruit_bitmap_font/glyph_cache.py
Normal 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]
|
||||
412
mkfont/adafruit_bitmap_font/pcf.py
Normal file
412
mkfont/adafruit_bitmap_font/pcf.py
Normal 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
|
||||
65
mkfont/adafruit_bitmap_font/ttf.py
Normal file
65
mkfont/adafruit_bitmap_font/ttf.py
Normal 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
64
mkfont/mkfont.py
Normal 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()
|
||||
Loading…
Reference in a new issue