Cozette/cozette_builder/bdffont.py
2020-02-10 21:48:52 +01:00

126 lines
3.7 KiB
Python

from __future__ import annotations
from typing import (
Dict,
Iterable,
List,
Literal,
NamedTuple,
TextIO,
Tuple,
Union,
)
import numpy as np # type: ignore
from fontTools.pens.ttGlyphPen import TTGlyphPen # type: ignore
Bit = Union[Literal[1], Literal[0]]
class BBX(NamedTuple):
w: int
h: int
x: int
y: int
class BdfGlyph:
def __init__(self, bits: np.array, meta: Dict[str, str]):
self.bbx = bbx = BBX(*[int(s) for s in meta["BBX"].split()])
# this is probably wrong
self.bits: np.array = bits[0 : bbx.h, 0 : bbx.w]
self.metadata = meta
@classmethod
def from_str(cls, s: str, meta: Dict[str, str]) -> BdfGlyph:
return cls.from_iterable((int(l, 16) for l in s.splitlines()), meta)
@classmethod
def from_iterable(
cls, values: Iterable[int], meta: Dict[str, str]
) -> BdfGlyph:
return BdfGlyph(
np.unpackbits(
np.array([[v] for v in values], dtype=np.uint8), axis=1
),
meta=meta,
)
def __str__(self) -> str:
return "\n".join(
"".join("#" if ch else " " for ch in line) for line in self.bits
)
def draw(self, ppp: float):
pen = TTGlyphPen(None)
for y, row in enumerate(reversed(self.bits)):
for x, col in enumerate(row):
if col:
pen.moveTo(
((x + self.bbx.x) * ppp, (y + self.bbx.y) * ppp)
)
pen.lineTo(
((x + self.bbx.x + 1) * ppp, (y + self.bbx.y) * ppp)
)
pen.lineTo(
(
(x + self.bbx.x + 1) * ppp,
(y + self.bbx.y + 1) * ppp,
)
)
pen.lineTo(
((x + self.bbx.x) * ppp, (y + self.bbx.y + 1) * ppp)
)
pen.lineTo(
((x + self.bbx.x) * ppp, (y + self.bbx.y) * ppp)
)
pen.closePath()
return pen.glyph()
def meta(self, k) -> List[str]:
return self.metadata[k].strip('"').strip().split()
def parse_char(bdfstream: TextIO) -> Tuple[Dict[str, str], List[int]]:
specs = {}
while not (line := bdfstream.readline()).startswith("BITMAP"):
parts = line.split(maxsplit=1)
specs[parts[0]] = parts[1].strip()
bitmap = []
while not (line := bdfstream.readline()).startswith("ENDCHAR"):
bitmap.append(int(line.strip(), 16))
return specs, bitmap
class BdfFont:
def __init__(self, metadata: Dict[str, str], glyphs: Dict[int, BdfGlyph]):
self.metadata: Dict[str, str] = metadata
self.glyphs: Dict[int, BdfGlyph] = glyphs
@classmethod
def from_bdf(cls, bdfstream: TextIO):
metadata = {}
while not (line := bdfstream.readline()).startswith("CHARS "):
try:
k, val = line.split(maxsplit=1)
metadata[k] = val.strip()
except ValueError:
pass
glyphs = {}
for i in range(int(line.split()[1])):
meta, char = parse_char(bdfstream)
glyphs[int(meta["ENCODING"])] = BdfGlyph.from_iterable(char, meta)
return cls(metadata, glyphs)
@property
def codepoints(self) -> Tuple[int, ...]:
return tuple(self.glyphs.keys())
@property
def str_codepoints(self) -> Tuple[str, ...]:
return tuple(f"U+{i:X}" for i in self.glyphs)
def meta(self, k) -> List[str]:
return self.metadata[k].strip('"').strip().split()