mirror of
https://github.com/daylinmorgan/monolisa-nerdfont-patch.git
synced 2024-12-21 22:40:44 -06:00
feat: upgrade to nf v3.0.0
This commit is contained in:
parent
1850f3b2b5
commit
2a0e39720f
29 changed files with 1835 additions and 315 deletions
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
|
@ -2,9 +2,9 @@ name: "Update Nerd Fonts"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
# schedule:
|
schedule:
|
||||||
# 1st and 15th at 12:00 AM
|
# 1st and 15th at 12:00 AM
|
||||||
# - cron: "0 0 1,15 * *"
|
- cron: "0 0 1,15 * *"
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
|
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
|
*.zip
|
||||||
|
|
||||||
MonoLisa/*
|
MonoLisa/*
|
||||||
|
@ -7,3 +184,4 @@ patched/*
|
||||||
|
|
||||||
nerd-fonts
|
nerd-fonts
|
||||||
.env
|
.env
|
||||||
|
font-patcher-log.txt
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
exclude: "^(src/.*|bin/font-patcher)"
|
exclude: "^(src/.*|bin/scripts|font-patcher)"
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v4.4.0
|
rev: v4.4.0
|
||||||
|
@ -6,10 +6,6 @@ repos:
|
||||||
- id: check-yaml
|
- id: check-yaml
|
||||||
- id: end-of-file-fixer
|
- id: end-of-file-fixer
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
- repo: https://github.com/pycqa/isort
|
|
||||||
rev: 5.12.0
|
|
||||||
hooks:
|
|
||||||
- id: isort
|
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: 23.1.0
|
rev: 23.1.0
|
||||||
hooks:
|
hooks:
|
||||||
|
|
4
Makefile
4
Makefile
|
@ -1,7 +1,7 @@
|
||||||
-include .env
|
-include .env
|
||||||
ARGS ?= -c
|
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 \
|
@./patch-monolisa \
|
||||||
$(ARGS) \
|
$(ARGS) \
|
||||||
-f MonoLisa/
|
-f MonoLisa/
|
||||||
|
@ -24,6 +24,8 @@ lint: ## run pre-commit hooks
|
||||||
|
|
||||||
clean: ## remove patched fonts
|
clean: ## remove patched fonts
|
||||||
@rm -rf patched/*
|
@rm -rf patched/*
|
||||||
|
@rm -f ./font-patcher-log.txt
|
||||||
|
@rm -f FontPatcher.zip
|
||||||
|
|
||||||
# depends on daylinmorgan/yartsu
|
# depends on daylinmorgan/yartsu
|
||||||
assets/help.svg:
|
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
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
REPO_URL='https://github.com/ryanoasis/nerd-fonts.git'
|
rm -rf font-patcher
|
||||||
|
wget https://github.com/ryanoasis/nerd-fonts/raw/master/FontPatcher.zip -O FontPatcher.zip
|
||||||
rm -rf nerd-fonts
|
unzip -u FontPatcher.zip -x "readme.md"
|
||||||
|
|
||||||
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!"
|
echo "don't forget to commit your changes!"
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# coding=utf8
|
# coding=utf8
|
||||||
# Nerd Fonts Version: 2.3.3
|
# Nerd Fonts Version: 3.0.0
|
||||||
# Script version is further down
|
# Script version is further down
|
||||||
|
|
||||||
from __future__ import absolute_import, print_function, unicode_literals
|
from __future__ import absolute_import, print_function, unicode_literals
|
||||||
|
|
||||||
# Change the script version when you edit this script:
|
# Change the script version when you edit this script:
|
||||||
script_version = "3.7.1"
|
script_version = "4.1.2"
|
||||||
|
|
||||||
version = "2.3.3"
|
version = "3.0.0"
|
||||||
projectName = "Nerd Fonts"
|
projectName = "Nerd Fonts"
|
||||||
projectNameAbbreviation = "NF"
|
projectNameAbbreviation = "NF"
|
||||||
projectNameSingular = projectName[:-1]
|
projectNameSingular = projectName[:-1]
|
||||||
|
@ -22,6 +22,7 @@ import errno
|
||||||
import subprocess
|
import subprocess
|
||||||
import json
|
import json
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
import logging
|
||||||
try:
|
try:
|
||||||
import configparser
|
import configparser
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -239,10 +240,10 @@ def force_panose_monospaced(font):
|
||||||
panose = list(font.os2_panose)
|
panose = list(font.os2_panose)
|
||||||
if panose[0] == 0: # 0 (1st value) = family kind; 0 = any (default)
|
if panose[0] == 0: # 0 (1st value) = family kind; 0 = any (default)
|
||||||
panose[0] = 2 # make kind latin text and display
|
panose[0] = 2 # make kind latin text and display
|
||||||
print(" Setting Panose 'Family Kind' to 'Latin Text and Display' (was 'Any')")
|
logger.info("Setting Panose 'Family Kind' to 'Latin Text and Display' (was 'Any')")
|
||||||
font.os2_panose = tuple(panose)
|
font.os2_panose = tuple(panose)
|
||||||
if panose[0] == 2 and panose[3] != 9:
|
if panose[0] == 2 and panose[3] != 9:
|
||||||
print(" Setting Panose 'Proportion' to 'Monospaced' (was '{}')".format(panose_proportion_to_text(panose[3])))
|
logger.info("Setting Panose 'Proportion' to 'Monospaced' (was '%s')", panose_proportion_to_text(panose[3]))
|
||||||
panose[3] = 9 # 3 (4th value) = proportion; 9 = monospaced
|
panose[3] = 9 # 3 (4th value) = proportion; 9 = monospaced
|
||||||
font.os2_panose = tuple(panose)
|
font.os2_panose = tuple(panose)
|
||||||
|
|
||||||
|
@ -296,10 +297,22 @@ def get_old_average_x_width(font):
|
||||||
}
|
}
|
||||||
for g in weights:
|
for g in weights:
|
||||||
if g not in font:
|
if g not in font:
|
||||||
sys.exit("{}: Can not determine ancient style xAvgCharWidth".format(projectName))
|
logger.critical("Can not determine ancient style xAvgCharWidth")
|
||||||
|
sys.exit(1)
|
||||||
s += font[g].width * weights[g]
|
s += font[g].width * weights[g]
|
||||||
return int(s / 1000)
|
return int(s / 1000)
|
||||||
|
|
||||||
|
def create_filename(fonts):
|
||||||
|
""" Determine filename from font object(s) """
|
||||||
|
sfnt = { k: v for l, k, v in fonts[0].sfnt_names }
|
||||||
|
sfnt_pfam = sfnt.get('Preferred Family', sfnt['Family'])
|
||||||
|
sfnt_psubfam = sfnt.get('Preferred Styles', sfnt['SubFamily'])
|
||||||
|
if len(fonts) > 1:
|
||||||
|
return sfnt_pfam
|
||||||
|
if len(sfnt_psubfam) > 0:
|
||||||
|
sfnt_psubfam = '-' + sfnt_psubfam
|
||||||
|
return (sfnt_pfam + sfnt_psubfam).replace(' ', '')
|
||||||
|
|
||||||
|
|
||||||
class font_patcher:
|
class font_patcher:
|
||||||
def __init__(self, args):
|
def __init__(self, args):
|
||||||
|
@ -311,6 +324,7 @@ class font_patcher:
|
||||||
self.font_dim = None # class 'dict'
|
self.font_dim = None # class 'dict'
|
||||||
self.font_extrawide = False
|
self.font_extrawide = False
|
||||||
self.source_monospaced = None # Later True or False
|
self.source_monospaced = None # Later True or False
|
||||||
|
self.symbolsonly = False
|
||||||
self.onlybitmaps = 0
|
self.onlybitmaps = 0
|
||||||
self.essential = set()
|
self.essential = set()
|
||||||
self.config = configparser.ConfigParser(empty_lines_in_values=False, allow_no_value=True)
|
self.config = configparser.ConfigParser(empty_lines_in_values=False, allow_no_value=True)
|
||||||
|
@ -336,7 +350,7 @@ class font_patcher:
|
||||||
|
|
||||||
# For very wide (almost square or wider) fonts we do not want to generate 2 cell wide Powerline glyphs
|
# For very wide (almost square or wider) fonts we do not want to generate 2 cell wide Powerline glyphs
|
||||||
if self.font_dim['height'] * 1.8 < self.font_dim['width'] * 2:
|
if self.font_dim['height'] * 1.8 < self.font_dim['width'] * 2:
|
||||||
print("Very wide and short font, disabling 2 cell Powerline glyphs")
|
logger.warning("Very wide and short font, disabling 2 cell Powerline glyphs")
|
||||||
self.font_extrawide = True
|
self.font_extrawide = True
|
||||||
|
|
||||||
# Prevent opening and closing the fontforge font. Makes things faster when patching
|
# Prevent opening and closing the fontforge font. Makes things faster when patching
|
||||||
|
@ -345,8 +359,12 @@ class font_patcher:
|
||||||
symfont = None
|
symfont = None
|
||||||
|
|
||||||
if not os.path.isdir(self.args.glyphdir):
|
if not os.path.isdir(self.args.glyphdir):
|
||||||
sys.exit("{}: Can not find symbol glyph directory {} "
|
logger.critical("Can not find symbol glyph directory %s "
|
||||||
"(probably you need to download the src/glyphs/ directory?)".format(projectName, self.args.glyphdir))
|
"(probably you need to download the src/glyphs/ directory?)", self.args.glyphdir)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if self.args.dry_run:
|
||||||
|
return
|
||||||
|
|
||||||
for patch in self.patch_set:
|
for patch in self.patch_set:
|
||||||
if patch['Enabled']:
|
if patch['Enabled']:
|
||||||
|
@ -356,11 +374,13 @@ class font_patcher:
|
||||||
symfont.close()
|
symfont.close()
|
||||||
symfont = None
|
symfont = None
|
||||||
if not os.path.isfile(self.args.glyphdir + patch['Filename']):
|
if not os.path.isfile(self.args.glyphdir + patch['Filename']):
|
||||||
sys.exit("{}: Can not find symbol source for '{}'\n{:>{}} (i.e. {})".format(
|
logger.critical("Can not find symbol source for '%s' (i.e. %s)",
|
||||||
projectName, patch['Name'], '', len(projectName), self.args.glyphdir + patch['Filename']))
|
patch['Name'], self.args.glyphdir + patch['Filename'])
|
||||||
|
sys.exit(1)
|
||||||
if not os.access(self.args.glyphdir + patch['Filename'], os.R_OK):
|
if not os.access(self.args.glyphdir + patch['Filename'], os.R_OK):
|
||||||
sys.exit("{}: Can not open symbol source for '{}'\n{:>{}} (i.e. {})".format(
|
logger.critical("Can not open symbol source for '%s' (i.e. %s)",
|
||||||
projectName, patch['Name'], '', len(projectName), self.args.glyphdir + patch['Filename']))
|
patch['Name'], self.args.glyphdir + patch['Filename'])
|
||||||
|
sys.exit(1)
|
||||||
symfont = fontforge.open(os.path.join(self.args.glyphdir, patch['Filename']))
|
symfont = fontforge.open(os.path.join(self.args.glyphdir, patch['Filename']))
|
||||||
symfont.encoding = 'UnicodeFull'
|
symfont.encoding = 'UnicodeFull'
|
||||||
|
|
||||||
|
@ -402,11 +422,11 @@ class font_patcher:
|
||||||
break
|
break
|
||||||
outfile = os.path.normpath(os.path.join(
|
outfile = os.path.normpath(os.path.join(
|
||||||
sanitize_filename(self.args.outputdir, True),
|
sanitize_filename(self.args.outputdir, True),
|
||||||
sanitize_filename(sourceFont.familyname) + ".ttc"))
|
sanitize_filename(create_filename(sourceFonts)) + ".ttc"))
|
||||||
sourceFonts[0].generateTtc(outfile, sourceFonts[1:], flags=gen_flags, layer=layer)
|
sourceFonts[0].generateTtc(outfile, sourceFonts[1:], flags=gen_flags, layer=layer)
|
||||||
message = " Generated {} fonts\n \===> '{}'".format(len(sourceFonts), outfile)
|
message = " Generated {} fonts\n \===> '{}'".format(len(sourceFonts), outfile)
|
||||||
else:
|
else:
|
||||||
fontname = sourceFont.fullname
|
fontname = create_filename(sourceFonts)
|
||||||
if not fontname:
|
if not fontname:
|
||||||
fontname = sourceFont.cidfontname
|
fontname = sourceFont.cidfontname
|
||||||
outfile = os.path.normpath(os.path.join(
|
outfile = os.path.normpath(os.path.join(
|
||||||
|
@ -414,9 +434,11 @@ class font_patcher:
|
||||||
sanitize_filename(fontname) + self.args.extension))
|
sanitize_filename(fontname) + self.args.extension))
|
||||||
bitmaps = str()
|
bitmaps = str()
|
||||||
if len(self.sourceFont.bitmapSizes):
|
if len(self.sourceFont.bitmapSizes):
|
||||||
if not self.args.quiet:
|
logger.debug("Preserving bitmaps {}".format(self.sourceFont.bitmapSizes))
|
||||||
print("Preserving bitmaps {}".format(self.sourceFont.bitmapSizes))
|
|
||||||
bitmaps = str('otf') # otf/ttf, both is bf_ttf
|
bitmaps = str('otf') # otf/ttf, both is bf_ttf
|
||||||
|
if self.args.dry_run:
|
||||||
|
logger.debug("=====> Filename '{}'".format(outfile))
|
||||||
|
return
|
||||||
sourceFont.generate(outfile, bitmap_type=bitmaps, flags=gen_flags)
|
sourceFont.generate(outfile, bitmap_type=bitmaps, flags=gen_flags)
|
||||||
message = " {}\n \===> '{}'".format(self.sourceFont.fullname, outfile)
|
message = " {}\n \===> '{}'".format(self.sourceFont.fullname, outfile)
|
||||||
|
|
||||||
|
@ -426,8 +448,7 @@ class font_patcher:
|
||||||
source_font = TableHEADWriter(self.args.font)
|
source_font = TableHEADWriter(self.args.font)
|
||||||
dest_font = TableHEADWriter(outfile)
|
dest_font = TableHEADWriter(outfile)
|
||||||
for idx in range(source_font.num_fonts):
|
for idx in range(source_font.num_fonts):
|
||||||
if not self.args.quiet:
|
logger.debug("Tweaking %d/%d", idx + 1, source_font.num_fonts)
|
||||||
print("{}: Tweaking {}/{}".format(projectName, idx + 1, source_font.num_fonts))
|
|
||||||
xwidth_s = ''
|
xwidth_s = ''
|
||||||
xwidth = self.xavgwidth[idx]
|
xwidth = self.xavgwidth[idx]
|
||||||
if isinstance(xwidth, int):
|
if isinstance(xwidth, int):
|
||||||
|
@ -438,26 +459,23 @@ class font_patcher:
|
||||||
dest_font.find_table([b'OS/2'], idx)
|
dest_font.find_table([b'OS/2'], idx)
|
||||||
d_xwidth = dest_font.getshort('avgWidth')
|
d_xwidth = dest_font.getshort('avgWidth')
|
||||||
if d_xwidth != xwidth:
|
if d_xwidth != xwidth:
|
||||||
if not self.args.quiet:
|
logger.debug("Changing xAvgCharWidth from %d to %d%s", d_xwidth, xwidth, xwidth_s)
|
||||||
print("Changing xAvgCharWidth from {} to {}{}".format(d_xwidth, xwidth, xwidth_s))
|
|
||||||
dest_font.putshort(xwidth, 'avgWidth')
|
dest_font.putshort(xwidth, 'avgWidth')
|
||||||
dest_font.reset_table_checksum()
|
dest_font.reset_table_checksum()
|
||||||
source_font.find_head_table(idx)
|
source_font.find_head_table(idx)
|
||||||
dest_font.find_head_table(idx)
|
dest_font.find_head_table(idx)
|
||||||
if source_font.flags & 0x08 == 0 and dest_font.flags & 0x08 != 0:
|
if source_font.flags & 0x08 == 0 and dest_font.flags & 0x08 != 0:
|
||||||
if not self.args.quiet:
|
logger.debug("Changing flags from 0x%X to 0x%X", dest_font.flags, dest_font.flags & ~0x08)
|
||||||
print("Changing flags from 0x{:X} to 0x{:X}".format(dest_font.flags, dest_font.flags & ~0x08))
|
|
||||||
dest_font.putshort(dest_font.flags & ~0x08, 'flags') # clear 'ppem_to_int'
|
dest_font.putshort(dest_font.flags & ~0x08, 'flags') # clear 'ppem_to_int'
|
||||||
if source_font.lowppem != dest_font.lowppem:
|
if source_font.lowppem != dest_font.lowppem:
|
||||||
if not self.args.quiet:
|
logger.debug("Changing lowestRecPPEM from %d to %d", dest_font.lowppem, source_font.lowppem)
|
||||||
print("Changing lowestRecPPEM from {} to {}".format(dest_font.lowppem, source_font.lowppem))
|
|
||||||
dest_font.putshort(source_font.lowppem, 'lowestRecPPEM')
|
dest_font.putshort(source_font.lowppem, 'lowestRecPPEM')
|
||||||
if dest_font.modified:
|
if dest_font.modified:
|
||||||
dest_font.reset_table_checksum()
|
dest_font.reset_table_checksum()
|
||||||
if dest_font.modified:
|
if dest_font.modified:
|
||||||
dest_font.reset_full_checksum()
|
dest_font.reset_full_checksum()
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
print("Can not handle font flags ({})".format(repr(error)))
|
logger.error("Can not handle font flags (%s)", repr(error))
|
||||||
finally:
|
finally:
|
||||||
try:
|
try:
|
||||||
source_font.close()
|
source_font.close()
|
||||||
|
@ -465,12 +483,13 @@ class font_patcher:
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
if self.args.is_variable:
|
if self.args.is_variable:
|
||||||
print("Warning: Source font is a variable open type font (VF) and the patch results will most likely not be what you want")
|
logger.critical("Source font is a variable open type font (VF) and the patch results will most likely not be what you want")
|
||||||
print(message)
|
print(message)
|
||||||
|
|
||||||
if self.args.postprocess:
|
if self.args.postprocess:
|
||||||
subprocess.call([self.args.postprocess, outfile])
|
subprocess.call([self.args.postprocess, outfile])
|
||||||
print("\nPost Processed: {}".format(outfile))
|
print("\n")
|
||||||
|
logger.info("Post Processed: %s", outfile)
|
||||||
|
|
||||||
|
|
||||||
def setup_name_backup(self, font):
|
def setup_name_backup(self, font):
|
||||||
|
@ -488,11 +507,8 @@ class font_patcher:
|
||||||
font.fullname = font.persistent["fullname"]
|
font.fullname = font.persistent["fullname"]
|
||||||
if isinstance(font.persistent["familyname"], str):
|
if isinstance(font.persistent["familyname"], str):
|
||||||
font.familyname = font.persistent["familyname"]
|
font.familyname = font.persistent["familyname"]
|
||||||
verboseAdditionalFontNameSuffix = " " + projectNameSingular
|
verboseAdditionalFontNameSuffix = ""
|
||||||
if self.args.windows: # attempt to shorten here on the additional name BEFORE trimming later
|
additionalFontNameSuffix = ""
|
||||||
additionalFontNameSuffix = " " + projectNameAbbreviation
|
|
||||||
else:
|
|
||||||
additionalFontNameSuffix = verboseAdditionalFontNameSuffix
|
|
||||||
if not self.args.complete:
|
if not self.args.complete:
|
||||||
# NOTE not all symbol fonts have appended their suffix here
|
# NOTE not all symbol fonts have appended their suffix here
|
||||||
if self.args.fontawesome:
|
if self.args.fontawesome:
|
||||||
|
@ -523,17 +539,24 @@ class font_patcher:
|
||||||
additionalFontNameSuffix += " WEA"
|
additionalFontNameSuffix += " WEA"
|
||||||
verboseAdditionalFontNameSuffix += " Plus Weather Icons"
|
verboseAdditionalFontNameSuffix += " Plus Weather Icons"
|
||||||
|
|
||||||
# if all source glyphs included simplify the name
|
# add mono signifier to beginning of name suffix
|
||||||
else:
|
|
||||||
additionalFontNameSuffix = " " + projectNameSingular + " Complete"
|
|
||||||
verboseAdditionalFontNameSuffix = " " + projectNameSingular + " Complete"
|
|
||||||
|
|
||||||
# add mono signifier to end of name
|
|
||||||
if self.args.single:
|
if self.args.single:
|
||||||
additionalFontNameSuffix += " M"
|
variant_abbrev = "M"
|
||||||
verboseAdditionalFontNameSuffix += " Mono"
|
variant_full = " Mono"
|
||||||
|
elif self.args.nonmono and not self.symbolsonly:
|
||||||
|
variant_abbrev = "P"
|
||||||
|
variant_full = " Propo"
|
||||||
|
else:
|
||||||
|
variant_abbrev = ""
|
||||||
|
variant_full = ""
|
||||||
|
|
||||||
if FontnameParserOK and self.args.makegroups:
|
ps_suffix = projectNameAbbreviation + variant_abbrev + additionalFontNameSuffix
|
||||||
|
|
||||||
|
# add 'Nerd Font' to beginning of name suffix
|
||||||
|
verboseAdditionalFontNameSuffix = " " + projectNameSingular + variant_full + verboseAdditionalFontNameSuffix
|
||||||
|
additionalFontNameSuffix = " " + projectNameSingular + variant_full + additionalFontNameSuffix
|
||||||
|
|
||||||
|
if FontnameParserOK and self.args.makegroups > 0:
|
||||||
use_fullname = isinstance(font.fullname, str) # Usually the fullname is better to parse
|
use_fullname = isinstance(font.fullname, str) # Usually the fullname is better to parse
|
||||||
# Use fullname if it is 'equal' to the fontname
|
# Use fullname if it is 'equal' to the fontname
|
||||||
if font.fullname:
|
if font.fullname:
|
||||||
|
@ -545,12 +568,14 @@ class font_patcher:
|
||||||
# Gohu fontnames hide the weight, but the file names are ok...
|
# Gohu fontnames hide the weight, but the file names are ok...
|
||||||
if parser_name.startswith('Gohu'):
|
if parser_name.startswith('Gohu'):
|
||||||
parser_name = os.path.splitext(os.path.basename(self.args.font))[0]
|
parser_name = os.path.splitext(os.path.basename(self.args.font))[0]
|
||||||
n = FontnameParser(parser_name)
|
n = FontnameParser(parser_name, logger)
|
||||||
if not n.parse_ok:
|
if not n.parse_ok:
|
||||||
print("Have only minimal naming information, check resulting name. Maybe omit --makegroups option")
|
logger.warning("Have only minimal naming information, check resulting name. Maybe specify --makegroups 0")
|
||||||
n.drop_for_powerline()
|
n.drop_for_powerline()
|
||||||
n.enable_short_families(True, "Noto")
|
n.enable_short_families(True, self.args.makegroups in [ 2, 3, 5, 6, ], self.args.makegroups in [ 3, 6, ])
|
||||||
n.set_for_windows(self.args.windows)
|
if not n.set_expect_no_italic(self.args.noitalic):
|
||||||
|
logger.critical("Detected 'Italic' slant but --has-no-italic specified")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
# All the following stuff is ignored in makegroups-mode
|
# All the following stuff is ignored in makegroups-mode
|
||||||
|
|
||||||
|
@ -598,23 +623,7 @@ class font_patcher:
|
||||||
if len(subFamily) == 0:
|
if len(subFamily) == 0:
|
||||||
subFamily = "Regular"
|
subFamily = "Regular"
|
||||||
|
|
||||||
if self.args.windows:
|
familyname += " " + projectNameSingular + variant_full
|
||||||
maxFamilyLength = 31
|
|
||||||
maxFontLength = maxFamilyLength - len('-' + subFamily)
|
|
||||||
familyname += " " + projectNameAbbreviation
|
|
||||||
if self.args.single:
|
|
||||||
familyname += "M"
|
|
||||||
fullname += " Windows Compatible"
|
|
||||||
|
|
||||||
# now make sure less than 32 characters name length
|
|
||||||
if len(fontname) > maxFontLength:
|
|
||||||
fontname = fontname[:maxFontLength]
|
|
||||||
if len(familyname) > maxFamilyLength:
|
|
||||||
familyname = familyname[:maxFamilyLength]
|
|
||||||
else:
|
|
||||||
familyname += " " + projectNameSingular
|
|
||||||
if self.args.single:
|
|
||||||
familyname += " Mono"
|
|
||||||
|
|
||||||
# Don't truncate the subfamily to keep fontname unique. MacOS treats fonts with
|
# Don't truncate the subfamily to keep fontname unique. MacOS treats fonts with
|
||||||
# the same name as the same font, even if subFamily is different. Make sure to
|
# the same name as the same font, even if subFamily is different. Make sure to
|
||||||
|
@ -627,6 +636,10 @@ class font_patcher:
|
||||||
reservedFontNameReplacements = {
|
reservedFontNameReplacements = {
|
||||||
'source' : 'sauce',
|
'source' : 'sauce',
|
||||||
'Source' : 'Sauce',
|
'Source' : 'Sauce',
|
||||||
|
'Bitstream Vera Sans Mono' : 'Bitstrom Wera',
|
||||||
|
'BitstreamVeraSansMono' : 'BitstromWera',
|
||||||
|
'bitstream vera sans mono' : 'bitstrom wera',
|
||||||
|
'bitstreamverasansmono' : 'bitstromwera',
|
||||||
'hermit' : 'hurmit',
|
'hermit' : 'hurmit',
|
||||||
'Hermit' : 'Hurmit',
|
'Hermit' : 'Hurmit',
|
||||||
'hasklig' : 'hasklug',
|
'hasklig' : 'hasklug',
|
||||||
|
@ -693,7 +706,7 @@ class font_patcher:
|
||||||
fullname = replace_font_name(fullname, additionalFontNameReplacements2)
|
fullname = replace_font_name(fullname, additionalFontNameReplacements2)
|
||||||
fontname = replace_font_name(fontname, additionalFontNameReplacements2)
|
fontname = replace_font_name(fontname, additionalFontNameReplacements2)
|
||||||
|
|
||||||
if not (FontnameParserOK and self.args.makegroups):
|
if not (FontnameParserOK and self.args.makegroups > 0):
|
||||||
# replace any extra whitespace characters:
|
# replace any extra whitespace characters:
|
||||||
font.familyname = " ".join(familyname.split())
|
font.familyname = " ".join(familyname.split())
|
||||||
font.fullname = " ".join(fullname.split())
|
font.fullname = " ".join(fullname.split())
|
||||||
|
@ -704,13 +717,9 @@ class font_patcher:
|
||||||
font.appendSFNTName(str('English (US)'), str('Compatible Full'), font.fullname)
|
font.appendSFNTName(str('English (US)'), str('Compatible Full'), font.fullname)
|
||||||
font.appendSFNTName(str('English (US)'), str('SubFamily'), subFamily)
|
font.appendSFNTName(str('English (US)'), str('SubFamily'), subFamily)
|
||||||
else:
|
else:
|
||||||
fam_suffix = projectNameSingular if not self.args.windows else projectNameAbbreviation
|
short_family = projectNameAbbreviation + variant_abbrev if self.args.makegroups >= 4 else projectNameSingular + variant_full
|
||||||
if self.args.single:
|
# inject_suffix(family, ps_fontname, short_family)
|
||||||
if self.args.windows:
|
n.inject_suffix(verboseAdditionalFontNameSuffix, ps_suffix, short_family)
|
||||||
fam_suffix += 'M'
|
|
||||||
else:
|
|
||||||
fam_suffix += ' Mono'
|
|
||||||
n.inject_suffix(verboseAdditionalFontNameSuffix, additionalFontNameSuffix, fam_suffix)
|
|
||||||
n.rename_font(font)
|
n.rename_font(font)
|
||||||
|
|
||||||
font.comment = projectInfo
|
font.comment = projectInfo
|
||||||
|
@ -726,25 +735,26 @@ class font_patcher:
|
||||||
self.sourceFont.version = str(self.sourceFont.cidversion) + ";" + projectName + " " + version
|
self.sourceFont.version = str(self.sourceFont.cidversion) + ";" + projectName + " " + version
|
||||||
self.sourceFont.sfntRevision = None # Auto-set (refreshed) by fontforge
|
self.sourceFont.sfntRevision = None # Auto-set (refreshed) by fontforge
|
||||||
self.sourceFont.appendSFNTName(str('English (US)'), str('Version'), "Version " + self.sourceFont.version)
|
self.sourceFont.appendSFNTName(str('English (US)'), str('Version'), "Version " + self.sourceFont.version)
|
||||||
|
# The Version SFNT name is later reused by the NameParser for UniqueID
|
||||||
# print("Version now is {}".format(sourceFont.version))
|
# print("Version now is {}".format(sourceFont.version))
|
||||||
|
|
||||||
|
|
||||||
def remove_ligatures(self):
|
def remove_ligatures(self):
|
||||||
# let's deal with ligatures (mostly for monospaced fonts)
|
# 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.configfile and self.config.read(self.args.configfile):
|
||||||
if self.args.removeligatures:
|
if self.args.removeligatures:
|
||||||
print("Removing ligatures from configfile `Subtables` section")
|
logger.info("Removing ligatures from configfile `Subtables` section")
|
||||||
ligature_subtables = json.loads(self.config.get("Subtables", "ligatures"))
|
ligature_subtables = json.loads(self.config.get("Subtables", "ligatures"))
|
||||||
for subtable in ligature_subtables:
|
for subtable in ligature_subtables:
|
||||||
print("Removing subtable:", subtable)
|
logger.debug("Removing subtable: %s", subtable)
|
||||||
try:
|
try:
|
||||||
self.sourceFont.removeLookupSubtable(subtable)
|
self.sourceFont.removeLookupSubtable(subtable)
|
||||||
print("Successfully removed subtable:", subtable)
|
logger.debug("Successfully removed subtable: %s", subtable)
|
||||||
except Exception:
|
except Exception:
|
||||||
print("Failed to remove subtable:", subtable)
|
logger.error("Failed to remove subtable: %s", subtable)
|
||||||
elif self.args.removeligatures:
|
elif self.args.removeligatures:
|
||||||
print("Unable to read configfile, unable to remove ligatures")
|
logger.error("Unable to read configfile, unable to remove ligatures")
|
||||||
|
|
||||||
|
|
||||||
def assert_monospace(self):
|
def assert_monospace(self):
|
||||||
|
@ -756,16 +766,17 @@ class font_patcher:
|
||||||
panose_mono = check_panose_monospaced(self.sourceFont)
|
panose_mono = check_panose_monospaced(self.sourceFont)
|
||||||
# The following is in fact "width_mono != panose_mono", but only if panose_mono is not 'unknown'
|
# The following is in fact "width_mono != panose_mono", but only if panose_mono is not 'unknown'
|
||||||
if (width_mono and panose_mono == 0) or (not width_mono and panose_mono == 1):
|
if (width_mono and panose_mono == 0) or (not width_mono and panose_mono == 1):
|
||||||
print(" Warning: Monospaced check: Panose assumed to be wrong")
|
logger.warning("Monospaced check: Panose assumed to be wrong")
|
||||||
print(" {} and {}".format(
|
logger.warning(" %s and %s",
|
||||||
report_advance_widths(self.sourceFont),
|
report_advance_widths(self.sourceFont),
|
||||||
panose_check_to_text(panose_mono, self.sourceFont.os2_panose)))
|
panose_check_to_text(panose_mono, self.sourceFont.os2_panose))
|
||||||
if self.args.single and not width_mono:
|
if self.args.single and not width_mono:
|
||||||
print(" Warning: Sourcefont is not monospaced - forcing to monospace not advisable, results might be useless")
|
logger.warning("Sourcefont is not monospaced - forcing to monospace not advisable, results might be useless")
|
||||||
if offending_char is not None:
|
if offending_char is not None:
|
||||||
print(" Offending char: 0x{:X}".format(offending_char))
|
logger.warning(" Offending char: %X", offending_char)
|
||||||
if self.args.single <= 1:
|
if self.args.single <= 1:
|
||||||
sys.exit(projectName + ": Font will not be patched! Give --mono (or -s, or --use-single-width-glyphs) twice to force patching")
|
logger.critical("Font will not be patched! Give --mono (or -s, or --use-single-width-glyphs) twice to force patching")
|
||||||
|
sys.exit(1)
|
||||||
if width_mono:
|
if width_mono:
|
||||||
force_panose_monospaced(self.sourceFont)
|
force_panose_monospaced(self.sourceFont)
|
||||||
|
|
||||||
|
@ -781,9 +792,9 @@ class font_patcher:
|
||||||
box_glyphs_current = len(list(self.sourceFont.selection.byGlyphs))
|
box_glyphs_current = len(list(self.sourceFont.selection.byGlyphs))
|
||||||
if box_glyphs_target > box_glyphs_current:
|
if box_glyphs_target > box_glyphs_current:
|
||||||
# Sourcefont does not have all of these glyphs, do not mix sets (overwrite existing)
|
# Sourcefont does not have all of these glyphs, do not mix sets (overwrite existing)
|
||||||
if not self.args.quiet and box_glyphs_current > 0:
|
if box_glyphs_current > 0:
|
||||||
print("INFO: {}/{} box drawing glyphs will be replaced".format(
|
logger.debug("%d/%d box drawing glyphs will be replaced",
|
||||||
box_glyphs_current, box_glyphs_target))
|
box_glyphs_current, box_glyphs_target)
|
||||||
box_enabled = True
|
box_enabled = True
|
||||||
else:
|
else:
|
||||||
# Sourcefont does have all of these glyphs
|
# Sourcefont does have all of these glyphs
|
||||||
|
@ -1020,14 +1031,14 @@ class font_patcher:
|
||||||
{'Enabled': self.args.fontawesomeextension, 'Name': "Font Awesome Extension", 'Filename': "font-awesome-extension.ttf", 'Exact': False, 'SymStart': 0xE000, 'SymEnd': 0xE0A9, 'SrcStart': 0xE200, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT}, # Maximize
|
{'Enabled': self.args.fontawesomeextension, 'Name': "Font Awesome Extension", 'Filename': "font-awesome-extension.ttf", 'Exact': False, 'SymStart': 0xE000, 'SymEnd': 0xE0A9, 'SrcStart': 0xE200, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT}, # Maximize
|
||||||
{'Enabled': self.args.powersymbols, 'Name': "Power Symbols", 'Filename': "Unicode_IEC_symbol_font.otf", 'Exact': True, 'SymStart': 0x23FB, 'SymEnd': 0x23FE, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT}, # Power, Power On/Off, Power On, Sleep
|
{'Enabled': self.args.powersymbols, 'Name': "Power Symbols", 'Filename': "Unicode_IEC_symbol_font.otf", 'Exact': True, 'SymStart': 0x23FB, 'SymEnd': 0x23FE, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT}, # Power, Power On/Off, Power On, Sleep
|
||||||
{'Enabled': self.args.powersymbols, 'Name': "Power Symbols", 'Filename': "Unicode_IEC_symbol_font.otf", 'Exact': True, 'SymStart': 0x2B58, 'SymEnd': 0x2B58, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT}, # Heavy Circle (aka Power Off)
|
{'Enabled': self.args.powersymbols, 'Name': "Power Symbols", 'Filename': "Unicode_IEC_symbol_font.otf", 'Exact': True, 'SymStart': 0x2B58, 'SymEnd': 0x2B58, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT}, # Heavy Circle (aka Power Off)
|
||||||
{'Enabled': self.args.material, 'Name': "Material legacy", 'Filename': "materialdesignicons-webfont.ttf", 'Exact': False, 'SymStart': 0xF001, 'SymEnd': 0xF847, 'SrcStart': 0xF500, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT},
|
{'Enabled': False , 'Name': "Material legacy", 'Filename': "materialdesignicons-webfont.ttf", 'Exact': False, 'SymStart': 0xF001, 'SymEnd': 0xF847, 'SrcStart': 0xF500, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT},
|
||||||
{'Enabled': self.args.material, 'Name': "Material", 'Filename': "materialdesign/MaterialDesignIconsDesktop.ttf", 'Exact': True, 'SymStart': 0xF0001,'SymEnd': 0xF1AF0,'SrcStart': None, 'ScaleRules': MDI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT},
|
{'Enabled': self.args.material, 'Name': "Material", 'Filename': "materialdesign/MaterialDesignIconsDesktop.ttf", 'Exact': True, 'SymStart': 0xF0001,'SymEnd': 0xF1AF0,'SrcStart': None, 'ScaleRules': MDI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT},
|
||||||
{'Enabled': self.args.weather, 'Name': "Weather Icons", 'Filename': "weather-icons/weathericons-regular-webfont.ttf", 'Exact': False, 'SymStart': 0xF000, 'SymEnd': 0xF0EB, 'SrcStart': 0xE300, 'ScaleRules': WEATH_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT},
|
{'Enabled': self.args.weather, 'Name': "Weather Icons", 'Filename': "weather-icons/weathericons-regular-webfont.ttf", 'Exact': False, 'SymStart': 0xF000, 'SymEnd': 0xF0EB, 'SrcStart': 0xE300, 'ScaleRules': WEATH_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT},
|
||||||
{'Enabled': self.args.fontlogos, 'Name': "Font Logos", 'Filename': "font-logos.ttf", 'Exact': True, 'SymStart': 0xF300, 'SymEnd': 0xF32F, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT},
|
{'Enabled': self.args.fontlogos, 'Name': "Font Logos", 'Filename': "font-logos.ttf", 'Exact': True, 'SymStart': 0xF300, 'SymEnd': 0xF32F, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT},
|
||||||
{'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons.ttf", 'Exact': False, 'SymStart': 0xF000, 'SymEnd': 0xF105, 'SrcStart': 0xF400, 'ScaleRules': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Magnifying glass
|
{'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons/octicons.ttf", 'Exact': False, 'SymStart': 0xF000, 'SymEnd': 0xF105, 'SrcStart': 0xF400, 'ScaleRules': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Magnifying glass
|
||||||
{'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons.ttf", 'Exact': True, 'SymStart': 0x2665, 'SymEnd': 0x2665, 'SrcStart': None, 'ScaleRules': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Heart
|
{'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons/octicons.ttf", 'Exact': True, 'SymStart': 0x2665, 'SymEnd': 0x2665, 'SrcStart': None, 'ScaleRules': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Heart
|
||||||
{'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons.ttf", 'Exact': True, 'SymStart': 0X26A1, 'SymEnd': 0X26A1, 'SrcStart': None, 'ScaleRules': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Zap
|
{'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons/octicons.ttf", 'Exact': True, 'SymStart': 0X26A1, 'SymEnd': 0X26A1, 'SrcStart': None, 'ScaleRules': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Zap
|
||||||
{'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons.ttf", 'Exact': False, 'SymStart': 0xF27C, 'SymEnd': 0xF27C, 'SrcStart': 0xF4A9, 'ScaleRules': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Desktop
|
{'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons/octicons.ttf", 'Exact': False, 'SymStart': 0xF27C, 'SymEnd': 0xF305, 'SrcStart': 0xF4A9, 'ScaleRules': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT},
|
||||||
{'Enabled': self.args.codicons, 'Name': "Codicons", 'Filename': "codicons/codicon.ttf", 'Exact': True, 'SymStart': 0xEA60, 'SymEnd': 0xEBEB, 'SrcStart': None, 'ScaleRules': CODI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT},
|
{'Enabled': self.args.codicons, 'Name': "Codicons", 'Filename': "codicons/codicon.ttf", 'Exact': True, 'SymStart': 0xEA60, 'SymEnd': 0xEBEB, 'SrcStart': None, 'ScaleRules': CODI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT},
|
||||||
{'Enabled': self.args.custom, 'Name': "Custom", 'Filename': self.args.custom, 'Exact': True, 'SymStart': 0x0000, 'SymEnd': 0x0000, 'SrcStart': None, 'ScaleRules': None, 'Attributes': CUSTOM_ATTR}
|
{'Enabled': self.args.custom, 'Name': "Custom", 'Filename': self.args.custom, 'Exact': True, 'SymStart': 0x0000, 'SymEnd': 0x0000, 'SrcStart': None, 'ScaleRules': None, 'Attributes': CUSTOM_ATTR}
|
||||||
]
|
]
|
||||||
|
@ -1102,11 +1113,22 @@ class font_patcher:
|
||||||
our_btb = typo_btb if use_typo else win_btb
|
our_btb = typo_btb if use_typo else win_btb
|
||||||
if our_btb == hhea_btb:
|
if our_btb == hhea_btb:
|
||||||
metrics = Metric.TYPO if use_typo else Metric.WIN # conforming font
|
metrics = Metric.TYPO if use_typo else Metric.WIN # conforming font
|
||||||
|
elif abs(our_btb - hhea_btb) / our_btb < 0.03:
|
||||||
|
logger.info("Font vertical metrics slightly off (%.1f%)", (our_btb - hhea_btb) / our_btb * 100.0)
|
||||||
|
metrics = Metric.TYPO if use_typo else Metric.WIN
|
||||||
else:
|
else:
|
||||||
# We trust the WIN metric more, see experiments in #1056
|
# Try the other metric
|
||||||
print("{}: WARNING Font vertical metrics inconsistent (HHEA {} / TYPO {} / WIN {}), using WIN".format(projectName, hhea_btb, typo_btb, win_btb))
|
our_btb = typo_btb if not use_typo else win_btb
|
||||||
our_btb = win_btb
|
if our_btb == hhea_btb:
|
||||||
metrics = Metric.WIN
|
logger.warning("Font vertical metrics probably wrong USE TYPO METRICS, assume opposite (i.e. %s)", 'True' if not use_typo else 'False')
|
||||||
|
use_typo = not use_typo
|
||||||
|
self.sourceFont.os2_use_typo_metrics = 1 if use_typo else 0
|
||||||
|
metrics = Metric.TYPO if use_typo else Metric.WIN
|
||||||
|
else:
|
||||||
|
# We trust the WIN metric more, see experiments in #1056
|
||||||
|
logger.warning("Font vertical metrics inconsistent (HHEA %d / TYPO %d / WIN %d), using WIN", hhea_btb, typo_btb, win_btb)
|
||||||
|
our_btb = win_btb
|
||||||
|
metrics = Metric.WIN
|
||||||
|
|
||||||
# print("FINI hhea {} typo {} win {} use {} {} {}".format(hhea_btb, typo_btb, win_btb, use_typo, our_btb != hhea_btb, self.sourceFont.fontname))
|
# print("FINI hhea {} typo {} win {} use {} {} {}".format(hhea_btb, typo_btb, win_btb, use_typo, our_btb != hhea_btb, self.sourceFont.fontname))
|
||||||
|
|
||||||
|
@ -1129,6 +1151,7 @@ class font_patcher:
|
||||||
if self.font_dim['height'] == 0:
|
if self.font_dim['height'] == 0:
|
||||||
# This can only happen if the input font is empty
|
# This can only happen if the input font is empty
|
||||||
# Assume we are using our prepared templates
|
# Assume we are using our prepared templates
|
||||||
|
self.symbolsonly = True
|
||||||
self.font_dim = {
|
self.font_dim = {
|
||||||
'xmin' : 0,
|
'xmin' : 0,
|
||||||
'ymin' : -self.sourceFont.descent,
|
'ymin' : -self.sourceFont.descent,
|
||||||
|
@ -1139,7 +1162,8 @@ class font_patcher:
|
||||||
}
|
}
|
||||||
our_btb = self.sourceFont.descent + self.sourceFont.ascent
|
our_btb = self.sourceFont.descent + self.sourceFont.ascent
|
||||||
elif self.font_dim['height'] < 0:
|
elif self.font_dim['height'] < 0:
|
||||||
sys.exit("{}: Can not detect sane font height".format(projectName))
|
logger.critical("Can not detect sane font height")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
# Make all metrics equal
|
# Make all metrics equal
|
||||||
self.sourceFont.os2_typolinegap = 0
|
self.sourceFont.os2_typolinegap = 0
|
||||||
|
@ -1153,12 +1177,13 @@ class font_patcher:
|
||||||
self.sourceFont.os2_use_typo_metrics = 1
|
self.sourceFont.os2_use_typo_metrics = 1
|
||||||
(check_hhea_btb, check_typo_btb, check_win_btb, _) = get_btb_metrics(self.sourceFont)
|
(check_hhea_btb, check_typo_btb, check_win_btb, _) = get_btb_metrics(self.sourceFont)
|
||||||
if check_hhea_btb != check_typo_btb or check_typo_btb != check_win_btb or check_win_btb != our_btb:
|
if check_hhea_btb != check_typo_btb or check_typo_btb != check_win_btb or check_win_btb != our_btb:
|
||||||
sys.exit("{}: Error in baseline to baseline code detected".format(projectName))
|
logger.critical("Error in baseline to baseline code detected")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
# Step 2
|
# Step 2
|
||||||
# Find the biggest char width and advance width
|
# Find the biggest char width and advance width
|
||||||
# 0x00-0x17f is the Latin Extended-A range
|
# 0x00-0x17f is the Latin Extended-A range
|
||||||
warned1 = self.args.quiet or self.args.nonmono # Do not warn if quiet or proportional target
|
warned1 = self.args.nonmono # Do not warn if proportional target
|
||||||
warned2 = warned1
|
warned2 = warned1
|
||||||
for glyph in range(0x21, 0x17f):
|
for glyph in range(0x21, 0x17f):
|
||||||
if glyph in range(0x7F, 0xBF) or glyph in [
|
if glyph in range(0x7F, 0xBF) or glyph in [
|
||||||
|
@ -1176,19 +1201,18 @@ class font_patcher:
|
||||||
if self.font_dim['width'] < self.sourceFont[glyph].width:
|
if self.font_dim['width'] < self.sourceFont[glyph].width:
|
||||||
self.font_dim['width'] = self.sourceFont[glyph].width
|
self.font_dim['width'] = self.sourceFont[glyph].width
|
||||||
if not warned1 and glyph > 0x7a: # NOT 'basic' glyph, which includes a-zA-Z
|
if not warned1 and glyph > 0x7a: # NOT 'basic' glyph, which includes a-zA-Z
|
||||||
print("Warning: Extended glyphs wider than basic glyphs, results might be useless\n {}".format(
|
logger.debug("Extended glyphs wider than basic glyphs, results might be useless\n %s",
|
||||||
report_advance_widths(self.sourceFont)))
|
report_advance_widths(self.sourceFont))
|
||||||
warned1 = True
|
warned1 = True
|
||||||
# print("New MAXWIDTH-A {:X} {} -> {} {}".format(glyph, self.sourceFont[glyph].width, self.font_dim['width'], xmax))
|
# print("New MAXWIDTH-A {:X} {} -> {} {}".format(glyph, self.sourceFont[glyph].width, self.font_dim['width'], xmax))
|
||||||
if xmax > self.font_dim['xmax']:
|
if xmax > self.font_dim['xmax']:
|
||||||
self.font_dim['xmax'] = xmax
|
self.font_dim['xmax'] = xmax
|
||||||
if not warned2 and glyph > 0x7a: # NOT 'basic' glyph, which includes a-zA-Z
|
if not warned2 and glyph > 0x7a: # NOT 'basic' glyph, which includes a-zA-Z
|
||||||
print("Info: Extended glyphs wider bounding box than basic glyphs")
|
logger.debug("Extended glyphs wider bounding box than basic glyphs")
|
||||||
warned2 = True
|
warned2 = True
|
||||||
# print("New MAXWIDTH-B {:X} {} -> {} {}".format(glyph, self.sourceFont[glyph].width, self.font_dim['width'], xmax))
|
# print("New MAXWIDTH-B {:X} {} -> {} {}".format(glyph, self.sourceFont[glyph].width, self.font_dim['width'], xmax))
|
||||||
if self.font_dim['width'] < self.font_dim['xmax']:
|
if self.font_dim['width'] < self.font_dim['xmax']:
|
||||||
if not self.args.quiet:
|
logger.debug("Font has negative right side bearing in extended glyphs")
|
||||||
print("Warning: Font has negative right side bearing in extended glyphs")
|
|
||||||
self.font_dim['xmax'] = self.font_dim['width'] # In fact 'xmax' is never used
|
self.font_dim['xmax'] = self.font_dim['width'] # In fact 'xmax' is never used
|
||||||
# print("FINAL", self.font_dim)
|
# print("FINAL", self.font_dim)
|
||||||
|
|
||||||
|
@ -1288,7 +1312,7 @@ class font_patcher:
|
||||||
if sym_glyph.altuni:
|
if sym_glyph.altuni:
|
||||||
possible_codes += [ v for v, s, r in sym_glyph.altuni if v > currentSourceFontGlyph ]
|
possible_codes += [ v for v, s, r in sym_glyph.altuni if v > currentSourceFontGlyph ]
|
||||||
if len(possible_codes) == 0:
|
if len(possible_codes) == 0:
|
||||||
print(" Can not determine codepoint of {:X}. Skipping...".format(sym_glyph.unicode))
|
logger.warning("Can not determine codepoint of %X. Skipping...", sym_glyph.unicode)
|
||||||
continue
|
continue
|
||||||
currentSourceFontGlyph = min(possible_codes)
|
currentSourceFontGlyph = min(possible_codes)
|
||||||
else:
|
else:
|
||||||
|
@ -1311,9 +1335,8 @@ class font_patcher:
|
||||||
# check if a glyph already exists in this location
|
# check if a glyph already exists in this location
|
||||||
if careful or 'careful' in sym_attr['params'] or currentSourceFontGlyph in self.essential:
|
if careful or 'careful' in sym_attr['params'] or currentSourceFontGlyph in self.essential:
|
||||||
if currentSourceFontGlyph in self.sourceFont:
|
if currentSourceFontGlyph in self.sourceFont:
|
||||||
if not self.args.quiet:
|
careful_type = 'essential' if currentSourceFontGlyph in self.essential else 'existing'
|
||||||
careful_type = 'essential' if currentSourceFontGlyph in self.essential else 'existing'
|
logger.debug("Found %s Glyph at %X. Skipping...", careful_type, currentSourceFontGlyph)
|
||||||
print(" Found {} Glyph at {:X}. Skipping...".format(careful_type, currentSourceFontGlyph))
|
|
||||||
# We don't want to touch anything so move to next Glyph
|
# We don't want to touch anything so move to next Glyph
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
|
@ -1461,8 +1484,8 @@ class font_patcher:
|
||||||
if self.args.single:
|
if self.args.single:
|
||||||
(xmin, _, xmax, _) = self.sourceFont[currentSourceFontGlyph].boundingBox()
|
(xmin, _, xmax, _) = self.sourceFont[currentSourceFontGlyph].boundingBox()
|
||||||
if int(xmax - xmin) > self.font_dim['width'] * (1 + (overlap or 0)):
|
if int(xmax - xmin) > self.font_dim['width'] * (1 + (overlap or 0)):
|
||||||
print("\n Warning: Scaled glyph U+{:X} wider than one monospace width ({} / {} (overlap {}))".format(
|
logger.warning("Scaled glyph %X wider than one monospace width (%d / %d (overlap %f))",
|
||||||
currentSourceFontGlyph, int(xmax - xmin), self.font_dim['width'], overlap))
|
currentSourceFontGlyph, int(xmax - xmin), self.font_dim['width'], overlap)
|
||||||
|
|
||||||
# end for
|
# end for
|
||||||
|
|
||||||
|
@ -1603,7 +1626,7 @@ def half_gap(gap, top):
|
||||||
gap_top = int(gap / 2)
|
gap_top = int(gap / 2)
|
||||||
gap_bottom = gap - gap_top
|
gap_bottom = gap - gap_top
|
||||||
if top:
|
if top:
|
||||||
print("Redistributing line gap of {} ({} top and {} bottom)".format(gap, gap_top, gap_bottom))
|
logger.info("Redistributing line gap of %d (%d top and %d bottom)", gap, gap_top, gap_bottom)
|
||||||
return gap_top
|
return gap_top
|
||||||
return gap_bottom
|
return gap_bottom
|
||||||
|
|
||||||
|
@ -1728,8 +1751,8 @@ def check_fontforge_min_version():
|
||||||
|
|
||||||
# versions tested: 20150612, 20150824
|
# versions tested: 20150612, 20150824
|
||||||
if actualVersion < minimumVersion:
|
if actualVersion < minimumVersion:
|
||||||
sys.stderr.write("{}: You seem to be using an unsupported (old) version of fontforge: {}\n".format(projectName, actualVersion))
|
logger.critical("You seem to be using an unsupported (old) version of fontforge: %d", actualVersion)
|
||||||
sys.stderr.write("{}: Please use at least version: {}\n".format(projectName, minimumVersion))
|
logger.critical("Please use at least version: %d", minimumVersion)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
def check_version_with_git(version):
|
def check_version_with_git(version):
|
||||||
|
@ -1777,7 +1800,6 @@ def setup_arguments():
|
||||||
parser.add_argument('-s', '--mono', '--use-single-width-glyphs', dest='single', default=False, action='count', help='Whether to generate the glyphs as single-width not double-width (default is double-width)')
|
parser.add_argument('-s', '--mono', '--use-single-width-glyphs', dest='single', default=False, action='count', help='Whether to generate the glyphs as single-width not double-width (default is double-width)')
|
||||||
parser.add_argument('-l', '--adjust-line-height', dest='adjustLineHeight', default=False, action='store_true', help='Whether to adjust line heights (attempt to center powerline separators more evenly)')
|
parser.add_argument('-l', '--adjust-line-height', dest='adjustLineHeight', default=False, action='store_true', help='Whether to adjust line heights (attempt to center powerline separators more evenly)')
|
||||||
parser.add_argument('-q', '--quiet', '--shutup', dest='quiet', default=False, action='store_true', help='Do not generate verbose output')
|
parser.add_argument('-q', '--quiet', '--shutup', dest='quiet', default=False, action='store_true', help='Do not generate verbose output')
|
||||||
parser.add_argument('-w', '--windows', dest='windows', default=False, action='store_true', help='Limit the internal font name to 31 characters (for Windows compatibility)')
|
|
||||||
parser.add_argument('-c', '--complete', dest='complete', default=False, action='store_true', help='Add all available Glyphs')
|
parser.add_argument('-c', '--complete', dest='complete', default=False, action='store_true', help='Add all available Glyphs')
|
||||||
parser.add_argument('--careful', dest='careful', default=False, action='store_true', help='Do not overwrite existing glyphs if detected')
|
parser.add_argument('--careful', dest='careful', default=False, action='store_true', help='Do not overwrite existing glyphs if detected')
|
||||||
parser.add_argument('--removeligs', '--removeligatures', dest='removeligatures', default=False, action='store_true', help='Removes ligatures specificed in JSON configuration file')
|
parser.add_argument('--removeligs', '--removeligatures', dest='removeligatures', default=False, action='store_true', help='Removes ligatures specificed in JSON configuration file')
|
||||||
|
@ -1787,15 +1809,28 @@ def setup_arguments():
|
||||||
parser.add_argument('-ext', '--extension', dest='extension', default="", type=str, nargs='?', help='Change font file type to create (e.g., ttf, otf)')
|
parser.add_argument('-ext', '--extension', dest='extension', default="", type=str, nargs='?', help='Change font file type to create (e.g., ttf, otf)')
|
||||||
parser.add_argument('-out', '--outputdir', dest='outputdir', default=".", type=str, nargs='?', help='The directory to output the patched font file to')
|
parser.add_argument('-out', '--outputdir', dest='outputdir', default=".", type=str, nargs='?', help='The directory to output the patched font file to')
|
||||||
parser.add_argument('--glyphdir', dest='glyphdir', default=__dir__ + "/src/glyphs/", type=str, nargs='?', help='Path to glyphs to be used for patching')
|
parser.add_argument('--glyphdir', dest='glyphdir', default=__dir__ + "/src/glyphs/", type=str, nargs='?', help='Path to glyphs to be used for patching')
|
||||||
parser.add_argument('--makegroups', dest='makegroups', default=False, action='store_true', help='Use alternative method to name patched fonts (experimental)')
|
parser.add_argument('--makegroups', dest='makegroups', default=1, type=int, nargs='?', help='Use alternative method to name patched fonts (recommended)', const=1, choices=range(0, 6 + 1))
|
||||||
|
# --makegroup has an additional undocumented numeric specifier. '--makegroup' is in fact '--makegroup 1'.
|
||||||
|
# Original font name: Hugo Sans Mono ExtraCondensed Light Italic
|
||||||
|
# NF Fam agg.
|
||||||
|
# 0 turned off, use old naming scheme [-] [-] [-]
|
||||||
|
# 1 HugoSansMono Nerd Font ExtraCondensed Light Italic [ ] [ ] [ ]
|
||||||
|
# 2 HugoSansMono Nerd Font ExtCn Light Italic [ ] [X] [ ]
|
||||||
|
# 3 HugoSansMono Nerd Font XCn Lt It [ ] [X] [X]
|
||||||
|
# 4 HugoSansMono NF ExtraCondensed Light Italic [X] [ ] [ ]
|
||||||
|
# 5 HugoSansMono NF ExtCn Light Italic [X] [X] [ ]
|
||||||
|
# 6 HugoSansMono NF XCn Lt It [X] [X] [X]
|
||||||
|
|
||||||
parser.add_argument('--variable-width-glyphs', dest='nonmono', default=False, action='store_true', help='Do not adjust advance width (no "overhang")')
|
parser.add_argument('--variable-width-glyphs', dest='nonmono', default=False, action='store_true', help='Do not adjust advance width (no "overhang")')
|
||||||
|
parser.add_argument('--has-no-italic', dest='noitalic', default=False, action='store_true', help='Font family does not have Italic (but Oblique)')
|
||||||
|
|
||||||
# progress bar arguments - https://stackoverflow.com/questions/15008758/parsing-boolean-values-with-argparse
|
# progress bar arguments - https://stackoverflow.com/questions/15008758/parsing-boolean-values-with-argparse
|
||||||
progressbars_group_parser = parser.add_mutually_exclusive_group(required=False)
|
progressbars_group_parser = parser.add_mutually_exclusive_group(required=False)
|
||||||
progressbars_group_parser.add_argument('--progressbars', dest='progressbars', action='store_true', help='Show percentage completion progress bars per Glyph Set')
|
progressbars_group_parser.add_argument('--progressbars', dest='progressbars', action='store_true', help='Show percentage completion progress bars per Glyph Set (default)')
|
||||||
progressbars_group_parser.add_argument('--no-progressbars', dest='progressbars', action='store_false', help='Don\'t show percentage completion progress bars per Glyph Set')
|
progressbars_group_parser.add_argument('--no-progressbars', dest='progressbars', action='store_false', help='Don\'t show percentage completion progress bars per Glyph Set')
|
||||||
parser.set_defaults(progressbars=True)
|
parser.set_defaults(progressbars=True)
|
||||||
parser.add_argument('--also-windows', dest='alsowindows', default=False, action='store_true', help='Create two fonts, the normal and the --windows version')
|
parser.add_argument('--debug', dest='debugmode', default=False, action='store_true', help='Verbose mode')
|
||||||
|
parser.add_argument('--dry', dest='dry_run', default=False, action='store_true', help='Do neither patch nor store the font, to check naming')
|
||||||
parser.add_argument('--xavgcharwidth', dest='xavgwidth', default=None, type=int, nargs='?', help='Adjust xAvgCharWidth (optional: concrete value)', const=True)
|
parser.add_argument('--xavgcharwidth', dest='xavgwidth', default=None, type=int, nargs='?', help='Adjust xAvgCharWidth (optional: concrete value)', const=True)
|
||||||
# --xavgcharwidth for compatibility with old applications like notepad and non-latin fonts
|
# --xavgcharwidth for compatibility with old applications like notepad and non-latin fonts
|
||||||
# Possible values with examples:
|
# Possible values with examples:
|
||||||
|
@ -1819,8 +1854,9 @@ def setup_arguments():
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if args.makegroups and not FontnameParserOK:
|
if args.makegroups > 0 and not FontnameParserOK:
|
||||||
sys.exit("{}: FontnameParser module missing (bin/scripts/name_parser/Fontname*), can not --makegroups".format(projectName))
|
logger.critical("FontnameParser module missing (bin/scripts/name_parser/Fontname*), specify --makegroups 0")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
# if you add a new font, set it to True here inside the if condition
|
# if you add a new font, set it to True here inside the if condition
|
||||||
if args.complete:
|
if args.complete:
|
||||||
|
@ -1853,24 +1889,23 @@ def setup_arguments():
|
||||||
font_complete = False
|
font_complete = False
|
||||||
args.complete = font_complete
|
args.complete = font_complete
|
||||||
|
|
||||||
if args.alsowindows:
|
|
||||||
args.windows = False
|
|
||||||
|
|
||||||
if args.nonmono and args.single:
|
if args.nonmono and args.single:
|
||||||
print("Warning: Specified contradicting --variable-width-glyphs and --use-single-width-glyph. Ignoring --variable-width-glyphs.")
|
logging.warning("Specified contradicting --variable-width-glyphs and --use-single-width-glyph. Ignoring --variable-width-glyphs.")
|
||||||
args.nonmono = False
|
args.nonmono = False
|
||||||
|
|
||||||
make_sure_path_exists(args.outputdir)
|
make_sure_path_exists(args.outputdir)
|
||||||
if not os.path.isfile(args.font):
|
if not os.path.isfile(args.font):
|
||||||
sys.exit("{}: Font file does not exist: {}".format(projectName, args.font))
|
logging.critical("Font file does not exist: %s", args.font)
|
||||||
|
sys.exit(1)
|
||||||
if not os.access(args.font, os.R_OK):
|
if not os.access(args.font, os.R_OK):
|
||||||
sys.exit("{}: Can not open font file for reading: {}".format(projectName, args.font))
|
logging.critical("Can not open font file for reading: %s", args.font)
|
||||||
|
sys.exit(1)
|
||||||
is_ttc = len(fontforge.fontsInFile(args.font)) > 1
|
is_ttc = len(fontforge.fontsInFile(args.font)) > 1
|
||||||
try:
|
try:
|
||||||
source_font_test = TableHEADWriter(args.font)
|
source_font_test = TableHEADWriter(args.font)
|
||||||
args.is_variable = source_font_test.find_table([b'avar', b'cvar', b'fvar', b'gvarb', b'HVAR', b'MVAR', b'VVAR'], 0)
|
args.is_variable = source_font_test.find_table([b'avar', b'cvar', b'fvar', b'gvarb', b'HVAR', b'MVAR', b'VVAR'], 0)
|
||||||
if args.is_variable:
|
if args.is_variable:
|
||||||
print(" Warning: Source font is a variable open type font (VF), opening might fail...")
|
logging.warning("Source font is a variable open type font (VF), opening might fail...")
|
||||||
except:
|
except:
|
||||||
args.is_variable = False
|
args.is_variable = False
|
||||||
finally:
|
finally:
|
||||||
|
@ -1885,16 +1920,20 @@ def setup_arguments():
|
||||||
args.extension = '.' + args.extension
|
args.extension = '.' + args.extension
|
||||||
if re.match("\.ttc$", args.extension, re.IGNORECASE):
|
if re.match("\.ttc$", args.extension, re.IGNORECASE):
|
||||||
if not is_ttc:
|
if not is_ttc:
|
||||||
sys.exit(projectName + ": Can not create True Type Collections from single font files")
|
logging.critical("Can not create True Type Collections from single font files")
|
||||||
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
if is_ttc:
|
if is_ttc:
|
||||||
sys.exit(projectName + ": Can not create single font files from True Type Collections")
|
logging.critical("Can not create single font files from True Type Collections")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
if isinstance(args.xavgwidth, int) and not isinstance(args.xavgwidth, bool):
|
if isinstance(args.xavgwidth, int) and not isinstance(args.xavgwidth, bool):
|
||||||
if args.xavgwidth < 0:
|
if args.xavgwidth < 0:
|
||||||
sys.exit(projectName + ": --xavgcharwidth takes no negative numbers")
|
logging.critical("--xavgcharwidth takes no negative numbers")
|
||||||
|
sys.exit(2)
|
||||||
if args.xavgwidth > 16384:
|
if args.xavgwidth > 16384:
|
||||||
sys.exit(projectName + ": --xavgcharwidth takes only numbers up to 16384")
|
logging.critical("--xavgcharwidth takes only numbers up to 16384")
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
return args
|
return args
|
||||||
|
|
||||||
|
@ -1902,24 +1941,43 @@ def setup_arguments():
|
||||||
def main():
|
def main():
|
||||||
global version
|
global version
|
||||||
git_version = check_version_with_git(version)
|
git_version = check_version_with_git(version)
|
||||||
print("{} Patcher v{} ({}) (ff {}) executing".format(
|
allversions = "Patcher v{} ({}) (ff {})".format(
|
||||||
projectName, git_version if git_version else version, script_version, fontforge.version()))
|
git_version if git_version else version, script_version, fontforge.version())
|
||||||
|
print("{} {}".format(projectName, allversions))
|
||||||
if git_version:
|
if git_version:
|
||||||
version = git_version
|
version = git_version
|
||||||
check_fontforge_min_version()
|
check_fontforge_min_version()
|
||||||
args = setup_arguments()
|
args = setup_arguments()
|
||||||
|
|
||||||
|
global logger
|
||||||
|
logger = logging.getLogger(os.path.basename(args.font))
|
||||||
|
logger.setLevel(logging.DEBUG)
|
||||||
|
f_handler = logging.FileHandler('font-patcher-log.txt')
|
||||||
|
f_handler.setFormatter(logging.Formatter('%(levelname)s: %(name)s %(message)s'))
|
||||||
|
logger.addHandler(f_handler)
|
||||||
|
logger.debug(allversions)
|
||||||
|
logger.debug("Options %s", repr(sys.argv[1:]))
|
||||||
|
c_handler = logging.StreamHandler(stream=sys.stdout)
|
||||||
|
c_handler.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
|
||||||
|
if not args.debugmode:
|
||||||
|
c_handler.setLevel(logging.INFO)
|
||||||
|
logger.addHandler(c_handler)
|
||||||
|
logger.debug("Naming mode %d", args.makegroups)
|
||||||
|
|
||||||
patcher = font_patcher(args)
|
patcher = font_patcher(args)
|
||||||
|
|
||||||
sourceFonts = []
|
sourceFonts = []
|
||||||
all_fonts = fontforge.fontsInFile(args.font)
|
all_fonts = fontforge.fontsInFile(args.font)
|
||||||
for i, subfont in enumerate(all_fonts):
|
for i, subfont in enumerate(all_fonts):
|
||||||
if len(all_fonts) > 1:
|
if len(all_fonts) > 1:
|
||||||
print("\n{}: Processing {} ({}/{})".format(projectName, subfont, i + 1, len(all_fonts)))
|
print("\n")
|
||||||
|
logger.info("Processing %s (%d/%d)", subfont, i + 1, len(all_fonts))
|
||||||
try:
|
try:
|
||||||
sourceFonts.append(fontforge.open("{}({})".format(args.font, subfont), 1)) # 1 = ("fstypepermitted",))
|
sourceFonts.append(fontforge.open("{}({})".format(args.font, subfont), 1)) # 1 = ("fstypepermitted",))
|
||||||
except Exception:
|
except Exception:
|
||||||
sys.exit("{}: Can not open font '{}', try to open with fontforge interactively to get more information".format(
|
logger.critical("Can not open font '%s', try to open with fontforge interactively to get more information",
|
||||||
projectName, subfont))
|
subfont)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
patcher.patch(sourceFonts[-1])
|
patcher.patch(sourceFonts[-1])
|
||||||
|
|
||||||
|
@ -1928,13 +1986,6 @@ def main():
|
||||||
patcher.setup_font_names(f)
|
patcher.setup_font_names(f)
|
||||||
patcher.generate(sourceFonts)
|
patcher.generate(sourceFonts)
|
||||||
|
|
||||||
# This mainly helps to improve CI runtime
|
|
||||||
if patcher.args.alsowindows:
|
|
||||||
patcher.args.windows = True
|
|
||||||
for f in sourceFonts:
|
|
||||||
patcher.setup_font_names(f)
|
|
||||||
patcher.generate(sourceFonts)
|
|
||||||
|
|
||||||
for f in sourceFonts:
|
for f in sourceFonts:
|
||||||
f.close()
|
f.close()
|
||||||
|
|
|
@ -140,7 +140,7 @@ def patch_single_font(
|
||||||
cmd = [
|
cmd = [
|
||||||
"fontforge",
|
"fontforge",
|
||||||
"-script",
|
"-script",
|
||||||
"./bin/font-patcher",
|
"./font-patcher",
|
||||||
"--glyphdir",
|
"--glyphdir",
|
||||||
"./src/glyphs/",
|
"./src/glyphs/",
|
||||||
"-out",
|
"-out",
|
||||||
|
@ -182,8 +182,6 @@ def patch_font_dir_docker(
|
||||||
f"{font_dir_path}:/in",
|
f"{font_dir_path}:/in",
|
||||||
"-v",
|
"-v",
|
||||||
f"{output_path}:/out",
|
f"{output_path}:/out",
|
||||||
"-u",
|
|
||||||
f"{os.getuid()}:{os.getegid()}",
|
|
||||||
"nerdfonts/patcher",
|
"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.
21
src/glyphs/octicons/LICENSE
Normal file
21
src/glyphs/octicons/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 GitHub Inc.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
32
src/glyphs/octicons/analyze_octicons
Executable file
32
src/glyphs/octicons/analyze_octicons
Executable file
|
@ -0,0 +1,32 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# coding=utf8
|
||||||
|
|
||||||
|
# This extracts the names and source and destination codepoints
|
||||||
|
# of the old octicons font file to keep their codepoints stable
|
||||||
|
#
|
||||||
|
# You do not need to redo it, the result is in the repo
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# fontforge analyze_octicons > mapping
|
||||||
|
|
||||||
|
import fontforge
|
||||||
|
|
||||||
|
octi_orig = "octicons.ttf"
|
||||||
|
current_cp = 0xF400
|
||||||
|
|
||||||
|
print("# Examining {}".format(octi_orig))
|
||||||
|
|
||||||
|
font = fontforge.open(octi_orig)
|
||||||
|
for glyph in font.glyphs("encoding"):
|
||||||
|
point = glyph.unicode
|
||||||
|
if point < 0:
|
||||||
|
continue
|
||||||
|
desti = glyph.unicode
|
||||||
|
if point < 0xF000:
|
||||||
|
desti = point
|
||||||
|
else:
|
||||||
|
desti = current_cp
|
||||||
|
current_cp += 1
|
||||||
|
print("{:X} {:X} {}".format(point, desti, glyph.glyphname))
|
||||||
|
|
||||||
|
font.close()
|
201
src/glyphs/octicons/generate
Executable file
201
src/glyphs/octicons/generate
Executable file
|
@ -0,0 +1,201 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# coding=utf8
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import fontforge
|
||||||
|
|
||||||
|
# Double-quotes required here, for version-bump.sh:
|
||||||
|
version = "2.3.3"
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
def renamer(old_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)
|
||||||
|
|
||||||
|
|
||||||
|
def addIcon(codepoint, name, filename):
|
||||||
|
"""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")
|
||||||
|
f.write(intro)
|
||||||
|
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(
|
||||||
|
"i='{}' i_oct_{}=$i\n".format(chr(codepoint), name.replace("-", "_"))
|
||||||
|
)
|
||||||
|
f.write("unset i\n")
|
||||||
|
|
||||||
|
|
||||||
|
print("\nReading mapping file")
|
||||||
|
old_mapping = []
|
||||||
|
with open("mapping", "r") as f:
|
||||||
|
for line in f.readlines():
|
||||||
|
if line.startswith("#"):
|
||||||
|
continue
|
||||||
|
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")
|
||||||
|
|
||||||
|
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)))
|
||||||
|
|
||||||
|
num_found = 0
|
||||||
|
num_missing = 0
|
||||||
|
misslist = ""
|
||||||
|
renamelist = ""
|
||||||
|
freeslots = []
|
||||||
|
|
||||||
|
new_mapping = []
|
||||||
|
for i, j, old_n in old_mapping:
|
||||||
|
if old_n in names:
|
||||||
|
names.remove(old_n)
|
||||||
|
new_mapping.append((i, j, old_n))
|
||||||
|
num_found += 1
|
||||||
|
continue
|
||||||
|
new_n = renamer(old_n)
|
||||||
|
if new_n in names:
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
names = list(names)
|
||||||
|
names.sort()
|
||||||
|
for n in list(names):
|
||||||
|
if len(freeslots) == 0:
|
||||||
|
break
|
||||||
|
i, j = freeslots[0]
|
||||||
|
new_mapping.append((i, j, n))
|
||||||
|
names.remove(n)
|
||||||
|
freeslots = freeslots[1:]
|
||||||
|
|
||||||
|
print("Filled in missing, remaining new {}".format(len(names)))
|
||||||
|
|
||||||
|
i_max = 0
|
||||||
|
j_max = 0
|
||||||
|
for i, j, _ in new_mapping:
|
||||||
|
i = int(i, 16)
|
||||||
|
j = int(j, 16)
|
||||||
|
if i > i_max:
|
||||||
|
i_max = i
|
||||||
|
if j > j_max:
|
||||||
|
j_max = j
|
||||||
|
|
||||||
|
for n in names:
|
||||||
|
i_max += 1
|
||||||
|
j_max += 1
|
||||||
|
new_mapping.append(("{:X}".format(i_max), "{:X}".format(j_max), n))
|
||||||
|
|
||||||
|
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:
|
||||||
|
for i, j, n in new_mapping:
|
||||||
|
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.em = 2048
|
||||||
|
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.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.")
|
||||||
|
|
||||||
|
for codepoint, _, name in new_mapping:
|
||||||
|
codepoint = int(codepoint, 16)
|
||||||
|
filename = name + subset + ".svg"
|
||||||
|
if filename not in svgs:
|
||||||
|
filename = name + subset_other + ".svg"
|
||||||
|
addIcon(codepoint, name, filename)
|
||||||
|
|
||||||
|
num_icons = len(new_mapping)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
print("Generating GlyphInfo {}".format(glyphsetfile))
|
||||||
|
createGlyphInfo(new_mapping, os.path.join(glyphsetsdir, glyphsetfile), intro)
|
||||||
|
print("Finished")
|
309
src/glyphs/octicons/mapping
Normal file
309
src/glyphs/octicons/mapping
Normal file
|
@ -0,0 +1,309 @@
|
||||||
|
2665 2665 heart
|
||||||
|
26A1 26A1 zap
|
||||||
|
F000 F400 light-bulb
|
||||||
|
F001 F401 repo
|
||||||
|
F002 F402 repo-forked
|
||||||
|
F005 F403 repo-push
|
||||||
|
F006 F404 repo-pull
|
||||||
|
F007 F405 book
|
||||||
|
F008 F406 accessibility
|
||||||
|
F009 F407 git-pull-request
|
||||||
|
F00A F408 mark-github
|
||||||
|
F00B F409 download
|
||||||
|
F00C F40A upload
|
||||||
|
F00D F40B accessibility-inset
|
||||||
|
F00E F40C alert-fill
|
||||||
|
F010 F40D file-code
|
||||||
|
F011 F40E apps
|
||||||
|
F012 F40F file-media
|
||||||
|
F013 F410 file-zip
|
||||||
|
F014 F411 archive
|
||||||
|
F015 F412 tag
|
||||||
|
F016 F413 file-directory
|
||||||
|
F017 F414 file-submodule
|
||||||
|
F018 F415 person
|
||||||
|
F019 F416 arrow-both
|
||||||
|
F01F F417 git-commit
|
||||||
|
F020 F418 git-branch
|
||||||
|
F023 F419 git-merge
|
||||||
|
F024 F41A mirror
|
||||||
|
F026 F41B issue-opened
|
||||||
|
F027 F41C issue-reopened
|
||||||
|
F028 F41D issue-closed
|
||||||
|
F02A F41E star
|
||||||
|
F02B F41F comment
|
||||||
|
F02C F420 question
|
||||||
|
F02D F421 alert
|
||||||
|
F02E F422 search
|
||||||
|
F02F F423 gear
|
||||||
|
F030 F424 arrow-down-left
|
||||||
|
F031 F425 tools
|
||||||
|
F032 F426 sign-out
|
||||||
|
F033 F427 rocket
|
||||||
|
F034 F428 rss
|
||||||
|
F035 F429 paste
|
||||||
|
F036 F42A sign-in
|
||||||
|
F037 F42B organization
|
||||||
|
F038 F42C device-mobile
|
||||||
|
F039 F42D unfold
|
||||||
|
F03A F42E check
|
||||||
|
F03B F42F mail
|
||||||
|
F03C F430 read
|
||||||
|
F03D F431 arrow-up
|
||||||
|
F03E F432 arrow-right
|
||||||
|
F03F F433 arrow-down
|
||||||
|
F040 F434 arrow-left
|
||||||
|
F041 F435 pin
|
||||||
|
F042 F436 gift
|
||||||
|
F043 F437 graph
|
||||||
|
F044 F438 triangle-left
|
||||||
|
F045 F439 credit-card
|
||||||
|
F046 F43A clock
|
||||||
|
F047 F43B ruby
|
||||||
|
F048 F43C broadcast
|
||||||
|
F049 F43D key
|
||||||
|
F04A F43E arrow-down-right
|
||||||
|
F04C F43F repo-clone
|
||||||
|
F04D F440 diff
|
||||||
|
F04E F441 eye
|
||||||
|
F04F F442 comment-discussion
|
||||||
|
F051 F443 arrow-switch
|
||||||
|
F052 F444 dot-fill
|
||||||
|
F053 F445 square-fill
|
||||||
|
F056 F446 device-camera
|
||||||
|
F057 F447 device-camera-video
|
||||||
|
F058 F448 pencil
|
||||||
|
F059 F449 info
|
||||||
|
F05A F44A triangle-right
|
||||||
|
F05B F44B triangle-down
|
||||||
|
F05C F44C link
|
||||||
|
F05D F44D plus
|
||||||
|
F05E F44E three-bars
|
||||||
|
F05F F44F code
|
||||||
|
F060 F450 location
|
||||||
|
F061 F451 list-unordered
|
||||||
|
F062 F452 list-ordered
|
||||||
|
F063 F453 quote
|
||||||
|
F064 F454 versions
|
||||||
|
F068 F455 calendar
|
||||||
|
F06A F456 lock
|
||||||
|
F06B F457 diff-added
|
||||||
|
F06C F458 diff-removed
|
||||||
|
F06D F459 diff-modified
|
||||||
|
F06E F45A diff-renamed
|
||||||
|
F070 F45B horizontal-rule
|
||||||
|
F071 F45C arrow-up-left
|
||||||
|
F075 F45D milestone
|
||||||
|
F076 F45E checklist
|
||||||
|
F077 F45F megaphone
|
||||||
|
F078 F460 chevron-right
|
||||||
|
F07B F461 bookmark
|
||||||
|
F07C F462 sliders
|
||||||
|
F07D F463 meter
|
||||||
|
F07E F464 history
|
||||||
|
F07F F465 link-external
|
||||||
|
F080 F466 mute
|
||||||
|
F081 F467 x
|
||||||
|
F084 F468 circle-slash
|
||||||
|
F085 F469 pulse
|
||||||
|
F087 F46A sync
|
||||||
|
F088 F46B telescope
|
||||||
|
F08C F46C arrow-up-right
|
||||||
|
F08D F46D home
|
||||||
|
F08F F46E stop
|
||||||
|
F091 F46F bug
|
||||||
|
F092 F470 logo-github
|
||||||
|
F094 F471 file-binary
|
||||||
|
F096 F472 database
|
||||||
|
F097 F473 server
|
||||||
|
F099 F474 diff-ignored
|
||||||
|
F09A F475 ellipsis
|
||||||
|
F09C F476 bell-fill
|
||||||
|
F09D F477 hubot
|
||||||
|
F09F F478 bell-slash
|
||||||
|
F0A0 F479 blocked
|
||||||
|
F0A1 F47A bookmark-fill
|
||||||
|
F0A2 F47B chevron-up
|
||||||
|
F0A3 F47C chevron-down
|
||||||
|
F0A4 F47D chevron-left
|
||||||
|
F0AA F47E triangle-up
|
||||||
|
F0AC F47F git-compare
|
||||||
|
F0AD F480 logo-gist
|
||||||
|
F0B0 F481 file-symlink-file
|
||||||
|
F0B1 F482 bookmark-slash
|
||||||
|
F0B2 F483 squirrel
|
||||||
|
F0B6 F484 globe
|
||||||
|
F0BA F485 unmute
|
||||||
|
F0BE F486 mention
|
||||||
|
F0C4 F487 package
|
||||||
|
F0C5 F488 browser
|
||||||
|
F0C8 F489 terminal
|
||||||
|
F0C9 F48A markdown
|
||||||
|
F0CA F48B dash
|
||||||
|
F0CC F48C fold
|
||||||
|
F0CF F48D inbox
|
||||||
|
F0D0 F48E trash
|
||||||
|
F0D1 F48F paintbrush
|
||||||
|
F0D2 F490 flame
|
||||||
|
F0D3 F491 briefcase
|
||||||
|
F0D4 F492 plug
|
||||||
|
F0D6 F493 bookmark-slash-fill
|
||||||
|
F0D7 F494 mortar-board
|
||||||
|
F0D8 F495 law
|
||||||
|
F0DA F496 thumbsup
|
||||||
|
F0DB F497 thumbsdown
|
||||||
|
F0DC F498 desktop-download
|
||||||
|
F0DD F499 beaker
|
||||||
|
F0DE F49A bell
|
||||||
|
F0E0 F49B cache
|
||||||
|
F0E1 F49C shield
|
||||||
|
F0E2 F49D bold
|
||||||
|
F0E3 F49E check-circle
|
||||||
|
F0E4 F49F italic
|
||||||
|
F0E5 F4A0 tasklist
|
||||||
|
F0E6 F4A1 verified
|
||||||
|
F0E7 F4A2 smiley
|
||||||
|
F0E8 F4A3 unverified
|
||||||
|
F101 F4A4 check-circle-fill
|
||||||
|
F102 F4A5 file
|
||||||
|
F103 F4A6 grabber
|
||||||
|
F104 F4A7 checkbox
|
||||||
|
F105 F4A8 reply
|
||||||
|
F27C F4A9 device-desktop
|
||||||
|
F27D F4AA circle
|
||||||
|
F27E F4AB clock-fill
|
||||||
|
F27F F4AC cloud
|
||||||
|
F280 F4AD cloud-offline
|
||||||
|
F281 F4AE code-of-conduct
|
||||||
|
F282 F4AF code-review
|
||||||
|
F283 F4B0 code-square
|
||||||
|
F284 F4B1 codescan
|
||||||
|
F285 F4B2 codescan-checkmark
|
||||||
|
F286 F4B3 codespaces
|
||||||
|
F287 F4B4 columns
|
||||||
|
F288 F4B5 command-palette
|
||||||
|
F289 F4B6 commit
|
||||||
|
F28A F4B7 container
|
||||||
|
F28B F4B8 copilot
|
||||||
|
F28C F4B9 copilot-error
|
||||||
|
F28D F4BA copilot-warning
|
||||||
|
F28E F4BB copy
|
||||||
|
F28F F4BC cpu
|
||||||
|
F290 F4BD cross-reference
|
||||||
|
F291 F4BE dependabot
|
||||||
|
F292 F4BF diamond
|
||||||
|
F293 F4C0 discussion-closed
|
||||||
|
F294 F4C1 discussion-duplicate
|
||||||
|
F295 F4C2 discussion-outdated
|
||||||
|
F296 F4C3 dot
|
||||||
|
F297 F4C4 duplicate
|
||||||
|
F298 F4C5 eye-closed
|
||||||
|
F299 F4C6 feed-discussion
|
||||||
|
F29A F4C7 feed-forked
|
||||||
|
F29B F4C8 feed-heart
|
||||||
|
F29C F4C9 feed-merged
|
||||||
|
F29D F4CA feed-person
|
||||||
|
F29E F4CB feed-repo
|
||||||
|
F29F F4CC feed-rocket
|
||||||
|
F2A0 F4CD feed-star
|
||||||
|
F2A1 F4CE feed-tag
|
||||||
|
F2A2 F4CF feed-trophy
|
||||||
|
F2A3 F4D0 file-added
|
||||||
|
F2A4 F4D1 file-badge
|
||||||
|
F2A5 F4D2 file-diff
|
||||||
|
F2A6 F4D3 file-directory-fill
|
||||||
|
F2A7 F4D4 file-directory-open-fill
|
||||||
|
F2A8 F4D5 file-moved
|
||||||
|
F2A9 F4D6 file-removed
|
||||||
|
F2AA F4D7 filter
|
||||||
|
F2AB F4D8 fiscal-host
|
||||||
|
F2AC F4D9 fold-down
|
||||||
|
F2AD F4DA fold-up
|
||||||
|
F2AE F4DB git-merge-queue
|
||||||
|
F2AF F4DC git-pull-request-closed
|
||||||
|
F2B0 F4DD git-pull-request-draft
|
||||||
|
F2B1 F4DE goal
|
||||||
|
F2B2 F4DF hash
|
||||||
|
F2B3 F4E0 heading
|
||||||
|
F2B4 F4E1 heart-fill
|
||||||
|
F2B5 F4E2 home-fill
|
||||||
|
F2B6 F4E3 hourglass
|
||||||
|
F2B7 F4E4 id-badge
|
||||||
|
F2B8 F4E5 image
|
||||||
|
F2B9 F4E6 infinity
|
||||||
|
F2BA F4E7 issue-draft
|
||||||
|
F2BB F4E8 issue-tracked-by
|
||||||
|
F2BC F4E9 issue-tracks
|
||||||
|
F2BD F4EA iterations
|
||||||
|
F2BE F4EB kebab-horizontal
|
||||||
|
F2BF F4EC key-asterisk
|
||||||
|
F2C0 F4ED log
|
||||||
|
F2C1 F4EE moon
|
||||||
|
F2C2 F4EF move-to-bottom
|
||||||
|
F2C3 F4F0 move-to-end
|
||||||
|
F2C4 F4F1 move-to-start
|
||||||
|
F2C5 F4F2 move-to-top
|
||||||
|
F2C6 F4F3 multi-select
|
||||||
|
F2C7 F4F4 no-entry
|
||||||
|
F2C8 F4F5 north-star
|
||||||
|
F2C9 F4F6 note
|
||||||
|
F2CA F4F7 number
|
||||||
|
F2CB F4F8 package-dependencies
|
||||||
|
F2CC F4F9 package-dependents
|
||||||
|
F2CD F4FA paper-airplane
|
||||||
|
F2CE F4FB paperclip
|
||||||
|
F2CF F4FC passkey-fill
|
||||||
|
F2D0 F4FD people
|
||||||
|
F2D1 F4FE person-add
|
||||||
|
F2D2 F4FF person-fill
|
||||||
|
F2D3 F500 play
|
||||||
|
F2D4 F501 plus-circle
|
||||||
|
F2D5 F502 project
|
||||||
|
F2D6 F503 project-roadmap
|
||||||
|
F2D7 F504 project-symlink
|
||||||
|
F2D8 F505 project-template
|
||||||
|
F2D9 F506 rel-file-path
|
||||||
|
F2DA F507 repo-deleted
|
||||||
|
F2DB F508 repo-locked
|
||||||
|
F2DC F509 repo-template
|
||||||
|
F2DD F50A report
|
||||||
|
F2DE F50B rows
|
||||||
|
F2DF F50C screen-full
|
||||||
|
F2E0 F50D screen-normal
|
||||||
|
F2E1 F50E share
|
||||||
|
F2E2 F50F share-android
|
||||||
|
F2E3 F510 shield-check
|
||||||
|
F2E4 F511 shield-lock
|
||||||
|
F2E5 F512 shield-slash
|
||||||
|
F2E6 F513 shield-x
|
||||||
|
F2E7 F514 sidebar-collapse
|
||||||
|
F2E8 F515 sidebar-expand
|
||||||
|
F2E9 F516 single-select
|
||||||
|
F2EA F517 skip
|
||||||
|
F2EB F518 skip-fill
|
||||||
|
F2EC F519 sort-asc
|
||||||
|
F2ED F51A sort-desc
|
||||||
|
F2EE F51B sparkle-fill
|
||||||
|
F2EF F51C sponsor-tiers
|
||||||
|
F2F0 F51D square
|
||||||
|
F2F1 F51E stack
|
||||||
|
F2F2 F51F star-fill
|
||||||
|
F2F3 F520 stopwatch
|
||||||
|
F2F4 F521 strikethrough
|
||||||
|
F2F5 F522 sun
|
||||||
|
F2F6 F523 tab
|
||||||
|
F2F7 F524 tab-external
|
||||||
|
F2F8 F525 table
|
||||||
|
F2F9 F526 telescope-fill
|
||||||
|
F2FA F527 trophy
|
||||||
|
F2FB F528 typography
|
||||||
|
F2FC F529 unlink
|
||||||
|
F2FD F52A unlock
|
||||||
|
F2FE F52B unread
|
||||||
|
F2FF F52C video
|
||||||
|
F300 F52D webhook
|
||||||
|
F301 F52E workflow
|
||||||
|
F302 F52F x-circle
|
||||||
|
F303 F530 x-circle-fill
|
||||||
|
F304 F531 zoom-in
|
||||||
|
F305 F532 zoom-out
|
BIN
src/glyphs/octicons/octicons.ttf
Normal file
BIN
src/glyphs/octicons/octicons.ttf
Normal file
Binary file not shown.
|
@ -433,4 +433,3 @@ Foobar.org is a distributed community of developers...
|
||||||
Company.com is a small business who likes to support community designers...
|
Company.com is a small business who likes to support community designers...
|
||||||
University.edu is a renowned educational institution with a strong design department...
|
University.edu is a renowned educational institution with a strong design department...
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
|
Binary file not shown.
Loading…
Reference in a new issue