Finish build scripts

This commit is contained in:
Slavfox 2020-02-10 21:48:52 +01:00
parent 2969e98636
commit a0d8de634d
15 changed files with 9312 additions and 9836 deletions

View file

@ -14,21 +14,14 @@ jobs:
steps:
- name: Install dependencies
run: |
pacman -Sy --noconfirm base git tar
- uses: actions/checkout@v2
pacman -Sy --noconfirm base git tar python python-pipenv
- name: Install fontforge
run: pacman -S --noconfirm fontforge
- uses: actions/checkout@v2
- name: Build fonts
run: |
fontforge -lang ff -c '\
Open("Cozette8/Cozette8.sfd"); \
Generate("cozette.", "otb"); \
Generate("cozette.ttf", "otb"); \
Generate("cozette.dfont","sbit"); \
Generate("cozette.","fnt"); \
Generate("cozette.","bdf"); \
'
pipenv install; pipenv run python build.py
- uses: ncipollo/release-action@v1
with:
artifacts: "*.otb, *.ttf, *.bdf, *.dfont, *.fnt"
artifacts: "build/*"
token: ${{ secrets.GITHUB_TOKEN }}

View file

@ -11,16 +11,45 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
#### Glyphs
- Nerdfonts:
- ****nf-custom-folder_open (U+E5FE)****
- ****nf-custom-c (U+E61D)****, C logo
- **nf-custom-cpp (U+E61E)**, C++ logo
- **nf-indent-line (U+E621)**, indentation guide
- **nf-custom-vim (U+E62B)**
- **nf-dev-visualstudio (U+E70C)**
- **nf-dev-terminal (U+E795)** (black `>_` symbol)
- **nf-dev-terminal_badge (U+E7A2)** (white `>_` symbol), also copied as
**nf-fa-terminal (U+F120)**
- **nf-dev-ruby (U+E739)**
- **nf-dev-ubuntu (U+E73A)**
- **nf-dev-php (U+E73D)**
- **nf-dev-markdown (U+E73E)**
- **nf-dev-erlang (U+E7AF)**
- **nf-dev-erlang (U+E7B1)**
- **nf-fa-h_square (U+F0FD)** \[My vim uses it for C headers\]
- Copied some nf glyphs to code points that should be the same glyph (eg.
nf-dev-heroku U+E77B and nf-seti-heroku U+E607)
- **nf-linux-archlinux (U+F303)**
- Four variations on a `+`:
- U+2719 OUTLINED GREEK CROSS
- U+271A HEAVY GREEK CROSS
- U+271B OPEN CENTRE CROSS
- U+271C HEAVY OPEN CENTRE CROSS
- **U+2719 OUTLINED GREEK CROSS**
- **U+271A HEAVY GREEK CROSS**
- **U+271B OPEN CENTRE CROSS**
- **U+271C HEAVY OPEN CENTRE CROSS**
These should come in handy for git prompts and the like.
#### Build scripts
### Fixed
- Automatic character map generation
- Automatic sample image generation
- True vector .ttf font building
### Changed
- Github Actions to use the new build scripts
#### Glyphs
@ -39,8 +68,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
#### Glyphs
- nf-dev-linux (U+E712) for vim statusbars on Linuces
- U+25CF BLACK CIRCLE for git prompts
- **U+25CF BLACK CIRCLE** for git prompts
- **nf-dev-linux (U+E712)** for vim statusbars on Linuces
#### Other
@ -52,8 +81,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
#### Glyphs
- nf-dev-apple (U+E711) for vim statusbars on Macs
- nf-oct-zap (U+26A1 HIGH VOLTAGE SIGN) for term prompts
- **nf-oct-zap (U+26A1 HIGH VOLTAGE SIGN)** for term prompts
- **nf-dev-apple (U+E711)** for vim statusbars on Macs
### Changed

8968
Cozette/Cozette.sfd Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -6,11 +6,13 @@ verify_ssl = true
[dev-packages]
black = "*"
mypy = "*"
isort = "*"
[packages]
numpy = "*"
Pillow = "*"
fonttools = "*"
crayons = "*"
[requires]
python_version = "3.8"

