-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
19 changed files
with
355 additions
and
249 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,3 +6,4 @@ | |
/master_ufo/ | ||
/Lilex (Autosaved).glyphs | ||
/generator/__pycache__ | ||
.ruff_cache |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
[tool.ruff] | ||
select = [ | ||
"E", # pycodestyle errors | ||
"W", # pycodestyle warnings | ||
"F", # pyflakes | ||
"I", # isort | ||
"B", # flake8-bugbear | ||
] | ||
ignore = [ | ||
"E501", # line too long, handled by black | ||
"B008", # do not perform function calls in argument defaults | ||
"C901", # too complex | ||
] | ||
|
||
[tool.ruff.per-file-ignores] | ||
"__init__.py" = ["F401"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,7 @@ | ||
fontmake==3.5.1 | ||
cu2qu==1.6.7 | ||
gftools==0.9.27 | ||
glyphsLib | ||
glyphsLib==6.2.1 | ||
arrrgs==0.0.5 | ||
ruff==0.0.259 | ||
pylint==2.17.1 |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
"""Lilex font builder module""" | ||
from .const import SUPPORTED_FORMATS | ||
from .font import GlyphsFont |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
"""Lilex builder constants""" | ||
|
||
UFO_PATH = "master_ufo" | ||
SUPPORTED_FORMATS = [ | ||
"ttf", | ||
"otf", | ||
"variable" | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
"""Glyphs helper""" | ||
from __future__ import annotations | ||
|
||
import sys | ||
from typing import Callable, List | ||
|
||
from glyphsLib import ( | ||
GSClass, | ||
GSFeature, | ||
GSFont, | ||
GSGlyph, | ||
build_masters, | ||
) | ||
|
||
from .const import SUPPORTED_FORMATS, UFO_PATH | ||
from .make import make | ||
|
||
LIGATURE_SUFFIX = ".liga" | ||
GlyphFilter = Callable[[GSGlyph], bool] | ||
|
||
class GlyphsFont: | ||
"""Glyphs font builder""" | ||
_font: GSFont = None | ||
_path: str | ||
|
||
def __init__(self, path: str): | ||
self._font = GSFont(path) | ||
self._path = path | ||
|
||
def ligatures(self) -> str: | ||
glyphs = self.glyphs(lambda x: x.name.endswith(LIGATURE_SUFFIX)) | ||
ligatures = [] | ||
for glyph in glyphs: | ||
ligatures.append(glyph.name.replace(LIGATURE_SUFFIX, "")) | ||
return ligatures | ||
|
||
def glyphs(self, _filter: GlyphFilter) -> List[str]: | ||
"""Returns a list of glyphs that match filter""" | ||
result = [] | ||
for glyph in self._font.glyphs: | ||
if _filter(glyph): | ||
result.append(glyph) | ||
return result | ||
|
||
def save(self): | ||
"""Saves the file to the same path from which it was opened""" | ||
self._font.save(self._path) | ||
|
||
def save_to(self, path: str) -> None: | ||
"""Saves the file to the specified path""" | ||
self._font.save(path) | ||
|
||
def set_classes(self, classes: List[GSClass]): | ||
"""Sets the font classes""" | ||
for cls in classes: | ||
if cls.name in self._font.classes: | ||
self._font.classes[cls.name] = cls | ||
else: | ||
self._font.classes.append(cls) | ||
|
||
def set_features(self, features: List[GSFeature]): | ||
"""Sets the font features""" | ||
for fea in features: | ||
if fea.name in self._font.features: | ||
self._font.features[fea.name] = fea | ||
else: | ||
self._font.features.append(fea) | ||
|
||
def build(self, formats: List[str], out_dir: str): | ||
print("Generating master UFOs") | ||
build_masters(self._path, UFO_PATH, write_skipexportglyphs=True) | ||
ds_path = f"{UFO_PATH}/{self._font.familyName}.designspace" | ||
for fmt in formats: | ||
if fmt not in SUPPORTED_FORMATS: | ||
print(f"Unsupported format '{fmt}'") | ||
sys.exit(1) | ||
make(ds_path, fmt, f"{out_dir}/{fmt}") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
"""Make helpers""" | ||
import subprocess as sp | ||
|
||
|
||
def make(ds_path: str, fmt: str, out_dir: str) -> bool: | ||
"""Wrapper for fontmake""" | ||
cmd = " ".join([ | ||
"fontmake", | ||
f"-m '{ds_path}'", | ||
f"-o '{fmt}'", | ||
f"--output-dir '{out_dir}'", | ||
"--autohint" | ||
]) | ||
with sp.Popen(cmd, shell=True, stdout=sp.PIPE) as child: | ||
child.communicate() | ||
return child.returncode == 0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
"""Lilex font generator""" | ||
from .ligatures import render_ligatures |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
"""Generator constants""" | ||
|
||
IGNORE_PREFIXES = { | ||
'parenleft question': [ | ||
'colon', | ||
'equal' | ||
'exclaim' | ||
], | ||
'less question': ['equal'], | ||
'parenleft question less': [ | ||
'equal', | ||
'exclaim' | ||
], | ||
} | ||
|
||
# Replacement ignore templates map | ||
# ignore sub | ||
IGNORE_TEMPLATES = { | ||
2: [ | ||
"1 1' 2", | ||
"1' 2 2" | ||
], | ||
3: [ | ||
"1 1' 2 3", | ||
"1' 2 3 3" | ||
], | ||
4: [ | ||
"1 1' 2 3 4", | ||
"1' 2 3 4 4" | ||
] | ||
} | ||
|
||
# Replacement templates map | ||
# sub | ||
REPLACE_TEMPLATES = { | ||
2: [ | ||
"LIG 2' by 1_2.liga", | ||
"1' 2 by LIG" | ||
], | ||
3: [ | ||
"LIG LIG 3' by 1_2_3.liga", | ||
"LIG 2' 3 by LIG", | ||
"1' 2 3 by LIG" | ||
], | ||
4: [ | ||
"LIG LIG LIG 4' by 1_2_3_4.liga", | ||
"LIG LIG 3' 4 by LIG", | ||
"LIG 2' 3 4 by LIG", | ||
"1' 2 3 4 by LIG" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
"""Ligatures feature generator module""" | ||
from typing import List | ||
|
||
from .const import IGNORE_PREFIXES, IGNORE_TEMPLATES, REPLACE_TEMPLATES | ||
|
||
|
||
def render_statements(statements: List[str], prefix: str) -> str: | ||
"""Renders fea statements""" | ||
return '\n'.join(map(lambda x: f' {prefix} {x};', statements)) | ||
|
||
def render_template(template: str, glyphs: List[str]) -> str: | ||
result = template | ||
for i, glyph in enumerate(glyphs): | ||
result = result.replace(str(i + 1), glyph) | ||
return result | ||
|
||
def get_ignore_prefixes(name: str, count: int) -> List[str]: | ||
ignores: List[str] = [] | ||
tail = '' | ||
for i in range(count - 1): | ||
tail += f' {i + 1}' | ||
for statement, starts in IGNORE_PREFIXES.items(): | ||
for start in starts: | ||
if name.startswith(start): | ||
ignores.append(f"{statement} 1' {tail}") | ||
return ignores | ||
|
||
def render_lookup(replace: List[str], ignore: List[str], glyphs: List[str]) -> str: | ||
name = '_'.join(glyphs) | ||
template = ( | ||
f"lookup {name}" + " { \n" | ||
f"{render_statements(ignore, 'ignore sub')}" | ||
f"{render_statements(replace, 'sub')}" | ||
"\n} " + f"{name};" | ||
) | ||
return render_template(template, glyphs) | ||
|
||
def render_ligature(name: str) -> str: | ||
"""Generates an OpenType feature code that replaces characters with ligatures. | ||
The `name` must be in the format `<glyph>_<glyph>`""" | ||
glyphs = name.split('_') | ||
count = len(glyphs) | ||
ignores = IGNORE_TEMPLATES[count] + get_ignore_prefixes(name, count) | ||
replaces = REPLACE_TEMPLATES[count] | ||
return render_lookup(replaces, ignores, glyphs) | ||
|
||
def render_ligatures(items: List[str]) -> str: | ||
"""Renders the list of ligatures in the OpenType feature""" | ||
result = "" | ||
# For the generated code to work correctly, | ||
# it is necessary to sort the list in descending order of the number of glyphs | ||
ligatures = sorted(items, key=lambda x: len(x.split('_')), reverse=True) | ||
for name in ligatures: | ||
result += render_ligature(name) + "\n" | ||
return result |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
"""Lilex helper entrypoint""" | ||
from arrrgs import arg, command, run | ||
from builder import SUPPORTED_FORMATS, GlyphsFont | ||
from generator import render_ligatures | ||
from glyphsLib import GSFeature | ||
from utils import read_classes, read_features, read_files | ||
|
||
FONT_FILE = "Lilex.glyphs" | ||
CLASSES_DIR = "./classes" | ||
FEATURES_DIR = "./features" | ||
OUT_DIR = "./build" | ||
|
||
@command() | ||
def regenerate(_, font: GlyphsFont): | ||
"""Saves the generated source file with features and classes""" | ||
font.save() | ||
print("☺️ Font source successfully regenerated") | ||
|
||
@command( | ||
arg("formats", nargs="*", help="Format list", default=SUPPORTED_FORMATS) | ||
) | ||
def build(args, font: GlyphsFont): | ||
"""Builds a binary font file""" | ||
font.build(args.formats, OUT_DIR) | ||
print("☺️ Font binaries successfully builded") | ||
|
||
def generate_calt(font: GlyphsFont) -> GSFeature: | ||
glyphs = font.ligatures() | ||
code = render_ligatures(glyphs) + read_files(f"{FEATURES_DIR}/calt") | ||
return GSFeature("calt", code) | ||
|
||
def prepare(args): | ||
font = GlyphsFont(FONT_FILE) | ||
|
||
cls = read_classes(CLASSES_DIR) | ||
fea = read_features(FEATURES_DIR) | ||
fea.append(generate_calt(font)) | ||
|
||
font.set_classes(cls) | ||
font.set_features(fea) | ||
return args, font | ||
|
||
if __name__ == "__main__": | ||
run(prepare=prepare) |
Oops, something went wrong.