mirror of
https://github.com/daylinmorgan/monolisa-nerdfont-patch.git
synced 2024-11-09 16:43:14 -06:00
Compare commits
2 commits
b71e7d3aef
...
2d64ecab8b
Author | SHA1 | Date | |
---|---|---|---|
2d64ecab8b | |||
9eb507579a |
4 changed files with 523 additions and 362 deletions
201
.task.mk
201
.task.mk
|
@ -1,7 +1,7 @@
|
|||
# }> [github.com/daylinmorgan/task.mk] <{ #
|
||||
# Copyright (c) 2022 Daylin Morgan
|
||||
# MIT License
|
||||
# version: v22.9.19-5-g5f593e3-dev
|
||||
# version: v22.9.28-dev
|
||||
#
|
||||
# task.mk should be included at the bottom of your Makefile with `-include .task.mk`
|
||||
# See below for the standard configuration options that should be set prior to including this file.
|
||||
|
@ -12,12 +12,13 @@ ACCENT_STYLE ?= b_yellow
|
|||
PARAMS_STYLE ?= $(ACCENT_STYLE)
|
||||
GOAL_STYLE ?= $(ACCENT_STYLE)
|
||||
MSG_STYLE ?= faint
|
||||
DIVIDER_STYLE ?= default
|
||||
DIVIDER ?= ─
|
||||
DIVIDER_STYLE ?= default
|
||||
HELP_SEP ?= │
|
||||
WRAP ?= 100
|
||||
# python f-string literals
|
||||
EPILOG ?=
|
||||
USAGE ?={ansi.$(HEADER_STYLE)}usage{ansi.end}:\n make <recipe>\n
|
||||
USAGE ?={ansi.header}usage{ansi.end}:\n make <recipe>\n
|
||||
INHERIT_SHELL ?=
|
||||
# ---- [builtin recipes] ---- #
|
||||
ifeq (help,$(firstword $(MAKECMDGOALS)))
|
||||
|
@ -25,10 +26,8 @@ ifeq (help,$(firstword $(MAKECMDGOALS)))
|
|||
export HELP_ARGS
|
||||
endif
|
||||
## h, help | show this help
|
||||
.PHONY: help h
|
||||
help h:
|
||||
$(call py,help_py)
|
||||
.PHONY: _help
|
||||
h help:
|
||||
$(call py,help_py) || { echo "exiting early!"; exit 1; }
|
||||
_help: export SHOW_HIDDEN=true
|
||||
_help: help
|
||||
ifdef PRINT_VARS
|
||||
|
@ -40,20 +39,21 @@ endif
|
|||
### | args: -ws --hidden
|
||||
### task.mk builtins: | args: -d --hidden
|
||||
## _print-ansi | show all possible ansi color code combinations
|
||||
.PHONY:
|
||||
_print-ansi:
|
||||
$(call py,print_ansi_py)
|
||||
# functions to take f-string literals and pass to python print
|
||||
tprint = $(call py,info_py,$(1))
|
||||
tprint-sh = $(call pysh,info_py,$(1))
|
||||
tprint = $(call py,print_py,$(1))
|
||||
tprint-sh = $(call pysh,print_py,$(1))
|
||||
tconfirm = $(call py,confirm_py,$(1))
|
||||
## _update-task.mk | downloads latest development version of task.mk
|
||||
_update-task.mk:
|
||||
$(call tprint,{a.b_cyan}Updating task.mk{a.end})
|
||||
curl https://raw.githubusercontent.com/daylinmorgan/task.mk/main/task.mk -o .task.mk
|
||||
export MAKEFILE_LIST
|
||||
.PHONY: h help _help _print-ansi _update-task.mk
|
||||
TASK_MAKEFILE_LIST := $(filter-out $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST)),$(MAKEFILE_LIST))
|
||||
export MAKEFILE_LIST MAKE TASK_MAKEFILE_LIST
|
||||
ifndef INHERIT_SHELL
|
||||
SHELL := /bin/bash
|
||||
SHELL := $(shell which bash)
|
||||
endif
|
||||
# ---- [python/bash script runner] ---- #
|
||||
define _newline
|
||||
|
@ -84,8 +84,10 @@ import argparse
|
|||
from collections import namedtuple
|
||||
import os
|
||||
import re
|
||||
$(ansi_py)
|
||||
$(quit_make_py)
|
||||
import subprocess
|
||||
import sys
|
||||
from textwrap import wrap
|
||||
$(utils_py)
|
||||
MaxLens = namedtuple("MaxLens", "goal msg")
|
||||
pattern = re.compile(
|
||||
r"^## (?P<goal>.*?) \| (?P<msg>.*?)(?:\s?\| args: (?P<msgargs>.*?))?$$|^### (?P<rawmsg>.*?)?(?:\s?\| args: (?P<rawargs>.*?))?$$"
|
||||
|
@ -100,9 +102,11 @@ def parseargs(argstring):
|
|||
parser.add_argument("-gs", "--goal-style", type=str)
|
||||
parser.add_argument("--hidden", action="store_true")
|
||||
return parser.parse_args(argstring.split())
|
||||
def divider(len):
|
||||
return ansi.style(f" {cfg.div*len}", "div_style")
|
||||
def gen_makefile():
|
||||
makefile = ""
|
||||
for file in os.getenv("MAKEFILE_LIST").split():
|
||||
for file in os.getenv("MAKEFILE_LIST", "").split():
|
||||
with open(file, "r") as f:
|
||||
makefile += f.read() + "\n\n"
|
||||
return makefile
|
||||
|
@ -132,58 +136,80 @@ def recipe_help_header(goal):
|
|||
item[0].get("msgargs", ""),
|
||||
)
|
||||
else:
|
||||
return f" {ansi.style(goal,'$(GOAL_STYLE)')}:"
|
||||
return f" {ansi.style(goal,'goal')}"
|
||||
def get_goal_deps(goal="task.mk"):
|
||||
make = os.getenv("MAKE", "make")
|
||||
cmd = [make, "-p", "-n", "-i"]
|
||||
for file in os.getenv("TASK_MAKEFILE_LIST", "").split():
|
||||
cmd.extend(["-f", file])
|
||||
database = subprocess.check_output(cmd, universal_newlines=True)
|
||||
dep_pattern = re.compile(r"^" + goal + ":(.*)?")
|
||||
for line in database.splitlines():
|
||||
match = dep_pattern.search(line)
|
||||
if match and match.groups()[0]:
|
||||
return wrap(
|
||||
f"{ansi.style('deps','default')}: {ansi.style(match.groups()[0].strip(),'msg')}",
|
||||
width=cfg.wrap,
|
||||
initial_indent=" ",
|
||||
subsequent_indent=" ",
|
||||
)
|
||||
def parse_goal(file, goal):
|
||||
goals = goal_pattern.findall(file)
|
||||
matched_goal = [i for i in goals if goal in i.split()]
|
||||
output = []
|
||||
if matched_goal:
|
||||
output.append(recipe_help_header(matched_goal[0]))
|
||||
deps = get_goal_deps(matched_goal[0])
|
||||
if deps:
|
||||
output.extend(deps)
|
||||
lines = file.splitlines()
|
||||
loc = [n for n, l in enumerate(lines) if l.startswith(f"{matched_goal[0]}:")][0]
|
||||
recipe = []
|
||||
for line in lines[loc + 1 :]:
|
||||
if not line.startswith("\t"):
|
||||
break
|
||||
recipe.append(line)
|
||||
output.append(divider(max((len(l) for l in recipe)) + 5))
|
||||
output.append("\n".join(recipe) + "\n")
|
||||
recipe.append(f" {line.strip()}")
|
||||
output.append(divider(max((len(l.strip()) for l in recipe))))
|
||||
output.append("\n".join(recipe))
|
||||
else:
|
||||
output.append(f"{ansi.b_red}ERROR{ansi.end} Failed to find goal: {goal}")
|
||||
deps = get_goal_deps(goal)
|
||||
if deps:
|
||||
output.append(recipe_help_header(goal))
|
||||
output.extend(deps)
|
||||
if not output:
|
||||
output.append(f"{ansi.style('ERROR','b_red')}: Failed to find goal: {goal}")
|
||||
return output
|
||||
def fmt_goal(goal, msg, max_goal_len, argstr):
|
||||
args = parseargs(argstr)
|
||||
goal_style = args.goal_style.strip() if args.goal_style else "$(GOAL_STYLE)"
|
||||
msg_style = args.msg_style.strip() if args.msg_style else "$(MSG_STYLE)"
|
||||
goal_style = args.goal_style.strip() if args.goal_style else "goal"
|
||||
msg_style = args.msg_style.strip() if args.msg_style else "msg"
|
||||
return (
|
||||
ansi.style(f" {goal:>{max_goal_len}}", goal_style)
|
||||
+ f" $(HELP_SEP) "
|
||||
+ ansi.style(msg, msg_style)
|
||||
)
|
||||
def divider(len):
|
||||
return ansi.style(f" {'$(DIVIDER)'*len}", "$(DIVIDER_STYLE)")
|
||||
def fmt_rawmsg(msg, argstr, maxlens):
|
||||
args = parseargs(argstr)
|
||||
lines = []
|
||||
msg_style = args.msg_style.strip() if args.msg_style else "$(MSG_STYLE)"
|
||||
msg_style = args.msg_style.strip() if args.msg_style else "msg"
|
||||
if not os.getenv("SHOW_HIDDEN") and args.hidden:
|
||||
return []
|
||||
if msg:
|
||||
if args.align == "sep":
|
||||
lines.append(
|
||||
f"{' '*(maxlens.goal+len('$(HELP_SEP)')+4)}{ansi.style(msg,msg_style)}"
|
||||
f"{' '*(maxlens.goal+len(cfg.sep)+4)}{ansi.style(msg,msg_style)}"
|
||||
)
|
||||
elif args.align == "center":
|
||||
lines.append(f" {ansi.style(msg.center(sum(maxlens)),msg_style)}")
|
||||
else:
|
||||
lines.append(f" {ansi.style(msg,msg_style)}")
|
||||
if args.divider:
|
||||
lines.append(divider(len("$(HELP_SEP)") + sum(maxlens) + 2))
|
||||
lines.append(divider(len(cfg.sep) + sum(maxlens) + 2))
|
||||
if args.whitespace:
|
||||
lines.append("\n")
|
||||
return lines
|
||||
def print_help():
|
||||
lines = [f"""$(USAGE)"""]
|
||||
lines = [cfg.usage]
|
||||
items = list(parse_help(gen_makefile()))
|
||||
maxlens = MaxLens(
|
||||
*(max((len(item[x]) for item in items if x in item)) for x in ["goal", "msg"])
|
||||
|
@ -197,25 +223,73 @@ def print_help():
|
|||
)
|
||||
if "rawmsg" in item:
|
||||
lines.extend(fmt_rawmsg(item["rawmsg"], item.get("rawargs", ""), maxlens))
|
||||
lines.append(f"""$(EPILOG)""")
|
||||
lines.append(cfg.epilog)
|
||||
print("\n".join(lines))
|
||||
def print_arg_help(help_args):
|
||||
print(f"{ansi.style('task.mk recipe help','header')}\n")
|
||||
for arg in help_args.split():
|
||||
print(f"{ansi.style('task.mk recipe help','$(HEADER_STYLE)')}\n")
|
||||
print("\n".join(parse_goal(gen_makefile(), arg)))
|
||||
print()
|
||||
def main():
|
||||
help_args = os.getenv("HELP_ARGS")
|
||||
if help_args:
|
||||
quit_make()
|
||||
print_arg_help(help_args)
|
||||
sys.exit(1)
|
||||
else:
|
||||
print_help()
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
endef
|
||||
define ansi_py
|
||||
define print_py
|
||||
$(utils_py)
|
||||
sys.stderr.write(f"""$(2)\n""")
|
||||
endef
|
||||
define print_ansi_py
|
||||
$(utils_py)
|
||||
sep = f"$(HELP_SEP)"
|
||||
codes_names = {getattr(ansi, attr): attr for attr in ansi.__dict__}
|
||||
for code in sorted(codes_names.keys(), key=lambda item: (len(item), item)):
|
||||
print(f"{codes_names[code]:>20} {sep} {code+'*****'+ansi.end} {sep} {repr(code)}")
|
||||
endef
|
||||
define vars_py
|
||||
import os
|
||||
$(utils_py)
|
||||
vars = "$2".split()
|
||||
length = max((len(v) for v in vars))
|
||||
print(f"{ansi.header}vars{ansi.end}:\n")
|
||||
for v in vars:
|
||||
print(f" {ansi.params}{v:<{length}}{ansi.end} = {os.getenv(v)}")
|
||||
print()
|
||||
endef
|
||||
define confirm_py
|
||||
import sys
|
||||
$(utils_py)
|
||||
def confirm():
|
||||
"""
|
||||
Ask user to enter Y or N (case-insensitive).
|
||||
:return: True if the answer is Y.
|
||||
:rtype: bool
|
||||
"""
|
||||
answer = ""
|
||||
while answer not in ["y", "n"]:
|
||||
answer = input(f"""$(2) {a.b_red}[Y/n]{a.end} """).lower()
|
||||
return answer == "y"
|
||||
if confirm():
|
||||
sys.exit()
|
||||
else:
|
||||
sys.exit(1)
|
||||
endef
|
||||
define utils_py
|
||||
import os
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
@dataclass
|
||||
class Config:
|
||||
div: str
|
||||
sep: str
|
||||
epilog: str
|
||||
usage: str
|
||||
wrap: int
|
||||
color2byte = dict(
|
||||
black=0,
|
||||
red=1,
|
||||
|
@ -247,6 +321,7 @@ class Ansi:
|
|||
)
|
||||
for name, byte in state2byte.items():
|
||||
self.setcode(name, f"\033[{byte}m")
|
||||
self.add_cfg()
|
||||
def setcode(self, name, escape_code):
|
||||
"""create attr for style and escape code"""
|
||||
if not sys.stdout.isatty() or os.getenv("NO_COLOR", False):
|
||||
|
@ -256,6 +331,9 @@ class Ansi:
|
|||
def custom(self, fg=None, bg=None):
|
||||
"""use custom color"""
|
||||
code, end = "\033[", "m"
|
||||
if not sys.stdout.isatty() or os.getenv("NO_COLOR", False):
|
||||
return ""
|
||||
else:
|
||||
if fg:
|
||||
if isinstance(fg, int):
|
||||
code += f"38;5;{fg}"
|
||||
|
@ -277,6 +355,17 @@ class Ansi:
|
|||
print("Expected one or three values for bg as a list")
|
||||
sys.exit(1)
|
||||
return code + end
|
||||
def add_cfg(self):
|
||||
cfg_styles = {
|
||||
"header": "$(HEADER_STYLE)",
|
||||
"accent": "$(ACCENT_STYLE)",
|
||||
"params": "$(PARAMS_STYLE)",
|
||||
"goal": "$(GOAL_STYLE)",
|
||||
"msg": "$(MSG_STYLE)",
|
||||
"div_style": "$(DIVIDER_STYLE)",
|
||||
}
|
||||
for name, style in cfg_styles.items():
|
||||
self.setcode(name, getattr(self, style))
|
||||
def style(self, text, style):
|
||||
if style not in self.__dict__:
|
||||
print(f"unknown style: {style}")
|
||||
|
@ -284,49 +373,7 @@ class Ansi:
|
|||
else:
|
||||
return f"{self.__dict__[style]}{text}{self.__dict__['end']}"
|
||||
a = ansi = Ansi()
|
||||
endef
|
||||
define info_py
|
||||
$(ansi_py)
|
||||
print(f"""$(2)""")
|
||||
endef
|
||||
define print_ansi_py
|
||||
$(ansi_py)
|
||||
sep = f"$(HELP_SEP)"
|
||||
codes_names = {getattr(ansi, attr): attr for attr in ansi.__dict__}
|
||||
for code in sorted(codes_names.keys(), key=lambda item: (len(item), item)):
|
||||
print(f"{codes_names[code]:>20} {sep} {code+'*****'+ansi.end} {sep} {repr(code)}")
|
||||
endef
|
||||
define vars_py
|
||||
import os
|
||||
$(ansi_py)
|
||||
vars = "$2".split()
|
||||
length = max((len(v) for v in vars))
|
||||
print(f"{ansi.$(HEADER_STYLE)}vars:{ansi.end}\n")
|
||||
for v in vars:
|
||||
print(f" {ansi.b_magenta}{v:<{length}}{ansi.end} = {os.getenv(v)}")
|
||||
print()
|
||||
endef
|
||||
define confirm_py
|
||||
import sys
|
||||
$(ansi_py)
|
||||
$(quit_make_py)
|
||||
def confirm():
|
||||
"""
|
||||
Ask user to enter Y or N (case-insensitive).
|
||||
:return: True if the answer is Y.
|
||||
:rtype: bool
|
||||
"""
|
||||
answer = ""
|
||||
while answer not in ["y", "n"]:
|
||||
answer = input(f"""$(2) {a.b_red}[Y/n]{a.end} """).lower()
|
||||
return answer == "y"
|
||||
if confirm():
|
||||
sys.exit()
|
||||
else:
|
||||
quit_make()
|
||||
endef
|
||||
define quit_make_py
|
||||
import os, signal
|
||||
def quit_make():
|
||||
os.kill(os.getppid(), signal.SIGQUIT)
|
||||
cfg = Config(
|
||||
"$(DIVIDER)", "$(HELP_SEP)", f"""$(EPILOG)""", f"""$(USAGE)""", int("$(WRAP)")
|
||||
)
|
||||
endef
|
||||
|
|
2
Makefile
2
Makefile
|
@ -45,5 +45,5 @@ lint:
|
|||
clean:
|
||||
@rm -r patched/*
|
||||
|
||||
USAGE = {a.b_green}Update MonoLisa with Nerd Fonts! {a.end}\n\n{a.$(HEADER_STYLE)}usage{a.end}:\n make <recipe>\n
|
||||
USAGE = {a.b_green}Update MonoLisa with Nerd Fonts! {a.end}\n\n{a.header}usage{a.end}:\n make <recipe>\n
|
||||
-include .task.mk
|
||||
|
|
618
bin/font-patcher
618
bin/font-patcher
|
@ -1,14 +1,14 @@
|
|||
#!/usr/bin/env python
|
||||
# coding=utf8
|
||||
# Nerd Fonts Version: 2.2.2
|
||||
# Nerd Fonts Version: 2.3.0-RC
|
||||
# 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.0.6"
|
||||
script_version = "3.1.1"
|
||||
|
||||
version = "2.2.2"
|
||||
version = "2.3.0-RC"
|
||||
projectName = "Nerd Fonts"
|
||||
projectNameAbbreviation = "NF"
|
||||
projectNameSingular = projectName[:-1]
|
||||
|
@ -82,15 +82,32 @@ class TableHEADWriter:
|
|||
i += 4
|
||||
extra = 0
|
||||
for j in range(4):
|
||||
extra = extra << 8
|
||||
if i + j <= end:
|
||||
extra += ord(self.f.read(1))
|
||||
extra = extra << 8
|
||||
checksum = (checksum + extra) & 0xFFFFFFFF
|
||||
return checksum
|
||||
|
||||
def find_head_table(self):
|
||||
def find_head_table(self, idx):
|
||||
""" Search all tables for the HEAD table and store its metadata """
|
||||
self.f.seek(4)
|
||||
# Use font with index idx if this is a font collection file
|
||||
self.f.seek(0, 0)
|
||||
tag = self.f.read(4)
|
||||
if tag == b'ttcf':
|
||||
self.f.seek(2*2, 1)
|
||||
self.num_fonts = self.getlong()
|
||||
if (idx >= self.num_fonts):
|
||||
raise Exception('Trying to access subfont index {} but have only {} fonts'.format(idx, num_fonts))
|
||||
for _ in range(idx + 1):
|
||||
offset = self.getlong()
|
||||
self.f.seek(offset, 0)
|
||||
elif idx != 0:
|
||||
raise Exception('Trying to access subfont but file is no collection')
|
||||
else:
|
||||
self.f.seek(0, 0)
|
||||
self.num_fonts = 1
|
||||
|
||||
self.f.seek(4, 1)
|
||||
numtables = self.getshort()
|
||||
self.f.seek(3*2, 1)
|
||||
|
||||
|
@ -102,7 +119,7 @@ class TableHEADWriter:
|
|||
self.tab_length = self.getlong()
|
||||
if tab_name == b'head':
|
||||
return
|
||||
raise Exception('No HEAD table found')
|
||||
raise Exception('No HEAD table found in font idx {}'.format(idx))
|
||||
|
||||
def goto(self, where):
|
||||
""" Go to a named location in the file or to the specified index """
|
||||
|
@ -146,60 +163,97 @@ class TableHEADWriter:
|
|||
self.modified = False
|
||||
self.f = open(filename, 'r+b')
|
||||
|
||||
self.find_head_table()
|
||||
self.find_head_table(0)
|
||||
|
||||
self.flags = self.getshort('flags')
|
||||
self.lowppem = self.getshort('lowestRecPPEM')
|
||||
self.checksum_adj = self.getlong('checksumAdjustment')
|
||||
|
||||
def check_panose_monospaced(font):
|
||||
""" Check if the font's Panose flags say it is monospaced """
|
||||
# https://forum.high-logic.com/postedfiles/Panose.pdf
|
||||
panose = list(font.os2_panose)
|
||||
if panose[0] < 2 or panose[0] > 5:
|
||||
return -1 # invalid Panose info
|
||||
panose_mono = ((panose[0] == 2 and panose[3] == 9) or
|
||||
(panose[0] == 3 and panose[3] == 3))
|
||||
return 1 if panose_mono else 0
|
||||
|
||||
def is_monospaced(font):
|
||||
""" Check if a font is probably monospaced """
|
||||
# Some fonts lie (or have not any Panose flag set), spot check monospaced:
|
||||
width = -1
|
||||
width_mono = True
|
||||
for glyph in [ 0x49, 0x4D, 0x57, 0x61, 0x69, 0x2E ]: # wide and slim glyphs 'I', 'M', 'W', 'a', 'i', '.'
|
||||
if not glyph in font:
|
||||
# A 'strange' font, believe Panose
|
||||
return check_panose_monospaced(font) == 1
|
||||
# print(" -> {} {}".format(glyph, font[glyph].width))
|
||||
if width < 0:
|
||||
width = font[glyph].width
|
||||
continue
|
||||
if font[glyph].width != width:
|
||||
# Exception for fonts like Code New Roman Regular or Hermit Light/Bold:
|
||||
# Allow small 'i' and dot to be smaller than normal
|
||||
# I believe the source fonts are buggy
|
||||
if glyph in [ 0x69, 0x2E ]:
|
||||
if width > font[glyph].width:
|
||||
continue
|
||||
(xmin, _, xmax, _) = font[glyph].boundingBox()
|
||||
if width > xmax - xmin:
|
||||
continue
|
||||
width_mono = False
|
||||
break
|
||||
# We believe our own check more then Panose ;-D
|
||||
return width_mono
|
||||
|
||||
def get_advance_width(font, extended, minimum):
|
||||
""" Get the maximum/minimum advance width in the extended(?) range """
|
||||
width = 0
|
||||
if extended:
|
||||
end = 0x17f
|
||||
else:
|
||||
end = 0x07e
|
||||
for glyph in range(0x21, end):
|
||||
if not glyph in font:
|
||||
continue
|
||||
if glyph in range(0x7F, 0xBF):
|
||||
continue # ignore special characters like '1/4' etc
|
||||
if width == 0:
|
||||
width = font[glyph].width
|
||||
continue
|
||||
if not minimum and width < font[glyph].width:
|
||||
width = font[glyph].width
|
||||
elif minimum and width > font[glyph].width:
|
||||
width = font[glyph].width
|
||||
return width
|
||||
|
||||
|
||||
class font_patcher:
|
||||
def __init__(self):
|
||||
self.args = None # class 'argparse.Namespace'
|
||||
def __init__(self, args):
|
||||
self.args = args # class 'argparse.Namespace'
|
||||
self.sym_font_args = []
|
||||
self.config = None # class 'configparser.ConfigParser'
|
||||
self.sourceFont = None # class 'fontforge.font'
|
||||
self.octiconsExactEncodingPosition = True
|
||||
self.patch_set = None # class 'list'
|
||||
self.font_dim = None # class 'dict'
|
||||
self.onlybitmaps = 0
|
||||
self.extension = ""
|
||||
self.essential = set()
|
||||
self.setup_arguments()
|
||||
self.config = configparser.ConfigParser(empty_lines_in_values=False, allow_no_value=True)
|
||||
if not os.path.isfile(self.args.font):
|
||||
sys.exit("{}: Font file does not exist: {}".format(projectName, self.args.font))
|
||||
if not os.access(self.args.font, os.R_OK):
|
||||
sys.exit("{}: Can not open font file for reading: {}".format(projectName, self.args.font))
|
||||
if len(fontforge.fontsInFile(self.args.font)) > 1:
|
||||
sys.exit("{}: Font file contains {} fonts, can only handle single font files".format(projectName,
|
||||
len(fontforge.fontsInFile(self.args.font))))
|
||||
try:
|
||||
self.sourceFont = fontforge.open(self.args.font, 1) # 1 = ("fstypepermitted",))
|
||||
except Exception:
|
||||
sys.exit(projectName + ": Can not open font, try to open with fontforge interactively to get more information")
|
||||
|
||||
def patch(self, font):
|
||||
self.sourceFont = font
|
||||
self.setup_version()
|
||||
self.get_essential_references()
|
||||
self.setup_name_backup()
|
||||
self.setup_name_backup(font)
|
||||
if self.args.single:
|
||||
self.assert_monospace()
|
||||
self.remove_ligatures()
|
||||
make_sure_path_exists(self.args.outputdir)
|
||||
self.check_position_conflicts()
|
||||
self.setup_patch_set()
|
||||
self.setup_line_dimensions()
|
||||
self.get_sourcefont_dimensions()
|
||||
self.sourceFont.encoding = 'UnicodeFull' # Update the font encoding to ensure that the Unicode glyphs are available
|
||||
self.onlybitmaps = self.sourceFont.onlybitmaps # Fetch this property before adding outlines. NOTE self.onlybitmaps initialized and never used
|
||||
if self.args.extension == "":
|
||||
self.extension = os.path.splitext(self.args.font)[1]
|
||||
else:
|
||||
self.extension = '.' + self.args.extension
|
||||
if re.match("\.ttc$", self.extension, re.IGNORECASE):
|
||||
sys.exit(projectName + ": Can not create True Type Collections")
|
||||
|
||||
|
||||
def patch(self):
|
||||
|
||||
print("{} Patcher v{} ({}) executing\n".format(projectName, version, script_version))
|
||||
|
||||
if self.args.single:
|
||||
# Force width to be equal on all glyphs to ensure the font is considered monospaced on Windows.
|
||||
|
@ -241,14 +295,11 @@ class font_patcher:
|
|||
symfont.em = self.sourceFont.em
|
||||
PreviousSymbolFilename = patch['Filename']
|
||||
|
||||
# If patch table doesn't include a source start and end, re-use the symbol font values
|
||||
# If patch table doesn't include a source start, re-use the symbol font values
|
||||
SrcStart = patch['SrcStart']
|
||||
SrcEnd = patch['SrcEnd']
|
||||
if not SrcStart:
|
||||
SrcStart = patch['SymStart']
|
||||
if not SrcEnd:
|
||||
SrcEnd = patch['SymEnd']
|
||||
self.copy_glyphs(SrcStart, SrcEnd, symfont, patch['SymStart'], patch['SymEnd'], patch['Exact'], patch['ScaleGlyph'], patch['Name'], patch['Attributes'])
|
||||
self.copy_glyphs(SrcStart, symfont, patch['SymStart'], patch['SymEnd'], patch['Exact'], patch['ScaleGlyph'], patch['Name'], patch['Attributes'])
|
||||
|
||||
if symfont:
|
||||
symfont.close()
|
||||
|
@ -262,21 +313,40 @@ class font_patcher:
|
|||
self.sourceFont["grave"].glyphclass="baseglyph"
|
||||
|
||||
|
||||
def generate(self):
|
||||
def generate(self, sourceFonts):
|
||||
sourceFont = sourceFonts[0]
|
||||
if len(sourceFonts) > 1:
|
||||
layer = None
|
||||
# use first non-background layer
|
||||
for l in sourceFont.layers:
|
||||
if not sourceFont.layers[l].is_background:
|
||||
layer = l
|
||||
break
|
||||
outfile = os.path.normpath(os.path.join(self.args.outputdir, sourceFont.familyname + ".ttc"))
|
||||
# the `PfEd-comments` flag is required for Fontforge to save '.comment' and '.fontlog'.
|
||||
if self.sourceFont.fullname != None:
|
||||
outfile = self.args.outputdir + "/" + self.sourceFont.fullname + self.extension
|
||||
self.sourceFont.generate(outfile, flags=(str('opentype'), str('PfEd-comments')))
|
||||
message = "\nGenerated: {} in '{}'".format(self.sourceFont.fullname, outfile)
|
||||
sourceFonts[0].generateTtc(outfile, sourceFonts[1:], flags=(str('opentype'), str('PfEd-comments')), layer=layer)
|
||||
message = "\nGenerated: {} fonts in '{}'".format(len(sourceFonts), outfile)
|
||||
else:
|
||||
outfile = self.args.outputdir + "/" + self.sourceFont.cidfontname + self.extension
|
||||
self.sourceFont.generate(outfile, flags=(str('opentype'), str('PfEd-comments')))
|
||||
message = "\nGenerated: {} in '{}'".format(self.sourceFont.fontname, outfile)
|
||||
fontname = sourceFont.fullname
|
||||
if not fontname:
|
||||
fontname = sourceFont.cidfontname
|
||||
outfile = os.path.normpath(os.path.join(self.args.outputdir, fontname + self.args.extension))
|
||||
# the `PfEd-comments` flag is required for Fontforge to save '.comment' and '.fontlog'.
|
||||
bitmaps = str()
|
||||
if len(self.sourceFont.bitmapSizes):
|
||||
print("Preserving bitmaps {}".format(self.sourceFont.bitmapSizes))
|
||||
bitmaps = str('otf') # otf/ttf, both is bf_ttf
|
||||
sourceFont.generate(outfile, bitmap_type=bitmaps, flags=(str('opentype'), str('PfEd-comments')))
|
||||
message = "\nGenerated: {} in '{}'".format(self.sourceFont.fullname, outfile)
|
||||
|
||||
# Adjust flags that can not be changed via fontforge
|
||||
try:
|
||||
source_font = TableHEADWriter(self.args.font)
|
||||
dest_font = TableHEADWriter(outfile)
|
||||
for idx in range(source_font.num_fonts):
|
||||
print("{}: Tweaking {}/{}".format(projectName, idx + 1, source_font.num_fonts))
|
||||
source_font.find_head_table(idx)
|
||||
dest_font.find_head_table(idx)
|
||||
if source_font.flags & 0x08 == 0 and dest_font.flags & 0x08 != 0:
|
||||
print("Changing flags from 0x{:X} to 0x{:X}".format(dest_font.flags, dest_font.flags & ~0x08))
|
||||
dest_font.putshort(dest_font.flags & ~0x08, 'flags') # clear 'ppem_to_int'
|
||||
|
@ -301,130 +371,19 @@ class font_patcher:
|
|||
print("\nPost Processed: {}".format(outfile))
|
||||
|
||||
|
||||
def setup_arguments(self):
|
||||
parser = argparse.ArgumentParser(
|
||||
description=(
|
||||
'Nerd Fonts Font Patcher: patches a given font with programming and development related glyphs\n\n'
|
||||
'* Website: https://www.nerdfonts.com\n'
|
||||
'* Version: ' + version + '\n'
|
||||
'* Development Website: https://github.com/ryanoasis/nerd-fonts\n'
|
||||
'* Changelog: https://github.com/ryanoasis/nerd-fonts/blob/master/changelog.md'),
|
||||
formatter_class=RawTextHelpFormatter
|
||||
)
|
||||
|
||||
# optional arguments
|
||||
parser.add_argument('font', help='The path to the font to patch (e.g., Inconsolata.otf)')
|
||||
parser.add_argument('-v', '--version', action='version', version=projectName + ": %(prog)s (" + version + ")")
|
||||
parser.add_argument('-s', '--mono', '--use-single-width-glyphs', dest='single', default=False, action='store_true', 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')
|
||||
parser.add_argument('--postprocess', dest='postprocess', default=False, type=str, nargs='?', help='Specify a Script for Post Processing')
|
||||
parser.add_argument('--configfile', dest='configfile', default=False, type=str, nargs='?', help='Specify a file path for JSON configuration file (see sample: src/config.sample.json)')
|
||||
parser.add_argument('--custom', dest='custom', default=False, type=str, nargs='?', help='Specify a custom symbol font. All new glyphs will be copied, with no scaling applied.')
|
||||
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('--variable-width-glyphs', dest='nonmono', default=False, action='store_true', help='Do not adjust advance width (no "overhang")')
|
||||
|
||||
# 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('--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')
|
||||
|
||||
# symbol fonts to include arguments
|
||||
sym_font_group = parser.add_argument_group('Symbol Fonts')
|
||||
sym_font_group.add_argument('--fontawesome', dest='fontawesome', default=False, action='store_true', help='Add Font Awesome Glyphs (http://fontawesome.io/)')
|
||||
sym_font_group.add_argument('--fontawesomeextension', dest='fontawesomeextension', default=False, action='store_true', help='Add Font Awesome Extension Glyphs (https://andrelzgava.github.io/font-awesome-extension/)')
|
||||
sym_font_group.add_argument('--fontlogos', '--fontlinux', dest='fontlogos', default=False, action='store_true', help='Add Font Logos Glyphs (https://github.com/Lukas-W/font-logos)')
|
||||
sym_font_group.add_argument('--octicons', dest='octicons', default=False, action='store_true', help='Add Octicons Glyphs (https://octicons.github.com)')
|
||||
sym_font_group.add_argument('--codicons', dest='codicons', default=False, action='store_true', help='Add Codicons Glyphs (https://github.com/microsoft/vscode-codicons)')
|
||||
sym_font_group.add_argument('--powersymbols', dest='powersymbols', default=False, action='store_true', help='Add IEC Power Symbols (https://unicodepowersymbol.com/)')
|
||||
sym_font_group.add_argument('--pomicons', dest='pomicons', default=False, action='store_true', help='Add Pomicon Glyphs (https://github.com/gabrielelana/pomicons)')
|
||||
sym_font_group.add_argument('--powerline', dest='powerline', default=False, action='store_true', help='Add Powerline Glyphs')
|
||||
sym_font_group.add_argument('--powerlineextra', dest='powerlineextra', default=False, action='store_true', help='Add Powerline Glyphs (https://github.com/ryanoasis/powerline-extra-symbols)')
|
||||
sym_font_group.add_argument('--material', '--materialdesignicons', '--mdi', dest='material', default=False, action='store_true', help='Add Material Design Icons (https://github.com/templarian/MaterialDesign)')
|
||||
sym_font_group.add_argument('--weather', '--weathericons', dest='weather', default=False, action='store_true', help='Add Weather Icons (https://github.com/erikflowers/weather-icons)')
|
||||
|
||||
self.args = parser.parse_args()
|
||||
|
||||
if self.args.makegroups and not FontnameParserOK:
|
||||
sys.exit(projectName + ": FontnameParser module missing (bin/scripts/name_parser/Fontname*), can not --makegroups".format(projectName))
|
||||
|
||||
# if you add a new font, set it to True here inside the if condition
|
||||
if self.args.complete:
|
||||
self.args.fontawesome = True
|
||||
self.args.fontawesomeextension = True
|
||||
self.args.fontlogos = True
|
||||
self.args.octicons = True
|
||||
self.args.codicons = True
|
||||
self.args.powersymbols = True
|
||||
self.args.pomicons = True
|
||||
self.args.powerline = True
|
||||
self.args.powerlineextra = True
|
||||
self.args.material = True
|
||||
self.args.weather = True
|
||||
|
||||
if not self.args.complete:
|
||||
# add the list of arguments for each symbol font to the list self.sym_font_args
|
||||
for action in sym_font_group._group_actions:
|
||||
self.sym_font_args.append(action.__dict__['option_strings'])
|
||||
|
||||
# determine whether or not all symbol fonts are to be used
|
||||
font_complete = True
|
||||
for sym_font_arg_aliases in self.sym_font_args:
|
||||
found = False
|
||||
for alias in sym_font_arg_aliases:
|
||||
if alias in sys.argv:
|
||||
found = True
|
||||
if found is not True:
|
||||
font_complete = False
|
||||
self.args.complete = font_complete
|
||||
|
||||
if self.args.alsowindows:
|
||||
self.args.windows = False
|
||||
|
||||
if self.args.nonmono and self.args.single:
|
||||
print("Warniung: Specified contradicting --variable-width-glyphs and --use-single-width-glyph. Ignoring --variable-width-glyphs.")
|
||||
self.args.nonmono = False
|
||||
|
||||
# this one also works but it needs to be updated every time a font is added
|
||||
# it was a conditional in self.setup_font_names() before, but it was missing
|
||||
# a symbol font, so it would name the font complete without being so sometimes.
|
||||
# that's why i did the above.
|
||||
#
|
||||
# if you add a new font, put it in here too, as the others are
|
||||
# self.args.complete = all([
|
||||
# self.args.fontawesome is True,
|
||||
# self.args.fontawesomeextension is True,
|
||||
# self.args.fontlogos is True,
|
||||
# self.args.octicons is True,
|
||||
# self.args.powersymbols is True,
|
||||
# self.args.pomicons is True,
|
||||
# self.args.powerline is True,
|
||||
# self.args.powerlineextra is True,
|
||||
# self.args.material is True,
|
||||
# self.args.weather is True
|
||||
# ])
|
||||
|
||||
|
||||
def setup_name_backup(self):
|
||||
def setup_name_backup(self, font):
|
||||
""" Store the original font names to be able to rename the font multiple times """
|
||||
self.original_fontname = self.sourceFont.fontname
|
||||
self.original_fullname = self.sourceFont.fullname
|
||||
self.original_familyname = self.sourceFont.familyname
|
||||
font.persistent = {
|
||||
"fontname": font.fontname,
|
||||
"fullname": font.fullname,
|
||||
"familyname": font.familyname,
|
||||
}
|
||||
|
||||
|
||||
def setup_font_names(self):
|
||||
self.sourceFont.fontname = self.original_fontname
|
||||
self.sourceFont.fullname = self.original_fullname
|
||||
self.sourceFont.familyname = self.original_familyname
|
||||
def setup_font_names(self, font):
|
||||
font.fontname = font.persistent["fontname"]
|
||||
font.fullname = font.persistent["fullname"]
|
||||
font.familyname = font.persistent["familyname"]
|
||||
verboseAdditionalFontNameSuffix = " " + projectNameSingular
|
||||
if self.args.windows: # attempt to shorten here on the additional name BEFORE trimming later
|
||||
additionalFontNameSuffix = " " + projectNameAbbreviation
|
||||
|
@ -471,14 +430,14 @@ class font_patcher:
|
|||
verboseAdditionalFontNameSuffix += " Mono"
|
||||
|
||||
if FontnameParserOK and self.args.makegroups:
|
||||
use_fullname = type(self.sourceFont.fullname) == str # Usually the fullname is better to parse
|
||||
use_fullname = type(font.fullname) == str # Usually the fullname is better to parse
|
||||
# Use fullname if it is 'equal' to the fontname
|
||||
if self.sourceFont.fullname:
|
||||
use_fullname |= self.sourceFont.fontname.lower() == FontnameTools.postscript_char_filter(self.sourceFont.fullname).lower()
|
||||
if font.fullname:
|
||||
use_fullname |= font.fontname.lower() == FontnameTools.postscript_char_filter(font.fullname).lower()
|
||||
# Use fullname for any of these source fonts (that are impossible to disentangle from the fontname, we need the blanks)
|
||||
for hit in [ 'Meslo' ]:
|
||||
use_fullname |= self.sourceFont.fontname.lower().startswith(hit.lower())
|
||||
parser_name = self.sourceFont.fullname if use_fullname else self.sourceFont.fontname
|
||||
use_fullname |= font.fontname.lower().startswith(hit.lower())
|
||||
parser_name = font.fullname if use_fullname else font.fontname
|
||||
# Gohu fontnames hide the weight, but the file names are ok...
|
||||
if parser_name.startswith('Gohu'):
|
||||
parser_name = os.path.splitext(os.path.basename(self.args.font))[0]
|
||||
|
@ -496,16 +455,16 @@ class font_patcher:
|
|||
# have an internal style defined (in sfnt_names)
|
||||
# using '([^-]*?)' to get the item before the first dash "-"
|
||||
# using '([^-]*(?!.*-))' to get the item after the last dash "-"
|
||||
fontname, fallbackStyle = re.match("^([^-]*).*?([^-]*(?!.*-))$", self.sourceFont.fontname).groups()
|
||||
fontname, fallbackStyle = re.match("^([^-]*).*?([^-]*(?!.*-))$", font.fontname).groups()
|
||||
|
||||
# dont trust 'sourceFont.familyname'
|
||||
# dont trust 'font.familyname'
|
||||
familyname = fontname
|
||||
|
||||
# fullname (filename) can always use long/verbose font name, even in windows
|
||||
if self.sourceFont.fullname != None:
|
||||
fullname = self.sourceFont.fullname + verboseAdditionalFontNameSuffix
|
||||
if font.fullname != None:
|
||||
fullname = font.fullname + verboseAdditionalFontNameSuffix
|
||||
else:
|
||||
fullname = self.sourceFont.cidfontname + verboseAdditionalFontNameSuffix
|
||||
fullname = font.cidfontname + verboseAdditionalFontNameSuffix
|
||||
|
||||
fontname = fontname + additionalFontNameSuffix.replace(" ", "")
|
||||
|
||||
|
@ -513,13 +472,13 @@ class font_patcher:
|
|||
# parse fontname if it fails:
|
||||
try:
|
||||
# search tuple:
|
||||
subFamilyTupleIndex = [x[1] for x in self.sourceFont.sfnt_names].index("SubFamily")
|
||||
subFamilyTupleIndex = [x[1] for x in font.sfnt_names].index("SubFamily")
|
||||
|
||||
# String ID is at the second index in the Tuple lists
|
||||
sfntNamesStringIDIndex = 2
|
||||
|
||||
# now we have the correct item:
|
||||
subFamily = self.sourceFont.sfnt_names[subFamilyTupleIndex][sfntNamesStringIDIndex]
|
||||
subFamily = font.sfnt_names[subFamilyTupleIndex][sfntNamesStringIDIndex]
|
||||
except IndexError:
|
||||
sys.stderr.write("{}: Could not find 'SubFamily' for given font, falling back to parsed fontname\n".format(projectName))
|
||||
subFamily = fallbackStyle
|
||||
|
@ -629,22 +588,22 @@ class font_patcher:
|
|||
|
||||
if not (FontnameParserOK and self.args.makegroups):
|
||||
# replace any extra whitespace characters:
|
||||
self.sourceFont.familyname = " ".join(familyname.split())
|
||||
self.sourceFont.fullname = " ".join(fullname.split())
|
||||
self.sourceFont.fontname = " ".join(fontname.split())
|
||||
font.familyname = " ".join(familyname.split())
|
||||
font.fullname = " ".join(fullname.split())
|
||||
font.fontname = " ".join(fontname.split())
|
||||
|
||||
self.sourceFont.appendSFNTName(str('English (US)'), str('Preferred Family'), self.sourceFont.familyname)
|
||||
self.sourceFont.appendSFNTName(str('English (US)'), str('Family'), self.sourceFont.familyname)
|
||||
self.sourceFont.appendSFNTName(str('English (US)'), str('Compatible Full'), self.sourceFont.fullname)
|
||||
self.sourceFont.appendSFNTName(str('English (US)'), str('SubFamily'), subFamily)
|
||||
font.appendSFNTName(str('English (US)'), str('Preferred Family'), font.familyname)
|
||||
font.appendSFNTName(str('English (US)'), str('Family'), font.familyname)
|
||||
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
|
||||
fam_suffix += ' Mono' if self.args.single else ''
|
||||
n.inject_suffix(verboseAdditionalFontNameSuffix, additionalFontNameSuffix, fam_suffix)
|
||||
n.rename_font(self.sourceFont)
|
||||
n.rename_font(font)
|
||||
|
||||
self.sourceFont.comment = projectInfo
|
||||
self.sourceFont.fontlog = projectInfo
|
||||
font.comment = projectInfo
|
||||
font.fontlog = projectInfo
|
||||
|
||||
|
||||
def setup_version(self):
|
||||
|
@ -678,10 +637,19 @@ class font_patcher:
|
|||
print("No configfile given, skipping configfile related actions")
|
||||
|
||||
|
||||
def check_position_conflicts(self):
|
||||
# Prevent glyph encoding position conflicts between glyph sets
|
||||
if self.args.fontawesome and self.args.octicons:
|
||||
self.octiconsExactEncodingPosition = False
|
||||
def assert_monospace(self):
|
||||
# Check if the sourcefont is monospaced
|
||||
width_mono = is_monospaced(self.sourceFont)
|
||||
panose_mono = check_panose_monospaced(self.sourceFont)
|
||||
# The following is in fact "width_mono != panose_mono", but only if panose_mono is not 'unknown'
|
||||
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(" Glyph widths {} / {} - {} and Panose says \"monospace {}\" ({})".format(get_advance_width(self.sourceFont, False, True),
|
||||
get_advance_width(self.sourceFont, False, False), get_advance_width(self.sourceFont, True, False), panose_mono, list(self.sourceFont.os2_panose)))
|
||||
if not width_mono:
|
||||
print(" Warning: Sourcefont is not monospaced - forcing to monospace not advisable, results might be useless")
|
||||
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")
|
||||
|
||||
|
||||
def setup_patch_set(self):
|
||||
|
@ -805,28 +773,28 @@ class font_patcher:
|
|||
# Define the character ranges
|
||||
# Symbol font ranges
|
||||
self.patch_set = [
|
||||
{'Enabled': True, 'Name': "Seti-UI + Custom", 'Filename': "original-source.otf", 'Exact': False, 'SymStart': 0xE4FA, 'SymEnd': 0xE534, 'SrcStart': 0xE5FA, 'SrcEnd': 0xE634, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT},
|
||||
{'Enabled': True, 'Name': "Devicons", 'Filename': "devicons.ttf", 'Exact': False, 'SymStart': 0xE600, 'SymEnd': 0xE6C5, 'SrcStart': 0xE700, 'SrcEnd': 0xE7C5, 'ScaleGlyph': DEVI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT},
|
||||
{'Enabled': self.args.powerline, 'Name': "Powerline Symbols", 'Filename': "powerline-symbols/PowerlineSymbols.otf", 'Exact': True, 'SymStart': 0xE0A0, 'SymEnd': 0xE0A2, 'SrcStart': None, 'SrcEnd': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_POWERLINE},
|
||||
{'Enabled': self.args.powerline, 'Name': "Powerline Symbols", 'Filename': "powerline-symbols/PowerlineSymbols.otf", 'Exact': True, 'SymStart': 0xE0B0, 'SymEnd': 0xE0B3, 'SrcStart': None, 'SrcEnd': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_POWERLINE},
|
||||
{'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 0xE0A3, 'SymEnd': 0xE0A3, 'SrcStart': None, 'SrcEnd': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_POWERLINE},
|
||||
{'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 0xE0B4, 'SymEnd': 0xE0C8, 'SrcStart': None, 'SrcEnd': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_POWERLINE},
|
||||
{'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 0xE0CA, 'SymEnd': 0xE0CA, 'SrcStart': None, 'SrcEnd': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_POWERLINE},
|
||||
{'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 0xE0CC, 'SymEnd': 0xE0D4, 'SrcStart': None, 'SrcEnd': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_POWERLINE},
|
||||
{'Enabled': self.args.pomicons, 'Name': "Pomicons", 'Filename': "Pomicons.otf", 'Exact': True, 'SymStart': 0xE000, 'SymEnd': 0xE00A, 'SrcStart': None, 'SrcEnd': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT},
|
||||
{'Enabled': self.args.fontawesome, 'Name': "Font Awesome", 'Filename': "font-awesome/FontAwesome.otf", 'Exact': True, 'SymStart': 0xF000, 'SymEnd': 0xF2E0, 'SrcStart': None, 'SrcEnd': None, 'ScaleGlyph': FONTA_SCALE_LIST, 'Attributes': SYM_ATTR_FONTA},
|
||||
{'Enabled': self.args.fontawesomeextension, 'Name': "Font Awesome Extension", 'Filename': "font-awesome-extension.ttf", 'Exact': False, 'SymStart': 0xE000, 'SymEnd': 0xE0A9, 'SrcStart': 0xE200, 'SrcEnd': 0xE2A9, 'ScaleGlyph': 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, 'SrcEnd': None, 'ScaleGlyph': 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, 'SrcEnd': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, # Heavy Circle (aka Power Off)
|
||||
{'Enabled': self.args.material, 'Name': "Material", 'Filename': "materialdesignicons-webfont.ttf", 'Exact': False, 'SymStart': 0xF001, 'SymEnd': 0xF847, 'SrcStart': 0xF500, 'SrcEnd': 0xFD46, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT},
|
||||
{'Enabled': self.args.weather, 'Name': "Weather Icons", 'Filename': "weather-icons/weathericons-regular-webfont.ttf", 'Exact': False, 'SymStart': 0xF000, 'SymEnd': 0xF0EB, 'SrcStart': 0xE300, 'SrcEnd': 0xE3EB, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT},
|
||||
{'Enabled': self.args.fontlogos, 'Name': "Font Logos", 'Filename': "font-logos.ttf", 'Exact': True, 'SymStart': 0xF300, 'SymEnd': 0xF32F, 'SrcStart': None, 'SrcEnd': None , 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT},
|
||||
{'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons.ttf", 'Exact': self.octiconsExactEncodingPosition, 'SymStart': 0xF000, 'SymEnd': 0xF105, 'SrcStart': 0xF400, 'SrcEnd': 0xF505, 'ScaleGlyph': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Magnifying glass
|
||||
{'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons.ttf", 'Exact': self.octiconsExactEncodingPosition, 'SymStart': 0x2665, 'SymEnd': 0x2665, 'SrcStart': None, 'SrcEnd': None, 'ScaleGlyph': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Heart
|
||||
{'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons.ttf", 'Exact': self.octiconsExactEncodingPosition, 'SymStart': 0X26A1, 'SymEnd': 0X26A1, 'SrcStart': None, 'SrcEnd': None, 'ScaleGlyph': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Zap
|
||||
{'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons.ttf", 'Exact': self.octiconsExactEncodingPosition, 'SymStart': 0xF27C, 'SymEnd': 0xF27C, 'SrcStart': 0xF4A9, 'SrcEnd': 0xF4A9, 'ScaleGlyph': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Desktop
|
||||
{'Enabled': self.args.codicons, 'Name': "Codicons", 'Filename': "codicons/codicon.ttf", 'Exact': True, 'SymStart': 0xEA60, 'SymEnd': 0xEBEB, 'SrcStart': None, 'SrcEnd': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT},
|
||||
{'Enabled': self.args.custom, 'Name': "Custom", 'Filename': self.args.custom, 'Exact': True, 'SymStart': 0x0000, 'SymEnd': 0x0000, 'SrcStart': 0x0000, 'SrcEnd': 0x0000, 'ScaleGlyph': None, 'Attributes': CUSTOM_ATTR}
|
||||
{'Enabled': True, 'Name': "Seti-UI + Custom", 'Filename': "original-source.otf", 'Exact': False, 'SymStart': 0xE4FA, 'SymEnd': 0xE534, 'SrcStart': 0xE5FA, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT},
|
||||
{'Enabled': True, 'Name': "Devicons", 'Filename': "devicons.ttf", 'Exact': False, 'SymStart': 0xE600, 'SymEnd': 0xE6C5, 'SrcStart': 0xE700, 'ScaleGlyph': DEVI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT},
|
||||
{'Enabled': self.args.powerline, 'Name': "Powerline Symbols", 'Filename': "powerline-symbols/PowerlineSymbols.otf", 'Exact': True, 'SymStart': 0xE0A0, 'SymEnd': 0xE0A2, 'SrcStart': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_POWERLINE},
|
||||
{'Enabled': self.args.powerline, 'Name': "Powerline Symbols", 'Filename': "powerline-symbols/PowerlineSymbols.otf", 'Exact': True, 'SymStart': 0xE0B0, 'SymEnd': 0xE0B3, 'SrcStart': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_POWERLINE},
|
||||
{'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 0xE0A3, 'SymEnd': 0xE0A3, 'SrcStart': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_POWERLINE},
|
||||
{'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 0xE0B4, 'SymEnd': 0xE0C8, 'SrcStart': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_POWERLINE},
|
||||
{'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 0xE0CA, 'SymEnd': 0xE0CA, 'SrcStart': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_POWERLINE},
|
||||
{'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 0xE0CC, 'SymEnd': 0xE0D4, 'SrcStart': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_POWERLINE},
|
||||
{'Enabled': self.args.pomicons, 'Name': "Pomicons", 'Filename': "Pomicons.otf", 'Exact': True, 'SymStart': 0xE000, 'SymEnd': 0xE00A, 'SrcStart': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT},
|
||||
{'Enabled': self.args.fontawesome, 'Name': "Font Awesome", 'Filename': "font-awesome/FontAwesome.otf", 'Exact': True, 'SymStart': 0xF000, 'SymEnd': 0xF2E0, 'SrcStart': None, 'ScaleGlyph': FONTA_SCALE_LIST, 'Attributes': SYM_ATTR_FONTA},
|
||||
{'Enabled': self.args.fontawesomeextension, 'Name': "Font Awesome Extension", 'Filename': "font-awesome-extension.ttf", 'Exact': False, 'SymStart': 0xE000, 'SymEnd': 0xE0A9, 'SrcStart': 0xE200, 'ScaleGlyph': 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, 'ScaleGlyph': 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, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT}, # Heavy Circle (aka Power Off)
|
||||
{'Enabled': self.args.material, 'Name': "Material", 'Filename': "materialdesignicons-webfont.ttf", 'Exact': False, 'SymStart': 0xF001, 'SymEnd': 0xF847, 'SrcStart': 0xF500, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT},
|
||||
{'Enabled': self.args.weather, 'Name': "Weather Icons", 'Filename': "weather-icons/weathericons-regular-webfont.ttf", 'Exact': False, 'SymStart': 0xF000, 'SymEnd': 0xF0EB, 'SrcStart': 0xE300, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT},
|
||||
{'Enabled': self.args.fontlogos, 'Name': "Font Logos", 'Filename': "font-logos.ttf", 'Exact': True, 'SymStart': 0xF300, 'SymEnd': 0xF32F, 'SrcStart': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT},
|
||||
{'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons.ttf", 'Exact': False, 'SymStart': 0xF000, 'SymEnd': 0xF105, 'SrcStart': 0xF400, 'ScaleGlyph': 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, 'ScaleGlyph': 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, 'ScaleGlyph': 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, 'ScaleGlyph': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Desktop
|
||||
{'Enabled': self.args.codicons, 'Name': "Codicons", 'Filename': "codicons/codicon.ttf", 'Exact': True, 'SymStart': 0xEA60, 'SymEnd': 0xEBEB, 'SrcStart': None, 'ScaleGlyph': None, 'Attributes': SYM_ATTR_DEFAULT},
|
||||
{'Enabled': self.args.custom, 'Name': "Custom", 'Filename': self.args.custom, 'Exact': True, 'SymStart': 0x0000, 'SymEnd': 0x0000, 'SrcStart': None, 'ScaleGlyph': None, 'Attributes': CUSTOM_ATTR}
|
||||
]
|
||||
|
||||
def setup_line_dimensions(self):
|
||||
|
@ -846,11 +814,6 @@ class font_patcher:
|
|||
self.sourceFont.hhea_ascent = self.sourceFont.os2_winascent
|
||||
self.sourceFont.hhea_descent = -self.sourceFont.os2_windescent
|
||||
|
||||
# Line gap add extra space on the bottom of the line which
|
||||
# doesn't allow the powerline glyphs to fill the entire line.
|
||||
self.sourceFont.hhea_linegap = 0
|
||||
self.sourceFont.os2_typolinegap = 0
|
||||
|
||||
def get_essential_references(self):
|
||||
"""Find glyphs that are needed for the basic glyphs"""
|
||||
# Sometimes basic glyphs are constructed from multiple other glyphs.
|
||||
|
@ -877,6 +840,38 @@ class font_patcher:
|
|||
self.font_dim['ymin'] = self.sourceFont.os2_typodescent
|
||||
self.font_dim['ymax'] = self.sourceFont.os2_typoascent
|
||||
|
||||
# Calculate font height
|
||||
self.font_dim['height'] = -self.font_dim['ymin'] + self.font_dim['ymax']
|
||||
if self.font_dim['height'] == 0:
|
||||
# This can only happen if the input font is empty
|
||||
# Assume we are using our prepared templates
|
||||
self.font_dim = {
|
||||
'xmin' : 0,
|
||||
'ymin' : -self.sourceFont.descent,
|
||||
'xmax' : self.sourceFont.em,
|
||||
'ymax' : self.sourceFont.ascent,
|
||||
'width' : self.sourceFont.em,
|
||||
'height': self.sourceFont.descent + self.sourceFont.ascent,
|
||||
}
|
||||
|
||||
# Line gap add extra space on the bottom of the line which
|
||||
# doesn't allow the powerline glyphs to fill the entire line.
|
||||
# Put half of the gap into the 'cell', each top and bottom
|
||||
gap = max(self.sourceFont.hhea_linegap, self.sourceFont.os2_typolinegap) # TODO probably wrong
|
||||
if self.sourceFont.os2_use_typo_metrics:
|
||||
gap = self.sourceFont.os2_typolinegap
|
||||
self.sourceFont.hhea_linegap = 0
|
||||
self.sourceFont.os2_typolinegap = 0
|
||||
if gap > 0:
|
||||
gap_top = int(gap / 2)
|
||||
gap_bottom = gap - gap_top
|
||||
self.font_dim['ymin'] -= gap_bottom
|
||||
self.font_dim['ymax'] += gap_top
|
||||
self.font_dim['height'] = -self.font_dim['ymin'] + self.font_dim['ymax']
|
||||
self.sourceFont.os2_typoascent = self.sourceFont.os2_typoascent + gap_top
|
||||
self.sourceFont.os2_typodescent = self.sourceFont.os2_typodescent - gap_bottom
|
||||
# TODO Check what to do with win and hhea values
|
||||
|
||||
# Find the biggest char width
|
||||
# Ignore the y-values, os2_winXXXXX values set above are used for line height
|
||||
#
|
||||
|
@ -890,22 +885,10 @@ class font_patcher:
|
|||
continue
|
||||
if self.font_dim['width'] < self.sourceFont[glyph].width:
|
||||
self.font_dim['width'] = self.sourceFont[glyph].width
|
||||
# print("New MAXWIDTH-A {} {} {}".format(glyph, self.sourceFont[glyph].width, xmax))
|
||||
if xmax > self.font_dim['xmax']:
|
||||
self.font_dim['xmax'] = xmax
|
||||
|
||||
# Calculate font height
|
||||
self.font_dim['height'] = abs(self.font_dim['ymin']) + self.font_dim['ymax']
|
||||
if self.font_dim['height'] == 0:
|
||||
# This can only happen if the input font is empty
|
||||
# Assume we are using our prepared templates
|
||||
self.font_dim = {
|
||||
'xmin' : 0,
|
||||
'ymin' : -self.sourceFont.descent,
|
||||
'xmax' : self.sourceFont.em,
|
||||
'ymax' : self.sourceFont.ascent,
|
||||
'width' : self.sourceFont.em,
|
||||
'height': abs(self.sourceFont.descent) + self.sourceFont.ascent,
|
||||
}
|
||||
# print("New MAXWIDTH-B {} {} {}".format(glyph, self.sourceFont[glyph].width, xmax))
|
||||
|
||||
|
||||
def get_scale_factor(self, sym_dim):
|
||||
|
@ -921,19 +904,16 @@ class font_patcher:
|
|||
return scale_ratio
|
||||
|
||||
|
||||
def copy_glyphs(self, sourceFontStart, sourceFontEnd, symbolFont, symbolFontStart, symbolFontEnd, exactEncoding, scaleGlyph, setName, attributes):
|
||||
def copy_glyphs(self, sourceFontStart, symbolFont, symbolFontStart, symbolFontEnd, exactEncoding, scaleGlyph, setName, attributes):
|
||||
""" Copies symbol glyphs into self.sourceFont """
|
||||
progressText = ''
|
||||
careful = False
|
||||
glyphSetLength = 0
|
||||
sourceFontCounter = 0
|
||||
|
||||
if self.args.careful:
|
||||
careful = True
|
||||
|
||||
if exactEncoding is False:
|
||||
sourceFontList = list(range(sourceFontStart, sourceFontEnd + 1))
|
||||
sourceFontCounter = 0
|
||||
|
||||
# Create glyphs from symbol font
|
||||
#
|
||||
# If we are going to copy all Glyphs, then assume we want to be careful
|
||||
|
@ -974,8 +954,8 @@ class font_patcher:
|
|||
possible_codes += [ v for v, s, r in sym_glyph.altuni if v > currentSourceFontGlyph ]
|
||||
currentSourceFontGlyph = min(possible_codes)
|
||||
else:
|
||||
# use source font defined hex values based on passed in start and end
|
||||
currentSourceFontGlyph = sourceFontList[sourceFontCounter]
|
||||
# use source font defined hex values based on passed in start (fills gaps; symbols are packed)
|
||||
currentSourceFontGlyph = sourceFontStart + sourceFontCounter
|
||||
sourceFontCounter += 1
|
||||
|
||||
if self.args.quiet is False:
|
||||
|
@ -1292,19 +1272,153 @@ def check_fontforge_min_version():
|
|||
sys.stderr.write("{}: Please use at least version: {}\n".format(projectName, minimumVersion))
|
||||
sys.exit(1)
|
||||
|
||||
def setup_arguments():
|
||||
parser = argparse.ArgumentParser(
|
||||
description=(
|
||||
'Nerd Fonts Font Patcher: patches a given font with programming and development related glyphs\n\n'
|
||||
'* Website: https://www.nerdfonts.com\n'
|
||||
'* Version: ' + version + '\n'
|
||||
'* Development Website: https://github.com/ryanoasis/nerd-fonts\n'
|
||||
'* Changelog: https://github.com/ryanoasis/nerd-fonts/blob/master/changelog.md'),
|
||||
formatter_class=RawTextHelpFormatter
|
||||
)
|
||||
|
||||
# optional arguments
|
||||
parser.add_argument('font', help='The path to the font to patch (e.g., Inconsolata.otf)')
|
||||
parser.add_argument('-v', '--version', action='version', version=projectName + ": %(prog)s (" + version + ")")
|
||||
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')
|
||||
parser.add_argument('--postprocess', dest='postprocess', default=False, type=str, nargs='?', help='Specify a Script for Post Processing')
|
||||
parser.add_argument('--configfile', dest='configfile', default=False, type=str, nargs='?', help='Specify a file path for JSON configuration file (see sample: src/config.sample.json)')
|
||||
parser.add_argument('--custom', dest='custom', default=False, type=str, nargs='?', help='Specify a custom symbol font. All new glyphs will be copied, with no scaling applied.')
|
||||
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('--variable-width-glyphs', dest='nonmono', default=False, action='store_true', help='Do not adjust advance width (no "overhang")')
|
||||
|
||||
# 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('--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')
|
||||
|
||||
# symbol fonts to include arguments
|
||||
sym_font_group = parser.add_argument_group('Symbol Fonts')
|
||||
sym_font_group.add_argument('--fontawesome', dest='fontawesome', default=False, action='store_true', help='Add Font Awesome Glyphs (http://fontawesome.io/)')
|
||||
sym_font_group.add_argument('--fontawesomeextension', dest='fontawesomeextension', default=False, action='store_true', help='Add Font Awesome Extension Glyphs (https://andrelzgava.github.io/font-awesome-extension/)')
|
||||
sym_font_group.add_argument('--fontlogos', '--fontlinux', dest='fontlogos', default=False, action='store_true', help='Add Font Logos Glyphs (https://github.com/Lukas-W/font-logos)')
|
||||
sym_font_group.add_argument('--octicons', dest='octicons', default=False, action='store_true', help='Add Octicons Glyphs (https://octicons.github.com)')
|
||||
sym_font_group.add_argument('--codicons', dest='codicons', default=False, action='store_true', help='Add Codicons Glyphs (https://github.com/microsoft/vscode-codicons)')
|
||||
sym_font_group.add_argument('--powersymbols', dest='powersymbols', default=False, action='store_true', help='Add IEC Power Symbols (https://unicodepowersymbol.com/)')
|
||||
sym_font_group.add_argument('--pomicons', dest='pomicons', default=False, action='store_true', help='Add Pomicon Glyphs (https://github.com/gabrielelana/pomicons)')
|
||||
sym_font_group.add_argument('--powerline', dest='powerline', default=False, action='store_true', help='Add Powerline Glyphs')
|
||||
sym_font_group.add_argument('--powerlineextra', dest='powerlineextra', default=False, action='store_true', help='Add Powerline Glyphs (https://github.com/ryanoasis/powerline-extra-symbols)')
|
||||
sym_font_group.add_argument('--material', '--materialdesignicons', '--mdi', dest='material', default=False, action='store_true', help='Add Material Design Icons (https://github.com/templarian/MaterialDesign)')
|
||||
sym_font_group.add_argument('--weather', '--weathericons', dest='weather', default=False, action='store_true', help='Add Weather Icons (https://github.com/erikflowers/weather-icons)')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.makegroups and not FontnameParserOK:
|
||||
sys.exit(projectName + ": FontnameParser module missing (bin/scripts/name_parser/Fontname*), can not --makegroups".format(projectName))
|
||||
|
||||
# if you add a new font, set it to True here inside the if condition
|
||||
if args.complete:
|
||||
args.fontawesome = True
|
||||
args.fontawesomeextension = True
|
||||
args.fontlogos = True
|
||||
args.octicons = True
|
||||
args.codicons = True
|
||||
args.powersymbols = True
|
||||
args.pomicons = True
|
||||
args.powerline = True
|
||||
args.powerlineextra = True
|
||||
args.material = True
|
||||
args.weather = True
|
||||
|
||||
if not args.complete:
|
||||
sym_font_args = []
|
||||
# add the list of arguments for each symbol font to the list sym_font_args
|
||||
for action in sym_font_group._group_actions:
|
||||
sym_font_args.append(action.__dict__['option_strings'])
|
||||
|
||||
# determine whether or not all symbol fonts are to be used
|
||||
font_complete = True
|
||||
for sym_font_arg_aliases in sym_font_args:
|
||||
found = False
|
||||
for alias in sym_font_arg_aliases:
|
||||
if alias in sys.argv:
|
||||
found = True
|
||||
if found is not True:
|
||||
font_complete = False
|
||||
args.complete = font_complete
|
||||
|
||||
if args.alsowindows:
|
||||
args.windows = False
|
||||
|
||||
if args.nonmono and args.single:
|
||||
print("Warniung: 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))
|
||||
if not os.access(args.font, os.R_OK):
|
||||
sys.exit("{}: Can not open font file for reading: {}".format(projectName, args.font))
|
||||
is_ttc = len(fontforge.fontsInFile(args.font)) > 1
|
||||
|
||||
if args.extension == "":
|
||||
args.extension = os.path.splitext(args.font)[1]
|
||||
else:
|
||||
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")
|
||||
else:
|
||||
if is_ttc:
|
||||
sys.exit(projectName + ": Can not create single font files from True Type Collections")
|
||||
|
||||
return args
|
||||
|
||||
|
||||
def main():
|
||||
print("{} Patcher v{} ({}) executing".format(projectName, version, script_version))
|
||||
check_fontforge_min_version()
|
||||
patcher = font_patcher()
|
||||
patcher.patch()
|
||||
args = setup_arguments()
|
||||
patcher = font_patcher(args)
|
||||
|
||||
sourceFonts = []
|
||||
all_fonts = fontforge.fontsInFile(args.font)
|
||||
for i, subfont in enumerate(all_fonts):
|
||||
print("\n{}: Processing {} ({}/{})".format(projectName, 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))
|
||||
|
||||
patcher.patch(sourceFonts[-1])
|
||||
|
||||
print("\nDone with Patch Sets, generating font...\n")
|
||||
patcher.setup_font_names()
|
||||
patcher.generate()
|
||||
for f in sourceFonts:
|
||||
patcher.setup_font_names(f)
|
||||
patcher.generate(sourceFonts)
|
||||
|
||||
# This mainly helps to improve CI runtime
|
||||
if patcher.args.alsowindows:
|
||||
patcher.args.windows = True
|
||||
patcher.setup_font_names()
|
||||
patcher.generate()
|
||||
for f in sourceFonts:
|
||||
patcher.setup_font_names(f)
|
||||
patcher.generate(sourceFonts)
|
||||
|
||||
for f in sourceFonts:
|
||||
f.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
Binary file not shown.
Loading…
Reference in a new issue