25
Pipfile.lock generated
View file

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "11679fbbfbfe6ca9cf64b908f74c67c912a0b96bff52aa6f34ff7528e5b9f449"
"sha256": "fbc5bf15a267368c830aa098f5765fbd1a8b70feb242039f517a2550fd29f22d"
},
"pipfile-spec": 6,
"requires": {
@ -16,6 +16,21 @@
]
},
"default": {
"colorama": {
"hashes": [
"sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff",
"sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"
],
"version": "==0.4.3"
},
"crayons": {
"hashes": [
"sha256:50e5fa729d313e2c607ae8bf7b53bb487652e10bd8e7a1e08c4bc8bf62755ffc",
"sha256:8c9e4a3a607bc10e9a9140d496ecd16c6805088dd16c852c378f1f1d5db7aeb6"
],
"index": "pypi",
"version": "==0.3.0"
},
"fonttools": {
"hashes": [
"sha256:0c16d5da7846a92aedcb448277d13d3b729f7de48a917768d97ebd0d17067d7f",
@ -110,6 +125,14 @@
],
"version": "==7.0"
},
"isort": {
"hashes": [
"sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1",
"sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"
],
"index": "pypi",
"version": "==4.3.21"
},
"mypy": {
"hashes": [
"sha256:0a9a45157e532da06fe56adcfef8a74629566b607fa2c1ac0122d1ff995c748a",

View file

@ -13,25 +13,37 @@ small - which prompted me to make Cozette.
# Installation
**Note:** vscode doesn't support bitmap fonts. Until Cozette has a vectorized
version, it won't work in vscode.
[You can get Cozette over at the Releases tab](https://github.com/slavfox/Cozette/releases)!
If you're on Linux, the preferred format is `.otb` or `.ttf`. To install the
font, just throw it in your fonts directory (you probably want to follow your
distro's instructions). On Ubuntu, if you don't want to reconsider your distro
choice, you might need to
Cozette is distributed in two main variants: bitmap and vector.
Bitmap fonts are, effectively, just that - bitmaps. They scale terribly, but
look nice and sharp (and pixel-perfect) if you use them at their intended point
size. Vector fonts scale well, but in this case, might look ugly at
smaller point sizes because of antialiasing issues and the like.
VSCode and a lot of other GUI applications don't support bitmap fonts, so you
will want to use CozetteVector there. In applications that do support bitmap
fonts, you will want to use the normal, bitmap Cozette (unless it's too
small for you, in which case, CozetteVector scales better).
If you're on Linux, the preferred format is `.otb` (for bitmaps) or `.ttf`
(for CozetteVector). To install the font, just throw it in your fonts directory
(you probably want to follow your distro's instructions). On Ubuntu, if you
don't want to reconsider your distro choice, you might need to
[specifically enable bitmap fonts](https://bugs.launchpad.net/ubuntu/+source/fontconfig/+bug/1560114).
If you're on Mac, download the `.dfont` and install it with `Font Book.app`.
Both the bitmap `Cozette.dfont` and the vector `CozetteVector.dfont` should
work.
If you're on Windows,
If you're on Windows, just grab `CozetteVector.ttf`. If you want to get the
bitmap versions to work,
[follow the instructions from here](https://wiki.archlinux.org/index.php/installation_guide).
# Unicode support
As of release 1.0, these are the characters included in Cozette:
As of the current version, these are the characters included in Cozette:
![characters.png](./img/characters.png)
@ -53,9 +65,10 @@ where Cozette is so far, in the order the features are going to be implemented:
- [ ] Nerdfonts:
- [x] nf-seti-*
- [ ] nf-dev-*
- [ ] Glyph map generation (so I don't have to keep `characters.png` up to date)
- [ ] "True" TTF version
- [ ] Windows support
- [x] Glyph map generation (so I don't have to keep `characters.png` up to
date)
- [x] "True" TTF version
- [x] Windows support
- [ ] Bold version
- [ ] Italic version
- [ ] Ligatures

143
build.py
View file

@ -1,22 +1,143 @@
import os
import subprocess
import tempfile
from dataclasses import dataclass
from pathlib import Path
from cozette_builder.imagegen import save_sample, save_charlist, read_sample
from shlex import quote
from shutil import copy
from typing import Optional, Sequence, cast
import crayons # type: ignore
from cozette_builder.imagegen import read_sample, save_charlist, save_sample
from cozette_builder.ttfbuilder import TTFBuilder
REPO_ROOT = Path(__file__).resolve().parent
bdfpath = REPO_ROOT / "Cozette8" / "Cozette-10.bdf"
BUILD_DIR = REPO_ROOT / "build"
FONTNAME = "Cozette"
def save_images():
save_charlist(bdfpath, REPO_ROOT / "img" / "characters.png")
@dataclass
class Generate:
filename: str
bitmap_fmt: Optional[str] = None
save_sample(
"Clozette",
read_sample(REPO_ROOT / "img" / "sample.txt"),
REPO_ROOT / "img" / "sample.png",
def __str__(self):
return (
f'Generate("{self.filename}", "{self.bitmap_fmt}")'
if self.bitmap_fmt
else f'Generate("{self.filename}")'
)
def save_images(bdfpath):
with tempfile.TemporaryDirectory() as tmpdirname:
print(crayons.yellow("Making tmp fontdir"))
tmpdirpath = Path(tmpdirname)
copy(bdfpath, tmpdirname)
subprocess.run(["mkfontdir", tmpdirname])
subprocess.run(["xset", "+fp", tmpdirname])
subprocess.run(["xset", "fp", "rehash"])
print(crayons.yellow("Saving character map"))
save_charlist(FONTNAME, bdfpath, REPO_ROOT / "img" / "characters.png")
print(crayons.yellow("Saving sample image"))
save_sample(
FONTNAME,
read_sample(REPO_ROOT / "img" / "sample.txt"),
REPO_ROOT / "img" / "sample.png",
)
subprocess.run(["xset", "-fp", tmpdirname])
subprocess.run(["xset", "fp", "rehash"])
def fontforge(open: Path, generate: Sequence[Generate]):
BUILD_DIR.mkdir(exist_ok=True)
script = "; ".join(
[
f'Open("{open}")',
'RenameGlyphs("AGL with PUA")',
'Reencode("unicode")',
]
+ [str(gen) for gen in generate]
)
# No idea why this doesn't work without shell=True
subprocess.run(
[f"fontforge -lang ff -c {quote(script)}"], cwd=BUILD_DIR, shell=True
)
if __name__ == "__main__":
# save_images()
TTFBuilder.from_bdf_path(bdfpath).build("cloze.ttf")
def rename_single(dir: Path, pattern: str, newname: str) -> Path:
return cast(Path, next(dir.glob(pattern)).rename(dir / newname))
def mkbdf() -> Path:
print()
fontforge(
open=REPO_ROOT / "Cozette" / "Cozette.sfd",
generate=[Generate("cozette.", bitmap_fmt="bdf")],
)
bdfpath = rename_single(BUILD_DIR, "*.bdf", "cozette.bdf")
return bdfpath
def gen_bitmap_formats(bdfpath: Path):
fontforge(
open=bdfpath,
generate=[
Generate("cozette.", "otb"),
Generate("cozette.", "fnt"),
Generate("cozette_bitmap.ttf", "otb"),
Generate("cozette_bitmap.dfont", "sbit"),
],
)
rename_single(BUILD_DIR, "*.fnt", "cozette.fnt")
def fix_ttf(ttfpath: Path):
outname = "CozetteVector"
script = "; ".join(
[
f'Open("{ttfpath}")',
"SelectWorthOutputting()",
"RemoveOverlap()",
"CorrectDirection()",
"Simplify()",
"Simplify(-1, 1.02)",
'RenameGlyphs("AGL with PUA")',
'Reencode("unicode")',
f'Generate("{outname}.dfont")',
f'Generate("{outname}.otf.dfont")',
f'Generate("{outname}.otf")',
"ScaleToEm(1024)",
f'Generate("{outname}.ttf")',
]
)
# No idea why this doesn't work without shell=True
subprocess.run(
[f"fontforge -lang ff -c {quote(script)}"], cwd=BUILD_DIR, shell=True
)
ttfpath.unlink()
if __name__ == "__main__":
BUILD_DIR.mkdir(exist_ok=True)
os.chdir(BUILD_DIR)
print(crayons.blue("Building .bdf..."))
bdfpath = mkbdf()
print(crayons.green("Done!", bold=True))
print(crayons.blue("Building bitmap formats..."))
gen_bitmap_formats(bdfpath)
print(crayons.green("Done!", bold=True))
print(crayons.blue("Saving sample images..."))
save_images(bdfpath)
print(crayons.green("Done!", bold=True))
print(crayons.blue("Generating TTF..."))
ttfbuilder = TTFBuilder.from_bdf_path(bdfpath)
ttfbuilder.build("cozette-tmp.ttf")
print(crayons.green("Done!", bold=True))
print(crayons.blue("Fixing TTF..."))
fix_ttf(Path("cozette-tmp.ttf"))
print(crayons.green("Done!", bold=True))

View file

@ -1,16 +1,18 @@
from __future__ import annotations
from typing import (
Dict,
Iterable,
Union,
List,
Literal,
NamedTuple,
Dict,
List,
TextIO,
Tuple,
Union,
)
import numpy as np # type: ignore
from fontTools.pens.ttGlyphPen import TTGlyphPen
from fontTools.pens.ttGlyphPen import TTGlyphPen # type: ignore
Bit = Union[Literal[1], Literal[0]]
@ -27,9 +29,7 @@ class BdfGlyph:
self.bbx = bbx = BBX(*[int(s) for s in meta["BBX"].split()])
# this is probably wrong
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.bits: np.array = bits[0 : bbx.h, 0 : bbx.w]
self.metadata = meta
@classmethod
@ -54,26 +54,39 @@ class BdfGlyph:
def draw(self, ppp: float):
pen = TTGlyphPen(None)
for y, row in reversed(enumerate(self.bits)):
for y, row in enumerate(reversed(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.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('"').strip().split()
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]
specs[parts[0]] = parts[1].strip()
bitmap = []
while not (line := bdfstream.readline()).startswith("ENDCHAR"):
bitmap.append(int(line.strip(), 16))
@ -90,8 +103,8 @@ class BdfFont:
metadata = {}
while not (line := bdfstream.readline()).startswith("CHARS "):
try:
meta, val = line.split(maxsplit=1)
metadata[meta] = val
k, val = line.split(maxsplit=1)
metadata[k] = val.strip()
except ValueError:
pass
glyphs = {}
@ -109,4 +122,4 @@ class BdfFont:
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()
return self.metadata[k].strip('"').strip().split()

View file

@ -1,11 +1,11 @@
import subprocess
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.bdffont import BdfFont
import platform
import tempfile
from pathlib import Path
from shlex import quote
from typing import Dict, NamedTuple, Optional, Tuple
from unicodedata import east_asian_width as charwidth
from cozette_builder.bdffont import BdfFont
Color = Tuple[int, int, int]
@ -45,7 +45,7 @@ def wrap_text(src: str, width=79) -> Sample:
idx = 0
linebreaks = []
while idx < len(src):
if running_w >= width:
if running_w - int(src[idx] == " ") >= width:
linebreaks.append(idx - 1)
running_w = 0
sample_h += 1
@ -58,7 +58,7 @@ def wrap_text(src: str, width=79) -> Sample:
running_w += 1 if charwidth(src[idx]) != "W" else 2
idx += 1
for idx in reversed(linebreaks):
src = src[:idx] + "\n" + src[idx:]
src = src[:idx].rstrip() + "\n" + src[idx:].lstrip()
return Sample(src, width, sample_h)
@ -73,9 +73,8 @@ def read_sample(src_path: Path) -> Sample:
return Sample(src, w, h)
# Todo: rewrite this to use files instead of a string
def save_sample(
font: str,
fnt: str,
sample: Sample,
output_path: Path,
fgcolor: str = "#abb2bf",
@ -85,7 +84,6 @@ def save_sample(
if palette is None:
palette = default_palette
colored = color_text(sample.text, palette)
print(colored)
with tempfile.NamedTemporaryFile("w", delete=False) as f:
f.write(colored)
fp = f.name
@ -97,12 +95,14 @@ def save_sample(
subprocess.run(
[
"xterm",
"-en",
"utf8",
"-fg",
fgcolor,
"-bg",
bgcolor,
"-fa",
font,
fnt,
"-geometry",
f"{sample.width}x{sample.height}",
"-dc",
@ -137,15 +137,17 @@ def color_text(text: str, palette: Palette) -> str:
return colored_text
def save_charlist(bdf_font: Path, output_path: Path):
def save_charlist(fnt: str, bdf_font: Path, output_path: Path):
with bdf_font.open() as f:
bdf = BdfFont.from_bdf(f)
save_sample(
"Clozette",
fnt,
wrap_text(
"".join(
chr(c) for c in bdf.codepoints if c >= 32 and c not in (127,)
" ".join(
chr(c) for c in bdf.codepoints if c > 32 and c not in (127,)
)
.replace("", "")
.replace("\u03a2 ", "")
),
output_path,
)

View file

@ -1,7 +1,23 @@
from pathlib import Path
from fontTools.fontBuilder import FontBuilder
from fontTools.pens.ttGlyphPen import TTGlyphPen
from cozette_builder.bdffont import BdfFont, BdfGlyph
from fontTools.fontBuilder import FontBuilder # type: ignore
from cozette_builder.bdffont import BdfFont
with (Path(__file__).resolve().parent.parent / "LICENSE").open() as f:
LICENSE_TEXT = f.read()
def get_version():
cozette_path = (
Path(__file__).resolve().parent.parent / "Cozette" / "Cozette.sfd"
)
with cozette_path.open() as f:
for line in f:
if line.startswith("Version:"):
_, v = line.split()
return v
class TTFBuilder:
@classmethod
@ -23,10 +39,11 @@ class TTFBuilder:
self.startx = int(startx)
self.starty = int(starty)
def build(self, output_path: Path):
def build(self, output_path: str):
glyph_names = {
k: glyph.meta("STARTCHAR")[0] for k, glyph in
self.bdf.glyphs.items()
k: glyph.meta("STARTCHAR")[0]
for k, glyph in self.bdf.glyphs.items()
if k >= 0
}
ascent = round(self.ascent * self.ppp)
descent = round(self.descent * self.ppp)
@ -34,36 +51,46 @@ class TTFBuilder:
self.fb.setupGlyphOrder(list(glyph_names.values()))
self.fb.setupCharacterMap(glyph_names)
advance_widths = {
name: 700 for name in glyph_names.values()
name: int(self.bdf.glyphs[k].meta("SWIDTH")[0])
for k, name in glyph_names.items()
}
# copied from fontbuilder for now
familyName = "HelloTestFont"
styleName = "TotallyNormal"
version = "0.1"
family_name = "CozetteVector"
style_name = "Regular"
version = get_version()
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,
)
# scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=IWS-Chapter08
namestrings = {
"familyName": {"en": family_name},
"styleName": {"en": style_name},
"uniqueFontIdentifier": f"fontBuilder: {family_name}.{style_name}",
"fullName": family_name,
"psName": f"{family_name}-{style_name}",
"version": f"Version {version}",
"copyright": "(c) 2020 Slavfox",
"manufacturer": "Slavfox",
"designer": "Slavfox",
"description": "Programming bitmap font optimized for coziness",
"vendorURL": "https://github.com/slavfox",
"designerURL": "https://github.com/slavfox",
"licenseDescription": LICENSE_TEXT,
"licenseInfoURL": "https://opensource.org/licenses/MIT",
"sampleText": "A wizards job is to vex chumps quickly in fog.",
}
self.fb.setupGlyf(
{name: self.bdf.glyphs[k].draw(self.ppp) for k, name in
glyph_names.items()}
{
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)
metrics = {
name: (w, self.fb.font["glyf"][name].xMin)
for name, w in advance_widths.items()
}
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.setupNameTable(namestrings)
self.fb.setupOS2(
sTypoAscender=ascent, usWinAscent=ascent, usWinDescent=descent
)
self.fb.setupPost()
self.fb.save(str(output_path))
self.fb.save(output_path)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -15,7 +15,7 @@ $BLUE$┏━━━━━━━━━━━━━━━━━━━━━━━
│ , . ` ~$BLUE$ │
╰───────────────────────────╯
$YELLOW$  
$BLUE$│ $FG$╔═══════════════╗
$BLUE$└──$FG$║ $GREEN$✓ ✔ $YELLOW$✕ ✖ $MAGENTA$✗ ✘ $FG$ ║$BLUE$▒
$FG$║ $BLUE$       $FG$║$BLUE$▒

10
pyproject.toml Normal file
View file

@ -0,0 +1,10 @@
[tool.isort]
multi_line_output = 3
include_trailing_comma = true
force_grid_wrap = 0
use_parentheses = true
line_length = 79
[tool.black]
target-version = ['py38']
line_length = 79