Compare commits

...

3 commits

Author SHA1 Message Date
c06928af07 docs: add svg for make help 2022-09-05 18:40:52 -05:00
90ef76cd4a update NF batteries 2022-09-05 18:40:36 -05:00
59b79dc2ac add .task.mk 2022-09-05 18:37:47 -05:00
6 changed files with 637 additions and 31 deletions

250
.task.mk Normal file
View file

@ -0,0 +1,250 @@
# }> [github.com/daylinmorgan/task.mk] <{ #
# Copyright (c) 2022 Daylin Morgan
# MIT License
# 22.9.5
#
# task.mk should be included at the bottom of your Makefile.
# See below for the standard configuration options that should be set prior to including this file.
# You can update your .task.mk with `make _update-task.mk`
# ---- CONFIG ---- #
HEADER_COLOR ?= b_cyan
PARAMS_COLOR ?= b_magenta
ACCENT_COLOR ?= b_yellow
GOAL_COLOR ?= $(ACCENT_COLOR)
MSG_COLOR ?= faint
HELP_SEP ?= |
HELP_SORT ?= # sort goals alphabetically
# python f-string literals
EPILOG ?=
define USAGE ?=
{ansi.$(HEADER_COLOR)}usage{ansi.end}:
make <recipe>
endef
# ---- [buitlin recipes] ---- #
## h, help | show this help
.PHONY: help h
help h:
$(call py,help_py)
.PHONY: _help
_help: export SHOW_HIDDEN=true
_help: help
ifdef PRINT_VARS
$(foreach v,$(PRINT_VARS),$(eval export $(v)))
.PHONY: vars v
vars v:
$(call py,vars_py,$(PRINT_VARS))
endif
## _print-ansi | show all possible ansi color code combinations
.PHONY:
_print-ansi:
$(call py,print_ansi_py)
# functions to take f-string literals and pass to python print
tprint = $(call py,info_py,$(1))
tprint-sh = $(call pysh,info_py,$(1))
_update-task.mk:
$(call tprint,Updating task.mk)
curl https://raw.githubusercontent.com/daylinmorgan/task.mk/main/task.mk -o .task.mk
export MAKEFILE_LIST
# ---- [python/bash script runner] ---- #
# modified from https://unix.stackexchange.com/a/223093
define \n
endef
escape_shellstring = $(subst `,\`,$(subst ",\",$(subst $$,\$$,$(subst \,\\,$1))))
escape_printf = $(subst \,\\,$(subst %,%%,$1))
create_string = $(subst $(\n),\n,$(call escape_shellstring,$(call escape_printf,$1)))
ifdef DEBUG
define py
@printf "Python Script:\n"
@printf -- "----------------\n"
@printf "$(call create_string,$($(1)))\n"
@printf -- "----------------\n"
@printf "$(call create_string,$($(1)))" | python3
endef
define tbash
@printf "Bash Script:\n"
@printf -- "----------------\n"
@printf "$(call create_string,$($(1)))\n"
@printf -- "----------------\n"
@printf "$(call create_string,$($(1)))" | bash
endef
else
py = @printf "$(call create_string,$($(1)))" | python3
tbash = @printf "$(call create_string,$($(1)))" | bash
endif
pysh = printf "$(call create_string,$($(1)))" | python3
# ---- [python scripts] ---- #
define help_py
import os
import re
$(ansi_py)
pattern = re.compile(r"^## (.*) \| (.*)")
makefile = ""
for file in os.getenv("MAKEFILE_LIST").split():
with open(file, "r") as f:
makefile += f.read() + "\n\n"
def get_help(file):
for line in file.splitlines():
match = pattern.search(line)
if match:
if not os.getenv("SHOW_HIDDEN") and match.groups()[0].startswith("_"):
continue
else:
yield match.groups()
print(f"""$(USAGE)""")
goals = list(get_help(makefile))
if os.getenv("SORT_HELP",False):
goals.sort(key=lambda i: i[0])
goal_len = max(len(goal[0]) for goal in goals)
for goal, msg in goals:
print(
f"{ansi.$(GOAL_COLOR)}{goal:>{goal_len}}{ansi.end} $(HELP_SEP) {ansi.$(MSG_COLOR)}{msg}{ansi.end}"
)
print(f"""$(EPILOG)""")
endef
define ansi_py
import os
import sys
color2byte = dict(
black=0,
red=1,
green=2,
yellow=3,
blue=4,
magenta=5,
cyan=6,
white=7,
)
state2byte = dict(
bold=1, faint=2, italic=3, underline=4, blink=5, fast_blink=6, crossed=9
)
def fg(byte):
return 30 + byte
def bg(byte):
return 40 + byte
class Ansi:
"""ANSI color codes"""
def setcode(self, name, escape_code):
if not sys.stdout.isatty() or os.getenv("NO_COLOR", False):
setattr(self, name, "")
else:
setattr(self, name, escape_code)
def __init__(self):
self.setcode("end", "\033[0m")
for name, byte in color2byte.items():
self.setcode(name, f"\033[{fg(byte)}m")
self.setcode(f"b_{name}", f"\033[1;{fg(byte)}m")
self.setcode(f"d_{name}", f"\033[2;{fg(byte)}m")
for bgname, bgbyte in color2byte.items():
self.setcode(f"{name}_on_{bgname}", f"\033[{bg(bgbyte)};{fg(byte)}m")
for name, byte in state2byte.items():
self.setcode(name, f"\033[{byte}m")
a = ansi = Ansi()
endef
define info_py
$(ansi_py)
print(f"""$(2)""")
endef
define print_ansi_py
$(ansi_py)
codes_names = {
getattr(ansi, attr): attr
for attr in dir(ansi)
if attr[0:1] != "_" and attr != "end" and attr != "setcode"
}
for code in sorted(codes_names.keys(), key=lambda item: (len(item), item)):
print("{:>20} {}".format(codes_names[code], code + "******" + ansi.end))
endef
define vars_py
import os
$(ansi_py)
vars = "$2".split()
length = max((len(v) for v in vars))
print(f"{ansi.$(HEADER_COLOR)}vars:{ansi.end}\n")
for v in vars:
print(f" {ansi.b_magenta}{v:<{length}}{ansi.end} = {os.getenv(v)}")
print()
endef

View file

@ -6,33 +6,52 @@ ML_TYPES := $(shell find MonoLisa -mindepth 1 -type d -printf "%f ")
UNKNOWN := $(filter-out $(OK_TYPES),$(ML_TYPES)) UNKNOWN := $(filter-out $(OK_TYPES),$(ML_TYPES))
$(if $(UNKNOWN),$(error unknown font type in ./MonoLisa: $(UNKNOWN))) $(if $(UNKNOWN),$(error unknown font type in ./MonoLisa: $(UNKNOWN)))
msg = $(call tprint,{a.bold}==>{a.end} {a.b_magenta}$(1){a.end} {a.bold}<=={a.end})
## patch | add nerd fonts to MonoLisa
.PHONY: patch .PHONY: patch
patch: $(addprefix patch-,$(ML_TYPES)) patch: $(addprefix patch-,$(ML_TYPES))
patch-%: ./bin/font-patcher patch-%: ./bin/font-patcher
@echo "==> Patching MonoLisa $* Files <==" $(call msg, Patching MonoLisa $* Files)
@./bin/patch-monolisa $* $(ARGS) @./bin/patch-monolisa $* $(ARGS)
## update-fonts | move fonts and update fc-cache
.PHONY: update-fonts .PHONY: update-fonts
update-fonts: update-fonts:
@echo "==> Adding Fonts To System <==" $(call msg,Adding Fonts To System)
@./bin/update-fonts @./bin/update-fonts
@fc-cache -f -v @fc-cache -f -v
## check | check fc-list for MonoLisa
.PHONY: check .PHONY: check
check: check:
@echo "==> Checking System For Fonts <==" $(call msg, Checking System for Fonts)
@fc-list | grep "MonoLisa" @fc-list | grep "MonoLisa"
## update-src | update nerd fonts source
.PHONY: update-src .PHONY: update-src
update-src: update-src:
@echo "==> Updating Source File <==" $(call msg,Updating Source Files)
@./bin/update-src @./bin/update-src
## lint | check shell scripts
.PHONY: lint .PHONY: lint
lint: lint:
@shfmt -w -s $(shell shfmt -f bin/) @shfmt -w -s $(shell shfmt -f bin/)
## clean | remove patched fonts
.PHONY: clean .PHONY: clean
clean: clean:
@rm -r patched/* @rm -r patched/*
define USAGE
{a.b_green}Update MonoLisa with Nerd Fonts! {a.end}
{a.$(HEADER_COLOR)}usage{a.end}:
make <recipe>
endef
-include .task.mk
$(if $(filter help,$(MAKECMDGOALS)),.task.mk: ; curl -fsSL https://raw.githubusercontent.com/daylinmorgan/task.mk/v22.9.5/task.mk -o .task.mk)

View file

@ -4,6 +4,10 @@
tested w/ MonoLisa v1.808 tested w/ MonoLisa v1.808
<p align="center">
<img src="./assets/help.svg" width=400>
</p>
## Before You Begin ## Before You Begin
First you will need to install `fontforge` First you will need to install `fontforge`

111
assets/help.svg Normal file
View file

@ -0,0 +1,111 @@
<svg class="rich-terminal shadow" viewBox="0 0 585.3333333333334 399.5333333333333" xmlns="http://www.w3.org/2000/svg">
<!-- Generated with Rich https://www.textualize.io -->
<style>
@font-face {
font-family: "Fira Code";
src: local("FiraCode-Regular"),
url("https://cdn.jsdelivr.net/gh/ryanoasis/nerd-fonts@2.1.0/patched-fonts/FiraCode/Regular/complete/Fira%20Code%20Regular%20Nerd%20Font%20Complete.ttf") format("truetype");
font-style: normal;
font-weight: 400;
}
@font-face {
font-family: "Fira Code";
src: local("FiraCode-Bold"),
url("https://cdn.jsdelivr.net/gh/ryanoasis/nerd-fonts@2.1.0/patched-fonts/FiraCode/Bold/complete/Fira%20Code%20Bold%20Nerd%20Font%20Complete.ttf") format("truetype");
font-style: bold;
font-weight: 700;
}
.terminal-747138606-matrix {
font-family: Fira Code, monospace;
font-size: 20px;
line-height: 24.4px;
font-variant-east-asian: full-width;
}
.terminal-747138606-title {
font-size: 18px;
font-weight: bold;
font-family: arial;
}
.shadow {
-webkit-filter: drop-shadow( 2px 5px 2px rgba(0, 0, 0, .7));
filter: drop-shadow( 2px 5px 2px rgba(0, 0, 0, .7));
}
.terminal-747138606-r1 { fill: #a6e3a1;font-weight: bold }
.terminal-747138606-r2 { fill: #c6d0f5 }
.terminal-747138606-r3 { fill: #94e2d5;font-weight: bold }
.terminal-747138606-r4 { fill: #f9e2af;font-weight: bold }
.terminal-747138606-r5 { fill: #8288a5 }
</style>
<defs>
<clipPath id="terminal-747138606-clip-terminal">
<rect x="0" y="0" width="548.0" height="316.2" />
</clipPath>
<clipPath id="terminal-747138606-line-0">
<rect x="0" y="1.5" width="549" height="24.65"/>
</clipPath>
<clipPath id="terminal-747138606-line-1">
<rect x="0" y="25.9" width="549" height="24.65"/>
</clipPath>
<clipPath id="terminal-747138606-line-2">
<rect x="0" y="50.3" width="549" height="24.65"/>
</clipPath>
<clipPath id="terminal-747138606-line-3">
<rect x="0" y="74.7" width="549" height="24.65"/>
</clipPath>
<clipPath id="terminal-747138606-line-4">
<rect x="0" y="99.1" width="549" height="24.65"/>
</clipPath>
<clipPath id="terminal-747138606-line-5">
<rect x="0" y="123.5" width="549" height="24.65"/>
</clipPath>
<clipPath id="terminal-747138606-line-6">
<rect x="0" y="147.9" width="549" height="24.65"/>
</clipPath>
<clipPath id="terminal-747138606-line-7">
<rect x="0" y="172.3" width="549" height="24.65"/>
</clipPath>
<clipPath id="terminal-747138606-line-8">
<rect x="0" y="196.7" width="549" height="24.65"/>
</clipPath>
<clipPath id="terminal-747138606-line-9">
<rect x="0" y="221.1" width="549" height="24.65"/>
</clipPath>
<clipPath id="terminal-747138606-line-10">
<rect x="0" y="245.5" width="549" height="24.65"/>
</clipPath>
<clipPath id="terminal-747138606-line-11">
<rect x="0" y="269.9" width="549" height="24.65"/>
</clipPath>
</defs>
<rect fill="#1e1e2e" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="10.1667" y="1" width="565" height="365.2" rx="8"/><text class="terminal-747138606-title" fill="#c6d0f5" text-anchor="middle" x="282" y="27">help</text>
<g transform="translate(32,22)">
<circle cx="0" cy="0" r="7" fill="#ff5f57"/>
<circle cx="22" cy="0" r="7" fill="#febc2e"/>
<circle cx="44" cy="0" r="7" fill="#28c840"/>
</g>
<g transform="translate(18.166666666666664, 41) scale(.95)" clip-path="url(#terminal-747138606-clip-terminal)">
<g class="terminal-747138606-matrix">
<text class="terminal-747138606-r1" x="0" y="20" textLength="402.6" clip-path="url(#terminal-747138606-line-0)">Update&#160;MonoLisa&#160;with&#160;Nerd&#160;Fonts!&#160;</text><text class="terminal-747138606-r2" x="549" y="20" textLength="12.2" clip-path="url(#terminal-747138606-line-0)">
</text><text class="terminal-747138606-r2" x="549" y="44.4" textLength="12.2" clip-path="url(#terminal-747138606-line-1)">
</text><text class="terminal-747138606-r3" x="0" y="68.8" textLength="61" clip-path="url(#terminal-747138606-line-2)">usage</text><text class="terminal-747138606-r2" x="61" y="68.8" textLength="12.2" clip-path="url(#terminal-747138606-line-2)">:</text><text class="terminal-747138606-r2" x="549" y="68.8" textLength="12.2" clip-path="url(#terminal-747138606-line-2)">
</text><text class="terminal-747138606-r2" x="97.6" y="93.2" textLength="158.6" clip-path="url(#terminal-747138606-line-3)">make&#160;&lt;recipe&gt;</text><text class="terminal-747138606-r2" x="549" y="93.2" textLength="12.2" clip-path="url(#terminal-747138606-line-3)">
</text><text class="terminal-747138606-r2" x="549" y="117.6" textLength="12.2" clip-path="url(#terminal-747138606-line-4)">
</text><text class="terminal-747138606-r4" x="0" y="142" textLength="146.4" clip-path="url(#terminal-747138606-line-5)">&#160;&#160;&#160;&#160;&#160;&#160;&#160;patch</text><text class="terminal-747138606-r2" x="146.4" y="142" textLength="36.6" clip-path="url(#terminal-747138606-line-5)">&#160;|&#160;</text><text class="terminal-747138606-r5" x="183" y="142" textLength="317.2" clip-path="url(#terminal-747138606-line-5)">add&#160;nerd&#160;fonts&#160;to&#160;MonoLisa</text><text class="terminal-747138606-r2" x="549" y="142" textLength="12.2" clip-path="url(#terminal-747138606-line-5)">
</text><text class="terminal-747138606-r4" x="0" y="166.4" textLength="146.4" clip-path="url(#terminal-747138606-line-6)">update-fonts</text><text class="terminal-747138606-r2" x="146.4" y="166.4" textLength="36.6" clip-path="url(#terminal-747138606-line-6)">&#160;|&#160;</text><text class="terminal-747138606-r5" x="183" y="166.4" textLength="366" clip-path="url(#terminal-747138606-line-6)">move&#160;fonts&#160;and&#160;update&#160;fc-cache</text><text class="terminal-747138606-r2" x="549" y="166.4" textLength="12.2" clip-path="url(#terminal-747138606-line-6)">
</text><text class="terminal-747138606-r4" x="0" y="190.8" textLength="146.4" clip-path="url(#terminal-747138606-line-7)">&#160;&#160;&#160;&#160;&#160;&#160;&#160;check</text><text class="terminal-747138606-r2" x="146.4" y="190.8" textLength="36.6" clip-path="url(#terminal-747138606-line-7)">&#160;|&#160;</text><text class="terminal-747138606-r5" x="183" y="190.8" textLength="317.2" clip-path="url(#terminal-747138606-line-7)">check&#160;fc-list&#160;for&#160;MonoLisa</text><text class="terminal-747138606-r2" x="549" y="190.8" textLength="12.2" clip-path="url(#terminal-747138606-line-7)">
</text><text class="terminal-747138606-r4" x="0" y="215.2" textLength="146.4" clip-path="url(#terminal-747138606-line-8)">&#160;&#160;update-src</text><text class="terminal-747138606-r2" x="146.4" y="215.2" textLength="36.6" clip-path="url(#terminal-747138606-line-8)">&#160;|&#160;</text><text class="terminal-747138606-r5" x="183" y="215.2" textLength="292.8" clip-path="url(#terminal-747138606-line-8)">update&#160;nerd&#160;fonts&#160;source</text><text class="terminal-747138606-r2" x="549" y="215.2" textLength="12.2" clip-path="url(#terminal-747138606-line-8)">
</text><text class="terminal-747138606-r4" x="0" y="239.6" textLength="146.4" clip-path="url(#terminal-747138606-line-9)">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;lint</text><text class="terminal-747138606-r2" x="146.4" y="239.6" textLength="36.6" clip-path="url(#terminal-747138606-line-9)">&#160;|&#160;</text><text class="terminal-747138606-r5" x="183" y="239.6" textLength="231.8" clip-path="url(#terminal-747138606-line-9)">check&#160;shell&#160;scripts</text><text class="terminal-747138606-r2" x="549" y="239.6" textLength="12.2" clip-path="url(#terminal-747138606-line-9)">
</text><text class="terminal-747138606-r4" x="0" y="264" textLength="146.4" clip-path="url(#terminal-747138606-line-10)">&#160;&#160;&#160;&#160;&#160;&#160;&#160;clean</text><text class="terminal-747138606-r2" x="146.4" y="264" textLength="36.6" clip-path="url(#terminal-747138606-line-10)">&#160;|&#160;</text><text class="terminal-747138606-r5" x="183" y="264" textLength="244" clip-path="url(#terminal-747138606-line-10)">remove&#160;patched&#160;fonts</text><text class="terminal-747138606-r2" x="549" y="264" textLength="12.2" clip-path="url(#terminal-747138606-line-10)">
</text><text class="terminal-747138606-r4" x="0" y="288.4" textLength="146.4" clip-path="url(#terminal-747138606-line-11)">&#160;&#160;&#160;&#160;&#160;h,&#160;help</text><text class="terminal-747138606-r2" x="146.4" y="288.4" textLength="36.6" clip-path="url(#terminal-747138606-line-11)">&#160;|&#160;</text><text class="terminal-747138606-r5" x="183" y="288.4" textLength="170.8" clip-path="url(#terminal-747138606-line-11)">show&#160;this&#160;help</text><text class="terminal-747138606-r2" x="549" y="288.4" textLength="12.2" clip-path="url(#terminal-747138606-line-11)">
</text><text class="terminal-747138606-r2" x="549" y="312.8" textLength="12.2" clip-path="url(#terminal-747138606-line-12)">
</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9 KiB

View file

@ -1,11 +1,14 @@
#!/usr/bin/env python #!/usr/bin/env python
# coding=utf8 # coding=utf8
# Nerd Fonts Version: 2.1.0 # Nerd Fonts Version: 2.2.1
# script version: 3.0.1 # Script version is further down
from __future__ import absolute_import, print_function, unicode_literals from __future__ import absolute_import, print_function, unicode_literals
version = "2.1.0" # Change the script version when you edit this script:
script_version = "3.0.3"
version = "2.2.1"
projectName = "Nerd Fonts" projectName = "Nerd Fonts"
projectNameAbbreviation = "NF" projectNameAbbreviation = "NF"
projectNameSingular = projectName[:-1] projectNameSingular = projectName[:-1]
@ -36,6 +39,122 @@ except ImportError:
) )
) )
# This is for experimenting
sys.path.insert(0, os.path.abspath(os.path.dirname(sys.argv[0])) + '/bin/scripts/name_parser/')
try:
from FontnameParser import FontnameParser
from FontnameTools import FontnameTools
FontnameParserOK = True
except ImportError:
FontnameParserOK = False
class TableHEADWriter:
""" Access to the HEAD table without external dependencies """
def getlong(self, pos = None):
""" Get four bytes from the font file as integer number """
if pos:
self.goto(pos)
return (ord(self.f.read(1)) << 24) + (ord(self.f.read(1)) << 16) + (ord(self.f.read(1)) << 8) + ord(self.f.read(1))
def getshort(self, pos = None):
""" Get two bytes from the font file as integer number """
if pos:
self.goto(pos)
return (ord(self.f.read(1)) << 8) + ord(self.f.read(1))
def putlong(self, num, pos = None):
""" Put number as four bytes into font file """
if pos:
self.goto(pos)
self.f.write(bytearray([(num >> 24) & 0xFF, (num >> 16) & 0xFF ,(num >> 8) & 0xFF, num & 0xFF]))
self.modified = True
def putshort(self, num, pos = None):
""" Put number as two bytes into font file """
if pos:
self.goto(pos)
self.f.write(bytearray([(num >> 8) & 0xFF, num & 0xFF]))
self.modified = True
def calc_checksum(self, start, end, checksum = 0):
""" Calculate a font table checksum, optionally ignoring another embedded checksum value (for table 'head') """
self.f.seek(start)
for i in range(start, end - 4, 4):
checksum += self.getlong()
checksum &= 0xFFFFFFFF
i += 4
extra = 0
for j in range(4):
if i + j <= end:
extra += ord(self.f.read(1))
extra = extra << 8
checksum = (checksum + extra) & 0xFFFFFFFF
return checksum
def find_head_table(self):
""" Search all tables for the HEAD table and store its metadata """
self.f.seek(4)
numtables = self.getshort()
self.f.seek(3*2, 1)
for i in range(numtables):
tab_name = self.f.read(4)
self.tab_check_offset = self.f.tell()
self.tab_check = self.getlong()
self.tab_offset = self.getlong()
self.tab_length = self.getlong()
if tab_name == b'head':
return
raise Exception('No HEAD table found')
def goto(self, where):
""" Go to a named location in the file or to the specified index """
if type(where) is str:
positions = {'checksumAdjustment': 2+2+4,
'flags': 2+2+4+4+4,
'lowestRecPPEM': 2+2+4+4+4+2+2+8+8+2+2+2+2+2,
}
where = self.tab_offset + positions[where]
self.f.seek(where)
def calc_full_checksum(self, check = False):
""" Calculate the whole file's checksum """
self.f.seek(0, 2)
self.end = self.f.tell()
full_check = self.calc_checksum(0, self.end, (-self.checksum_adj) & 0xFFFFFFFF)
if check and (0xB1B0AFBA - full_check) & 0xFFFFFFFF != self.checksum_adj:
sys.exit("Checksum of whole font is bad")
return full_check
def calc_table_checksum(self, check = False):
tab_check_new = self.calc_checksum(self.tab_offset, self.tab_offset + self.tab_length - 1, (-self.checksum_adj) & 0xFFFFFFFF)
if check and tab_check_new != self.tab_check:
sys.exit("Checksum of 'head' in font is bad")
return tab_check_new
def reset_table_checksum(self):
new_check = self.calc_table_checksum()
self.putlong(new_check, self.tab_check_offset)
def reset_full_checksum(self):
new_adj = (0xB1B0AFBA - self.calc_full_checksum()) & 0xFFFFFFFF
self.putlong(new_adj, 'checksumAdjustment')
def close(self):
self.f.close()
def __init__(self, filename):
self.modified = False
self.f = open(filename, 'r+b')
self.find_head_table()
self.flags = self.getshort('flags')
self.lowppem = self.getshort('lowestRecPPEM')
self.checksum_adj = self.getlong('checksumAdjustment')
class font_patcher: class font_patcher:
def __init__(self): def __init__(self):
@ -44,7 +163,6 @@ class font_patcher:
self.config = None # class 'configparser.ConfigParser' self.config = None # class 'configparser.ConfigParser'
self.sourceFont = None # class 'fontforge.font' self.sourceFont = None # class 'fontforge.font'
self.octiconsExactEncodingPosition = True self.octiconsExactEncodingPosition = True
self.fontlinuxExactEncodingPosition = True
self.patch_set = None # class 'list' self.patch_set = None # class 'list'
self.font_dim = None # class 'dict' self.font_dim = None # class 'dict'
self.onlybitmaps = 0 self.onlybitmaps = 0
@ -62,7 +180,8 @@ class font_patcher:
self.sourceFont = fontforge.open(self.args.font, 1) # 1 = ("fstypepermitted",)) self.sourceFont = fontforge.open(self.args.font, 1) # 1 = ("fstypepermitted",))
except Exception: except Exception:
sys.exit(projectName + ": Can not open font, try to open with fontforge interactively to get more information") sys.exit(projectName + ": Can not open font, try to open with fontforge interactively to get more information")
self.setup_font_names() self.setup_version()
self.setup_name_backup()
self.remove_ligatures() self.remove_ligatures()
make_sure_path_exists(self.args.outputdir) make_sure_path_exists(self.args.outputdir)
self.check_position_conflicts() self.check_position_conflicts()
@ -81,7 +200,7 @@ class font_patcher:
def patch(self): def patch(self):
print("{} Patcher v{} executing\n".format(projectName, version)) print("{} Patcher v{} ({}) executing\n".format(projectName, version, script_version))
if self.args.single: if self.args.single:
# Force width to be equal on all glyphs to ensure the font is considered monospaced on Windows. # Force width to be equal on all glyphs to ensure the font is considered monospaced on Windows.
@ -124,19 +243,53 @@ class font_patcher:
if symfont: if symfont:
symfont.close() symfont.close()
print("\nDone with Patch Sets, generating font...")
# The grave accent and fontforge:
# If the type is 'auto' fontforge changes it to 'mark' on export.
# We can not prevent this. So set it to 'baseglyph' instead, as
# that resembles the most common expectations.
# This is not needed with fontforge March 2022 Release anymore.
if "grave" in self.sourceFont:
self.sourceFont["grave"].glyphclass="baseglyph"
def generate(self):
# the `PfEd-comments` flag is required for Fontforge to save '.comment' and '.fontlog'. # the `PfEd-comments` flag is required for Fontforge to save '.comment' and '.fontlog'.
if self.sourceFont.fullname != None: if self.sourceFont.fullname != None:
self.sourceFont.generate(self.args.outputdir + "/" + self.sourceFont.fullname + self.extension, flags=(str('opentype'), str('PfEd-comments'))) outfile = self.args.outputdir + "/" + self.sourceFont.fullname + self.extension
print("\nGenerated: {}".format(self.sourceFont.fontname)) self.sourceFont.generate(outfile, flags=(str('opentype'), str('PfEd-comments')))
message = "\nGenerated: {} in '{}'".format(self.sourceFont.fullname, outfile)
else: else:
self.sourceFont.generate(self.args.outputdir + "/" + self.sourceFont.cidfontname + self.extension, flags=(str('opentype'), str('PfEd-comments'))) outfile = self.args.outputdir + "/" + self.sourceFont.cidfontname + self.extension
print("\nGenerated: {}".format(self.sourceFont.fullname)) self.sourceFont.generate(outfile, flags=(str('opentype'), str('PfEd-comments')))
message = "\nGenerated: {} in '{}'".format(self.sourceFont.fontname, outfile)
# Adjust flags that can not be changed via fontforge
try:
source_font = TableHEADWriter(self.args.font)
dest_font = TableHEADWriter(outfile)
if source_font.flags & 0x08 == 0 and dest_font.flags & 0x08 != 0:
print("Changing flags from 0x{:X} to 0x{:X}".format(dest_font.flags, dest_font.flags & ~0x08))
dest_font.putshort(dest_font.flags & ~0x08, 'flags') # clear 'ppem_to_int'
if source_font.lowppem != dest_font.lowppem:
print("Changing lowestRecPPEM from {} to {}".format(dest_font.lowppem, source_font.lowppem))
dest_font.putshort(source_font.lowppem, 'lowestRecPPEM')
if dest_font.modified:
dest_font.reset_table_checksum()
dest_font.reset_full_checksum()
except Exception as error:
print("Can not handle font flags ({})".format(repr(error)))
finally:
try:
source_font.close()
dest_font.close()
except:
pass
print(message)
if self.args.postprocess: if self.args.postprocess:
subprocess.call([self.args.postprocess, self.args.outputdir + "/" + self.sourceFont.fullname + self.extension]) subprocess.call([self.args.postprocess, outfile])
print("\nPost Processed: {}".format(self.sourceFont.fullname)) print("\nPost Processed: {}".format(outfile))
def setup_arguments(self): def setup_arguments(self):
@ -166,12 +319,14 @@ class font_patcher:
parser.add_argument('-ext', '--extension', dest='extension', default="", type=str, nargs='?', help='Change font file type to create (e.g., ttf, otf)') parser.add_argument('-ext', '--extension', dest='extension', default="", type=str, nargs='?', help='Change font file type to create (e.g., ttf, otf)')
parser.add_argument('-out', '--outputdir', dest='outputdir', default=".", type=str, nargs='?', help='The directory to output the patched font file to') parser.add_argument('-out', '--outputdir', dest='outputdir', default=".", type=str, nargs='?', help='The directory to output the patched font file to')
parser.add_argument('--glyphdir', dest='glyphdir', default=__dir__ + "/src/glyphs/", type=str, nargs='?', help='Path to glyphs to be used for patching') parser.add_argument('--glyphdir', dest='glyphdir', default=__dir__ + "/src/glyphs/", type=str, nargs='?', help='Path to glyphs to be used for patching')
parser.add_argument('--makegroups', dest='makegroups', default=False, action='store_true', help='Use alternative method to name patched fonts (experimental)')
# progress bar arguments - https://stackoverflow.com/questions/15008758/parsing-boolean-values-with-argparse # progress bar arguments - https://stackoverflow.com/questions/15008758/parsing-boolean-values-with-argparse
progressbars_group_parser = parser.add_mutually_exclusive_group(required=False) progressbars_group_parser = parser.add_mutually_exclusive_group(required=False)
progressbars_group_parser.add_argument('--progressbars', dest='progressbars', action='store_true', help='Show percentage completion progress bars per Glyph Set') progressbars_group_parser.add_argument('--progressbars', dest='progressbars', action='store_true', help='Show percentage completion progress bars per Glyph Set')
progressbars_group_parser.add_argument('--no-progressbars', dest='progressbars', action='store_false', help='Don\'t show percentage completion progress bars per Glyph Set') progressbars_group_parser.add_argument('--no-progressbars', dest='progressbars', action='store_false', help='Don\'t show percentage completion progress bars per Glyph Set')
parser.set_defaults(progressbars=True) parser.set_defaults(progressbars=True)
parser.add_argument('--also-windows', dest='alsowindows', default=False, action='store_true', help='Create two fonts, the normal and the --windows version')
# symbol fonts to include arguments # symbol fonts to include arguments
sym_font_group = parser.add_argument_group('Symbol Fonts') sym_font_group = parser.add_argument_group('Symbol Fonts')
@ -189,6 +344,9 @@ class font_patcher:
self.args = parser.parse_args() self.args = parser.parse_args()
if self.args.makegroups and not FontnameParserOK:
sys.exit(projectName + ": FontnameParser module missing (bin/scripts/name_parser/Fontname*), can not --makegroups".format(projectName))
# if you add a new font, set it to True here inside the if condition # if you add a new font, set it to True here inside the if condition
if self.args.complete: if self.args.complete:
self.args.fontawesome = True self.args.fontawesome = True
@ -219,6 +377,9 @@ class font_patcher:
font_complete = False font_complete = False
self.args.complete = font_complete self.args.complete = font_complete
if self.args.alsowindows:
self.args.windows = False
# this one also works but it needs to be updated every time a font is added # this one also works but it needs to be updated every time a font is added
# it was a conditional in self.setup_font_names() before, but it was missing # it was a conditional in self.setup_font_names() before, but it was missing
# a symbol font, so it would name the font complete without being so sometimes. # a symbol font, so it would name the font complete without being so sometimes.
@ -239,7 +400,17 @@ class font_patcher:
# ]) # ])
def setup_name_backup(self):
""" Store the original font names to be able to rename the font multiple times """
self.original_fontname = self.sourceFont.fontname
self.original_fullname = self.sourceFont.fullname
self.original_familyname = self.sourceFont.familyname
def setup_font_names(self): def setup_font_names(self):
self.sourceFont.fontname = self.original_fontname
self.sourceFont.fullname = self.original_fullname
self.sourceFont.familyname = self.original_familyname
verboseAdditionalFontNameSuffix = " " + projectNameSingular verboseAdditionalFontNameSuffix = " " + projectNameSingular
if self.args.windows: # attempt to shorten here on the additional name BEFORE trimming later if self.args.windows: # attempt to shorten here on the additional name BEFORE trimming later
additionalFontNameSuffix = " " + projectNameAbbreviation additionalFontNameSuffix = " " + projectNameAbbreviation
@ -285,6 +456,27 @@ class font_patcher:
additionalFontNameSuffix += " M" additionalFontNameSuffix += " M"
verboseAdditionalFontNameSuffix += " Mono" verboseAdditionalFontNameSuffix += " Mono"
if FontnameParserOK and self.args.makegroups:
use_fullname = type(self.sourceFont.fullname) == str # Usually the fullname is better to parse
# Use fullname if it is 'equal' to the fontname
if self.sourceFont.fullname:
use_fullname |= self.sourceFont.fontname.lower() == FontnameTools.postscript_char_filter(self.sourceFont.fullname).lower()
# Use fullname for any of these source fonts (that are impossible to disentangle from the fontname, we need the blanks)
for hit in [ 'Meslo' ]:
use_fullname |= self.sourceFont.fontname.lower().startswith(hit.lower())
parser_name = self.sourceFont.fullname if use_fullname else self.sourceFont.fontname
# Gohu fontnames hide the weight, but the file names are ok...
if parser_name.startswith('Gohu'):
parser_name = os.path.splitext(os.path.basename(self.args.font))[0]
n = FontnameParser(parser_name)
if not n.parse_ok:
print("Have only minimal naming information, check resulting name. Maybe omit --makegroups option")
n.drop_for_powerline()
n.enable_short_families(True, "Noto")
n.set_for_windows(self.args.windows)
# All the following stuff is ignored in makegroups-mode
# basically split the font name around the dash "-" to get the fontname and the style (e.g. Bold) # basically split the font name around the dash "-" to get the fontname and the style (e.g. Bold)
# this does not seem very reliable so only use the style here as a fallback if the font does not # this does not seem very reliable so only use the style here as a fallback if the font does not
# have an internal style defined (in sfnt_names) # have an internal style defined (in sfnt_names)
@ -343,8 +535,9 @@ class font_patcher:
familyname += " Mono" familyname += " Mono"
# Don't truncate the subfamily to keep fontname unique. MacOS treats fonts with # Don't truncate the subfamily to keep fontname unique. MacOS treats fonts with
# the same name as the same font, even if subFamily is different. # the same name as the same font, even if subFamily is different. Make sure to
fontname += '-' + subFamily # keep the resulting fontname (PostScript name) valid by removing spaces.
fontname += '-' + subFamily.replace(' ', '')
# rename font # rename font
# #
@ -418,6 +611,7 @@ class font_patcher:
fullname = replace_font_name(fullname, additionalFontNameReplacements2) fullname = replace_font_name(fullname, additionalFontNameReplacements2)
fontname = replace_font_name(fontname, additionalFontNameReplacements2) fontname = replace_font_name(fontname, additionalFontNameReplacements2)
if not (FontnameParserOK and self.args.makegroups):
# replace any extra whitespace characters: # replace any extra whitespace characters:
self.sourceFont.familyname = " ".join(familyname.split()) self.sourceFont.familyname = " ".join(familyname.split())
self.sourceFont.fullname = " ".join(fullname.split()) self.sourceFont.fullname = " ".join(fullname.split())
@ -427,9 +621,18 @@ class font_patcher:
self.sourceFont.appendSFNTName(str('English (US)'), str('Family'), self.sourceFont.familyname) self.sourceFont.appendSFNTName(str('English (US)'), str('Family'), self.sourceFont.familyname)
self.sourceFont.appendSFNTName(str('English (US)'), str('Compatible Full'), self.sourceFont.fullname) self.sourceFont.appendSFNTName(str('English (US)'), str('Compatible Full'), self.sourceFont.fullname)
self.sourceFont.appendSFNTName(str('English (US)'), str('SubFamily'), subFamily) self.sourceFont.appendSFNTName(str('English (US)'), str('SubFamily'), subFamily)
else:
fam_suffix = projectNameSingular if not self.args.windows else projectNameAbbreviation
fam_suffix += ' Mono' if self.args.single else ''
n.inject_suffix(verboseAdditionalFontNameSuffix, additionalFontNameSuffix, fam_suffix)
n.rename_font(self.sourceFont)
self.sourceFont.comment = projectInfo self.sourceFont.comment = projectInfo
self.sourceFont.fontlog = projectInfo self.sourceFont.fontlog = projectInfo
def setup_version(self):
""" Add the Nerd Font version to the original version """
# print("Version was {}".format(sourceFont.version)) # print("Version was {}".format(sourceFont.version))
if self.sourceFont.version != None: if self.sourceFont.version != None:
self.sourceFont.version += ";" + projectName + " " + version self.sourceFont.version += ";" + projectName + " " + version
@ -463,8 +666,6 @@ class font_patcher:
# Prevent glyph encoding position conflicts between glyph sets # Prevent glyph encoding position conflicts between glyph sets
if self.args.fontawesome and self.args.octicons: if self.args.fontawesome and self.args.octicons:
self.octiconsExactEncodingPosition = False self.octiconsExactEncodingPosition = False
if self.args.fontawesome or self.args.octicons:
self.fontlinuxExactEncodingPosition = False
def setup_patch_set(self): def setup_patch_set(self):
@ -603,7 +804,7 @@ class font_patcher:
{'Enabled': self.args.powersymbols, 'Name': "Power Symbols", 'Filename': "Unicode_IEC_symbol_font.otf", 'Exact': True, 'SymStart': 0x2B58, 'SymEnd': 0x2B58, 'SrcStart': None, 'SrcEnd': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, # Heavy Circle (aka Power Off) {'Enabled': self.args.powersymbols, 'Name': "Power Symbols", 'Filename': "Unicode_IEC_symbol_font.otf", 'Exact': True, 'SymStart': 0x2B58, 'SymEnd': 0x2B58, 'SrcStart': None, 'SrcEnd': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, # Heavy Circle (aka Power Off)
{'Enabled': self.args.material, 'Name': "Material", 'Filename': "materialdesignicons-webfont.ttf", 'Exact': False, 'SymStart': 0xF001, 'SymEnd': 0xF847, 'SrcStart': 0xF500, 'SrcEnd': 0xFD46, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, {'Enabled': self.args.material, 'Name': "Material", 'Filename': "materialdesignicons-webfont.ttf", 'Exact': False, 'SymStart': 0xF001, 'SymEnd': 0xF847, 'SrcStart': 0xF500, 'SrcEnd': 0xFD46, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT},
{'Enabled': self.args.weather, 'Name': "Weather Icons", 'Filename': "weather-icons/weathericons-regular-webfont.ttf", 'Exact': False, 'SymStart': 0xF000, 'SymEnd': 0xF0EB, 'SrcStart': 0xE300, 'SrcEnd': 0xE3EB, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, {'Enabled': self.args.weather, 'Name': "Weather Icons", 'Filename': "weather-icons/weathericons-regular-webfont.ttf", 'Exact': False, 'SymStart': 0xF000, 'SymEnd': 0xF0EB, 'SrcStart': 0xE300, 'SrcEnd': 0xE3EB, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT},
{'Enabled': self.args.fontlinux, 'Name': "Font Logos (Font Linux)", 'Filename': "font-logos.ttf", 'Exact': self.fontlinuxExactEncodingPosition, 'SymStart': 0xF100, 'SymEnd': 0xF12D, 'SrcStart': 0xF300, 'SrcEnd': 0xF32D, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, {'Enabled': self.args.fontlinux, 'Name': "Font Logos (Font Linux)", 'Filename': "font-logos.ttf", 'Exact': True, 'SymStart': 0xF300, 'SymEnd': 0xF32F, 'SrcStart': None, 'SrcEnd': None , 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT},
{'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons.ttf", 'Exact': self.octiconsExactEncodingPosition, 'SymStart': 0xF000, 'SymEnd': 0xF105, 'SrcStart': 0xF400, 'SrcEnd': 0xF505, 'ScaleGlyph': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Magnifying glass {'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons.ttf", 'Exact': self.octiconsExactEncodingPosition, 'SymStart': 0xF000, 'SymEnd': 0xF105, 'SrcStart': 0xF400, 'SrcEnd': 0xF505, 'ScaleGlyph': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Magnifying glass
{'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons.ttf", 'Exact': self.octiconsExactEncodingPosition, 'SymStart': 0x2665, 'SymEnd': 0x2665, 'SrcStart': None, 'SrcEnd': None, 'ScaleGlyph': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Heart {'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons.ttf", 'Exact': self.octiconsExactEncodingPosition, 'SymStart': 0x2665, 'SymEnd': 0x2665, 'SrcStart': None, 'SrcEnd': None, 'ScaleGlyph': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Heart
{'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons.ttf", 'Exact': self.octiconsExactEncodingPosition, 'SymStart': 0X26A1, 'SymEnd': 0X26A1, 'SrcStart': None, 'SrcEnd': None, 'ScaleGlyph': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Zap {'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons.ttf", 'Exact': self.octiconsExactEncodingPosition, 'SymStart': 0X26A1, 'SymEnd': 0X26A1, 'SrcStart': None, 'SrcEnd': None, 'ScaleGlyph': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Zap
@ -653,7 +854,9 @@ class font_patcher:
# Ignore the y-values, os2_winXXXXX values set above are used for line height # Ignore the y-values, os2_winXXXXX values set above are used for line height
# #
# 0x00-0x17f is the Latin Extended-A range # 0x00-0x17f is the Latin Extended-A range
for glyph in range(0x00, 0x17f): for glyph in range(0x21, 0x17f):
if glyph in range(0x7F, 0xBF):
continue # ignore special characters like '1/4' etc
try: try:
(_, _, xmax, _) = self.sourceFont[glyph].boundingBox() (_, _, xmax, _) = self.sourceFont[glyph].boundingBox()
except TypeError: except TypeError:
@ -665,6 +868,17 @@ class font_patcher:
# Calculate font height # Calculate font height
self.font_dim['height'] = abs(self.font_dim['ymin']) + self.font_dim['ymax'] self.font_dim['height'] = abs(self.font_dim['ymin']) + self.font_dim['ymax']
if self.font_dim['height'] == 0:
# This can only happen if the input font is empty
# Assume we are using our prepared templates
self.font_dim = {
'xmin' : 0,
'ymin' : -self.sourceFont.descent,
'xmax' : self.sourceFont.em,
'ymax' : self.sourceFont.ascent,
'width' : self.sourceFont.em,
'height': abs(self.sourceFont.descent) + self.sourceFont.ascent,
}
def get_scale_factor(self, sym_dim): def get_scale_factor(self, sym_dim):
@ -1030,6 +1244,14 @@ def main():
check_fontforge_min_version() check_fontforge_min_version()
patcher = font_patcher() patcher = font_patcher()
patcher.patch() patcher.patch()
print("\nDone with Patch Sets, generating font...\n")
patcher.setup_font_names()
patcher.generate()
# This mainly helps to improve CI runtime
if patcher.args.alsowindows:
patcher.args.windows = True
patcher.setup_font_names()
patcher.generate()
if __name__ == "__main__": if __name__ == "__main__":

Binary file not shown.