diff options
author | Rasmus <rasmus@notion.se> | 2022-05-26 21:20:06 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-05-26 21:20:06 +0300 |
commit | 07960766590650e516a75ce6ceba91b68a5fa551 (patch) | |
tree | f0c82cd40cb68950bf8229d14cbc850fec41e5ba /misc | |
parent | 633839ad550073f9d12e6cea7964a30523974b68 (diff) | |
download | inter-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-x | misc/fontbuild | 32 | ||||
-rw-r--r-- | misc/glyphs-scripts/generate-opsz-layers.py | 71 | ||||
-rwxr-xr-x | misc/makezip.sh | 1 | ||||
-rw-r--r-- | misc/makezip2.sh | 111 | ||||
-rw-r--r-- | misc/tools/patch-version.py | 32 | ||||
-rw-r--r-- | misc/tools/postprocess-designspace.py | 91 | ||||
-rwxr-xr-x | misc/tools/postprocess-single-axis-vfs.py (renamed from misc/tools/fix-vf-meta.py) | 1 | ||||
-rw-r--r-- | misc/tools/postprocess-vf.py | 115 | ||||
-rw-r--r-- | misc/tools/rename.py | 171 | ||||
-rw-r--r-- | misc/tools/subset-designspace.py | 53 |
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) |