mirror of
https://github.com/daylinmorgan/monolisa-nerdfont-patch.git
synced 2025-01-04 10:30:43 -06:00
Compare commits
3 commits
5b946b0aca
...
2a0e39720f
Author | SHA1 | Date | |
---|---|---|---|
2a0e39720f | |||
1850f3b2b5 | |||
063aefe4d8 |
25 changed files with 1173 additions and 246 deletions
178
.gitignore
vendored
178
.gitignore
vendored
|
@ -1,3 +1,180 @@
|
|||
# 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/*
|
||||
|
@ -7,3 +184,4 @@ patched/*
|
|||
|
||||
nerd-fonts
|
||||
.env
|
||||
font-patcher-log.txt
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
exclude: "^(src/.*|bin/font-patcher)"
|
||||
exclude: "^(src/.*|bin/scripts|font-patcher)"
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.4.0
|
||||
|
@ -6,10 +6,6 @@ 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: ./bin/font-patcher ## apply nerd fonts patch |> -gs b_magenta -ms bold
|
||||
patch: ## apply nerd fonts patch |> -gs b_magenta -ms bold
|
||||
@./patch-monolisa \
|
||||
$(ARGS) \
|
||||
-f MonoLisa/
|
||||
|
@ -24,6 +24,8 @@ 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:
|
||||
|
|
418
bin/scripts/name_parser/FontnameParser.py
Normal file
418
bin/scripts/name_parser/FontnameParser.py
Normal file
|
@ -0,0 +1,418 @@
|
|||
#!/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)
|
441
bin/scripts/name_parser/FontnameTools.py
Normal file
441
bin/scripts/name_parser/FontnameTools.py
Normal file
|
@ -0,0 +1,441 @@
|
|||
#!/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,15 +1,7 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
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
|
||||
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"
|
||||
|
||||
echo "don't forget to commit your changes!"
|
||||
|
|
|
@ -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.1"
|
||||
script_version = "4.1.2"
|
||||
|
||||
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)
|
||||
# the tables have been removed from the repo with >this< commit
|
||||
# Usually removes 'fi' ligs that end up being only one cell wide, and 'ldot'
|
||||
if self.args.configfile and self.config.read(self.args.configfile):
|
||||
if self.args.removeligatures:
|
||||
logger.info("Removing ligatures from configfile `Subtables` section")
|
|
@ -140,7 +140,7 @@ def patch_single_font(
|
|||
cmd = [
|
||||
"fontforge",
|
||||
"-script",
|
||||
"./bin/font-patcher",
|
||||
"./font-patcher",
|
||||
"--glyphdir",
|
||||
"./src/glyphs/",
|
||||
"-out",
|
||||
|
@ -182,8 +182,6 @@ def patch_font_dir_docker(
|
|||
f"{font_dir_path}:/in",
|
||||
"-v",
|
||||
f"{output_path}:/out",
|
||||
"-u",
|
||||
f"{os.getuid()}:{os.getegid()}",
|
||||
"nerdfonts/patcher",
|
||||
]
|
||||
|
||||
|
|
Binary file not shown.
|
@ -1,59 +0,0 @@
|
|||
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
|
|
@ -1,59 +0,0 @@
|
|||
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
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
0
src/glyphs/devicons.ttf
Executable file → Normal file
0
src/glyphs/devicons.ttf
Executable file → 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,78 +10,93 @@ 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'
|
||||
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
|
||||
|
||||
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(u'#!/usr/bin/env bash\n')
|
||||
"""Write the glyphinfo file"""
|
||||
with open(filepathname, "w", encoding="utf8") as f:
|
||||
f.write("#!/usr/bin/env bash\n")
|
||||
f.write(intro)
|
||||
f.write(u'# Script Version: (autogenerated)\n')
|
||||
f.write(u'test -n "$__i_oct_loaded" && return || __i_oct_loaded=1\n')
|
||||
f.write("# Script Version: (autogenerated)\n")
|
||||
f.write('test -n "$__i_oct_loaded" && return || __i_oct_loaded=1\n')
|
||||
for _, codepoint, name in icon_datasets:
|
||||
codepoint = int(codepoint, 16)
|
||||
f.write(u"i='{}' i_oct_{}=$i\n".format(chr(codepoint), name.replace('-', '_')))
|
||||
f.write(u'unset i\n')
|
||||
f.write(
|
||||
"i='{}' i_oct_{}=$i\n".format(chr(codepoint), name.replace("-", "_"))
|
||||
)
|
||||
f.write("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 = []
|
||||
|
@ -93,18 +108,22 @@ 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()
|
||||
|
@ -116,7 +135,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
|
||||
|
@ -131,50 +150,52 @@ 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 = 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)
|
||||
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)
|
||||
|
||||
print('Generating GlyphInfo {}'.format(glyphsetfile))
|
||||
print("Generating GlyphInfo {}".format(glyphsetfile))
|
||||
createGlyphInfo(new_mapping, os.path.join(glyphsetsdir, glyphsetfile), intro)
|
||||
print('Finished')
|
||||
print("Finished")
|
||||
|
|
|
@ -433,4 +433,3 @@ 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...
|
||||
-----
|
||||
|
||||
|
|
Binary file not shown.
Loading…
Reference in a new issue