mirror of
https://github.com/daylinmorgan/monolisa-nerdfont-patch.git
synced 2025-01-07 19:37:32 -06:00
Compare commits
No commits in common. "2a0e39720f871c05f36b62eb50e7478875e41c16" and "5b946b0aca550d8ff2e54eca3a02bcbb9a2a25d5" have entirely different histories.
2a0e39720f
...
5b946b0aca
25 changed files with 246 additions and 1173 deletions
178
.gitignore
vendored
178
.gitignore
vendored
|
@ -1,180 +1,3 @@
|
|||
# Created by https://www.toptal.com/developers/gitignore/api/python
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=python
|
||||
|
||||
### Python ###
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/#use-with-ide
|
||||
.pdm.toml
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
### Python Patch ###
|
||||
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
|
||||
poetry.toml
|
||||
|
||||
# ruff
|
||||
.ruff_cache/
|
||||
|
||||
# LSP config files
|
||||
pyrightconfig.json
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/python
|
||||
#
|
||||
*.zip
|
||||
|
||||
MonoLisa/*
|
||||
|
@ -184,4 +7,3 @@ patched/*
|
|||
|
||||
nerd-fonts
|
||||
.env
|
||||
font-patcher-log.txt
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
exclude: "^(src/.*|bin/scripts|font-patcher)"
|
||||
exclude: "^(src/.*|bin/font-patcher)"
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.4.0
|
||||
|
@ -6,6 +6,10 @@ repos:
|
|||
- id: check-yaml
|
||||
- id: end-of-file-fixer
|
||||
- id: trailing-whitespace
|
||||
- repo: https://github.com/pycqa/isort
|
||||
rev: 5.12.0
|
||||
hooks:
|
||||
- id: isort
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 23.1.0
|
||||
hooks:
|
||||
|
|
4
Makefile
4
Makefile
|
@ -1,7 +1,7 @@
|
|||
-include .env
|
||||
ARGS ?= -c
|
||||
|
||||
patch: ## apply nerd fonts patch |> -gs b_magenta -ms bold
|
||||
patch: ./bin/font-patcher ## apply nerd fonts patch |> -gs b_magenta -ms bold
|
||||
@./patch-monolisa \
|
||||
$(ARGS) \
|
||||
-f MonoLisa/
|
||||
|
@ -24,8 +24,6 @@ lint: ## run pre-commit hooks
|
|||
|
||||
clean: ## remove patched fonts
|
||||
@rm -rf patched/*
|
||||
@rm -f ./font-patcher-log.txt
|
||||
@rm -f FontPatcher.zip
|
||||
|
||||
# depends on daylinmorgan/yartsu
|
||||
assets/help.svg:
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
# Change the script version when you edit this script:
|
||||
script_version = "4.1.2"
|
||||
script_version = "4.1.1"
|
||||
|
||||
version = "3.0.0"
|
||||
projectName = "Nerd Fonts"
|
||||
|
@ -741,7 +741,7 @@ class font_patcher:
|
|||
|
||||
def remove_ligatures(self):
|
||||
# let's deal with ligatures (mostly for monospaced fonts)
|
||||
# Usually removes 'fi' ligs that end up being only one cell wide, and 'ldot'
|
||||
# the tables have been removed from the repo with >this< commit
|
||||
if self.args.configfile and self.config.read(self.args.configfile):
|
||||
if self.args.removeligatures:
|
||||
logger.info("Removing ligatures from configfile `Subtables` section")
|
|
@ -1,418 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# coding=utf8
|
||||
|
||||
import re
|
||||
from FontnameTools import FontnameTools
|
||||
|
||||
|
||||
class FontnameParser:
|
||||
"""Parse a font name and generate all kinds of names"""
|
||||
|
||||
def __init__(self, filename, logger):
|
||||
"""Parse a font filename and store the results"""
|
||||
self.parse_ok = False
|
||||
self.use_short_families = (
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
) # ( camelcase name, short styles, aggressive )
|
||||
self.keep_regular_in_family = None # None = auto, True, False
|
||||
self.suppress_preferred_if_identical = True
|
||||
self.family_suff = ""
|
||||
self.ps_fontname_suff = ""
|
||||
self.short_family_suff = ""
|
||||
self.name_subst = []
|
||||
[
|
||||
self.parse_ok,
|
||||
self._basename,
|
||||
self.weight_token,
|
||||
self.style_token,
|
||||
self.other_token,
|
||||
self._rest,
|
||||
] = FontnameTools.parse_font_name(filename)
|
||||
self.basename = self._basename
|
||||
self.rest = self._rest
|
||||
self.add_name_substitution_table(FontnameTools.SIL_TABLE)
|
||||
self.rename_oblique = True
|
||||
self.logger = logger
|
||||
|
||||
def _make_ps_name(self, n, is_family):
|
||||
"""Helper to limit font name length in PS names"""
|
||||
fam = "family " if is_family else ""
|
||||
limit = 31 if is_family else 63
|
||||
if len(n) <= limit:
|
||||
return n
|
||||
r = re.search("(.*)(-.*)", n)
|
||||
if not r:
|
||||
new_n = n[:limit]
|
||||
else:
|
||||
q = limit - len(r.groups()[1])
|
||||
if q < 1:
|
||||
q = 1
|
||||
self.logger.error(
|
||||
"====-< Shortening too long PS {}name: Garbage warning".format(fam)
|
||||
)
|
||||
new_n = r.groups()[0][:q] + r.groups()[1]
|
||||
if new_n != n:
|
||||
self.logger.error(
|
||||
"====-< Shortening too long PS {}name: {} -> {}".format(fam, n, new_n)
|
||||
)
|
||||
return new_n
|
||||
|
||||
def _shortened_name(self):
|
||||
"""Return a blank free basename-rest combination"""
|
||||
if not self.use_short_families[0]:
|
||||
return (self.basename, self.rest)
|
||||
else:
|
||||
return (FontnameTools.concat(self.basename, self.rest).replace(" ", ""), "")
|
||||
|
||||
def set_keep_regular_in_family(self, keep):
|
||||
"""Familyname may contain 'Regular' where it should normally be suppressed"""
|
||||
self.keep_regular_in_family = keep
|
||||
|
||||
def set_expect_no_italic(self, noitalic):
|
||||
"""Prevents rewriting Oblique as family name part"""
|
||||
# To prevent naming clashes usually Oblique is moved out in the family name
|
||||
# because some fonts have Italic and Oblique, and we want to generate pure
|
||||
# RIBBI families in ID1/2.
|
||||
# But some fonts have Oblique instead of Italic, here the prevential movement
|
||||
# is not needed, or rather contraproductive. This can not be detected on a
|
||||
# font file level but needs to be specified per family from the outside.
|
||||
# Returns true if setting was successful.
|
||||
if "Italic" in self.style_token:
|
||||
self.rename_oblique = True
|
||||
return not noitalic
|
||||
self.rename_oblique = not noitalic
|
||||
return True
|
||||
|
||||
def set_suppress_preferred(self, suppress):
|
||||
"""Suppress ID16/17 if it is identical to ID1/2 (True is default)"""
|
||||
self.suppress_preferred_if_identical = suppress
|
||||
|
||||
def inject_suffix(self, family, ps_fontname, short_family):
|
||||
"""Add a custom additonal string that shows up in the resulting names"""
|
||||
self.family_suff = family.strip()
|
||||
self.ps_fontname_suff = ps_fontname.replace(" ", "")
|
||||
self.short_family_suff = short_family.strip()
|
||||
return self
|
||||
|
||||
def enable_short_families(self, camelcase_name, prefix, aggressive):
|
||||
"""Enable short styles in Family when (original) font name starts with prefix; enable CamelCase basename in (Typog.) Family"""
|
||||
# camelcase_name is boolean
|
||||
# prefix is either a string or False/True
|
||||
if isinstance(prefix, str):
|
||||
prefix = self._basename.startswith(prefix)
|
||||
self.use_short_families = (camelcase_name, prefix, aggressive)
|
||||
return self
|
||||
|
||||
def add_name_substitution_table(self, table):
|
||||
"""Have some fonts renamed, takes list of tuples (regex, replacement)"""
|
||||
# The regex will be anchored to name begin and used case insensitive
|
||||
# Replacement can have regex matches, mind to catch the correct source case
|
||||
self.name_subst = table
|
||||
self.basename = self._basename
|
||||
self.rest = self._rest
|
||||
for regex, replacement in self.name_subst:
|
||||
base_and_rest = self.basename + (" " + self.rest if len(self.rest) else "")
|
||||
m = re.match(regex, base_and_rest, re.IGNORECASE)
|
||||
if not m:
|
||||
continue
|
||||
i = len(self.basename) - len(m.group(0))
|
||||
if i < 0:
|
||||
self.basename = m.expand(replacement).rstrip()
|
||||
self.rest = self.rest[-(i + 1) :].lstrip()
|
||||
else:
|
||||
self.basename = m.expand(replacement) + self.basename[len(m.group(0)) :]
|
||||
return self
|
||||
|
||||
def drop_for_powerline(self):
|
||||
"""Remove 'for Powerline' from all names (can not be undone)"""
|
||||
if "Powerline" in self.other_token:
|
||||
idx = self.other_token.index("Powerline")
|
||||
self.other_token.pop(idx)
|
||||
if idx > 0 and self.other_token[idx - 1] == "For":
|
||||
self.other_token.pop(idx - 1)
|
||||
self._basename = re.sub(
|
||||
r"(\b|for\s?)?powerline\b", "", self._basename, 1, re.IGNORECASE
|
||||
).strip()
|
||||
self.add_name_substitution_table(self.name_subst) # re-evaluate
|
||||
return self
|
||||
|
||||
### Following the creation of the name parts:
|
||||
#
|
||||
# Relevant websites
|
||||
# https://www.fonttutorials.com/how-to-name-font-family/
|
||||
# https://docs.microsoft.com/en-us/typography/opentype/spec/name#name-ids
|
||||
# https://docs.microsoft.com/en-us/typography/opentype/spec/os2#fss
|
||||
# https://docs.microsoft.com/en-us/typography/opentype/spec/head#macstyle
|
||||
|
||||
# Example (mind that they group 'semibold' as classic-group-of-4 Bold, while we will always only take bold as Bold):
|
||||
# Adobe Caslon Pro Regular ID1: Adobe Caslon Pro ID2: Regular
|
||||
# Adobe Caslon Pro Italic ID1: Adobe Caslon Pro ID2: Italic
|
||||
# Adobe Caslon Pro Semibold ID1: Adobe Caslon Pro ID2: Bold ID16: Adobe Caslon Pro ID17: Semibold
|
||||
# Adobe Caslon Pro Semibold Italic ID1: Adobe Caslon Pro ID2: Bold Italic ID16: Adobe Caslon Pro ID17: Semibold Italic
|
||||
# Adobe Caslon Pro Bold ID1: Adobe Caslon Pro Bold ID2: Regular ID16: Adobe Caslon Pro ID17: Bold
|
||||
# Adobe Caslon Pro Bold Italic ID1: Adobe Caslon Pro Bold ID2: Italic ID16: Adobe Caslon Pro ID17: Bold Italic
|
||||
|
||||
# fontname === preferred_family + preferred_styles
|
||||
# fontname === family + subfamily
|
||||
#
|
||||
# familybase = basename + rest + other (+ suffix)
|
||||
# ID 1/2 just have self.style in the subfamily, all the rest ends up in the family
|
||||
# ID 16/17 have self.style and self.weight in the subfamily, the rest ends up in the family
|
||||
|
||||
def fullname(self):
|
||||
"""Get the SFNT Fullname (ID 4)"""
|
||||
styles = self.style_token
|
||||
weights = self.weight_token
|
||||
if self.keep_regular_in_family == None:
|
||||
keep_regular = FontnameTools.is_keep_regular(
|
||||
self._basename + " " + self._rest
|
||||
)
|
||||
else:
|
||||
keep_regular = self.keep_regular_in_family
|
||||
if "Regular" in styles and (
|
||||
not keep_regular or len(self.weight_token) > 0
|
||||
): # This is actually a malformed font name
|
||||
styles = list(self.style_token)
|
||||
styles.remove("Regular")
|
||||
# For naming purposes we want Oblique to be part of the styles
|
||||
(weights, styles) = FontnameTools.make_oblique_style(weights, styles)
|
||||
(name, rest) = self._shortened_name()
|
||||
if self.use_short_families[1]:
|
||||
[weights, styles] = FontnameTools.short_styles(
|
||||
[weights, styles], self.use_short_families[2]
|
||||
)
|
||||
return FontnameTools.concat(
|
||||
name, rest, self.other_token, self.short_family_suff, weights, styles
|
||||
)
|
||||
|
||||
def psname(self):
|
||||
"""Get the SFNT PostScriptName (ID 6)"""
|
||||
# This is almost self.family() + '-' + self.subfamily()
|
||||
(name, rest) = self._shortened_name()
|
||||
styles = self.style_token
|
||||
weights = self.weight_token
|
||||
if self.use_short_families[1]:
|
||||
styles = FontnameTools.short_styles(styles, self.use_short_families[2])
|
||||
weights = FontnameTools.short_styles(weights, self.use_short_families[2])
|
||||
fam = FontnameTools.camel_casify(
|
||||
FontnameTools.concat(name, rest, self.other_token, self.ps_fontname_suff)
|
||||
)
|
||||
sub = FontnameTools.camel_casify(FontnameTools.concat(weights, styles))
|
||||
if len(sub) > 0:
|
||||
sub = "-" + sub
|
||||
fam = FontnameTools.postscript_char_filter(fam)
|
||||
sub = FontnameTools.postscript_char_filter(sub)
|
||||
return self._make_ps_name(fam + sub, False)
|
||||
|
||||
def preferred_family(self):
|
||||
"""Get the SFNT Preferred Familyname (ID 16)"""
|
||||
(name, rest) = self._shortened_name()
|
||||
pfn = FontnameTools.concat(name, rest, self.other_token, self.family_suff)
|
||||
if self.suppress_preferred_if_identical and pfn == self.family():
|
||||
# Do not set if identical to ID 1
|
||||
return ""
|
||||
return pfn
|
||||
|
||||
def preferred_styles(self):
|
||||
"""Get the SFNT Preferred Styles (ID 17)"""
|
||||
styles = self.style_token
|
||||
weights = self.weight_token
|
||||
# For naming purposes we want Oblique to be part of the styles
|
||||
(weights, styles) = FontnameTools.make_oblique_style(weights, styles)
|
||||
pfs = FontnameTools.concat(weights, styles)
|
||||
if self.suppress_preferred_if_identical and pfs == self.subfamily():
|
||||
# Do not set if identical to ID 2
|
||||
return ""
|
||||
return pfs
|
||||
|
||||
def family(self):
|
||||
"""Get the SFNT Familyname (ID 1)"""
|
||||
# We use the short form of the styles to save on number of chars
|
||||
(name, rest) = self._shortened_name()
|
||||
other = self.other_token
|
||||
weights = self.weight_token
|
||||
aggressive = self.use_short_families[2]
|
||||
if not self.rename_oblique:
|
||||
(weights, styles) = FontnameTools.make_oblique_style(weights, [])
|
||||
if self.use_short_families[1]:
|
||||
[other, weights] = FontnameTools.short_styles([other, weights], aggressive)
|
||||
weights = [w if w != "Oblique" else "Obl" for w in weights]
|
||||
return FontnameTools.concat(name, rest, other, self.short_family_suff, weights)
|
||||
|
||||
def subfamily(self):
|
||||
"""Get the SFNT SubFamily (ID 2)"""
|
||||
styles = self.style_token
|
||||
weights = self.weight_token
|
||||
if not self.rename_oblique:
|
||||
(weights, styles) = FontnameTools.make_oblique_style(weights, styles)
|
||||
if len(styles) == 0:
|
||||
if "Oblique" in weights:
|
||||
return FontnameTools.concat(styles, "Italic")
|
||||
return "Regular"
|
||||
if "Oblique" in weights and not "Italic" in styles:
|
||||
return FontnameTools.concat(styles, "Italic")
|
||||
return FontnameTools.concat(styles)
|
||||
|
||||
def ps_familyname(self):
|
||||
"""Get the PS Familyname"""
|
||||
fam = self.preferred_family()
|
||||
if len(fam) < 1:
|
||||
fam = self.family()
|
||||
return self._make_ps_name(fam, True)
|
||||
|
||||
def macstyle(self, style):
|
||||
"""Modify a given macStyle value for current name, just bits 0 and 1 touched"""
|
||||
b = style & (~3)
|
||||
b |= 1 if "Bold" in self.style_token else 0
|
||||
b |= 2 if "Italic" in self.style_token else 0
|
||||
return b
|
||||
|
||||
def fs_selection(self, fs):
|
||||
"""Modify a given fsSelection value for current name, bits 0, 5, 6, 8, 9 touched"""
|
||||
ITALIC = 1 << 0
|
||||
BOLD = 1 << 5
|
||||
REGULAR = 1 << 6
|
||||
WWS = 1 << 8
|
||||
OBLIQUE = 1 << 9
|
||||
b = fs & (~(ITALIC | BOLD | REGULAR | WWS | OBLIQUE))
|
||||
if "Bold" in self.style_token:
|
||||
b |= BOLD
|
||||
# Ignore Italic if we have Oblique
|
||||
if "Oblique" in self.weight_token:
|
||||
b |= OBLIQUE
|
||||
elif "Italic" in self.style_token:
|
||||
b |= ITALIC
|
||||
# Regular is just the basic weight
|
||||
if len(self.weight_token) == 0:
|
||||
b |= REGULAR
|
||||
b |= WWS # We assert this by our naming process
|
||||
return b
|
||||
|
||||
def checklen(self, max_len, entry_id, name):
|
||||
"""Check the length of a name string and report violations"""
|
||||
if len(name) <= max_len:
|
||||
self.logger.debug(
|
||||
"=====> {:18} ok ({:2} <={:2}): {}".format(
|
||||
entry_id, len(name), max_len, name
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.logger.error(
|
||||
"====-< {:18} too long ({:2} > {:2}): {}".format(
|
||||
entry_id, len(name), max_len, name
|
||||
)
|
||||
)
|
||||
return name
|
||||
|
||||
def rename_font(self, font):
|
||||
"""Rename the font to include all information we found (font is fontforge font object)"""
|
||||
font.fondname = None
|
||||
font.fontname = self.psname()
|
||||
font.fullname = self.fullname()
|
||||
font.familyname = self.ps_familyname()
|
||||
|
||||
# We have to work around several issues in fontforge:
|
||||
#
|
||||
# a. Remove some entries from SFNT table; fontforge has no API function for that
|
||||
#
|
||||
# b. Fontforge does not allow to set SubFamily (and other) to any value:
|
||||
#
|
||||
# Fontforge lets you set any value, unless it is the default value. If it
|
||||
# is the default value it does not set anything. It also does not remove
|
||||
# a previously existing non-default value. Why it is done this way is
|
||||
# unclear:
|
||||
# fontforge/python.c SetSFNTName() line 11431
|
||||
# return( 1 ); /* If they set it to the default, there's nothing to do */
|
||||
#
|
||||
# Then is the question: What is the default? It is taken from the
|
||||
# currently set fontname (??!). The fontname is parsed and everything
|
||||
# behind the dash is the default SubFamily:
|
||||
# fontforge/tottf.c DefaultTTFEnglishNames()
|
||||
# fontforge/splinefont.c _GetModifiers()
|
||||
#
|
||||
# To fix this without touching Fontforge we need to set the SubFamily
|
||||
# directly in the SFNT table:
|
||||
#
|
||||
# c. Fontforge has the bug that it allows to write empty-string to a SFNT field
|
||||
# and it is actually embedded as empty string, but empty strings are not
|
||||
# shown if you query the sfnt_names *rolleyes*
|
||||
|
||||
version_tag = ""
|
||||
sfnt_list = []
|
||||
TO_DEL = [
|
||||
"Family",
|
||||
"SubFamily",
|
||||
"Fullname",
|
||||
"PostScriptName",
|
||||
"Preferred Family",
|
||||
"Preferred Styles",
|
||||
"Compatible Full",
|
||||
"WWS Family",
|
||||
"WWS Subfamily",
|
||||
"UniqueID",
|
||||
"CID findfont Name",
|
||||
]
|
||||
# Remove these entries in all languages and add (at least the vital ones) some
|
||||
# back, but only as 'English (US)'. This makes sure we do not leave contradicting
|
||||
# names over different languages.
|
||||
for l, k, v in list(font.sfnt_names):
|
||||
if not k in TO_DEL:
|
||||
sfnt_list += [(l, k, v)]
|
||||
if k == "Version" and l == "English (US)":
|
||||
version_tag = " " + v.split()[-1]
|
||||
|
||||
sfnt_list += [
|
||||
(
|
||||
"English (US)",
|
||||
"Family",
|
||||
self.checklen(31, "Family (ID 1)", self.family()),
|
||||
)
|
||||
] # 1
|
||||
sfnt_list += [
|
||||
(
|
||||
"English (US)",
|
||||
"SubFamily",
|
||||
self.checklen(31, "SubFamily (ID 2)", self.subfamily()),
|
||||
)
|
||||
] # 2
|
||||
sfnt_list += [("English (US)", "UniqueID", self.fullname() + version_tag)] # 3
|
||||
sfnt_list += [
|
||||
(
|
||||
"English (US)",
|
||||
"Fullname",
|
||||
self.checklen(63, "Fullname (ID 4)", self.fullname()),
|
||||
)
|
||||
] # 4
|
||||
sfnt_list += [
|
||||
(
|
||||
"English (US)",
|
||||
"PostScriptName",
|
||||
self.checklen(63, "PSN (ID 6)", self.psname()),
|
||||
)
|
||||
] # 6
|
||||
|
||||
p_fam = self.preferred_family()
|
||||
if len(p_fam):
|
||||
sfnt_list += [
|
||||
(
|
||||
"English (US)",
|
||||
"Preferred Family",
|
||||
self.checklen(31, "PrefFamily (ID 16)", p_fam),
|
||||
)
|
||||
] # 16
|
||||
p_sty = self.preferred_styles()
|
||||
if len(p_sty):
|
||||
sfnt_list += [
|
||||
(
|
||||
"English (US)",
|
||||
"Preferred Styles",
|
||||
self.checklen(31, "PrefStyles (ID 17)", p_sty),
|
||||
)
|
||||
] # 17
|
||||
|
||||
font.sfnt_names = tuple(sfnt_list)
|
||||
|
||||
font.macstyle = self.macstyle(0)
|
||||
font.os2_stylemap = self.fs_selection(0)
|
|
@ -1,441 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# coding=utf8
|
||||
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
class FontnameTools:
|
||||
"""Deconstruct a font filename to get standardized name parts"""
|
||||
|
||||
@staticmethod
|
||||
def front_upper(word):
|
||||
"""Capitalize a string (but keep case of subsequent chars)"""
|
||||
return word[:1].upper() + word[1:]
|
||||
|
||||
@staticmethod
|
||||
def camel_casify(word):
|
||||
"""Remove blanks and use CamelCase for the new word"""
|
||||
return "".join(map(FontnameTools.front_upper, word.split(" ")))
|
||||
|
||||
@staticmethod
|
||||
def camel_explode(word):
|
||||
"""Explode CamelCase -> Camel Case"""
|
||||
# But do not explode "JetBrains" etc at string start...
|
||||
excludes = [
|
||||
"JetBrains",
|
||||
"DejaVu",
|
||||
"OpenDyslexicAlta",
|
||||
"OpenDyslexicMono",
|
||||
"OpenDyslexic",
|
||||
"DaddyTimeMono",
|
||||
"InconsolataGo",
|
||||
"ProFontWindows",
|
||||
"ProFont",
|
||||
"ProggyClean",
|
||||
]
|
||||
m = re.match("(" + "|".join(excludes) + ")(.*)", word)
|
||||
(prefix, word) = m.group(1, 2) if m != None else ("", word)
|
||||
if len(word) == 0:
|
||||
return prefix
|
||||
parts = re.split("(?<=[a-z0-9])(?=[A-Z])", word)
|
||||
if len(prefix):
|
||||
parts.insert(0, prefix)
|
||||
return " ".join(parts)
|
||||
|
||||
@staticmethod
|
||||
def drop_empty(l):
|
||||
"""Remove empty strings from list of strings"""
|
||||
return [x for x in l if len(x) > 0]
|
||||
|
||||
@staticmethod
|
||||
def concat(*all_things):
|
||||
"""Flatten list of (strings or lists of strings) to a blank-separated string"""
|
||||
all = []
|
||||
for thing in all_things:
|
||||
if type(thing) is not list:
|
||||
all.append(thing)
|
||||
else:
|
||||
all += thing
|
||||
return " ".join(FontnameTools.drop_empty(all))
|
||||
|
||||
@staticmethod
|
||||
def unify_style_names(style_name):
|
||||
"""Substitude some known token with standard wording"""
|
||||
known_names = {
|
||||
# Source of the table is the current sourcefonts
|
||||
# Left side needs to be lower case
|
||||
"-": "",
|
||||
"book": "",
|
||||
"text": "",
|
||||
"ce": "CE",
|
||||
#'semibold': 'Demi',
|
||||
"ob": "Oblique",
|
||||
"it": "Italic",
|
||||
"i": "Italic",
|
||||
"b": "Bold",
|
||||
"normal": "Regular",
|
||||
"c": "Condensed",
|
||||
"r": "Regular",
|
||||
"m": "Medium",
|
||||
"l": "Light",
|
||||
}
|
||||
if style_name in known_names:
|
||||
return known_names[style_name.lower()]
|
||||
return style_name
|
||||
|
||||
@staticmethod
|
||||
def find_in_dicts(key, dicts):
|
||||
"""Find an entry in a list of dicts, return entry and in which list it was"""
|
||||
for i, d in enumerate(dicts):
|
||||
if key in d:
|
||||
return (d[key], i)
|
||||
return (None, 0)
|
||||
|
||||
@staticmethod
|
||||
def get_shorten_form_idx(aggressive, prefix, form_if_prefixed):
|
||||
"""Get the tuple index of known_* data tables"""
|
||||
if aggressive:
|
||||
return 0
|
||||
if len(prefix):
|
||||
return form_if_prefixed
|
||||
return 1
|
||||
|
||||
@staticmethod
|
||||
def shorten_style_name(name, aggressive):
|
||||
"""Substitude some known styles to short form"""
|
||||
# If aggressive is False create the mild short form
|
||||
# aggressive == True: Always use first form of everything
|
||||
# aggressive == False:
|
||||
# - has no modifier: use the second form
|
||||
# - has modifier: use second form of mod plus first form of weights2
|
||||
# - has modifier: use second form of mod plus second form of widths
|
||||
name_rest = name
|
||||
name_pre = ""
|
||||
form = FontnameTools.get_shorten_form_idx(aggressive, "", 0)
|
||||
for mod in FontnameTools.known_modifiers:
|
||||
if name.startswith(mod) and len(name) > len(
|
||||
mod
|
||||
): # Second condition specifically for 'Demi'
|
||||
name_pre = FontnameTools.known_modifiers[mod][form]
|
||||
name_rest = name[len(mod) :]
|
||||
break
|
||||
subst, i = FontnameTools.find_in_dicts(
|
||||
name_rest, [FontnameTools.known_weights2, FontnameTools.known_widths]
|
||||
)
|
||||
form = FontnameTools.get_shorten_form_idx(aggressive, name_pre, i)
|
||||
if isinstance(subst, tuple):
|
||||
return name_pre + subst[form]
|
||||
if not len(name_pre):
|
||||
# The following sets do not allow modifiers
|
||||
subst, _ = FontnameTools.find_in_dicts(
|
||||
name_rest, [FontnameTools.known_weights1, FontnameTools.known_slopes]
|
||||
)
|
||||
if isinstance(subst, tuple):
|
||||
return subst[form]
|
||||
return name
|
||||
|
||||
@staticmethod
|
||||
def short_styles(lists, aggressive):
|
||||
"""Shorten all style names in a list or a list of lists"""
|
||||
if not len(lists) or not isinstance(lists[0], list):
|
||||
return list(
|
||||
map(lambda x: FontnameTools.shorten_style_name(x, aggressive), lists)
|
||||
)
|
||||
return [
|
||||
list(map(lambda x: FontnameTools.shorten_style_name(x, aggressive), styles))
|
||||
for styles in lists
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def make_oblique_style(weights, styles):
|
||||
"""Move "Oblique" from weights to styles for font naming purposes"""
|
||||
if "Oblique" in weights:
|
||||
weights = list(weights)
|
||||
weights.remove("Oblique")
|
||||
styles = list(styles)
|
||||
styles.append("Oblique")
|
||||
return (weights, styles)
|
||||
|
||||
@staticmethod
|
||||
def get_name_token(name, tokens, allow_regex_token=False):
|
||||
"""Try to find any case insensitive token from tokens in the name, return tuple with found token-list and rest"""
|
||||
# The default mode (allow_regex_token = False) will try to find any verbatim string in the
|
||||
# tokens list (case insensitive matching) and give that tokens list item back with
|
||||
# unchanged case (i.e. [ 'Bold' ] will match "bold" and return it as [ 'Bold', ]
|
||||
# In the regex mode (allow_regex_token = True) it will use the tokens elements as
|
||||
# regexes and return the original (i.e. from name) case.
|
||||
#
|
||||
# Token are always used in a regex and may not capture, use non capturing
|
||||
# grouping if needed (?: ... )
|
||||
lower_tokens = [t.lower() for t in tokens]
|
||||
not_matched = ""
|
||||
all_tokens = []
|
||||
j = 1
|
||||
regex = re.compile("(.*?)(" + "|".join(tokens) + ")(.*)", re.IGNORECASE)
|
||||
while j:
|
||||
j = regex.match(name)
|
||||
if not j:
|
||||
break
|
||||
if len(j.groups()) != 3:
|
||||
sys.exit("Malformed regex in FontnameTools.get_name_token()")
|
||||
not_matched += (
|
||||
" " + j.groups()[0]
|
||||
) # Blanc prevents unwanted concatenation of unmatched substrings
|
||||
tok = j.groups()[1].lower()
|
||||
if tok in lower_tokens:
|
||||
tok = tokens[lower_tokens.index(tok)]
|
||||
tok = FontnameTools.unify_style_names(tok)
|
||||
if len(tok):
|
||||
all_tokens.append(tok)
|
||||
name = j.groups()[2] # Recurse rest
|
||||
not_matched += " " + name
|
||||
return (not_matched.strip(), all_tokens)
|
||||
|
||||
@staticmethod
|
||||
def postscript_char_filter(name):
|
||||
"""Filter out characters that are not allowed in Postscript names"""
|
||||
# The name string must be restricted to the printable ASCII subset, codes 33 to 126,
|
||||
# except for the 10 characters '[', ']', '(', ')', '{', '}', '<', '>', '/', '%'
|
||||
out = ""
|
||||
for c in name:
|
||||
if c in "[](){}<>/%" or ord(c) < 33 or ord(c) > 126:
|
||||
continue
|
||||
out += c
|
||||
return out
|
||||
|
||||
SIL_TABLE = [
|
||||
("(a)nonymous", r"\1nonymice"),
|
||||
("(b)itstream( ?)(v)era( ?sans ?mono)?", r"\1itstrom\2Wera"),
|
||||
("(s)ource", r"\1auce"),
|
||||
("(h)ermit", r"\1urmit"),
|
||||
("(h)asklig", r"\1asklug"),
|
||||
("(s)hare", r"\1hure"),
|
||||
("IBM[- ]?plex", r"Blex"), # We do not keep the case here
|
||||
("(t)erminus", r"\1erminess"),
|
||||
("(l)iberation", r"\1iteration"),
|
||||
("iA([- ]?)writer", r"iM\1Writing"),
|
||||
("(a)nka/(c)oder", r"\1na\2onder"),
|
||||
("(c)ascadia( ?)(c)ode", r"\1askaydia\2\3ove"),
|
||||
("(c)ascadia( ?)(m)ono", r"\1askaydia\2\3ono"),
|
||||
("(m)( ?)plus", r"\1+"), # Added this, because they use a plus symbol :->
|
||||
("Gohufont", r"GohuFont"), # Correct to CamelCase
|
||||
# Noone cares that font names starting with a digit are forbidden:
|
||||
("IBM 3270", r"3270"), # for historical reasons and 'IBM' is a TM or something
|
||||
# Some name parts that are too long for us
|
||||
("(.*sans ?m)ono", r"\1"), # Various SomenameSansMono fonts
|
||||
("(.*code ?lat)in Expanded", r"\1X"), # for 'M PLUS Code Latin Expanded'
|
||||
("(.*code ?lat)in", r"\1"), # for 'M PLUS Code Latin'
|
||||
("(b)ig( ?)(b)lue( ?)(t)erminal", r"\1ig\3lue\5erm"), # Shorten BigBlueTerminal
|
||||
("(.*)437TT", r"\g<1>437"), # Shorten BigBlueTerminal 437 TT even further
|
||||
("(.*dyslexic ?alt)a", r"\1"), # Open Dyslexic Alta -> Open Dyslexic Alt
|
||||
("(.*dyslexic ?m)ono", r"\1"), # Open Dyslexic Mono -> Open Dyslexic M
|
||||
("(overpass ?m)ono", r"\1"), # Overpass Mono -> Overpass M
|
||||
("(proggyclean) ?tt", r"\1"), # Remove TT from ProggyClean
|
||||
(
|
||||
"(terminess) ?\(ttf\)",
|
||||
r"\1",
|
||||
), # Remove TTF from Terminus (after renamed to Terminess)
|
||||
("(im ?writing ?q)uattro", r"\1uat"), # Rename iM Writing Quattro to Quat
|
||||
(
|
||||
"(im ?writing ?(mono|duo|quat)) ?s",
|
||||
r"\1",
|
||||
), # Remove S from all iM Writing styles
|
||||
]
|
||||
|
||||
# From https://adobe-type-tools.github.io/font-tech-notes/pdfs/5088.FontNames.pdf
|
||||
# The first short variant is from the linked table.
|
||||
# The second (longer) short variant is from diverse fonts like Noto.
|
||||
# We can
|
||||
# - use the long form
|
||||
# - use the very short form (first)
|
||||
# - use mild short form:
|
||||
# - has no modifier: use the second form
|
||||
# - has modifier: use second form of mod plus first form of weights2
|
||||
# - has modifier: use second form of mod plus second form of widths
|
||||
# This is encoded in get_shorten_form_idx()
|
||||
known_weights1 = { # can not take modifiers
|
||||
"Medium": ("Md", "Med"),
|
||||
"Nord": ("Nd", "Nord"),
|
||||
"Book": ("Bk", "Book"),
|
||||
"Poster": ("Po", "Poster"),
|
||||
"Demi": (
|
||||
"Dm",
|
||||
"Demi",
|
||||
), # Demi is sometimes used as a weight, sometimes as a modifier
|
||||
"Regular": ("Rg", "Reg"),
|
||||
"Display": ("DS", "Disp"),
|
||||
"Super": ("Su", "Sup"),
|
||||
"Retina": ("Rt", "Ret"),
|
||||
}
|
||||
known_weights2 = { # can take modifiers
|
||||
"Black": ("Blk", "Black"),
|
||||
"Bold": ("Bd", "Bold"),
|
||||
"Heavy": ("Hv", "Heavy"),
|
||||
"Thin": ("Th", "Thin"),
|
||||
"Light": ("Lt", "Light"),
|
||||
" ": (), # Just for CodeClimate :-/
|
||||
}
|
||||
known_widths = { # can take modifiers
|
||||
"Compressed": ("Cm", "Comp"),
|
||||
"Extended": ("Ex", "Extd"),
|
||||
"Condensed": ("Cn", "Cond"),
|
||||
"Narrow": ("Nr", "Narrow"),
|
||||
"Compact": ("Ct", "Compact"),
|
||||
}
|
||||
known_slopes = { # can not take modifiers
|
||||
"Inclined": ("Ic", "Incl"),
|
||||
"Oblique": ("Obl", "Obl"),
|
||||
"Italic": ("It", "Italic"),
|
||||
"Upright": ("Up", "Uprght"),
|
||||
"Kursiv": ("Ks", "Kurs"),
|
||||
"Sloped": ("Sl", "Slop"),
|
||||
}
|
||||
known_modifiers = {
|
||||
"Demi": ("Dm", "Dem"),
|
||||
"Ultra": ("Ult", "Ult"),
|
||||
"Semi": ("Sm", "Sem"),
|
||||
"Extra": ("X", "Ext"),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def is_keep_regular(basename):
|
||||
"""This has been decided by the font designers, we need to mimic that (for comparison purposes)"""
|
||||
KEEP_REGULAR = [
|
||||
"Agave",
|
||||
"Arimo",
|
||||
"Aurulent",
|
||||
"Cascadia",
|
||||
"Cousine",
|
||||
"Fantasque",
|
||||
"Fira",
|
||||
"Overpass",
|
||||
"Lilex",
|
||||
"Inconsolata$", # not InconsolataGo
|
||||
"IAWriter",
|
||||
"Meslo",
|
||||
"Monoid",
|
||||
"Mononoki",
|
||||
"Hack",
|
||||
"JetBrains Mono",
|
||||
"Noto Sans",
|
||||
"Noto Serif",
|
||||
"Victor",
|
||||
]
|
||||
for kr in KEEP_REGULAR:
|
||||
if (basename.rstrip() + "$").startswith(kr):
|
||||
return True
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def _parse_simple_font_name(name):
|
||||
"""Parse a filename that does not follow the 'FontFamilyName-FontStyle' pattern"""
|
||||
# No dash in name, maybe we have blanc separated filename?
|
||||
if " " in name:
|
||||
return FontnameTools.parse_font_name(name.replace(" ", "-"))
|
||||
# Do we have a number-name boundary?
|
||||
p = re.split("(?<=[0-9])(?=[a-zA-Z])", name)
|
||||
if len(p) > 1:
|
||||
return FontnameTools.parse_font_name("-".join(p))
|
||||
# Or do we have CamelCase?
|
||||
n = FontnameTools.camel_explode(name)
|
||||
if n != name:
|
||||
return FontnameTools.parse_font_name(n.replace(" ", "-"))
|
||||
return (False, FontnameTools.camel_casify(name), [], [], [], "")
|
||||
|
||||
@staticmethod
|
||||
def parse_font_name(name):
|
||||
"""Expects a filename following the 'FontFamilyName-FontStyle' pattern and returns ... parts"""
|
||||
name = re.sub(
|
||||
r"\bsemi-condensed\b", "SemiCondensed", name, 1, re.IGNORECASE
|
||||
) # Just for "3270 Semi-Condensed" :-/
|
||||
name = re.sub("[_\s]+", " ", name)
|
||||
matches = re.match(r"([^-]+)(?:-(.*))?", name)
|
||||
familyname = FontnameTools.camel_casify(matches.group(1))
|
||||
style = matches.group(2)
|
||||
|
||||
if not style:
|
||||
return FontnameTools._parse_simple_font_name(name)
|
||||
|
||||
# These are the FontStyle keywords we know, in three categories
|
||||
# Weights end up as Typographic Family parts ('after the dash')
|
||||
# Styles end up as Family parts (for classic grouping of four)
|
||||
# Others also end up in Typographic Family ('before the dash')
|
||||
weights = (
|
||||
[
|
||||
m + s
|
||||
for s in list(FontnameTools.known_weights2)
|
||||
+ list(FontnameTools.known_widths)
|
||||
for m in list(FontnameTools.known_modifiers) + [""]
|
||||
if m != s
|
||||
]
|
||||
+ list(FontnameTools.known_weights1)
|
||||
+ list(FontnameTools.known_slopes)
|
||||
)
|
||||
styles = [
|
||||
"Bold",
|
||||
"Italic",
|
||||
"Regular",
|
||||
"Normal",
|
||||
]
|
||||
weights = [w for w in weights if w not in styles]
|
||||
# Some font specialities:
|
||||
other = [
|
||||
"-",
|
||||
"Book",
|
||||
"For",
|
||||
"Powerline",
|
||||
"Text", # Plex
|
||||
"IIx", # Profont IIx
|
||||
"LGC", # Inconsolata LGC
|
||||
r"\bCE\b", # ProggycleanTT CE
|
||||
r"[12][cmp]n?", # MPlus
|
||||
r"(?:uni-)?1[14]", # GohuFont uni
|
||||
]
|
||||
|
||||
# Sometimes used abbreviations
|
||||
weight_abbrevs = [
|
||||
"ob",
|
||||
"c",
|
||||
"m",
|
||||
"l",
|
||||
]
|
||||
style_abbrevs = [
|
||||
"it",
|
||||
"r",
|
||||
"b",
|
||||
"i",
|
||||
]
|
||||
|
||||
(style, weight_token) = FontnameTools.get_name_token(style, weights)
|
||||
(style, style_token) = FontnameTools.get_name_token(style, styles)
|
||||
(style, other_token) = FontnameTools.get_name_token(style, other, True)
|
||||
if (
|
||||
len(style) < 4 and style.lower() != "pro"
|
||||
): # Prevent 'r' of Pro to be detected as style_abbrev
|
||||
(style, weight_token_abbrevs) = FontnameTools.get_name_token(
|
||||
style, weight_abbrevs
|
||||
)
|
||||
(style, style_token_abbrevs) = FontnameTools.get_name_token(
|
||||
style, style_abbrevs
|
||||
)
|
||||
weight_token += weight_token_abbrevs
|
||||
style_token += style_token_abbrevs
|
||||
while "Regular" in style_token and len(style_token) > 1:
|
||||
# Correct situation where "Regular" and something else is given
|
||||
style_token.remove("Regular")
|
||||
|
||||
# Recurse to see if unmatched stuff between dashes can belong to familyname
|
||||
matches2 = re.match(r"(\w+)-(.*)", style)
|
||||
if matches2:
|
||||
return FontnameTools.parse_font_name(
|
||||
familyname + matches2.group(1) + "-" + matches2.group(2)
|
||||
)
|
||||
|
||||
style = re.sub(
|
||||
r"(^|\s)\d+(\.\d+)+(\s|$)", r"\1\3", style
|
||||
) # Remove (free standing) version numbers
|
||||
style_parts = FontnameTools.drop_empty(style.split(" "))
|
||||
style = " ".join(map(FontnameTools.front_upper, style_parts))
|
||||
familyname = FontnameTools.camel_explode(familyname)
|
||||
return (True, familyname, weight_token, style_token, other_token, style)
|
|
@ -1,7 +1,15 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
rm -rf font-patcher
|
||||
wget https://github.com/ryanoasis/nerd-fonts/raw/master/FontPatcher.zip -O FontPatcher.zip
|
||||
unzip -u FontPatcher.zip -x "readme.md"
|
||||
REPO_URL='https://github.com/ryanoasis/nerd-fonts.git'
|
||||
|
||||
rm -rf nerd-fonts
|
||||
|
||||
git clone --filter=blob:none --no-checkout --depth 1 --sparse $REPO_URL
|
||||
cd nerd-fonts || exit
|
||||
git sparse-checkout add src/glyphs
|
||||
git checkout
|
||||
|
||||
cp font-patcher ../bin/font-patcher
|
||||
cp src/glyphs/** ../src/glyphs -r
|
||||
|
||||
echo "don't forget to commit your changes!"
|
||||
|
|
|
@ -140,7 +140,7 @@ def patch_single_font(
|
|||
cmd = [
|
||||
"fontforge",
|
||||
"-script",
|
||||
"./font-patcher",
|
||||
"./bin/font-patcher",
|
||||
"--glyphdir",
|
||||
"./src/glyphs/",
|
||||
"-out",
|
||||
|
@ -182,6 +182,8 @@ def patch_font_dir_docker(
|
|||
f"{font_dir_path}:/in",
|
||||
"-v",
|
||||
f"{output_path}:/out",
|
||||
"-u",
|
||||
f"{os.getuid()}:{os.getegid()}",
|
||||
"nerdfonts/patcher",
|
||||
]
|
||||
|
||||
|
|
BIN
src/glyphs/FontAwesome.otf
Normal file
BIN
src/glyphs/FontAwesome.otf
Normal file
Binary file not shown.
|
@ -0,0 +1,59 @@
|
|||
SplineFontDB: 3.0
|
||||
FontName: Symbols-1000-em
|
||||
FullName: Symbols-1000-em
|
||||
FamilyName: Symbols
|
||||
Weight: Regular
|
||||
Copyright: Copyright (c) 2016, Ryan McIntyre
|
||||
Version: 001.000
|
||||
ItalicAngle: 0
|
||||
UnderlinePosition: -100
|
||||
UnderlineWidth: 50
|
||||
Ascent: 800
|
||||
Descent: 200
|
||||
InvalidEm: 0
|
||||
LayerCount: 2
|
||||
Layer: 0 0 "Back" 1
|
||||
Layer: 1 0 "Fore" 0
|
||||
XUID: [1021 913 -638292798 6571593]
|
||||
FSType: 0
|
||||
OS2Version: 0
|
||||
OS2_WeightWidthSlopeOnly: 0
|
||||
OS2_UseTypoMetrics: 1
|
||||
CreationTime: 1480466430
|
||||
ModificationTime: 1480467813
|
||||
PfmFamily: 17
|
||||
TTFWeight: 400
|
||||
TTFWidth: 5
|
||||
LineGap: 90
|
||||
VLineGap: 0
|
||||
OS2TypoAscent: 0
|
||||
OS2TypoAOffset: 1
|
||||
OS2TypoDescent: 0
|
||||
OS2TypoDOffset: 1
|
||||
OS2TypoLinegap: 90
|
||||
OS2WinAscent: 0
|
||||
OS2WinAOffset: 1
|
||||
OS2WinDescent: 0
|
||||
OS2WinDOffset: 1
|
||||
HheadAscent: 0
|
||||
HheadAOffset: 1
|
||||
HheadDescent: 0
|
||||
HheadDOffset: 1
|
||||
OS2Vendor: 'PfEd'
|
||||
MarkAttachClasses: 1
|
||||
DEI: 91125
|
||||
LangName: 1033
|
||||
Encoding: UnicodeFull
|
||||
UnicodeInterp: none
|
||||
NameList: AGL For New Fonts
|
||||
DisplaySize: -72
|
||||
AntiAlias: 1
|
||||
FitToEm: 0
|
||||
WinInfo: 64 8 8
|
||||
OnlyBitmaps: 1
|
||||
BeginPrivate: 0
|
||||
EndPrivate
|
||||
TeXData: 1 0 0 346030 173015 115343 0 1048576 115343 783286 444596 497025 792723 393216 433062 380633 303038 157286 324010 404750 52429 2506097 1059062 262144
|
||||
BeginChars: 1114112 0
|
||||
EndChars
|
||||
EndSplineFont
|
|
@ -0,0 +1,59 @@
|
|||
SplineFontDB: 3.0
|
||||
FontName: Symbols-2048-em
|
||||
FullName: Symbols-2048-em
|
||||
FamilyName: Symbols
|
||||
Weight: Regular
|
||||
Copyright: Copyright (c) 2016, Ryan McIntyre
|
||||
Version: 001.000
|
||||
ItalicAngle: 0
|
||||
UnderlinePosition: -204
|
||||
UnderlineWidth: 102
|
||||
Ascent: 1638
|
||||
Descent: 410
|
||||
InvalidEm: 0
|
||||
LayerCount: 2
|
||||
Layer: 0 0 "Back" 1
|
||||
Layer: 1 0 "Fore" 0
|
||||
XUID: [1021 913 -638292798 6571593]
|
||||
FSType: 0
|
||||
OS2Version: 0
|
||||
OS2_WeightWidthSlopeOnly: 0
|
||||
OS2_UseTypoMetrics: 1
|
||||
CreationTime: 1480466430
|
||||
ModificationTime: 1480467841
|
||||
PfmFamily: 17
|
||||
TTFWeight: 400
|
||||
TTFWidth: 5
|
||||
LineGap: 184
|
||||
VLineGap: 0
|
||||
OS2TypoAscent: 0
|
||||
OS2TypoAOffset: 1
|
||||
OS2TypoDescent: 0
|
||||
OS2TypoDOffset: 1
|
||||
OS2TypoLinegap: 184
|
||||
OS2WinAscent: 0
|
||||
OS2WinAOffset: 1
|
||||
OS2WinDescent: 0
|
||||
OS2WinDOffset: 1
|
||||
HheadAscent: 0
|
||||
HheadAOffset: 1
|
||||
HheadDescent: 0
|
||||
HheadDOffset: 1
|
||||
OS2Vendor: 'PfEd'
|
||||
MarkAttachClasses: 1
|
||||
DEI: 91125
|
||||
LangName: 1033
|
||||
Encoding: UnicodeFull
|
||||
UnicodeInterp: none
|
||||
NameList: AGL For New Fonts
|
||||
DisplaySize: -72
|
||||
AntiAlias: 1
|
||||
FitToEm: 0
|
||||
WinInfo: 64 8 8
|
||||
OnlyBitmaps: 1
|
||||
BeginPrivate: 0
|
||||
EndPrivate
|
||||
TeXData: 1 0 0 346030 173015 115343 0 1048576 115343 783286 444596 497025 792723 393216 433062 380633 303038 157286 324010 404750 52429 2506097 1059062 262144
|
||||
BeginChars: 1114112 0
|
||||
EndChars
|
||||
EndSplineFont
|
BIN
src/glyphs/PowerlineSymbols.otf
Normal file
BIN
src/glyphs/PowerlineSymbols.otf
Normal file
Binary file not shown.
BIN
src/glyphs/Symbols Template 1000 em.ttf
Normal file
BIN
src/glyphs/Symbols Template 1000 em.ttf
Normal file
Binary file not shown.
BIN
src/glyphs/Symbols Template 2048 em.ttf
Normal file
BIN
src/glyphs/Symbols Template 2048 em.ttf
Normal file
Binary file not shown.
BIN
src/glyphs/Symbols-1000-em Nerd Font Complete.ttf
Normal file
BIN
src/glyphs/Symbols-1000-em Nerd Font Complete.ttf
Normal file
Binary file not shown.
BIN
src/glyphs/Symbols-2048-em Nerd Font Complete.ttf
Normal file
BIN
src/glyphs/Symbols-2048-em Nerd Font Complete.ttf
Normal file
Binary file not shown.
0
src/glyphs/devicons.ttf
Normal file → Executable file
0
src/glyphs/devicons.ttf
Normal file → Executable file
BIN
src/glyphs/octicons.ttf
Normal file
BIN
src/glyphs/octicons.ttf
Normal file
Binary file not shown.
|
@ -14,10 +14,10 @@ import fontforge
|
|||
octi_orig = "octicons.ttf"
|
||||
current_cp = 0xF400
|
||||
|
||||
print("# Examining {}".format(octi_orig))
|
||||
print('# Examining {}'.format(octi_orig))
|
||||
|
||||
font = fontforge.open(octi_orig)
|
||||
for glyph in font.glyphs("encoding"):
|
||||
for glyph in font.glyphs('encoding'):
|
||||
point = glyph.unicode
|
||||
if point < 0:
|
||||
continue
|
||||
|
|
|
@ -10,93 +10,78 @@ import fontforge
|
|||
# Double-quotes required here, for version-bump.sh:
|
||||
version = "2.3.3"
|
||||
|
||||
archive = "v18.3.0.tar.gz"
|
||||
archive = 'v18.3.0.tar.gz'
|
||||
|
||||
vectorsdir = "icons"
|
||||
fontdir = "."
|
||||
fontfile = "octicons.ttf"
|
||||
glyphsetfile = "i_oct.sh"
|
||||
glyphsetsdir = "../../../bin/scripts/lib"
|
||||
|
||||
subset = "-16" # use 16 px subset if possible
|
||||
subset_other = "-24" # use 24 px subset otherwise
|
||||
vectorsdir = 'icons'
|
||||
fontdir = '.'
|
||||
fontfile = 'octicons.ttf'
|
||||
glyphsetfile = 'i_oct.sh'
|
||||
glyphsetsdir = '../../../bin/scripts/lib'
|
||||
|
||||
subset = '-16' # use 16 px subset if possible
|
||||
subset_other = '-24' # use 24 px subset otherwise
|
||||
|
||||
def renamer(old_name):
|
||||
"""Return new equivalent icon name"""
|
||||
""" Return new equivalent icon name """
|
||||
return {
|
||||
"trashcan": "trash",
|
||||
"cloud-download": "download",
|
||||
"cloud-upload": "upload",
|
||||
"clippy": "paste",
|
||||
"mail-read": "read",
|
||||
"primitive-dot": "dot-fill",
|
||||
"primitive-square": "square-fill",
|
||||
"settings": "sliders",
|
||||
"dashboard": "meter",
|
||||
"paintcan": "paintbrush",
|
||||
}.get(old_name, old_name)
|
||||
|
||||
'trashcan' : 'trash',
|
||||
'cloud-download' : 'download',
|
||||
'cloud-upload' : 'upload',
|
||||
'clippy' : 'paste',
|
||||
'mail-read' : 'read',
|
||||
'primitive-dot' : 'dot-fill',
|
||||
'primitive-square' : 'square-fill',
|
||||
'settings' : 'sliders',
|
||||
'dashboard' : 'meter',
|
||||
'paintcan' : 'paintbrush',
|
||||
}.get(old_name, old_name)
|
||||
|
||||
def addIcon(codepoint, name, filename):
|
||||
"""Add one outline file and rescale/move"""
|
||||
dBB = [120, 0, 1000 - 120, 900] # just some nice sizes
|
||||
""" Add one outline file and rescale/move """
|
||||
dBB = [120, 0, 1000-120, 900] # just some nice sizes
|
||||
filename = os.path.join(vectorsdir, filename)
|
||||
glyph = font.createChar(codepoint, name)
|
||||
glyph.importOutlines(filename)
|
||||
glyph.manualHints = True
|
||||
|
||||
|
||||
def createGlyphInfo(icon_datasets, filepathname, into):
|
||||
"""Write the glyphinfo file"""
|
||||
with open(filepathname, "w", encoding="utf8") as f:
|
||||
f.write("#!/usr/bin/env bash\n")
|
||||
""" Write the glyphinfo file """
|
||||
with open(filepathname, 'w', encoding = 'utf8') as f:
|
||||
f.write(u'#!/usr/bin/env bash\n')
|
||||
f.write(intro)
|
||||
f.write("# Script Version: (autogenerated)\n")
|
||||
f.write('test -n "$__i_oct_loaded" && return || __i_oct_loaded=1\n')
|
||||
f.write(u'# Script Version: (autogenerated)\n')
|
||||
f.write(u'test -n "$__i_oct_loaded" && return || __i_oct_loaded=1\n')
|
||||
for _, codepoint, name in icon_datasets:
|
||||
codepoint = int(codepoint, 16)
|
||||
f.write(
|
||||
"i='{}' i_oct_{}=$i\n".format(chr(codepoint), name.replace("-", "_"))
|
||||
)
|
||||
f.write("unset i\n")
|
||||
f.write(u"i='{}' i_oct_{}=$i\n".format(chr(codepoint), name.replace('-', '_')))
|
||||
f.write(u'unset i\n')
|
||||
|
||||
|
||||
print("\nReading mapping file")
|
||||
print('\nReading mapping file')
|
||||
old_mapping = []
|
||||
with open("mapping", "r") as f:
|
||||
with open('mapping', 'r') as f:
|
||||
for line in f.readlines():
|
||||
if line.startswith("#"):
|
||||
if line.startswith('#'):
|
||||
continue
|
||||
old_mapping.append(tuple(re.split(" +", line.strip())))
|
||||
print("Found {} entries".format(len(old_mapping)))
|
||||
old_mapping.append(tuple(re.split(' +', line.strip())))
|
||||
print('Found {} entries'.format(len(old_mapping)))
|
||||
old_mapping.sort(key=(lambda x: x[0]))
|
||||
|
||||
print('Fetching octicons archive "{}"\n'.format(archive))
|
||||
if subprocess.call(
|
||||
"curl -OL https://github.com/primer/octicons/archive/" + archive, shell=True
|
||||
):
|
||||
sys.exit("Error fetching octicons archive")
|
||||
print("\nUnpacking octicons archive")
|
||||
if subprocess.call(
|
||||
"rm -rf icons octicons-* && tar zxf *.gz && mv octicons-*/icons . && rm -rf octicons-*",
|
||||
shell=True,
|
||||
):
|
||||
sys.exit("Error unpacking archive")
|
||||
if subprocess.call('curl -OL https://github.com/primer/octicons/archive/' + archive, shell=True):
|
||||
sys.exit('Error fetching octicons archive')
|
||||
print('\nUnpacking octicons archive')
|
||||
if subprocess.call('rm -rf icons octicons-* && tar zxf *.gz && mv octicons-*/icons . && rm -rf octicons-*', shell=True):
|
||||
sys.exit('Error unpacking archive')
|
||||
|
||||
svgs = os.listdir(vectorsdir)
|
||||
print("Found {} svgs".format(len(svgs)))
|
||||
names = {
|
||||
s[0 : -len("-xx.svg")]
|
||||
for s in svgs
|
||||
if s.endswith(subset + ".svg") or s.endswith(subset_other + ".svg")
|
||||
}
|
||||
print("Found {} icons after de-duplicating\n".format(len(names)))
|
||||
print('Found {} svgs'.format(len(svgs)))
|
||||
names = { s[0:-len('-xx.svg')] for s in svgs if s.endswith(subset + '.svg') or s.endswith(subset_other + '.svg') }
|
||||
print('Found {} icons after de-duplicating\n'.format(len(names)))
|
||||
|
||||
num_found = 0
|
||||
num_missing = 0
|
||||
misslist = ""
|
||||
renamelist = ""
|
||||
misslist = ''
|
||||
renamelist = ''
|
||||
freeslots = []
|
||||
|
||||
new_mapping = []
|
||||
|
@ -108,22 +93,18 @@ for i, j, old_n in old_mapping:
|
|||
continue
|
||||
new_n = renamer(old_n)
|
||||
if new_n in names:
|
||||
renamelist += "Renamed {} -> {}\n".format(old_n, new_n)
|
||||
renamelist += 'Renamed {} -> {}\n'.format(old_n, new_n)
|
||||
names.remove(new_n)
|
||||
new_mapping.append((i, j, new_n))
|
||||
num_found += 1
|
||||
continue
|
||||
misslist += "Missing {}\n".format(old_n)
|
||||
misslist += 'Missing {}\n'.format(old_n)
|
||||
freeslots.append((i, j))
|
||||
num_missing += 1
|
||||
|
||||
print(renamelist)
|
||||
print(misslist)
|
||||
print(
|
||||
"Found {} (of {}, missing {}) and new {}".format(
|
||||
num_found, len(old_mapping), num_missing, len(names)
|
||||
)
|
||||
)
|
||||
print('Found {} (of {}, missing {}) and new {}'.format(num_found, len(old_mapping), num_missing, len(names)))
|
||||
|
||||
names = list(names)
|
||||
names.sort()
|
||||
|
@ -135,7 +116,7 @@ for n in list(names):
|
|||
names.remove(n)
|
||||
freeslots = freeslots[1:]
|
||||
|
||||
print("Filled in missing, remaining new {}".format(len(names)))
|
||||
print('Filled in missing, remaining new {}'.format(len(names)))
|
||||
|
||||
i_max = 0
|
||||
j_max = 0
|
||||
|
@ -150,52 +131,50 @@ for i, j, _ in new_mapping:
|
|||
for n in names:
|
||||
i_max += 1
|
||||
j_max += 1
|
||||
new_mapping.append(("{:X}".format(i_max), "{:X}".format(j_max), n))
|
||||
new_mapping.append(('{:X}'.format(i_max), '{:X}'.format(j_max), n))
|
||||
|
||||
print("Appended remaining new, total new mapping {}".format(len(new_mapping)))
|
||||
print('Appended remaining new, total new mapping {}'.format(len(new_mapping)))
|
||||
|
||||
new_mapping.sort(key=(lambda x: x[0]))
|
||||
with open("mapping", "w") as f:
|
||||
with open('mapping', 'w') as f:
|
||||
for i, j, n in new_mapping:
|
||||
f.write("{} {} {}\n".format(i, j, n))
|
||||
f.write('{} {} {}\n'.format(i, j, n))
|
||||
|
||||
font = fontforge.font()
|
||||
font.fontname = "OcticonsNerdFont-Regular"
|
||||
font.fullname = "Octicons Nerd Font Regular"
|
||||
font.familyname = "Octicons Nerd Font"
|
||||
font.fontname = 'OcticonsNerdFont-Regular'
|
||||
font.fullname = 'Octicons Nerd Font Regular'
|
||||
font.familyname = 'Octicons Nerd Font'
|
||||
font.em = 2048
|
||||
font.encoding = "UnicodeFull"
|
||||
font.encoding = 'UnicodeFull'
|
||||
|
||||
# Add valid space glyph to avoid "unknown character" box on IE11
|
||||
glyph = font.createChar(32)
|
||||
glyph.width = 200
|
||||
|
||||
font.sfntRevision = None # Auto-set (refreshed) by fontforge
|
||||
font.sfntRevision = None # Auto-set (refreshed) by fontforge
|
||||
font.version = version
|
||||
font.copyright = "GitHub Inc."
|
||||
font.appendSFNTName("English (US)", "Version", archive + "; " + version)
|
||||
font.appendSFNTName(
|
||||
"English (US)", "Vendor URL", "https://github.com/ryanoasis/nerd-fonts"
|
||||
)
|
||||
font.appendSFNTName("English (US)", "Copyright", "GitHub Inc.")
|
||||
font.copyright = 'GitHub Inc.'
|
||||
font.appendSFNTName('English (US)', 'Version', archive + '; ' + version)
|
||||
font.appendSFNTName('English (US)', 'Vendor URL', 'https://github.com/ryanoasis/nerd-fonts')
|
||||
font.appendSFNTName('English (US)', 'Copyright', 'GitHub Inc.')
|
||||
|
||||
for codepoint, _, name in new_mapping:
|
||||
codepoint = int(codepoint, 16)
|
||||
filename = name + subset + ".svg"
|
||||
filename = name + subset + '.svg'
|
||||
if filename not in svgs:
|
||||
filename = name + subset_other + ".svg"
|
||||
filename = name + subset_other + '.svg'
|
||||
addIcon(codepoint, name, filename)
|
||||
|
||||
num_icons = len(new_mapping)
|
||||
|
||||
print("Generating {} with {} glyphs".format(fontfile, num_icons))
|
||||
print('Generating {} with {} glyphs'.format(fontfile, num_icons))
|
||||
font.generate(os.path.join(fontdir, fontfile), flags=("no-FFTM-table",))
|
||||
|
||||
codepoints = [int(p, 16) for _, p, _ in new_mapping]
|
||||
intro = "# Octicons ({} icons)\n".format(num_icons)
|
||||
intro += "# Codepoints: {:X}-{:X} with gaps\n".format(min(codepoints), max(codepoints))
|
||||
intro += "# Nerd Fonts Version: {}\n".format(version)
|
||||
codepoints = [ int(p, 16) for _, p, _ in new_mapping ]
|
||||
intro = u'# Octicons ({} icons)\n'.format(num_icons)
|
||||
intro += u'# Codepoints: {:X}-{:X} with gaps\n'.format(min(codepoints), max(codepoints))
|
||||
intro += u'# Nerd Fonts Version: {}\n'.format(version)
|
||||
|
||||
print("Generating GlyphInfo {}".format(glyphsetfile))
|
||||
print('Generating GlyphInfo {}'.format(glyphsetfile))
|
||||
createGlyphInfo(new_mapping, os.path.join(glyphsetsdir, glyphsetfile), intro)
|
||||
print("Finished")
|
||||
print('Finished')
|
||||
|
|
|
@ -433,3 +433,4 @@ Foobar.org is a distributed community of developers...
|
|||
Company.com is a small business who likes to support community designers...
|
||||
University.edu is a renowned educational institution with a strong design department...
|
||||
-----
|
||||
|
||||
|
|
BIN
src/glyphs/weathericons-regular-webfont.ttf
Normal file
BIN
src/glyphs/weathericons-regular-webfont.ttf
Normal file
Binary file not shown.
Loading…
Reference in a new issue