diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2d3ca6b..bacb600 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: "Update Nerd Fonts" on: workflow_dispatch: - # schedule: + schedule: # 1st and 15th at 12:00 AM - # - cron: "0 0 1,15 * *" + - cron: "0 0 1,15 * *" permissions: contents: write diff --git a/.gitignore b/.gitignore index 85cfc48..6345245 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,180 @@ +# Created by https://www.toptal.com/developers/gitignore/api/python +# Edit at https://www.toptal.com/developers/gitignore?templates=python + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +### Python Patch ### +# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration +poetry.toml + +# ruff +.ruff_cache/ + +# LSP config files +pyrightconfig.json + +# End of https://www.toptal.com/developers/gitignore/api/python +# *.zip MonoLisa/* @@ -7,3 +184,4 @@ patched/* nerd-fonts .env +font-patcher-log.txt diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f9fb549..1315301 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,4 +1,4 @@ -exclude: "^(src/.*|bin/font-patcher)" +exclude: "^(src/.*|bin/scripts|font-patcher)" repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 @@ -6,10 +6,6 @@ repos: - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace - - repo: https://github.com/pycqa/isort - rev: 5.12.0 - hooks: - - id: isort - repo: https://github.com/psf/black rev: 23.1.0 hooks: diff --git a/Makefile b/Makefile index 9eb81c3..16de967 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ -include .env ARGS ?= -c -patch: ./bin/font-patcher ## apply nerd fonts patch |> -gs b_magenta -ms bold +patch: ## apply nerd fonts patch |> -gs b_magenta -ms bold @./patch-monolisa \ $(ARGS) \ -f MonoLisa/ @@ -24,6 +24,8 @@ lint: ## run pre-commit hooks clean: ## remove patched fonts @rm -rf patched/* + @rm -f ./font-patcher-log.txt + @rm -f FontPatcher.zip # depends on daylinmorgan/yartsu assets/help.svg: diff --git a/bin/scripts/name_parser/FontnameParser.py b/bin/scripts/name_parser/FontnameParser.py new file mode 100644 index 0000000..e6d70c5 --- /dev/null +++ b/bin/scripts/name_parser/FontnameParser.py @@ -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) diff --git a/bin/scripts/name_parser/FontnameTools.py b/bin/scripts/name_parser/FontnameTools.py new file mode 100644 index 0000000..6b6c099 --- /dev/null +++ b/bin/scripts/name_parser/FontnameTools.py @@ -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) diff --git a/bin/update-src b/bin/update-src index a99b369..19a38da 100755 --- a/bin/update-src +++ b/bin/update-src @@ -1,15 +1,7 @@ #!/usr/bin/env bash -REPO_URL='https://github.com/ryanoasis/nerd-fonts.git' - -rm -rf nerd-fonts - -git clone --filter=blob:none --no-checkout --depth 1 --sparse $REPO_URL -cd nerd-fonts || exit -git sparse-checkout add src/glyphs -git checkout - -cp font-patcher ../bin/font-patcher -cp src/glyphs/** ../src/glyphs -r +rm -rf font-patcher +wget https://github.com/ryanoasis/nerd-fonts/raw/master/FontPatcher.zip -O FontPatcher.zip +unzip -u FontPatcher.zip -x "readme.md" echo "don't forget to commit your changes!" diff --git a/bin/font-patcher b/font-patcher similarity index 88% rename from bin/font-patcher rename to font-patcher index fc64a38..55639be 100755 --- a/bin/font-patcher +++ b/font-patcher @@ -1,14 +1,14 @@ #!/usr/bin/env python # coding=utf8 -# Nerd Fonts Version: 2.3.3 +# Nerd Fonts Version: 3.0.0 # Script version is further down from __future__ import absolute_import, print_function, unicode_literals # 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" projectNameAbbreviation = "NF" projectNameSingular = projectName[:-1] @@ -22,6 +22,7 @@ import errno import subprocess import json from enum import Enum +import logging try: import configparser except ImportError: @@ -239,10 +240,10 @@ def force_panose_monospaced(font): panose = list(font.os2_panose) if panose[0] == 0: # 0 (1st value) = family kind; 0 = any (default) 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) 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 font.os2_panose = tuple(panose) @@ -296,10 +297,22 @@ def get_old_average_x_width(font): } for g in weights: 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] 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: def __init__(self, args): @@ -311,6 +324,7 @@ class font_patcher: self.font_dim = None # class 'dict' self.font_extrawide = False self.source_monospaced = None # Later True or False + self.symbolsonly = False self.onlybitmaps = 0 self.essential = set() 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 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 # Prevent opening and closing the fontforge font. Makes things faster when patching @@ -345,8 +359,12 @@ class font_patcher: symfont = None if not os.path.isdir(self.args.glyphdir): - sys.exit("{}: Can not find symbol glyph directory {} " - "(probably you need to download the src/glyphs/ directory?)".format(projectName, self.args.glyphdir)) + logger.critical("Can not find symbol glyph directory %s " + "(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: if patch['Enabled']: @@ -356,11 +374,13 @@ class font_patcher: symfont.close() symfont = None if not os.path.isfile(self.args.glyphdir + patch['Filename']): - sys.exit("{}: Can not find symbol source for '{}'\n{:>{}} (i.e. {})".format( - projectName, patch['Name'], '', len(projectName), self.args.glyphdir + patch['Filename'])) + logger.critical("Can not find symbol source for '%s' (i.e. %s)", + patch['Name'], self.args.glyphdir + patch['Filename']) + sys.exit(1) if not os.access(self.args.glyphdir + patch['Filename'], os.R_OK): - sys.exit("{}: Can not open symbol source for '{}'\n{:>{}} (i.e. {})".format( - projectName, patch['Name'], '', len(projectName), self.args.glyphdir + patch['Filename'])) + logger.critical("Can not open symbol source for '%s' (i.e. %s)", + patch['Name'], self.args.glyphdir + patch['Filename']) + sys.exit(1) symfont = fontforge.open(os.path.join(self.args.glyphdir, patch['Filename'])) symfont.encoding = 'UnicodeFull' @@ -402,11 +422,11 @@ class font_patcher: break outfile = os.path.normpath(os.path.join( 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) message = " Generated {} fonts\n \===> '{}'".format(len(sourceFonts), outfile) else: - fontname = sourceFont.fullname + fontname = create_filename(sourceFonts) if not fontname: fontname = sourceFont.cidfontname outfile = os.path.normpath(os.path.join( @@ -414,9 +434,11 @@ class font_patcher: sanitize_filename(fontname) + self.args.extension)) bitmaps = str() if len(self.sourceFont.bitmapSizes): - if not self.args.quiet: - print("Preserving bitmaps {}".format(self.sourceFont.bitmapSizes)) + logger.debug("Preserving bitmaps {}".format(self.sourceFont.bitmapSizes)) 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) message = " {}\n \===> '{}'".format(self.sourceFont.fullname, outfile) @@ -426,8 +448,7 @@ class font_patcher: source_font = TableHEADWriter(self.args.font) dest_font = TableHEADWriter(outfile) for idx in range(source_font.num_fonts): - if not self.args.quiet: - print("{}: Tweaking {}/{}".format(projectName, idx + 1, source_font.num_fonts)) + logger.debug("Tweaking %d/%d", idx + 1, source_font.num_fonts) xwidth_s = '' xwidth = self.xavgwidth[idx] if isinstance(xwidth, int): @@ -438,26 +459,23 @@ class font_patcher: dest_font.find_table([b'OS/2'], idx) d_xwidth = dest_font.getshort('avgWidth') if d_xwidth != xwidth: - if not self.args.quiet: - print("Changing xAvgCharWidth from {} to {}{}".format(d_xwidth, xwidth, xwidth_s)) + logger.debug("Changing xAvgCharWidth from %d to %d%s", d_xwidth, xwidth, xwidth_s) dest_font.putshort(xwidth, 'avgWidth') dest_font.reset_table_checksum() source_font.find_head_table(idx) dest_font.find_head_table(idx) if source_font.flags & 0x08 == 0 and dest_font.flags & 0x08 != 0: - if not self.args.quiet: - print("Changing flags from 0x{:X} to 0x{:X}".format(dest_font.flags, dest_font.flags & ~0x08)) + logger.debug("Changing flags from 0x%X to 0x%X", dest_font.flags, dest_font.flags & ~0x08) dest_font.putshort(dest_font.flags & ~0x08, 'flags') # clear 'ppem_to_int' if source_font.lowppem != dest_font.lowppem: - if not self.args.quiet: - print("Changing lowestRecPPEM from {} to {}".format(dest_font.lowppem, source_font.lowppem)) + logger.debug("Changing lowestRecPPEM from %d to %d", dest_font.lowppem, source_font.lowppem) dest_font.putshort(source_font.lowppem, 'lowestRecPPEM') if dest_font.modified: dest_font.reset_table_checksum() if dest_font.modified: dest_font.reset_full_checksum() except Exception as error: - print("Can not handle font flags ({})".format(repr(error))) + logger.error("Can not handle font flags (%s)", repr(error)) finally: try: source_font.close() @@ -465,12 +483,13 @@ class font_patcher: except: pass 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) if self.args.postprocess: 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): @@ -488,11 +507,8 @@ class font_patcher: font.fullname = font.persistent["fullname"] if isinstance(font.persistent["familyname"], str): font.familyname = font.persistent["familyname"] - verboseAdditionalFontNameSuffix = " " + projectNameSingular - if self.args.windows: # attempt to shorten here on the additional name BEFORE trimming later - additionalFontNameSuffix = " " + projectNameAbbreviation - else: - additionalFontNameSuffix = verboseAdditionalFontNameSuffix + verboseAdditionalFontNameSuffix = "" + additionalFontNameSuffix = "" if not self.args.complete: # NOTE not all symbol fonts have appended their suffix here if self.args.fontawesome: @@ -523,17 +539,24 @@ class font_patcher: additionalFontNameSuffix += " WEA" verboseAdditionalFontNameSuffix += " Plus Weather Icons" - # if all source glyphs included simplify the name - else: - additionalFontNameSuffix = " " + projectNameSingular + " Complete" - verboseAdditionalFontNameSuffix = " " + projectNameSingular + " Complete" - - # add mono signifier to end of name + # add mono signifier to beginning of name suffix if self.args.single: - additionalFontNameSuffix += " M" - verboseAdditionalFontNameSuffix += " Mono" + variant_abbrev = "M" + 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 if it is 'equal' to the fontname if font.fullname: @@ -545,12 +568,14 @@ class font_patcher: # Gohu fontnames hide the weight, but the file names are ok... if parser_name.startswith('Gohu'): parser_name = os.path.splitext(os.path.basename(self.args.font))[0] - n = FontnameParser(parser_name) + n = FontnameParser(parser_name, logger) 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.enable_short_families(True, "Noto") - n.set_for_windows(self.args.windows) + n.enable_short_families(True, self.args.makegroups in [ 2, 3, 5, 6, ], self.args.makegroups in [ 3, 6, ]) + 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 @@ -598,23 +623,7 @@ class font_patcher: if len(subFamily) == 0: subFamily = "Regular" - if self.args.windows: - 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" + familyname += " " + projectNameSingular + variant_full # 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 @@ -627,6 +636,10 @@ class font_patcher: reservedFontNameReplacements = { 'source' : 'sauce', 'Source' : 'Sauce', + 'Bitstream Vera Sans Mono' : 'Bitstrom Wera', + 'BitstreamVeraSansMono' : 'BitstromWera', + 'bitstream vera sans mono' : 'bitstrom wera', + 'bitstreamverasansmono' : 'bitstromwera', 'hermit' : 'hurmit', 'Hermit' : 'Hurmit', 'hasklig' : 'hasklug', @@ -693,7 +706,7 @@ class font_patcher: fullname = replace_font_name(fullname, 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: font.familyname = " ".join(familyname.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('SubFamily'), subFamily) else: - fam_suffix = projectNameSingular if not self.args.windows else projectNameAbbreviation - if self.args.single: - if self.args.windows: - fam_suffix += 'M' - else: - fam_suffix += ' Mono' - n.inject_suffix(verboseAdditionalFontNameSuffix, additionalFontNameSuffix, fam_suffix) + short_family = projectNameAbbreviation + variant_abbrev if self.args.makegroups >= 4 else projectNameSingular + variant_full + # inject_suffix(family, ps_fontname, short_family) + n.inject_suffix(verboseAdditionalFontNameSuffix, ps_suffix, short_family) n.rename_font(font) font.comment = projectInfo @@ -726,25 +735,26 @@ class font_patcher: self.sourceFont.version = str(self.sourceFont.cidversion) + ";" + projectName + " " + version self.sourceFont.sfntRevision = None # Auto-set (refreshed) by fontforge 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)) def remove_ligatures(self): # let's deal with ligatures (mostly for monospaced fonts) - # the tables have been removed from the repo with >this< commit + # Usually removes 'fi' ligs that end up being only one cell wide, and 'ldot' if self.args.configfile and self.config.read(self.args.configfile): if self.args.removeligatures: - print("Removing ligatures from configfile `Subtables` section") + logger.info("Removing ligatures from configfile `Subtables` section") ligature_subtables = json.loads(self.config.get("Subtables", "ligatures")) for subtable in ligature_subtables: - print("Removing subtable:", subtable) + logger.debug("Removing subtable: %s", subtable) try: self.sourceFont.removeLookupSubtable(subtable) - print("Successfully removed subtable:", subtable) + logger.debug("Successfully removed subtable: %s", subtable) except Exception: - print("Failed to remove subtable:", subtable) + logger.error("Failed to remove subtable: %s", subtable) 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): @@ -756,16 +766,17 @@ class font_patcher: panose_mono = check_panose_monospaced(self.sourceFont) # 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): - print(" Warning: Monospaced check: Panose assumed to be wrong") - print(" {} and {}".format( + logger.warning("Monospaced check: Panose assumed to be wrong") + logger.warning(" %s and %s", 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: - 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: - print(" Offending char: 0x{:X}".format(offending_char)) + logger.warning(" Offending char: %X", offending_char) 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: force_panose_monospaced(self.sourceFont) @@ -781,9 +792,9 @@ class font_patcher: box_glyphs_current = len(list(self.sourceFont.selection.byGlyphs)) if box_glyphs_target > box_glyphs_current: # Sourcefont does not have all of these glyphs, do not mix sets (overwrite existing) - if not self.args.quiet and box_glyphs_current > 0: - print("INFO: {}/{} box drawing glyphs will be replaced".format( - box_glyphs_current, box_glyphs_target)) + if box_glyphs_current > 0: + logger.debug("%d/%d box drawing glyphs will be replaced", + box_glyphs_current, box_glyphs_target) box_enabled = True else: # 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.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.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.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.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.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.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': 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': 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': 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': 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.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 if our_btb == hhea_btb: 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: - # We trust the WIN metric more, see experiments in #1056 - print("{}: WARNING Font vertical metrics inconsistent (HHEA {} / TYPO {} / WIN {}), using WIN".format(projectName, hhea_btb, typo_btb, win_btb)) - our_btb = win_btb - metrics = Metric.WIN + # Try the other metric + our_btb = typo_btb if not use_typo else win_btb + if our_btb == hhea_btb: + 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)) @@ -1129,6 +1151,7 @@ class font_patcher: if self.font_dim['height'] == 0: # This can only happen if the input font is empty # Assume we are using our prepared templates + self.symbolsonly = True self.font_dim = { 'xmin' : 0, 'ymin' : -self.sourceFont.descent, @@ -1139,7 +1162,8 @@ class font_patcher: } our_btb = self.sourceFont.descent + self.sourceFont.ascent 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 self.sourceFont.os2_typolinegap = 0 @@ -1153,12 +1177,13 @@ class font_patcher: self.sourceFont.os2_use_typo_metrics = 1 (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: - 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 # Find the biggest char width and advance width # 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 for glyph in range(0x21, 0x17f): 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: self.font_dim['width'] = self.sourceFont[glyph].width 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( - report_advance_widths(self.sourceFont))) + logger.debug("Extended glyphs wider than basic glyphs, results might be useless\n %s", + report_advance_widths(self.sourceFont)) warned1 = True # print("New MAXWIDTH-A {:X} {} -> {} {}".format(glyph, self.sourceFont[glyph].width, self.font_dim['width'], xmax)) if xmax > self.font_dim['xmax']: self.font_dim['xmax'] = xmax 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 # 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 not self.args.quiet: - print("Warning: Font has negative right side bearing in extended glyphs") + logger.debug("Font has negative right side bearing in extended glyphs") self.font_dim['xmax'] = self.font_dim['width'] # In fact 'xmax' is never used # print("FINAL", self.font_dim) @@ -1288,7 +1312,7 @@ class font_patcher: if sym_glyph.altuni: possible_codes += [ v for v, s, r in sym_glyph.altuni if v > currentSourceFontGlyph ] 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 currentSourceFontGlyph = min(possible_codes) else: @@ -1311,9 +1335,8 @@ class font_patcher: # check if a glyph already exists in this location if careful or 'careful' in sym_attr['params'] or currentSourceFontGlyph in self.essential: if currentSourceFontGlyph in self.sourceFont: - if not self.args.quiet: - careful_type = 'essential' if currentSourceFontGlyph in self.essential else 'existing' - print(" Found {} Glyph at {:X}. Skipping...".format(careful_type, currentSourceFontGlyph)) + careful_type = 'essential' if currentSourceFontGlyph in self.essential else 'existing' + logger.debug("Found %s Glyph at %X. Skipping...", careful_type, currentSourceFontGlyph) # We don't want to touch anything so move to next Glyph continue else: @@ -1461,8 +1484,8 @@ class font_patcher: if self.args.single: (xmin, _, xmax, _) = self.sourceFont[currentSourceFontGlyph].boundingBox() 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( - currentSourceFontGlyph, int(xmax - xmin), self.font_dim['width'], overlap)) + logger.warning("Scaled glyph %X wider than one monospace width (%d / %d (overlap %f))", + currentSourceFontGlyph, int(xmax - xmin), self.font_dim['width'], overlap) # end for @@ -1603,7 +1626,7 @@ def half_gap(gap, top): gap_top = int(gap / 2) gap_bottom = gap - gap_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_bottom @@ -1728,8 +1751,8 @@ def check_fontforge_min_version(): # versions tested: 20150612, 20150824 if actualVersion < minimumVersion: - sys.stderr.write("{}: You seem to be using an unsupported (old) version of fontforge: {}\n".format(projectName, actualVersion)) - sys.stderr.write("{}: Please use at least version: {}\n".format(projectName, minimumVersion)) + logger.critical("You seem to be using an unsupported (old) version of fontforge: %d", actualVersion) + logger.critical("Please use at least version: %d", minimumVersion) sys.exit(1) 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('-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('-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('--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') @@ -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('-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('--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('--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 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') 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) # --xavgcharwidth for compatibility with old applications like notepad and non-latin fonts # Possible values with examples: @@ -1819,8 +1854,9 @@ def setup_arguments(): args = parser.parse_args() - if args.makegroups and not FontnameParserOK: - sys.exit("{}: FontnameParser module missing (bin/scripts/name_parser/Fontname*), can not --makegroups".format(projectName)) + if args.makegroups > 0 and not FontnameParserOK: + 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 args.complete: @@ -1853,24 +1889,23 @@ def setup_arguments(): font_complete = False args.complete = font_complete - if args.alsowindows: - args.windows = False - 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 make_sure_path_exists(args.outputdir) 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): - 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 try: 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) 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: args.is_variable = False finally: @@ -1885,16 +1920,20 @@ def setup_arguments(): args.extension = '.' + args.extension if re.match("\.ttc$", args.extension, re.IGNORECASE): 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: 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 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: - sys.exit(projectName + ": --xavgcharwidth takes only numbers up to 16384") + logging.critical("--xavgcharwidth takes only numbers up to 16384") + sys.exit(2) return args @@ -1902,24 +1941,43 @@ def setup_arguments(): def main(): global version git_version = check_version_with_git(version) - print("{} Patcher v{} ({}) (ff {}) executing".format( - projectName, git_version if git_version else version, script_version, fontforge.version())) + allversions = "Patcher v{} ({}) (ff {})".format( + git_version if git_version else version, script_version, fontforge.version()) + print("{} {}".format(projectName, allversions)) if git_version: version = git_version check_fontforge_min_version() 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) sourceFonts = [] all_fonts = fontforge.fontsInFile(args.font) for i, subfont in enumerate(all_fonts): 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: sourceFonts.append(fontforge.open("{}({})".format(args.font, subfont), 1)) # 1 = ("fstypepermitted",)) except Exception: - sys.exit("{}: Can not open font '{}', try to open with fontforge interactively to get more information".format( - projectName, subfont)) + logger.critical("Can not open font '%s', try to open with fontforge interactively to get more information", + subfont) + sys.exit(1) patcher.patch(sourceFonts[-1]) @@ -1928,13 +1986,6 @@ def main(): patcher.setup_font_names(f) 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: f.close() diff --git a/patch-monolisa b/patch-monolisa index 47f42e2..165d994 100755 --- a/patch-monolisa +++ b/patch-monolisa @@ -140,7 +140,7 @@ def patch_single_font( cmd = [ "fontforge", "-script", - "./bin/font-patcher", + "./font-patcher", "--glyphdir", "./src/glyphs/", "-out", @@ -182,8 +182,6 @@ def patch_font_dir_docker( f"{font_dir_path}:/in", "-v", f"{output_path}:/out", - "-u", - f"{os.getuid()}:{os.getegid()}", "nerdfonts/patcher", ] diff --git a/src/glyphs/FontAwesome.otf b/src/glyphs/FontAwesome.otf deleted file mode 100644 index 401ec0f..0000000 Binary files a/src/glyphs/FontAwesome.otf and /dev/null differ diff --git a/src/glyphs/NerdFontsSymbols 1000 EM Nerd Font Complete Blank.sfd b/src/glyphs/NerdFontsSymbols 1000 EM Nerd Font Complete Blank.sfd deleted file mode 100644 index d49553e..0000000 --- a/src/glyphs/NerdFontsSymbols 1000 EM Nerd Font Complete Blank.sfd +++ /dev/null @@ -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 diff --git a/src/glyphs/NerdFontsSymbols 2048 EM Nerd Font Complete Blank.sfd b/src/glyphs/NerdFontsSymbols 2048 EM Nerd Font Complete Blank.sfd deleted file mode 100644 index 4c0601b..0000000 --- a/src/glyphs/NerdFontsSymbols 2048 EM Nerd Font Complete Blank.sfd +++ /dev/null @@ -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 diff --git a/src/glyphs/PowerlineSymbols.otf b/src/glyphs/PowerlineSymbols.otf deleted file mode 100644 index b1582af..0000000 Binary files a/src/glyphs/PowerlineSymbols.otf and /dev/null differ diff --git a/src/glyphs/Symbols Template 1000 em.ttf b/src/glyphs/Symbols Template 1000 em.ttf deleted file mode 100644 index 677bca7..0000000 Binary files a/src/glyphs/Symbols Template 1000 em.ttf and /dev/null differ diff --git a/src/glyphs/Symbols Template 2048 em.ttf b/src/glyphs/Symbols Template 2048 em.ttf deleted file mode 100644 index 3941c11..0000000 Binary files a/src/glyphs/Symbols Template 2048 em.ttf and /dev/null differ diff --git a/src/glyphs/Symbols-1000-em Nerd Font Complete.ttf b/src/glyphs/Symbols-1000-em Nerd Font Complete.ttf deleted file mode 100644 index 713c242..0000000 Binary files a/src/glyphs/Symbols-1000-em Nerd Font Complete.ttf and /dev/null differ diff --git a/src/glyphs/Symbols-2048-em Nerd Font Complete.ttf b/src/glyphs/Symbols-2048-em Nerd Font Complete.ttf deleted file mode 100644 index 60db517..0000000 Binary files a/src/glyphs/Symbols-2048-em Nerd Font Complete.ttf and /dev/null differ diff --git a/src/glyphs/codicons/LICENSE.txt b/src/glyphs/codicons/LICENSE.txt index 085c3ca..964cba1 100644 --- a/src/glyphs/codicons/LICENSE.txt +++ b/src/glyphs/codicons/LICENSE.txt @@ -49,7 +49,7 @@ exhaustive, and do not form part of our licenses. such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. More_considerations - for the public: + for the public: wiki.creativecommons.org/Considerations_for_licensees ======================================================================= @@ -392,4 +392,4 @@ understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses. -Creative Commons may be contacted at creativecommons.org. \ No newline at end of file +Creative Commons may be contacted at creativecommons.org. diff --git a/src/glyphs/devicons.ttf b/src/glyphs/devicons.ttf old mode 100755 new mode 100644 diff --git a/src/glyphs/octicons.ttf b/src/glyphs/octicons.ttf deleted file mode 100644 index ff0dda1..0000000 Binary files a/src/glyphs/octicons.ttf and /dev/null differ diff --git a/src/glyphs/octicons/LICENSE b/src/glyphs/octicons/LICENSE new file mode 100644 index 0000000..163074d --- /dev/null +++ b/src/glyphs/octicons/LICENSE @@ -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. diff --git a/src/glyphs/octicons/analyze_octicons b/src/glyphs/octicons/analyze_octicons new file mode 100755 index 0000000..a273b9e --- /dev/null +++ b/src/glyphs/octicons/analyze_octicons @@ -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() diff --git a/src/glyphs/octicons/generate b/src/glyphs/octicons/generate new file mode 100755 index 0000000..29af0e6 --- /dev/null +++ b/src/glyphs/octicons/generate @@ -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") diff --git a/src/glyphs/octicons/mapping b/src/glyphs/octicons/mapping new file mode 100644 index 0000000..880fca0 --- /dev/null +++ b/src/glyphs/octicons/mapping @@ -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 diff --git a/src/glyphs/octicons/octicons.ttf b/src/glyphs/octicons/octicons.ttf new file mode 100644 index 0000000..f864314 Binary files /dev/null and b/src/glyphs/octicons/octicons.ttf differ diff --git a/src/glyphs/powerline-symbols/LICENSE.txt b/src/glyphs/powerline-symbols/LICENSE.txt index 23b2ab2..71c497a 100644 --- a/src/glyphs/powerline-symbols/LICENSE.txt +++ b/src/glyphs/powerline-symbols/LICENSE.txt @@ -1,21 +1,21 @@ Copyright 2013 Kim Silkebækken and other contributors https://github.com/powerline/powerline -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 +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 +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 +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. diff --git a/src/glyphs/weather-icons/OFL-FAQ.txt b/src/glyphs/weather-icons/OFL-FAQ.txt index e058b79..11f9028 100644 --- a/src/glyphs/weather-icons/OFL-FAQ.txt +++ b/src/glyphs/weather-icons/OFL-FAQ.txt @@ -82,10 +82,10 @@ Consult the copyright statement(s) in the license for ways to contact the origin 1.19 What do you mean in condition 4 of the OFL's permissions and conditions? Can you provide examples of abusive promotion / endorsement / advertisement vs. normal acknowledgement? The intent is that the goodwill and reputation of the author(s) should not be used in a way that makes it sound like the original author(s) endorse or approve of a specific Modified Version or software bundle. For example, it would not be right to advertise a word processor by naming the author(s) in a listing of software features, or to promote a Modified Version on a web site by saying "designed by ...". However, it would be appropriate to acknowledge the author(s) if your software package has a list of people who deserve thanks. We realize that this can seem to be a grey area, but the standard used to judge an acknowledgement is that if the acknowledgement benefits the author(s) it is allowed, but if it primarily benefits other parties, or could reflect poorly on the author(s), then it is not. -1.20 I'm writing a small app for mobile platforms, do I need to include the whole package? +1.20 I'm writing a small app for mobile platforms, do I need to include the whole package? If you bundle a font under the OFL with your mobile app you must comply with the terms of the license. At a minimum you must include the copyright statement, the license notice and the license text. A mention of this information in your About box or Changelog, with a link to where the font package is from, is good practice, and the extra space needed to carry these items is very small. You do not, however, need to include the full contents of the font package - only the fonts you use and the copyright and license that apply to them. For example, if you only use the regular weight in your app, you do not need to include the italic and bold versions. -1.21 What about including OFL fonts by default in my firmware or dedicated operating system? +1.21 What about including OFL fonts by default in my firmware or dedicated operating system? Many such systems are restricted and turned into appliances so that users cannot study or modify them. Using open fonts to increase quality and language coverage is a great idea, but you need to be aware that if there is a way for users to extract fonts you cannot legally prevent them from doing that. The fonts themselves, including any changes you make to them, must be distributed under the OFL even if your firmware has a more restrictive license. If you do transform the fonts and change their formats when you include them in your firmware you must respect any names reserved by the font authors via the RFN mechanism and pick your own font name. Alternatively if you directly add a font under the OFL to the font folder of your firmware without modifying or optimizing it you are simply bundling the font like with any other software collection, and do not need to make any further changes. 1.22 Can I make and publish CMS themes or templates that use OFL fonts? Can I include the fonts themselves in the themes or templates? Can I sell the whole package? @@ -103,7 +103,7 @@ No. The terms of use of such services cannot replace or restrict the terms of th NOTE: This section often refers to a separate paper on 'Web Fonts & RFNs'. This is available at http://scripts.sil.org/OFL_web_fonts_and_RFNs 2.1 Can I make webpages using these fonts? -Yes! Go ahead! Using CSS (Cascading Style Sheets) is recommended. Your three best options are: +Yes! Go ahead! Using CSS (Cascading Style Sheets) is recommended. Your three best options are: - referring directly in your stylesheet to open fonts which may be available on the user's system - providing links to download the full package of the font - either from your own website or from elsewhere - so users can install it themselves - using @font-face to distribute the font directly to browsers. This is recommended and explicitly allowed by the licensing model because it is distribution. The font file itself is distributed with other components of the webpage. It is not embedded in the webpage but referenced through a web address which will cause the browser to retrieve and use the corresponding font to render the webpage (see 1.11 and 1.15 for details related to embedding fonts into documents). As you take advantage of the @font-face cross-platform standard, be aware that web fonts are often tuned for a web environment and not intended for installation and use outside a browser. The reasons in favour of using web fonts are to allow design of dynamic text elements instead of static graphics, to make it easier for content to be localized and translated, indexed and searched, and all this with cross-platform open standards without depending on restricted extensions or plugins. You should check the CSS cascade (the order in which fonts are being called or delivered to your users) when testing. @@ -119,12 +119,12 @@ If the original font data or metadata is changed, or the WOFF-specific metadata Please note that most WOFF conversion tools and online services do not meet the two requirements listed above, and so their output must be considered a Modified Version. So be very careful and check to be sure that the tool or service you're using is compressing unchanged data and completely and accurately reflecting the original font metadata. 2.3 What about other web font formats such as EOT/EOTLite/CWT/etc.? -In most cases these formats alter the original font data more than WOFF, and do not completely support appropriate metadata, so their use must be considered modification and RFNs may not be used. However, there may be certain formats or usage scenarios that may allow the use of RFNs. See http://scripts.sil.org/OFL_web_fonts_and_RFNs +In most cases these formats alter the original font data more than WOFF, and do not completely support appropriate metadata, so their use must be considered modification and RFNs may not be used. However, there may be certain formats or usage scenarios that may allow the use of RFNs. See http://scripts.sil.org/OFL_web_fonts_and_RFNs 2.4 Can I make OFL fonts available through web font online services? Yes, you are welcome to include OFL fonts in online web font services as long as you properly meet all the conditions of the license. The origin and open status of the font should be clear among the other fonts you are hosting. Authorship, copyright notices and license information must be sufficiently visible to your users or subscribers so they know where the font comes from and the rights granted by the author(s). Make sure the font file contains the needed copyright notice(s) and licensing information in its metadata. Please double-check the accuracy of every field to prevent contradictory information. Other font formats, including EOT/EOTLite/CWT and superior alternatives like WOFF, already provide fields for this information. Remember that if you modify the font within your library or convert it to another format for any reason the OFL restrictions apply and you need to change the names accordingly. Please respect the author's wishes as expressed in the OFL and do not misrepresent original designers and their work. Don't lump quality open fonts together with dubious freeware or public domain fonts. Consider how you can best work with the original designers and foundries, support their efforts and generate goodwill that will benefit your service. (See 1.17 for details related to URL-based access restrictions methods or DRM mechanisms). -2.5 Some web font formats and services provide ways of "optimizing" the font for a particular website or web application; is that allowed? +2.5 Some web font formats and services provide ways of "optimizing" the font for a particular website or web application; is that allowed? Yes, it is permitted, but remember that these optimized versions are Modified Versions and so must follow OFL requirements like appropriate renaming. Also you need to bear in mind the other important parameters beyond compression, speed and responsiveness: you need to consider the audience of your particular website or web application, as choosing some optimization parameters may turn out to be less than ideal for them. Subsetting by removing certain glyphs or features may seriously limit functionality of the font in various languages that your users expect. It may also introduce degradation of quality in the rendering or specific bugs on the various target platforms compared to the original font from upstream. In other words, remember that one person's optimized font may be another person's missing feature. Various advanced typographic features (OpenType, Graphite or AAT) are also available through CSS and may provide the desired effects without the need to modify the font. 2.6 Is subsetting a web font considered modification? @@ -136,7 +136,7 @@ Yes. If a web font is optimized only in ways that preserve Functional Equivalenc 2.8 How do you know if an optimization to a web font preserves Functional Equivalence? Functional Equivalence is described in full in the 'Web fonts and RFNs' paper at http://scripts.sil.org/OFL_web_fonts_and_RFNs, in general, an optimized font is deemed to be Functionally Equivalent (FE) to the Original Version if it: -- Supports the same full character inventory. If a character can be properly displayed using the Original Version, then that same character, encoded correctly on a web page, will display properly. +- Supports the same full character inventory. If a character can be properly displayed using the Original Version, then that same character, encoded correctly on a web page, will display properly. - Provides the same smart font behavior. Any dynamic shaping behavior that works with the Original Version should work when optimized, unless the browser or environment does not support it. There does not need to be guaranteed support in the client, but there should be no forced degradation of smart font or shaping behavior, such as the removal or obfuscation of OpenType, Graphite or AAT tables. - Presents text with no obvious degradation in visual quality. The lettershapes should be equally (or more) readable, within limits of the rendering platform. - Preserves original author, project and license metadata. At a minimum, this should include: Copyright and authorship; The license as stated in the Original Version, whether that is the full text of the OFL or a link to the web version; Any RFN declarations; Information already present in the font or documentation that points back to the Original Version, such as a link to the project or the author's website. @@ -178,7 +178,7 @@ No, but please consider sharing your improvements with others. You may find that 3.7 If a trademark is claimed in the OFL font, does that trademark need to remain in modified fonts? Yes. Any trademark notices must remain in any derivative fonts to respect trademark laws, but you may add any additional trademarks you claim, officially registered or not. For example if an OFL font called "Foo" contains a notice that "Foo is a trademark of Acme", then if you rename the font to "Bar" when creating a Modified Version, the new trademark notice could say "Foo is a trademark of Acme Inc. - Bar is a trademark of Roadrunner Technologies Ltd.". Trademarks work alongside the OFL and are not subject to the terms of the licensing agreement. The OFL does not grant any rights under trademark law. Bear in mind that trademark law varies from country to country and that there are no international trademark conventions as there are for copyright. You may need to significantly invest in registering and defending a trademark for it to remain valid in the countries you are interested in. This may be costly for an individual independent designer. -3.8 If I commit changes to a font (or publish a branch in a DVCS) as part of a public open source software project, do I have to change the internal font names? +3.8 If I commit changes to a font (or publish a branch in a DVCS) as part of a public open source software project, do I have to change the internal font names? Only if there are declared RFNs. Making a public commit or publishing a public branch is effectively redistributing your modifications, so any change to the font will require that you do not use the RFNs. Even if there are no RFNs, it may be useful to change the name or add a suffix indicating that a particular version of the font is still in development and not released yet. This will clearly indicate to users and fellow designers that this particular font is not ready for release yet. See section 5 for more details. @@ -198,13 +198,13 @@ If you want to release your fonts under the OFL, we recommend you do the followi 4.2.4 Include the relevant practical documentation on the license by adding the current OFL-FAQ.txt file in your package. -4.2.5 If you wish you can use the OFL graphics (http://scripts.sil.org/OFL_logo) on your website. +4.2.5 If you wish you can use the OFL graphics (http://scripts.sil.org/OFL_logo) on your website. 4.3 Will you make my font OFL for me? -We won't do the work for you. We can, however, try to answer your questions, unfortunately we do not have the resources to review and check your font packages for correct use of the OFL. We recommend you turn to designers, foundries or consulting companies with experience in doing open font design to provide this service to you. +We won't do the work for you. We can, however, try to answer your questions, unfortunately we do not have the resources to review and check your font packages for correct use of the OFL. We recommend you turn to designers, foundries or consulting companies with experience in doing open font design to provide this service to you. 4.4 Will you distribute my OFL font for me? -No, although if the font is of sufficient quality and general interest we may include a link to it on our partial list of OFL fonts on the OFL web site. You may wish to consider other open font catalogs or hosting services, such as the Unifont Font Guide (http://unifont.org/fontguide), The League of Movable Type (http://theleagueofmovabletype.com) or the Open Font Library (http://openfontlibrary.org/), which despite the name has no direct relationship to the OFL or SIL. We do not endorse any particular catalog or hosting service - it is your responsibility to determine if the service is right for you and if it treats authors with fairness. +No, although if the font is of sufficient quality and general interest we may include a link to it on our partial list of OFL fonts on the OFL web site. You may wish to consider other open font catalogs or hosting services, such as the Unifont Font Guide (http://unifont.org/fontguide), The League of Movable Type (http://theleagueofmovabletype.com) or the Open Font Library (http://openfontlibrary.org/), which despite the name has no direct relationship to the OFL or SIL. We do not endorse any particular catalog or hosting service - it is your responsibility to determine if the service is right for you and if it treats authors with fairness. 4.5 Why should I use the OFL for my fonts? - to meet needs for fonts that can be modified to support lesser-known languages @@ -216,15 +216,15 @@ No, although if the font is of sufficient quality and general interest we may in - to allow your font to be included in Libre Software operating systems like Ubuntu - to give your font world status and wide, unrestricted distribution - to educate students about quality typeface and font design -- to expand your test base and get more useful feedback +- to expand your test base and get more useful feedback - to extend your reach to new markets when users see your metadata and go to your website - to get your font more easily into one of the web font online services - to attract attention for your commercial fonts - to make money through web font services - to make money by bundling fonts with applications - to make money adjusting and extending existing open fonts -- to get a better chance that foundations/NGOs/charities/companies who commission fonts will pick you -- to be part of a sharing design and development community +- to get a better chance that foundations/NGOs/charities/companies who commission fonts will pick you +- to be part of a sharing design and development community - to give back and contribute to a growing body of font sources @@ -237,7 +237,7 @@ These are font names, or portions of font names, that the author has chosen to r The best way to acknowledge the source of the design is to thank the original authors and any other contributors in the files that are distributed with your revised font (although no acknowledgement is required). The FONTLOG is a natural place to do this. Reserved Font Names ensure that the only fonts that have the original names are the unmodified Original Versions. This allows designers to maintain artistic integrity while allowing collaboration to happen. It eliminates potential confusion and name conflicts. When choosing a name, be creative and avoid names that reuse almost all the same letters in the same order or sound like the original. It will help everyone if Original Versions and Modified Versions can easily be distinguished from one another and from other derivatives. Any substitution and matching mechanism is outside the scope of the license. 5.3 What do you mean by "primary name as presented to the user"? Are you referring to the font menu name? -Yes, this applies to the font menu name and other mechanisms that specify a font in a document. It would be fine, however, to keep a text reference to the original fonts in the description field, in your modified source file or in documentation provided alongside your derivative as long as no one could be confused that your modified source is the original. But you cannot use the Reserved Font Names in any way to identify the font to the user (unless the Copyright Holder(s) allow(s) it through a separate agreement). Users who install derivatives (Modified Versions) on their systems should not see any of the original Reserved Font Names in their font menus, for example. Again, this is to ensure that users are not confused and do not mistake one font for another and so expect features only another derivative or the Original Version can actually offer. +Yes, this applies to the font menu name and other mechanisms that specify a font in a document. It would be fine, however, to keep a text reference to the original fonts in the description field, in your modified source file or in documentation provided alongside your derivative as long as no one could be confused that your modified source is the original. But you cannot use the Reserved Font Names in any way to identify the font to the user (unless the Copyright Holder(s) allow(s) it through a separate agreement). Users who install derivatives (Modified Versions) on their systems should not see any of the original Reserved Font Names in their font menus, for example. Again, this is to ensure that users are not confused and do not mistake one font for another and so expect features only another derivative or the Original Version can actually offer. 5.4 Am I not allowed to use any part of the Reserved Font Names? You may not use individual words from the Reserved Font Names, but you would be allowed to use parts of words, as long as you do not use any word from the Reserved Font Names entirely. We do not recommend using parts of words because of potential confusion, but it is allowed. For example, if "Foobar" was a Reserved Font Name, you would be allowed to use "Foo" or "bar", although we would not recommend it. Such an unfortunate choice would confuse the users of your fonts as well as make it harder for other designers to contribute. @@ -330,12 +330,12 @@ SIL gives permission to publish unofficial translations into other languages pro "This is an unofficial translation of the SIL Open Font License into . It was not published by SIL International, and does not legally state the distribution terms for fonts that use the OFL. A release under the OFL is only valid when using the original English text. However, we recognize that this unofficial translation will help users and designers not familiar with English to better understand and use the OFL. We encourage designers who consider releasing their creation under the OFL to read the OFL-FAQ in their own language if it is available. Please go to http://scripts.sil.org/OFL for the official version of the license and the accompanying OFL-FAQ." -- Keep your unofficial translation current and update it at our request if needed, for example if there is any ambiguity which could lead to confusion. +- Keep your unofficial translation current and update it at our request if needed, for example if there is any ambiguity which could lead to confusion. If you start such a unofficial translation effort of the OFL and OFL-FAQ please let us know. 8.7 Does the OFL have an explicit expiration term? -No, the implicit intent of the OFL is that the permissions granted are perpetual and irrevocable. +No, the implicit intent of the OFL is that the permissions granted are perpetual and irrevocable. 9 ABOUT SIL INTERNATIONAL @@ -393,7 +393,7 @@ ChangeLog - Corrected minor typos in the documentation - Fixed position of combining inverted breve below (U+032F) - Added OpenType/Graphite smart code for Armenian -- Added Armenian glyphs (U+0531 -> U+0587) +- Added Armenian glyphs (U+0531 -> U+0587) - Released as "NewWorldFontFamily" 1 Jan 2005 (Joe Smith) GlobalFontFamily Version 1.0 @@ -433,4 +433,3 @@ Foobar.org is a distributed community of developers... Company.com is a small business who likes to support community designers... University.edu is a renowned educational institution with a strong design department... ----- - diff --git a/src/glyphs/weather-icons/OFL.txt b/src/glyphs/weather-icons/OFL.txt index fd295ff..e8a4fd1 100644 --- a/src/glyphs/weather-icons/OFL.txt +++ b/src/glyphs/weather-icons/OFL.txt @@ -22,7 +22,7 @@ with others. The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, +fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The diff --git a/src/glyphs/weathericons-regular-webfont.ttf b/src/glyphs/weathericons-regular-webfont.ttf deleted file mode 100644 index 948f0a5..0000000 Binary files a/src/glyphs/weathericons-regular-webfont.ttf and /dev/null differ