summaryrefslogtreecommitdiff
path: root/misc
diff options
context:
space:
mode:
authorRasmus <rasmus@notion.se>2022-05-26 21:20:06 +0300
committerGitHub <noreply@github.com>2022-05-26 21:20:06 +0300
commit07960766590650e516a75ce6ceba91b68a5fa551 (patch)
treef0c82cd40cb68950bf8229d14cbc850fec41e5ba /misc
parent633839ad550073f9d12e6cea7964a30523974b68 (diff)
downloadinter-07960766590650e516a75ce6ceba91b68a5fa551.tar.xz
UPM 2048 and opsz axis (#462)
- UPM is adjusted to 2048 - Additional opsz VF axis (multi master) added which will eventually replace the separate Display family - New tooling that uses fontmake instead of Inter's own fontbuild toolchain. (The old toolchain is still supported, i.e. `make -f Makefile_v1.make ...`)
Diffstat (limited to 'misc')
-rwxr-xr-xmisc/fontbuild32
-rw-r--r--misc/glyphs-scripts/generate-opsz-layers.py71
-rwxr-xr-xmisc/makezip.sh1
-rw-r--r--misc/makezip2.sh111
-rw-r--r--misc/tools/patch-version.py32
-rw-r--r--misc/tools/postprocess-designspace.py91
-rwxr-xr-xmisc/tools/postprocess-single-axis-vfs.py (renamed from misc/tools/fix-vf-meta.py)1
-rw-r--r--misc/tools/postprocess-vf.py115
-rw-r--r--misc/tools/rename.py171
-rw-r--r--misc/tools/subset-designspace.py53
10 files changed, 665 insertions, 13 deletions
diff --git a/misc/fontbuild b/misc/fontbuild
index d51bb48df..68d26d4a4 100755
--- a/misc/fontbuild
+++ b/misc/fontbuild
@@ -13,6 +13,7 @@ import logging
import re
import signal
import subprocess
+import defcon
from fontTools.designspaceLib import DesignSpaceDocument
from mutatorMath.ufo.document import DesignSpaceDocumentReader
@@ -445,7 +446,7 @@ class Main(object):
source.styleName = "Thin Italic"
source.name = "thinitalic"
source.font.info.styleName = source.styleName
- else:
+ elif source.styleName:
# name "Black" => "black"
source.name = source.styleName.lower().replace(' ', '')
@@ -521,8 +522,9 @@ class Main(object):
verbose=True
)
- designspace = DesignSpaceDocument()
- designspace.read(designspace_file)
+ designspace = DesignSpaceDocument.fromfile(designspace_file)
+
+ master0_ufo = defcon.Font(designspace.sources[0].path)
# Generate UFOs for instances
instance_weight = dict()
@@ -544,29 +546,35 @@ class Main(object):
if len(instances) > 0:
fatal('unknown style(s): %s' % ', '.join(list(instances)))
- ufos = apply_instance_data(designspace_file, instance_files)
+ ufos = apply_instance_data(designspace, instance_files)
# patch ufos (list of defcon.Font instances)
italicAngleKey = 'com.schriftgestaltung.customParameter.' +\
'InstanceDescriptorAsGSInstance.italicAngle'
- for font in ufos:
+ for ufo in ufos:
+ # set metrics
+ ufo.info.ascender = master0_ufo.info.ascender
+ ufo.info.capHeight = master0_ufo.info.capHeight
+ ufo.info.descender = master0_ufo.info.descender
+ ufo.info.xHeight = master0_ufo.info.xHeight
+
# move italicAngle from lib to info
- italicAngle = font.lib.get(italicAngleKey)
+ italicAngle = ufo.lib.get(italicAngleKey)
if italicAngle != None:
italicAngle = float(italicAngle)
- del(font.lib[italicAngleKey])
- font.info.italicAngle = italicAngle
+ del(ufo.lib[italicAngleKey])
+ ufo.info.italicAngle = italicAngle
# clear anchors
if EXCLUDE_ANCHORS:
- for g in font:
+ for g in ufo:
g.clearAnchors()
# update font info
- weight = instance_weight[basename(font.path)]
- setFontInfo(font, weight)
+ weight = instance_weight[basename(ufo.path)]
+ setFontInfo(ufo, weight)
- font.save()
+ ufo.save()
def checkfont(self, fontfile, q):
diff --git a/misc/glyphs-scripts/generate-opsz-layers.py b/misc/glyphs-scripts/generate-opsz-layers.py
new file mode 100644
index 000000000..11a57a8ef
--- /dev/null
+++ b/misc/glyphs-scripts/generate-opsz-layers.py
@@ -0,0 +1,71 @@
+#MenuTitle: Generate opsz brace layers
+# -*- coding: utf-8 -*-
+import GlyphsApp
+import copy
+
+Font = Glyphs.font
+selectedLayers = Font.selectedLayers
+
+# Glyphs.font.selection - selected glyphs in "Font" tab
+# Glyphs.font.selectedFontMaster
+
+def build_axis_coordinates(g, master):
+ coordinates = {}
+ for i in range(len(g.parent.axes)):
+ axis = g.parent.axes[i]
+ if axis.axisTag == "opsz":
+ coordinates[axis.axisId] = 72
+ else:
+ coordinates[axis.axisId] = master.axes[i]
+ return coordinates
+
+
+def process_glyph(g, axes):
+ print("processing glyph %r" % g.name)
+
+ existing_opsz_layers = {}
+ for layer in g.layers:
+ # print("- %s (%r) %r" % (
+ # layer.name, layer.associatedMasterId, layer.attributes['coordinates']))
+ if layer.attributes['coordinates']:
+ existing_opsz_layers[layer.associatedMasterId] = layer
+ # print("existing_opsz_layers %r" % existing_opsz_layers)
+
+ for master in g.parent.masters:
+ if master.name.startswith("Regular"):
+ # Regular uses dedicated master for opsz
+ continue
+ layer = g.layers[master.id]
+ # print("%s" % layer.name)
+ if master.id in existing_opsz_layers:
+ print("skip layer %s with existing opsz brace layer" % layer.name)
+ continue
+
+ newLayer = layer.copy()
+ # Note: "same name" matters for designspace generation!
+ newLayer.name = "opsz"
+ newLayer.associatedMasterId = master.id
+ newLayer.attributes['coordinates'] = build_axis_coordinates(g, master)
+ g.layers.append(newLayer)
+ print("layer.guides %r" % layer.guides)
+ print("created layer %s (copy of %r)" % (newLayer.name, layer.name))
+
+
+if Glyphs.font and Glyphs.font.selectedLayers:
+ try:
+ Glyphs.font.disableUpdateInterface()
+ glyphs = set([l.parent for l in Glyphs.font.selectedLayers])
+ print(glyphs)
+ axes = {}
+ for i in range(len(Glyphs.font.axes)):
+ axis = Glyphs.font.axes[i]
+ axes[axis.axisTag] = {"index":i, "axis":axis}
+ # print("%r => %r" % (axis.axisTag, axis.axisId))
+ for g in glyphs:
+ try:
+ g.beginUndo()
+ process_glyph(g, axes)
+ finally:
+ g.endUndo()
+ finally:
+ Glyphs.font.enableUpdateInterface()
diff --git a/misc/makezip.sh b/misc/makezip.sh
index ba0ed6ed5..02757e16c 100755
--- a/misc/makezip.sh
+++ b/misc/makezip.sh
@@ -138,6 +138,7 @@ fi
# copy misc stuff
cp misc/dist/install*.txt "$ZIPDIR/"
cp LICENSE.txt "$ZIPDIR/"
+mkdir -p "$(dirname "$OUTFILE_ABS")"
# wait for processes to finish
wait
diff --git a/misc/makezip2.sh b/misc/makezip2.sh
new file mode 100644
index 000000000..50180aeca
--- /dev/null
+++ b/misc/makezip2.sh
@@ -0,0 +1,111 @@
+#!/bin/bash -e
+cd "$(dirname "$0")/.."
+
+OPT_HELP=false
+OPT_REVEAL_IN_FINDER=false
+OUTFILE=
+
+# parse args
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+ -h*|--h*)
+ OPT_HELP=true
+ shift
+ ;;
+ -reveal-in-finder)
+ OPT_REVEAL_IN_FINDER=true
+ shift
+ ;;
+ -*)
+ echo "$0: Unknown option $1" >&2
+ OPT_HELP=true
+ shift
+ ;;
+ *)
+ if [[ "$OUTFILE" != "" ]] && ! $OPT_HELP; then
+ echo "$0: Extra unexpected argument(s) after <outfile>" >&2
+ OPT_HELP=true
+ fi
+ OUTFILE=$1
+ shift
+ ;;
+ esac
+done
+if $OPT_HELP; then
+ echo "Usage: $0 [options] <outfile>"
+ echo "Options:"
+ echo " -h, -help Show help."
+ echo " -reveal-in-finder After creating the zip file, show it in Finder"
+ exit 1
+fi
+
+# tmp dir
+ZIPDIR=build/tmp/zip
+FONTDIR=build/fonts
+
+# convert relative path to absolute if needed
+OUTFILE_ABS=$OUTFILE
+if [[ "$OUTFILE_ABS" != /* ]]; then
+ OUTFILE_ABS=$PWD/$OUTFILE_ABS
+fi
+
+# cleanup any previous build
+rm -rf "$ZIPDIR"
+rm -f build/tmp/a.zip
+
+# create directories
+mkdir -p \
+ "$ZIPDIR/Inter Desktop" \
+ "$ZIPDIR/Inter Hinted for Windows/Desktop" \
+ "$ZIPDIR/Inter Hinted for Windows/Web" \
+ "$ZIPDIR/Inter Variable" \
+ "$ZIPDIR/Inter Variable/Single axis" \
+ "$ZIPDIR/Inter Web"
+
+# copy font files
+# ----------------------------------------------------------------------------
+
+# Inter Desktop
+cp $FONTDIR/static/Inter-*.otf "$ZIPDIR/Inter Desktop/" &
+cp $FONTDIR/var/Inter-V.var.ttf "$ZIPDIR/Inter Desktop/Inter-V.ttf" &
+
+# Inter Hinted for Windows
+cp "misc/dist/about hinted fonts.txt" "$ZIPDIR/Inter Hinted for Windows/" &
+cp $FONTDIR/static-hinted/Inter-*.ttf "$ZIPDIR/Inter Hinted for Windows/Desktop/" &
+cp $FONTDIR/static-hinted/Inter-*.woff* "$ZIPDIR/Inter Hinted for Windows/Web/" &
+cp misc/dist/inter.css "$ZIPDIR/Inter Hinted for Windows/Web/" &
+
+# Inter Variable
+cp $FONTDIR/var/Inter.var.ttf \
+ "$ZIPDIR/Inter Variable/Inter.ttf" &
+cp $FONTDIR/var/Inter-roman.var.ttf \
+ "$ZIPDIR/Inter Variable/Single axis/Inter-roman.ttf" &
+cp $FONTDIR/var/Inter-italic.var.ttf \
+ "$ZIPDIR/Inter Variable/Single axis/Inter-italic.ttf" &
+
+# Inter Web
+cp $FONTDIR/static/Inter-*.woff* "$ZIPDIR/Inter Web/" &
+cp $FONTDIR/var/Inter.var.woff2 "$ZIPDIR/Inter Web/" &
+cp $FONTDIR/var/Inter-roman.var.woff2 "$ZIPDIR/Inter Web/" &
+cp $FONTDIR/var/Inter-italic.var.woff2 "$ZIPDIR/Inter Web/" &
+cp misc/dist/inter.css "$ZIPDIR/Inter Web/" &
+# ----------------------------------------------------------------------------
+
+# copy misc stuff
+cp misc/dist/install*.txt "$ZIPDIR/"
+cp LICENSE.txt "$ZIPDIR/"
+mkdir -p "$(dirname "$OUTFILE_ABS")"
+
+# wait for processes to finish
+wait
+
+# zip
+pushd "$ZIPDIR" >/dev/null
+zip -q -X -r "$OUTFILE_ABS" *
+popd >/dev/null
+rm -rf "$ZIPDIR"
+
+echo "Created $OUTFILE"
+if $OPT_REVEAL_IN_FINDER && [ -f /usr/bin/open ]; then
+ /usr/bin/open --reveal "$OUTFILE"
+fi
diff --git a/misc/tools/patch-version.py b/misc/tools/patch-version.py
new file mode 100644
index 000000000..49b80ca62
--- /dev/null
+++ b/misc/tools/patch-version.py
@@ -0,0 +1,32 @@
+# Updates "?v=x" in files
+import os, sys, re
+from os.path import dirname, basename, abspath, relpath, join as pjoin
+sys.path.append(abspath(pjoin(dirname(__file__), 'tools')))
+from common import BASEDIR, getVersion
+
+version = getVersion()
+
+def updateCSSFile(filename):
+ regex = re.compile(r'(url\("[^"]+?v=)([^"]+)("\))')
+ with open(filename, 'r') as f:
+ s = f.read()
+ s = regex.sub(lambda m: '%s%s%s' % (m.group(1), version, m.group(3)), s)
+ with open(filename, 'w') as f:
+ f.write(s)
+
+
+def updateHTMLFile(filename):
+ regex = re.compile(r'((?:href|src)="[^"]+?v=)([^"]+)(")')
+ with open(filename, 'r') as f:
+ s = f.read()
+ s = regex.sub(lambda m: '%s%s%s' % (m.group(1), version, m.group(3)), s)
+ with open(filename, 'w') as f:
+ f.write(s)
+
+for fn in sys.argv[1:]:
+ if fn.endswith(".css"):
+ updateCSSFile(fn)
+ elif fn.endswith(".html"):
+ updateHTMLFile(fn)
+ else:
+ raise "Unexpected file type %r" % fn
diff --git a/misc/tools/postprocess-designspace.py b/misc/tools/postprocess-designspace.py
new file mode 100644
index 000000000..8d406fca0
--- /dev/null
+++ b/misc/tools/postprocess-designspace.py
@@ -0,0 +1,91 @@
+import sys, os, os.path, re
+import defcon
+from multiprocessing import Pool
+from fontTools.designspaceLib import DesignSpaceDocument
+from datetime import datetime
+
+sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), 'tools')))
+from common import getGitHash, getVersion
+
+def update_version(ufo):
+ version = getVersion()
+ buildtag, buildtagErrs = getGitHash()
+ now = datetime.utcnow()
+ if buildtag == "" or len(buildtagErrs) > 0:
+ buildtag = "src"
+ print("warning: getGitHash() failed: %r" % buildtagErrs, file=sys.stderr)
+ versionMajor, versionMinor = [int(num) for num in version.split(".")]
+ ufo.info.versionMajor = versionMajor
+ ufo.info.versionMinor = versionMinor
+ ufo.info.year = now.year
+ ufo.info.openTypeNameVersion = "Version %d.%03d;git-%s" % (versionMajor, versionMinor, buildtag)
+ psFamily = re.sub(r'\s', '', ufo.info.familyName)
+ psStyle = re.sub(r'\s', '', ufo.info.styleName)
+ ufo.info.openTypeNameUniqueID = "%s-%s:%d:%s" % (psFamily, psStyle, now.year, buildtag)
+ ufo.info.openTypeHeadCreated = now.strftime("%Y/%m/%d %H:%M:%S")
+
+def fix_opsz_maximum(designspace):
+ for a in designspace.axes:
+ if a.tag == "opsz":
+ # TODO: find maximum by looking at the source
+ a.maximum = 72
+ break
+ return designspace
+
+def should_decompose_glyph(g):
+ # A trivial glyph is one that does not use components or where component transformation
+ # does not include mirroring (i.e. "flipped").
+ if g.components and len(g.components) > 0:
+ for c in g.components:
+ # has non-trivial transformation? (i.e. scaled)
+ # Example of optimally trivial transformation:
+ # (1, 0, 0, 1, 0, 0) no scale or offset
+ # Example of scaled transformation matrix:
+ # (-1.0, 0, 0.3311, 1, 1464.0, 0) flipped x axis, sheered and offset
+ xScale = c.transformation[0]
+ yScale = c.transformation[3]
+ # If glyph is reflected along x or y axes, it won't slant well.
+ if xScale < 0 or yScale < 0:
+ return True
+ return False
+
+def find_glyphs_to_decompose(designspace):
+ source = designspace.sources[int(len(designspace.sources)/2)]
+ print("find_glyphs_to_decompose sourcing from %r" % source.name)
+ ufo = defcon.Font(source.path)
+ return sorted([g.name for g in ufo if should_decompose_glyph(g)])
+
+def set_ufo_filter(ufo, **filter_dict):
+ filters = ufo.lib.setdefault("com.github.googlei18n.ufo2ft.filters", [])
+ for i in range(len(filters)):
+ if filters[i].get("name") == filter_dict["name"]:
+ filters[i] = filter_dict
+ return
+ filters.append(filter_dict)
+
+def update_source_ufo(ufo_file, glyphs_to_decompose):
+ print("update %s" % os.path.basename(ufo_file))
+ ufo = defcon.Font(ufo_file)
+ update_version(ufo)
+ set_ufo_filter(ufo, name="decomposeComponents", include=glyphs_to_decompose)
+ ufo.save(ufo_file)
+
+def update_sources(designspace):
+ glyphs_to_decompose = find_glyphs_to_decompose(designspace)
+ #print("glyphs marked to be decomposed: %s" % ', '.join(glyphs_to_decompose))
+ sources = [source for source in designspace.sources]
+ # sources = [s for s in sources if s.name == "Inter Thin"] # DEBUG
+ source_files = list(set([s.path for s in sources]))
+ with Pool(len(source_files)) as p:
+ p.starmap(update_source_ufo, [(file, glyphs_to_decompose) for file in source_files])
+ return designspace
+
+def main(argv):
+ designspace_file = argv[1]
+ designspace = DesignSpaceDocument.fromfile(designspace_file)
+ designspace = fix_opsz_maximum(designspace)
+ designspace = update_sources(designspace)
+ designspace.write(designspace_file)
+
+if __name__ == '__main__':
+ main(sys.argv)
diff --git a/misc/tools/fix-vf-meta.py b/misc/tools/postprocess-single-axis-vfs.py
index 5e300d701..189c4fb3b 100755
--- a/misc/tools/fix-vf-meta.py
+++ b/misc/tools/postprocess-single-axis-vfs.py
@@ -1,4 +1,3 @@
-#!/usr/bin/env python
#
# from gftools
# https://github.com/googlefonts/gftools/blob/
diff --git a/misc/tools/postprocess-vf.py b/misc/tools/postprocess-vf.py
new file mode 100644
index 000000000..e0fb66fc7
--- /dev/null
+++ b/misc/tools/postprocess-vf.py
@@ -0,0 +1,115 @@
+import sys, os, os.path, argparse, re
+from fontTools.ttLib import TTFont
+
+# Adoptation of fonttools/blob/master/Snippets/rename-fonts.py
+
+WINDOWS_ENGLISH_IDS = 3, 1, 0x409
+MAC_ROMAN_IDS = 1, 0, 0
+
+LEGACY_FAMILY = 1
+SUBFAMILY_NAME = 2
+TRUETYPE_UNIQUE_ID = 3
+FULL_NAME = 4
+POSTSCRIPT_NAME = 6
+PREFERRED_FAMILY = 16
+TYPO_SUBFAMILY_NAME = 17
+WWS_FAMILY = 21
+
+
+FAMILY_RELATED_IDS = set([
+ LEGACY_FAMILY,
+ TRUETYPE_UNIQUE_ID,
+ FULL_NAME,
+ POSTSCRIPT_NAME,
+ PREFERRED_FAMILY,
+ WWS_FAMILY,
+])
+
+whitespace_re = re.compile(r'\s+')
+
+
+def removeWhitespace(s):
+ return whitespace_re.sub("", s)
+
+
+def set_full_name(font, fullName, fullNamePs):
+ nameTable = font["name"]
+ nameTable.setName(fullName, FULL_NAME, 1, 0, 0) # mac
+ nameTable.setName(fullName, FULL_NAME, 3, 1, 0x409) # windows
+ nameTable.setName(fullNamePs, POSTSCRIPT_NAME, 1, 0, 0) # mac
+ nameTable.setName(fullNamePs, POSTSCRIPT_NAME, 3, 1, 0x409) # windows
+
+
+def get_family_name(font):
+ nameTable = font["name"]
+ r = None
+ for plat_id, enc_id, lang_id in (WINDOWS_ENGLISH_IDS, MAC_ROMAN_IDS):
+ for name_id in (PREFERRED_FAMILY, LEGACY_FAMILY):
+ r = nameTable.getName(nameID=name_id, platformID=plat_id, platEncID=enc_id, langID=lang_id)
+ if r is not None:
+ break
+ if r is not None:
+ break
+ if not r:
+ raise ValueError("family name not found")
+ return r.toUnicode()
+
+
+def fix_fullname(font):
+ fullName = get_family_name(font)
+ fullNamePs = removeWhitespace(fullName)
+ set_full_name(font, fullName, fullNamePs)
+ return fullName
+
+
+def clear_subfamily_name(font):
+ nameTable = font["name"]
+ rmrecs = []
+ for rec in nameTable.names:
+ if rec.nameID == SUBFAMILY_NAME or rec.nameID == TYPO_SUBFAMILY_NAME:
+ rmrecs.append(rec)
+ for rec in rmrecs:
+ nameTable.removeNames(rec.nameID, rec.platformID, rec.platEncID, rec.langID)
+
+
+def fix_unique_id(font, fullName):
+ fontIdRecs = []
+ newId = ''
+ nameTable = font["name"]
+ for rec in nameTable.names:
+ if rec.nameID == TRUETYPE_UNIQUE_ID:
+ if newId == '':
+ oldId = rec.toUnicode()
+ newId = removeWhitespace(fullName)
+ p = oldId.find(':')
+ if p > -1:
+ newId += oldId[p:]
+ fontIdRecs.append(rec)
+ for rec in fontIdRecs:
+ nameTable.setName(newId, rec.nameID, rec.platformID, rec.platEncID, rec.langID)
+
+
+def main():
+ argparser = argparse.ArgumentParser(description='Fix names in variable font')
+ a = lambda *args, **kwargs: argparser.add_argument(*args, **kwargs)
+ a('-o', '--output', metavar='<file>',
+ help='Output font file. Defaults to input file (overwrite)')
+ a('input', metavar='<file>',
+ help='Input font file')
+ args = argparser.parse_args()
+
+ infile = args.input
+ outfile = args.output or infile
+
+ font = TTFont(infile, recalcBBoxes=False, recalcTimestamp=False)
+
+ fullName = fix_fullname(font)
+ fix_unique_id(font, fullName)
+ clear_subfamily_name(font)
+
+ font.save(outfile)
+ font.close()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/misc/tools/rename.py b/misc/tools/rename.py
new file mode 100644
index 000000000..6793e391c
--- /dev/null
+++ b/misc/tools/rename.py
@@ -0,0 +1,171 @@
+import sys, os, os.path, argparse, re
+from fontTools.ttLib import TTFont
+
+# Adoptation of fonttools/blob/master/Snippets/rename-fonts.py
+
+WINDOWS_ENGLISH_IDS = 3, 1, 0x409
+MAC_ROMAN_IDS = 1, 0, 0
+
+LEGACY_FAMILY = 1
+TRUETYPE_UNIQUE_ID = 3
+FULL_NAME = 4
+POSTSCRIPT_NAME = 6
+PREFERRED_FAMILY = 16
+SUBFAMILY_NAME = 17
+WWS_FAMILY = 21
+
+
+FAMILY_RELATED_IDS = set([
+ LEGACY_FAMILY,
+ TRUETYPE_UNIQUE_ID,
+ FULL_NAME,
+ POSTSCRIPT_NAME,
+ PREFERRED_FAMILY,
+ WWS_FAMILY,
+])
+
+whitespace_re = re.compile(r'\s+')
+
+
+def removeWhitespace(s):
+ return whitespace_re.sub("", s)
+
+
+def setFullName(font, fullName):
+ nameTable = font["name"]
+ nameTable.setName(fullName, FULL_NAME, 1, 0, 0) # mac
+ nameTable.setName(fullName, FULL_NAME, 3, 1, 0x409) # windows
+ nameTable.setName(fullName, POSTSCRIPT_NAME, 1, 0, 0) # mac
+ nameTable.setName(fullName, POSTSCRIPT_NAME, 3, 1, 0x409) # windows
+
+
+def getFamilyName(font):
+ nameTable = font["name"]
+ r = None
+ for plat_id, enc_id, lang_id in (WINDOWS_ENGLISH_IDS, MAC_ROMAN_IDS):
+ for name_id in (PREFERRED_FAMILY, LEGACY_FAMILY):
+ r = nameTable.getName(nameID=name_id, platformID=plat_id, platEncID=enc_id, langID=lang_id)
+ if r is not None:
+ break
+ if r is not None:
+ break
+ if not r:
+ raise ValueError("family name not found")
+ return r.toUnicode()
+
+
+def renameStylesGoogleFonts(font):
+ familyName = getFamilyName(font)
+
+ # collect subfamily (style) name IDs for variable font's named instances
+ vfInstanceSubfamilyNameIds = set()
+ if "fvar" in font:
+ for namedInstance in font["fvar"].instances:
+ vfInstanceSubfamilyNameIds.add(namedInstance.subfamilyNameID)
+
+ nameTable = font["name"]
+ for rec in nameTable.names:
+ rid = rec.nameID
+ if rid in (FULL_NAME, LEGACY_FAMILY):
+ # style part of family name
+ s = rec.toUnicode()
+ start = s.find(familyName)
+ if start != -1:
+ s = familyName + " " + removeWhitespace(s[start + len(familyName):])
+ else:
+ s = removeWhitespace(s)
+ if s != "Italic" and s.endswith("Italic"):
+ # fixup e.g. "ExtraBoldItalic" -> "ExtraBold Italic"
+ s = s[:len(s) - len("Italic")] + " Italic"
+ rec.string = s.strip()
+ if rid in (SUBFAMILY_NAME,) or rid in vfInstanceSubfamilyNameIds:
+ s = removeWhitespace(rec.toUnicode())
+ if s != "Italic" and s.endswith("Italic"):
+ # fixup e.g. "ExtraBoldItalic" -> "ExtraBold Italic"
+ s = s[:len(s) - len("Italic")] + " Italic"
+ rec.string = s.strip()
+ # else: ignore standard names unrelated to style
+
+
+def setFamilyName(font, nextFamilyName):
+ prevFamilyName = getFamilyName(font)
+ if prevFamilyName == nextFamilyName:
+ return
+ # raise Exception("identical family name")
+
+ def renameRecord(nameRecord, prevFamilyName, nextFamilyName):
+ # replaces prevFamilyName with nextFamilyName in nameRecord
+ s = nameRecord.toUnicode()
+ start = s.find(prevFamilyName)
+ if start != -1:
+ end = start + len(prevFamilyName)
+ nextFamilyName = s[:start] + nextFamilyName + s[end:]
+ nameRecord.string = nextFamilyName
+ return s, nextFamilyName
+
+ # postcript name can't contain spaces
+ psPrevFamilyName = prevFamilyName.replace(" ", "")
+ psNextFamilyName = nextFamilyName.replace(" ", "")
+ for rec in font["name"].names:
+ name_id = rec.nameID
+ if name_id not in FAMILY_RELATED_IDS:
+ # leave uninteresting records unmodified
+ continue
+ if name_id == POSTSCRIPT_NAME:
+ old, new = renameRecord(rec, psPrevFamilyName, psNextFamilyName)
+ elif name_id == TRUETYPE_UNIQUE_ID:
+ # The Truetype Unique ID rec may contain either the PostScript Name or the Full Name
+ if psPrevFamilyName in rec.toUnicode():
+ # Note: This is flawed -- a font called "Foo" renamed to "Bar Lol";
+ # if this record is not a PS record, it will incorrectly be rename "BarLol".
+ # However, in practice this is not abig deal since it's just an ID.
+ old, new = renameRecord(rec, psPrevFamilyName, psNextFamilyName)
+ else:
+ old, new = renameRecord(rec, prevFamilyName, nextFamilyName)
+ else:
+ old, new = renameRecord(rec, prevFamilyName, nextFamilyName)
+ # print(" %r: '%s' -> '%s'" % (rec, old, new))
+
+
+def main():
+ argparser = argparse.ArgumentParser(
+ description='Rename family and/or styles of font'
+ )
+ a = lambda *args, **kwargs: argparser.add_argument(*args, **kwargs)
+ a('-o', '--output', metavar='<file>',
+ help='Output font file. Defaults to input file (overwrite)')
+ a('--family', metavar='<name>',
+ help='Rename family to <name>')
+ a('--google-style', action='store_true',
+ help='Rename style names to Google Fonts standards')
+ a('input', metavar='<file>',
+ help='Input font file')
+ args = argparser.parse_args()
+
+ infile = args.input
+ outfile = args.output or infile
+
+ font = TTFont(infile, recalcBBoxes=False, recalcTimestamp=False)
+ editCount = 0
+ try:
+ if args.family:
+ editCount += 1
+ setFamilyName(font, args.family)
+
+ if args.google_style:
+ editCount += 1
+ renameStylesGoogleFonts(font)
+
+ if editCount == 0:
+ print("no rename options provided", file=sys.stderr)
+ argparser.print_help(sys.stderr)
+ sys.exit(1)
+ return
+
+ font.save(outfile)
+ finally:
+ font.close()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/misc/tools/subset-designspace.py b/misc/tools/subset-designspace.py
new file mode 100644
index 000000000..2b7c6b566
--- /dev/null
+++ b/misc/tools/subset-designspace.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python
+import sys, os
+from os.path import dirname, basename, abspath, relpath, join as pjoin
+from fontTools.designspaceLib import DesignSpaceDocument
+
+
+def subset_designspace(designspace, filename):
+ italic = filename.find('italic') != -1
+
+ rmlist = []
+ for a in designspace.axes:
+ if a.tag == "slnt" or a.tag == "ital" or a.tag == "opsz":
+ rmlist.append(a)
+ for a in rmlist:
+ designspace.axes.remove(a)
+
+ rmlist = []
+ hasDefault = not italic
+ for source in designspace.sources:
+ isitalic = source.name.find('Italic') != -1
+ if italic != isitalic or source.name.endswith('Display') or source.name.endswith('opsz'):
+ rmlist.append(source)
+ elif italic and not hasDefault:
+ source.copyLib = True
+ source.copyInfo = True
+ source.copyGroups = True
+ source.copyFeatures = True
+ hasDefault = True
+ for source in rmlist:
+ designspace.sources.remove(source)
+
+ rmlist = []
+ for instance in designspace.instances:
+ isitalic = instance.name.find('Italic') != -1
+ if italic != isitalic:
+ rmlist.append(instance)
+ for instance in rmlist:
+ designspace.instances.remove(instance)
+
+ print("write %s" % relpath(filename, os.getcwd()))
+ designspace.write(filename)
+
+
+def main(argv):
+ src_designspace_file = argv[1]
+ dst_designspace_file = argv[2]
+ designspace = DesignSpaceDocument.fromfile(src_designspace_file)
+ designspace.lib.clear()
+ subset_designspace(designspace, dst_designspace_file)
+
+
+if __name__ == '__main__':
+ main(sys.argv)