diff --git a/build.py b/build.py index ba2679d..5c3e68d 100644 --- a/build.py +++ b/build.py @@ -1,55 +1,22 @@ from pathlib import Path -from cozette_builder.imagegen import save_sample, save_charlist, Sample +from cozette_builder.imagegen import save_sample, save_charlist, read_sample +from cozette_builder.ttfbuilder import TTFBuilder -SAMPLE_TEXT = """ -$BLUE$┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -┃ $MAGENTA$♡$NC$ $FG$Cozette$NC$ $MAGENTA$♡$BLUE$ ┃ -┡━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ -│ $WHITE$A B C D E F G H I J K L M $BLUE$│ -│ $WHITE$N O P Q R S T U V W X Y Z $BLUE$│ -╞═══════════════════════════╡ -│ 1 2 3 4 5 6 7 8 9 0 │ -╞═══════════════════════════╡ -│ $WHITE$a b c d e f g h i j k l m $BLUE$│ -│ $WHITE$n o p q r s t u v w x y z $BLUE$│ -├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┤ -│ $RED$() [] {} <> ? / \ : " ' ;$BLUE$ │ -│ $RED$! @ # $ % ^ & * _ = + - |$BLUE$ │ -│ , . ` ~$BLUE$ │ -╰───────────────────────────╯ - $YELLOW$   -   - $BLUE$│ $FG$╔═══════════════╗ - $BLUE$└──$FG$║ $GREEN$✓ ✔ $YELLOW$✕ ✖ $MAGENTA$✗ ✘ $FG$ ║$BLUE$▒ - $FG$║ $BLUE$       $FG$║$BLUE$▒ - $FG$╚═══════════════╝$BLUE$▒ - ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ - - - $GREEN$Pchnąć w tę łódź jeża lub - ósm skrzyń fig. - - Eĥoŝanĝo ĉiuĵaŭde. - - Příliš žluťoučký kůň - úpěl ďábelské ódy. - - $FG$♠ ♣ $MAGENTA$♥ ♦ -""" REPO_ROOT = Path(__file__).resolve().parent - +bdfpath = REPO_ROOT / "Cozette8" / "Cozette-10.bdf" def save_images(): - bdfpath = REPO_ROOT / "Cozette8" / "Cozette-10.bdf" save_charlist(bdfpath, REPO_ROOT / "img" / "characters.png") save_sample( "Clozette", - Sample(SAMPLE_TEXT, 30, 32), + read_sample(REPO_ROOT / "img" / "sample.txt"), REPO_ROOT / "img" / "sample.png", ) if __name__ == "__main__": - save_images() + # save_images() + TTFBuilder.from_bdf_path(bdfpath).build("cloze.ttf") + diff --git a/cozette_builder/bdf/__init__.py b/cozette_builder/bdf/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/cozette_builder/bdf/bdffont.py b/cozette_builder/bdffont.py similarity index 63% rename from cozette_builder/bdf/bdffont.py rename to cozette_builder/bdffont.py index 853bf38..c5afa44 100644 --- a/cozette_builder/bdf/bdffont.py +++ b/cozette_builder/bdffont.py @@ -9,7 +9,8 @@ from typing import ( TextIO, Tuple, ) -import numpy as np +import numpy as np # type: ignore +from fontTools.pens.ttGlyphPen import TTGlyphPen Bit = Union[Literal[1], Literal[0]] @@ -29,7 +30,7 @@ class BdfGlyph: self.bits: np.array = np.pad(bits, [[0, 0], [bbx.x, 0]])[ bbx.y : bbx.y + bbx.h, bbx.x : bbx.x + bbx.w ] - self.meta = meta + self.metadata = meta @classmethod def from_str(cls, s: str, meta: Dict[str, str]) -> BdfGlyph: @@ -51,8 +52,24 @@ class BdfGlyph: "".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 reversed(enumerate(self.bits)): + for x, col in enumerate(row): + if col: + pen.moveTo((x * ppp, y * ppp)) + pen.lineTo(((x + 1) * ppp, y * ppp)) + pen.lineTo(((x + 1) * ppp, (y + 1) * ppp)) + pen.lineTo((x * ppp, (y + 1) * ppp)) + pen.lineTo((x * ppp, y * ppp)) + pen.closePath() + return pen.glyph() -def parse_char(bdfstream: TextIO): + def meta(self, k) -> List[str]: + return self.metadata[k].strip().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) @@ -64,15 +81,19 @@ def parse_char(bdfstream: TextIO): class BdfFont: - def __init__(self, metadata: List[str], glyphs: Dict[int, BdfGlyph]): - self.metadata: List[str] = metadata + 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 = [] + metadata = {} while not (line := bdfstream.readline()).startswith("CHARS "): - metadata.append(line) + try: + meta, val = line.split(maxsplit=1) + metadata[meta] = val + except ValueError: + pass glyphs = {} for i in range(int(line.split()[1])): meta, char = parse_char(bdfstream) @@ -86,3 +107,6 @@ class BdfFont: @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('"').strip().split() diff --git a/cozette_builder/imagegen.py b/cozette_builder/imagegen.py index d85563c..3447f88 100644 --- a/cozette_builder/imagegen.py +++ b/cozette_builder/imagegen.py @@ -3,7 +3,7 @@ from typing import Tuple, Dict, Optional, NamedTuple from shlex import quote from pathlib import Path from unicodedata import east_asian_width as charwidth -from cozette_builder.bdf.bdffont import BdfFont +from cozette_builder.bdffont import BdfFont import platform import tempfile diff --git a/cozette_builder/ttfbuilder.py b/cozette_builder/ttfbuilder.py new file mode 100644 index 0000000..5f24c40 --- /dev/null +++ b/cozette_builder/ttfbuilder.py @@ -0,0 +1,69 @@ +from pathlib import Path +from fontTools.fontBuilder import FontBuilder +from fontTools.pens.ttGlyphPen import TTGlyphPen +from cozette_builder.bdffont import BdfFont, BdfGlyph + +class TTFBuilder: + @classmethod + def from_bdf_path(cls, path: Path): + with path.open() as f: + bdf = BdfFont.from_bdf(f) + return cls(bdf) + + def __init__(self, bdf: BdfFont): + self.bdf = bdf + upm = 1000 + self.fb = FontBuilder(unitsPerEm=upm) + self.ppp = upm / int(self.bdf.meta("SIZE")[0]) + w, h, startx, starty = self.bdf.meta("FONTBOUNDINGBOX") + self.ascent = int(self.bdf.meta("FONT_ASCENT")[0]) + self.descent = int(self.bdf.meta("FONT_DESCENT")[0]) + self.w = int(w) + self.h = int(h) + self.startx = int(startx) + self.starty = int(starty) + + def build(self, output_path: Path): + glyph_names = { + k: glyph.meta("STARTCHAR")[0] for k, glyph in + self.bdf.glyphs.items() + } + ascent = round(self.ascent * self.ppp) + descent = round(self.descent * self.ppp) + + self.fb.setupGlyphOrder(list(glyph_names.values())) + self.fb.setupCharacterMap(glyph_names) + advance_widths = { + name: 700 for name in glyph_names.values() + } + # copied from fontbuilder for now + familyName = "HelloTestFont" + styleName = "TotallyNormal" + version = "0.1" + + nameStrings = dict( + familyName=dict(en=familyName, nl="HalloTestFont"), + styleName=dict(en=styleName, nl="TotaalNormaal"), + uniqueFontIdentifier="fontBuilder: " + familyName + "." + styleName, + fullName=familyName + "-" + styleName, + psName=familyName + "-" + styleName, + version="Version " + version, + ) + self.fb.setupGlyf( + {name: self.bdf.glyphs[k].draw(self.ppp) for k, name in + glyph_names.items()} + ) + metrics = {} + glyphTable = self.fb.font["glyf"] + for name in glyph_names.values(): + metrics[name] = (700, glyphTable[name].xMin) + self.fb.setupHorizontalMetrics(metrics) + self.fb.setupHorizontalHeader(ascent=ascent, descent=-descent) + self.fb.setupNameTable(nameStrings) + self.fb.setupOS2(sTypoAscender=ascent, usWinAscent=ascent, + usWinDescent=descent) + self.fb.setupPost() + self.fb.save(str(output_path)) + + + diff --git a/img/sample.txt b/img/sample.txt index 10bcb9e..485f32b 100644 --- a/img/sample.txt +++ b/img/sample.txt @@ -31,4 +31,4 @@ $BLUE$┏━━━━━━━━━━━━━━━━━━━━━━━ Příliš žluťoučký kůň úpěl ďábelské ódy. - $FG$♠ ♣ $MAGENTA$♥ ♦ + $FG$♠ $MAGENTA$♥ $FG$♣ $MAGENTA$♦