From 3f174fcef6b614ee58716b7ec1b2744e137069ae Mon Sep 17 00:00:00 2001 From: Rasmus Andersson Date: Mon, 26 Sep 2022 17:09:36 -0700 Subject: Remove slnt/ital VF axis This removes the slant/italic variable axis and breaks up the font in two: roman and italic. This change will allow diverging designs for italic (for example single-storey a). It also addresses the fact that most software, including web browsers, doesn't handle VFs with slnt or ital well. --- misc/dist/install-linux.txt | 4 +- misc/dist/install-mac.txt | 8 +- misc/dist/install-win.txt | 7 +- misc/dist/inter.css | 239 +++++---------------- misc/makezip2.sh | 50 ++--- misc/tools/postprocess-designspace.py | 16 +- misc/tools/postprocess-single-axis-vfs.py | 302 --------------------------- misc/tools/postprocess-vf.py | 24 +-- misc/tools/postprocess-vf2.py | 332 ++++++++++++++++++++++++++++++ 9 files changed, 436 insertions(+), 546 deletions(-) delete mode 100755 misc/tools/postprocess-single-axis-vfs.py create mode 100755 misc/tools/postprocess-vf2.py (limited to 'misc') diff --git a/misc/dist/install-linux.txt b/misc/dist/install-linux.txt index 7e83c95f8..d04e35b35 100644 --- a/misc/dist/install-linux.txt +++ b/misc/dist/install-linux.txt @@ -7,8 +7,8 @@ differently. These instructions are for the most common Linux distributions: 1. Create a folder called ".fonts" in your home directory. Example: mkdir -p ~/.fonts -2. Copy the otf files in the "Inter Desktop" folder into your .fonts directory - Example: cp "Inter Desktop"/*.otf ~/.fonts/ +2. Copy the otf files in the "Desktop" folder into your .fonts directory + Example: cp "Desktop"/*.otf ~/.fonts/ You may have to restart apps and/or your window server session. diff --git a/misc/dist/install-mac.txt b/misc/dist/install-mac.txt index 36c0517d4..f68ea93c5 100644 --- a/misc/dist/install-mac.txt +++ b/misc/dist/install-mac.txt @@ -6,13 +6,13 @@ Using Font Book: 1. Open the "Font Book" application. 2. In the main menu, select File, then Add Fonts... -3. Find the "Inter Desktop" folder, select the folder (or open the folder and +3. Find the "Desktop" folder, select the folder (or open the folder and select all the files inside the folder) and press the Open button. Using Finder: -1. Copy the "Inter Desktop" folder -2. Press cmd-shift-G in Finder and go to: ~/Library/Fonts +1. Copy the files in the "Desktop" folder +2. Press cmd-shift-G in Finder and go to: ~/Library/Fonts 3. Delete any existing "Inter" files and folders -4. Paste the "Inter Desktop" folder +4. Paste the copied files diff --git a/misc/dist/install-win.txt b/misc/dist/install-win.txt index 85c22af00..b1c757a11 100644 --- a/misc/dist/install-win.txt +++ b/misc/dist/install-win.txt @@ -1,8 +1,8 @@ Installing on Windows 10: -1. Copy the "Inter Desktop" folder in the zip file to your Desktop. -2. Open the "Inter Desktop" folder on your Desktop. +1. Copy the "Desktop" folder in the zip file to your Desktop. +2. Open the "Desktop" folder on your Desktop. 4. Select all font files. 5. Right-click the selected files and choose "Install for all users". @@ -21,5 +21,4 @@ of text. However, the hints for Inter are automatically generated and are not always a good thing. If you do prefer to use the version with hints, use the font files in the -"Inter Hinted for Windows/Desktop" folder instead of "Inter Desktop". - +"Desktop with TrueType hints" folder instead of "Desktop". diff --git a/misc/dist/inter.css b/misc/dist/inter.css index f45001012..78359f9d7 100644 --- a/misc/dist/inter.css +++ b/misc/dist/inter.css @@ -1,200 +1,61 @@ +/* Variable fonts usage: +:root { font-family: 'Inter', sans-serif; } +@supports (font-variation-settings: normal) { + :root { font-family: 'InterVar', sans-serif; font-optical-sizing: auto; } +} */ @font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 100; - font-display: swap; - src: url("Inter-Thin.woff2?v=3.19") format("woff2"), - url("Inter-Thin.woff?v=3.19") format("woff"); -} -@font-face { - font-family: 'Inter'; - font-style: italic; - font-weight: 100; - font-display: swap; - src: url("Inter-ThinItalic.woff2?v=3.19") format("woff2"), - url("Inter-ThinItalic.woff?v=3.19") format("woff"); -} - -@font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 200; - font-display: swap; - src: url("Inter-ExtraLight.woff2?v=3.19") format("woff2"), - url("Inter-ExtraLight.woff?v=3.19") format("woff"); -} -@font-face { - font-family: 'Inter'; - font-style: italic; - font-weight: 200; - font-display: swap; - src: url("Inter-ExtraLightItalic.woff2?v=3.19") format("woff2"), - url("Inter-ExtraLightItalic.woff?v=3.19") format("woff"); -} - -@font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 300; - font-display: swap; - src: url("Inter-Light.woff2?v=3.19") format("woff2"), - url("Inter-Light.woff?v=3.19") format("woff"); -} -@font-face { - font-family: 'Inter'; - font-style: italic; - font-weight: 300; - font-display: swap; - src: url("Inter-LightItalic.woff2?v=3.19") format("woff2"), - url("Inter-LightItalic.woff?v=3.19") format("woff"); -} - -@font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 400; - font-display: swap; - src: url("Inter-Regular.woff2?v=3.19") format("woff2"), - url("Inter-Regular.woff?v=3.19") format("woff"); -} -@font-face { - font-family: 'Inter'; - font-style: italic; - font-weight: 400; - font-display: swap; - src: url("Inter-Italic.woff2?v=3.19") format("woff2"), - url("Inter-Italic.woff?v=3.19") format("woff"); -} - -@font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 500; - font-display: swap; - src: url("Inter-Medium.woff2?v=3.19") format("woff2"), - url("Inter-Medium.woff?v=3.19") format("woff"); -} -@font-face { - font-family: 'Inter'; - font-style: italic; - font-weight: 500; - font-display: swap; - src: url("Inter-MediumItalic.woff2?v=3.19") format("woff2"), - url("Inter-MediumItalic.woff?v=3.19") format("woff"); -} - -@font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 600; - font-display: swap; - src: url("Inter-SemiBold.woff2?v=3.19") format("woff2"), - url("Inter-SemiBold.woff?v=3.19") format("woff"); -} -@font-face { - font-family: 'Inter'; - font-style: italic; - font-weight: 600; - font-display: swap; - src: url("Inter-SemiBoldItalic.woff2?v=3.19") format("woff2"), - url("Inter-SemiBoldItalic.woff?v=3.19") format("woff"); -} - -@font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 700; - font-display: swap; - src: url("Inter-Bold.woff2?v=3.19") format("woff2"), - url("Inter-Bold.woff?v=3.19") format("woff"); -} -@font-face { - font-family: 'Inter'; - font-style: italic; - font-weight: 700; - font-display: swap; - src: url("Inter-BoldItalic.woff2?v=3.19") format("woff2"), - url("Inter-BoldItalic.woff?v=3.19") format("woff"); -} - -@font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 800; - font-display: swap; - src: url("Inter-ExtraBold.woff2?v=3.19") format("woff2"), - url("Inter-ExtraBold.woff?v=3.19") format("woff"); -} -@font-face { - font-family: 'Inter'; - font-style: italic; - font-weight: 800; - font-display: swap; - src: url("Inter-ExtraBoldItalic.woff2?v=3.19") format("woff2"), - url("Inter-ExtraBoldItalic.woff?v=3.19") format("woff"); -} - -@font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 900; - font-display: swap; - src: url("Inter-Black.woff2?v=3.19") format("woff2"), - url("Inter-Black.woff?v=3.19") format("woff"); -} -@font-face { - font-family: 'Inter'; - font-style: italic; - font-weight: 900; - font-display: swap; - src: url("Inter-BlackItalic.woff2?v=3.19") format("woff2"), - url("Inter-BlackItalic.woff?v=3.19") format("woff"); -} - -/* ------------------------------------------------------- -Variable font. -Usage: - - html { font-family: 'Inter', sans-serif; } - @supports (font-variation-settings: normal) { - html { font-family: 'Inter var', sans-serif; } - } -*/ -@font-face { - font-family: 'Inter var'; + font-family: 'InterVar'; font-weight: 100 900; font-display: swap; font-style: normal; font-named-instance: 'Regular'; - src: url("Inter-roman.var.woff2?v=3.19") format("woff2"); + src: url("Inter.var.woff2?v=3.19") format("woff2-variations"), + url("Inter.var.woff2?v=3.19") format("woff2"); } @font-face { - font-family: 'Inter var'; + font-family: 'InterVar'; font-weight: 100 900; font-display: swap; font-style: italic; font-named-instance: 'Italic'; - src: url("Inter-italic.var.woff2?v=3.19") format("woff2"); -} - - -/* -------------------------------------------------------------------------- -[EXPERIMENTAL] Multi-axis, single variable font. - -Slant axis is not yet widely supported (as of February 2019) and thus this -multi-axis single variable font is opt-in rather than the default. - -When using this, you will probably need to set font-variation-settings -explicitly, e.g. - - * { font-variation-settings: "slnt" 0deg } - .italic { font-variation-settings: "slnt" 10deg } - -*/ -@font-face { - font-family: 'Inter var experimental'; - font-weight: 100 900; - font-display: swap; - font-style: oblique 0deg 10deg; - src: url("Inter.var.woff2?v=3.19") format("woff2"); -} + src: url("Inter-italic.var.woff2?v=3.19") format("woff2-variations"), + url("Inter-italic.var.woff2?v=3.19") format("woff2"); +} +/* static fonts "Inter" */ +@font-face { font-family: 'Inter'; font-style: normal; font-weight: 100; font-display: swap; src: url("Inter-Thin.woff2?v=3.19") format("woff2"); } +@font-face { font-family: 'Inter'; font-style: italic; font-weight: 100; font-display: swap; src: url("Inter-ThinItalic.woff2?v=3.19") format("woff2"); } +@font-face { font-family: 'Inter'; font-style: normal; font-weight: 200; font-display: swap; src: url("Inter-ExtraLight.woff2?v=3.19") format("woff2"); } +@font-face { font-family: 'Inter'; font-style: italic; font-weight: 200; font-display: swap; src: url("Inter-ExtraLightItalic.woff2?v=3.19") format("woff2"); } +@font-face { font-family: 'Inter'; font-style: normal; font-weight: 300; font-display: swap; src: url("Inter-Light.woff2?v=3.19") format("woff2"); } +@font-face { font-family: 'Inter'; font-style: italic; font-weight: 300; font-display: swap; src: url("Inter-LightItalic.woff2?v=3.19") format("woff2"); } +@font-face { font-family: 'Inter'; font-style: normal; font-weight: 400; font-display: swap; src: url("Inter-Regular.woff2?v=3.19") format("woff2"); } +@font-face { font-family: 'Inter'; font-style: italic; font-weight: 400; font-display: swap; src: url("Inter-Italic.woff2?v=3.19") format("woff2"); } +@font-face { font-family: 'Inter'; font-style: normal; font-weight: 500; font-display: swap; src: url("Inter-Medium.woff2?v=3.19") format("woff2"); } +@font-face { font-family: 'Inter'; font-style: italic; font-weight: 500; font-display: swap; src: url("Inter-MediumItalic.woff2?v=3.19") format("woff2"); } +@font-face { font-family: 'Inter'; font-style: normal; font-weight: 600; font-display: swap; src: url("Inter-SemiBold.woff2?v=3.19") format("woff2"); } +@font-face { font-family: 'Inter'; font-style: italic; font-weight: 600; font-display: swap; src: url("Inter-SemiBoldItalic.woff2?v=3.19") format("woff2"); } +@font-face { font-family: 'Inter'; font-style: normal; font-weight: 700; font-display: swap; src: url("Inter-Bold.woff2?v=3.19") format("woff2"); } +@font-face { font-family: 'Inter'; font-style: italic; font-weight: 700; font-display: swap; src: url("Inter-BoldItalic.woff2?v=3.19") format("woff2"); } +@font-face { font-family: 'Inter'; font-style: normal; font-weight: 800; font-display: swap; src: url("Inter-ExtraBold.woff2?v=3.19") format("woff2"); } +@font-face { font-family: 'Inter'; font-style: italic; font-weight: 800; font-display: swap; src: url("Inter-ExtraBoldItalic.woff2?v=3.19") format("woff2"); } +@font-face { font-family: 'Inter'; font-style: normal; font-weight: 900; font-display: swap; src: url("Inter-Black.woff2?v=3.19") format("woff2"); } +@font-face { font-family: 'Inter'; font-style: italic; font-weight: 900; font-display: swap; src: url("Inter-BlackItalic.woff2?v=3.19") format("woff2"); } +/* static fonts "InterDisplay" */ +@font-face { font-family: 'InterDisplay'; font-style: normal; font-weight: 100; font-display: swap; src: url("Inter-DisplayThin.woff2?v=3.19") format("woff2"); } +@font-face { font-family: 'InterDisplay'; font-style: italic; font-weight: 100; font-display: swap; src: url("Inter-DisplayThinItalic.woff2?v=3.19") format("woff2"); } +@font-face { font-family: 'InterDisplay'; font-style: normal; font-weight: 200; font-display: swap; src: url("Inter-DisplayExtraLight.woff2?v=3.19") format("woff2"); } +@font-face { font-family: 'InterDisplay'; font-style: italic; font-weight: 200; font-display: swap; src: url("Inter-DisplayExtraLightItalic.woff2?v=3.19") format("woff2"); } +@font-face { font-family: 'InterDisplay'; font-style: normal; font-weight: 300; font-display: swap; src: url("Inter-DisplayLight.woff2?v=3.19") format("woff2"); } +@font-face { font-family: 'InterDisplay'; font-style: italic; font-weight: 300; font-display: swap; src: url("Inter-DisplayLightItalic.woff2?v=3.19") format("woff2"); } +@font-face { font-family: 'InterDisplay'; font-style: normal; font-weight: 400; font-display: swap; src: url("Inter-DisplayRegular.woff2?v=3.19") format("woff2"); } +@font-face { font-family: 'InterDisplay'; font-style: italic; font-weight: 400; font-display: swap; src: url("Inter-DisplayItalic.woff2?v=3.19") format("woff2"); } +@font-face { font-family: 'InterDisplay'; font-style: normal; font-weight: 500; font-display: swap; src: url("Inter-DisplayMedium.woff2?v=3.19") format("woff2"); } +@font-face { font-family: 'InterDisplay'; font-style: italic; font-weight: 500; font-display: swap; src: url("Inter-DisplayMediumItalic.woff2?v=3.19") format("woff2"); } +@font-face { font-family: 'InterDisplay'; font-style: normal; font-weight: 600; font-display: swap; src: url("Inter-DisplaySemiBold.woff2?v=3.19") format("woff2"); } +@font-face { font-family: 'InterDisplay'; font-style: italic; font-weight: 600; font-display: swap; src: url("Inter-DisplaySemiBoldItalic.woff2?v=3.19") format("woff2"); } +@font-face { font-family: 'InterDisplay'; font-style: normal; font-weight: 700; font-display: swap; src: url("Inter-DisplayBold.woff2?v=3.19") format("woff2"); } +@font-face { font-family: 'InterDisplay'; font-style: italic; font-weight: 700; font-display: swap; src: url("Inter-DisplayBoldItalic.woff2?v=3.19") format("woff2"); } +@font-face { font-family: 'InterDisplay'; font-style: normal; font-weight: 800; font-display: swap; src: url("Inter-DisplayExtraBold.woff2?v=3.19") format("woff2"); } +@font-face { font-family: 'InterDisplay'; font-style: italic; font-weight: 800; font-display: swap; src: url("Inter-DisplayExtraBoldItalic.woff2?v=3.19") format("woff2"); } +@font-face { font-family: 'InterDisplay'; font-style: normal; font-weight: 900; font-display: swap; src: url("Inter-DisplayBlack.woff2?v=3.19") format("woff2"); } +@font-face { font-family: 'InterDisplay'; font-style: italic; font-weight: 900; font-display: swap; src: url("Inter-DisplayBlackItalic.woff2?v=3.19") format("woff2"); } diff --git a/misc/makezip2.sh b/misc/makezip2.sh index 50180aeca..2dcfda03e 100644 --- a/misc/makezip2.sh +++ b/misc/makezip2.sh @@ -55,40 +55,30 @@ 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" + "$ZIPDIR/Desktop" \ + "$ZIPDIR/Desktop with TrueType hints" \ + "$ZIPDIR/Variable" \ + "$ZIPDIR/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/" & +# Desktop +cp $FONTDIR/static/Inter-*.otf "$ZIPDIR/Desktop/" & + +# Hinted for Windows +cp "misc/dist/about hinted fonts.txt" "$ZIPDIR/Desktop with TrueType hints/" & +cp $FONTDIR/static-hinted/Inter-*.ttf "$ZIPDIR/Desktop with TrueType hints/" & + +# Variable ("Inter" and "Inter V") +cp $FONTDIR/var/Inter*.var.ttf "$ZIPDIR/Variable/" & + +# Web +cp $FONTDIR/static/Inter-*.woff* "$ZIPDIR/Web/" & +cp $FONTDIR/var/Inter.var.woff2 "$ZIPDIR/Web/" & +cp $FONTDIR/var/Inter-italic.var.woff2 "$ZIPDIR/Web/" & +cp misc/dist/inter.css "$ZIPDIR/Web/" & + # ---------------------------------------------------------------------------- # copy misc stuff diff --git a/misc/tools/postprocess-designspace.py b/misc/tools/postprocess-designspace.py index 9d5203e69..51ad5ab85 100644 --- a/misc/tools/postprocess-designspace.py +++ b/misc/tools/postprocess-designspace.py @@ -24,14 +24,23 @@ def update_version(ufo): 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): +def fix_opsz_range(designspace): + # TODO: find extremes by looking at the source for a in designspace.axes: if a.tag == "opsz": - # TODO: find maximum by looking at the source + a.minimum = 14 a.maximum = 32 break return designspace +def fix_wght_range(designspace): + for a in designspace.axes: + if a.tag == "wght": + a.minimum = 100 + a.maximum = 900 + 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"). @@ -83,7 +92,8 @@ def update_sources(designspace): def main(argv): designspace_file = argv[1] designspace = DesignSpaceDocument.fromfile(designspace_file) - designspace = fix_opsz_maximum(designspace) + designspace = fix_opsz_range(designspace) + designspace = fix_wght_range(designspace) designspace = update_sources(designspace) designspace.write(designspace_file) diff --git a/misc/tools/postprocess-single-axis-vfs.py b/misc/tools/postprocess-single-axis-vfs.py deleted file mode 100755 index 4e34a2557..000000000 --- a/misc/tools/postprocess-single-axis-vfs.py +++ /dev/null @@ -1,302 +0,0 @@ -# -# from gftools -# https://github.com/googlefonts/gftools/blob/ -# deebf9eb018856ffc7f0d939aea18606dc432c5a/bin/gftools-fix-vf-meta.py -# -""" -Fontmake can only generate a single variable font. It cannot generate a -family of variable fonts, that are related to one another. - -This script will update the nametables and STAT tables so a family -which has more than one variable font will work correctly in desktop -applications. - -It will also work on single font VF families by creating a better STAT -table. - -TODO make script work on VFs which have multiple axises. We'll need to -change the axis array format to v4 (we're using v1), -https://docs.microsoft.com/en-us/typography/opentype/spec/stat#axis-value-table-format-4 - -Atm, the script will work well for single axis fonts and families which -have a single vf for Roman and another for Italic/Condensed, both using the wght -axis (covers 95% of GF cases). -""" -import os, sys -from argparse import ArgumentParser -from fontTools.ttLib import TTFont, newTable -from fontTools.ttLib.tables import otTables - -if sys.version_info.major == 3: - unicode = str - -OS_2_WEIGHT_CLASS = { - 'Thin': 100, - 'ExtraLight': 200, - 'Light': 300, - 'Regular': 400, - '': 400, - 'Medium': 500, - 'SemiBold': 600, - 'Bold': 700, - 'ExtraBold': 800, - 'Black': 900, -} - - -def _parse_styles(stylename): - bold, italic = False, False - if 'Italic' in stylename: - italic = True - bold = False - if 'Bold' == stylename or 'Bold Italic' == stylename: - bold = True - return bold, italic - - -def set_fsselection(style, fsselection,): - bold, italic = _parse_styles(style) - - mask = 0b1100001 - fsselection = (fsselection | mask) ^ mask - - if bold: - fsselection |= 0b100000 - else: - fsselection |= 0b1000000 - if italic: - # unset Reg bit - fsselection = (fsselection | 0b1000000) ^ 0b1000000 - fsselection |= 0b1 - return fsselection - - -def set_mac_style(stylename, macstyle): - bold, italic = _parse_styles(stylename) - - mask = ~0b11 - bold_bit = 0b1 if bold else 0b0 - italic_bit = 0b10 if italic else 0b0 - - macstyle = (macstyle | mask) ^ mask - macstyle |= (bold_bit + italic_bit) - return macstyle - - -def set_weight_class(stylename): - weight = stylename.replace('Italic', '').replace(' ', '') - return OS_2_WEIGHT_CLASS[weight] - - -def fonts_are_same_family(ttfonts): - """Check fonts have the same preferred family name or family name""" - family_names = [] - for ttfont in ttfonts: - pref_family_name = ttfont['name'].getName(16, 3, 1, 1033) - family_name = ttfont['name'].getName(1, 3, 1, 1033) - name = pref_family_name if pref_family_name else family_name - family_names.append(name.toUnicode()) - if len(set(family_names)) != 1: - return False - return True - - -def fix_bits(ttfont): - """Set fsSelection, macStyle and usWeightClass to correct values. - - The values must be derived from the default style. By default, the - Regular instance's values are used""" - dflt_style = _get_vf_default_style(ttfont) - ttfont['OS/2'].fsSelection = set_fsselection( - dflt_style, ttfont['OS/2'].fsSelection - ) - ttfont['OS/2'].usWeightClass = set_weight_class(dflt_style) - ttfont['head'].macStyle = set_mac_style( - dflt_style, ttfont['head'].macStyle - ) - - -def create_stat_table(ttfont): - """Atm, Fontmake is only able to produce a basic stat table. Because of - this, we'll create a STAT using the font's fvar table.""" - stat = newTable('STAT') - stat.table = otTables.STAT() - stat.table.Version = 0x00010001 - - # # Build DesignAxisRecords from fvar - stat.table.DesignAxisRecord = otTables.AxisRecordArray() - stat.table.DesignAxisRecord.Axis = [] - - stat_axises = stat.table.DesignAxisRecord.Axis - - # TODO (M Foley) add support for fonts which have multiple - # axises e.g Barlow - if len(ttfont['fvar'].axes) > 1: - raise Exception('VFs with more than one axis are currently ' - 'not supported.') - - for idx, axis in enumerate(ttfont['fvar'].axes): - append_stat_axis(stat, axis.axisTag, axis.axisNameID) - - # Build AxisValueArrays for each namedInstance from fvar namedInstances - stat.table.AxisValueArray = otTables.AxisValueArray() - stat.table.AxisValueArray.AxisValue = [] - - for idx, instance in enumerate(ttfont['fvar'].instances): - append_stat_record(stat, 0, list(instance.coordinates.values())[0], instance.subfamilyNameID) - - # Set ElidedFallbackNameID - stat.table.ElidedFallbackNameID = 2 - ttfont['STAT'] = stat - - -def _get_vf_types(ttfonts): - styles = [] - for ttfont in ttfonts: - styles.append(_get_vf_type(ttfont)) - return styles - - -def _get_vf_type(ttfont): - if ttfont['head'].macStyle & 0b10: - return 'Italic' - return 'Roman' - - -def _get_vf_default_style(ttfont): - """Return the name record string of the default style""" - default_fvar_val = ttfont['fvar'].axes[0].defaultValue - - name_id = None - for inst in ttfont['fvar'].instances: - if inst.coordinates['wght'] == default_fvar_val: - name_id = inst.subfamilyNameID - return ttfont['name'].getName(name_id, 3, 1, 1033).toUnicode() - - -def add_other_vf_styles_to_nametable(ttfont, text_records): - """Each nametable in a font must reference every font in the family. - Since fontmake doesn't append the other families to the nametable, - we'll do this ourselves. Skip this step if these records already - exist.""" - found = set() - for name in ttfont['name'].names[:-len(text_records)-1:-1]: - found.add(name.toUnicode()) - leftover = set(text_records) - found - - if leftover: - nameid = ttfont['name'].names[-1].nameID + 1 - for record in leftover: - ttfont['name'].setName(unicode(record), nameid, 3, 1, 1033) - nameid += 1 - - -def get_custom_name_record(ttfont, text): - """Return a name record by text. Record ID must be greater than 255""" - for record in ttfont['name'].names[::-1]: - if record.nameID > 255: - rec_text = record.toUnicode() - if rec_text == text: - return record - return None - - -def append_stat_axis(stat, tag, namerecord_id): - """Add a STAT axis if the tag does not exist already.""" - has_tags = [] - axises = stat.table.DesignAxisRecord.Axis - for axis in axises: - has_tags.append(axis.AxisTag) - - if tag in has_tags: - raise Exception('{} has already been declared in the STAT table') - - axis_record = otTables.AxisRecord() - axis_record.AxisTag = tag - axis_record.AxisNameID = namerecord_id - axis_record.AxisOrdering = len(axises) - axises.append(axis_record) - - -def append_stat_record(stat, axis_index, value, namerecord_id, linked_value=None): - records = stat.table.AxisValueArray.AxisValue - axis_record = otTables.AxisValue() - axis_record.Format = 1 - axis_record.ValueNameID = namerecord_id - axis_record.Value = value - axis_record.AxisIndex = axis_index - - axis_record.Flags = 0 - if linked_value: - axis_record.Format = 3 - axis_record.LinkedValue = linked_value - records.append(axis_record) - - -def get_stat_axis_index(ttfont, axis_name): - axises = ttfont['STAT'].table.DesignAxisRecord.Axis - available_axises = [a.AxisTag for a in axises] - for idx, axis in enumerate(axises): - if axis.AxisTag == axis_name: - return idx - raise Exception('{} is not a valid axis. Font has [{}] axises'.format( - axis_name, available_axises) - ) - - -def set_stat_for_font_in_family(ttfont, family_styles): - """Based on examples from: - https://docs.microsoft.com/en-us/typography/opentype/spec/stat""" - font_type = _get_vf_type(ttfont) - # See example 5 - if font_type == 'Roman' and 'Italic' in family_styles: - name_record = get_custom_name_record(ttfont, 'Italic') - append_stat_axis(ttfont['STAT'], 'ital', name_record.nameID) - - name_record = get_custom_name_record(ttfont, 'Roman') - axis_idx = get_stat_axis_index(ttfont, 'ital') - append_stat_record(ttfont['STAT'], axis_idx, 0, name_record.nameID, linked_value=1.0) - - elif font_type == 'Italic' and 'Roman' in family_styles: - name_record = get_custom_name_record(ttfont, 'Italic') - append_stat_axis(ttfont['STAT'], 'ital', name_record.nameID) - - name_record = get_custom_name_record(ttfont, 'Italic') - axis_idx = get_stat_axis_index(ttfont, 'ital') - append_stat_record(ttfont['STAT'], axis_idx, 1.0, name_record.nameID) - - -def harmonize_vf_families(ttfonts): - """Make sure the fonts which are part of a vf family reference each other - in both the nametable and STAT table. For examples see: - https://docs.microsoft.com/en-us/typography/opentype/spec/stat - - """ - family_styles = _get_vf_types(ttfonts) - for ttfont in ttfonts: - add_other_vf_styles_to_nametable(ttfont, family_styles) - set_stat_for_font_in_family(ttfont, family_styles) - - -def main(): - parser = ArgumentParser() - parser.add_argument('fonts', nargs='+', - help='All fonts within a font family must be included') - args = parser.parse_args() - font_paths = args.fonts - ttfonts = [TTFont(p) for p in font_paths] - if not fonts_are_same_family(ttfonts): - raise Exception('Fonts have different family_names: [{}]'.format( - ', '.join(map(os.path.basename, font_paths)) - )) - - for ttfont in ttfonts: - fix_bits(ttfont) - create_stat_table(ttfont) - harmonize_vf_families(ttfonts) - - for path, ttfont in zip(font_paths, ttfonts): - ttfont.save(path) - -if __name__ == '__main__': - main() diff --git a/misc/tools/postprocess-vf.py b/misc/tools/postprocess-vf.py index e0fb66fc7..7e2bf785c 100644 --- a/misc/tools/postprocess-vf.py +++ b/misc/tools/postprocess-vf.py @@ -28,7 +28,7 @@ FAMILY_RELATED_IDS = set([ whitespace_re = re.compile(r'\s+') -def removeWhitespace(s): +def remove_whitespace(s): return whitespace_re.sub("", s) @@ -57,19 +57,19 @@ def get_family_name(font): def fix_fullname(font): fullName = get_family_name(font) - fullNamePs = removeWhitespace(fullName) + fullNamePs = remove_whitespace(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 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): @@ -80,7 +80,7 @@ def fix_unique_id(font, fullName): if rec.nameID == TRUETYPE_UNIQUE_ID: if newId == '': oldId = rec.toUnicode() - newId = removeWhitespace(fullName) + newId = remove_whitespace(fullName) p = oldId.find(':') if p > -1: newId += oldId[p:] @@ -105,7 +105,7 @@ def main(): fullName = fix_fullname(font) fix_unique_id(font, fullName) - clear_subfamily_name(font) + #clear_subfamily_name(font) font.save(outfile) font.close() diff --git a/misc/tools/postprocess-vf2.py b/misc/tools/postprocess-vf2.py new file mode 100755 index 000000000..4eb29f981 --- /dev/null +++ b/misc/tools/postprocess-vf2.py @@ -0,0 +1,332 @@ +#!/usr/bin/env python3 +# +# from gftools +# https://github.com/googlefonts/gftools/blob/ +# 8b53f595a08d1b3f86f86eb97e07b15b1f52f671/bin/gftools-fix-vf-meta.py +# +""" +Add a STAT table to a weight only variable font. + +This script can also add STAT tables to a variable font family which +consists of two fonts, one for Roman, the other for Italic. +Both of these fonts must also only contain a weight axis. + +For variable fonts with multiple axes, write a python script which +uses fontTools.otlLib.builder.buildStatTable e.g +https://github.com/googlefonts/literata/blob/main/sources/gen_stat.py + +The generated STAT tables use format 2 Axis Values. These are needed in +order for Indesign to work. + +Special mention to Thomas Linard for reviewing the output of this script. + + +Usage: + +Single family: +gftools fix-vf-meta FontFamily[wght].ttf + +Roman + Italic family: +gftools fix-vf-meta FontFamily[wght].ttf FontFamily-Italic[wght].ttf +""" +from fontTools.otlLib.builder import buildStatTable +from fontTools.ttLib import TTFont +# from gftools.utils import font_is_italic +import argparse, re + + +whitespace_re = re.compile(r'\s+') + + +def remove_whitespace(s): + return whitespace_re.sub("", s) + + +def font_is_italic(ttfont): + if ttfont['head'].macStyle & 0b10: + return True + return False + # # Check if the font has the word "Italic" in its stylename + # stylename = ttfont["name"].getName(2, 3, 1, 0x409).toUnicode() + # return True if "Italic" in stylename else False + + +def font_has_mac_names(ttfont): + for record in ttfont['name'].names: + if record.platformID == 1: + return True + return False + + +def build_stat(roman_font, italic_font=None): + roman_wght_axis = dict( + tag="wght", + name="Weight", + values=build_wght_axis_values(roman_font), + ) + roman_opsz_axis = dict( + tag="opsz", + name="Optical size", + values=build_opsz_axis_values(roman_font), + ) + roman_axes = [roman_wght_axis, roman_opsz_axis] + + if italic_font: + # We need to create a new Italic axis in the Roman font + roman_axes.append(dict( + tag="ital", + name="Italic", + values=[ + dict( + name="Roman", + flags=2, + value=0.0, + linkedValue=1.0, + ) + ] + )) + italic_wght_axis = dict( + tag="wght", + name="Weight", + values=build_wght_axis_values(italic_font), + ) + italic_opsz_axis = dict( + tag="opsz", + name="Optical size", + values=build_opsz_axis_values(italic_font), + ) + italic_ital_axis = dict( + tag="ital", + name="Italic", + values=[ + dict( + name="Italic", + value=1.0, + ) + ] + ) + italic_axes = [italic_wght_axis, italic_opsz_axis, italic_ital_axis] + #print("buildStatTable(italic_font)", italic_axes) + buildStatTable(italic_font, italic_axes) + #print("buildStatTable(roman_font)", roman_axes) + buildStatTable(roman_font, roman_axes) + + +def build_stat_v2(roman_font, italic_font=None): + roman_wght_axis = dict( + tag="wght", + name="Weight", + ) + roman_opsz_axis = dict( + tag="opsz", + name="Optical size", + ) + roman_axes = [roman_wght_axis, roman_opsz_axis] + locations = [ + dict(name='Regular', location=dict(wght=400, opsz=14)), + dict(name='Regular Display', location=dict(wght=400, opsz=32)), + dict(name='Bold', location=dict(wght=700, opsz=14)), + dict(name='Bold Display', location=dict(wght=700, opsz=32)), + ] + buildStatTable(roman_font, roman_axes, locations) + + +def build_opsz_axis_values(ttfont): + nametable = ttfont['name'] + instances = ttfont['fvar'].instances + + val_min = 0.0 + val_max = 0.0 + for instance in instances: + opsz_val = instance.coordinates["opsz"] + if val_min == 0.0 or opsz_val < val_min: + val_min = opsz_val + if val_max == 0.0 or opsz_val > val_max: + val_max = opsz_val + + return [ + { + "name": "Regular", + "value": val_min, + "linkedValue": val_max, + "flags": 2, + }, + { + "name": "Display", + "value": val_max, + }, + ] + + # results = [] + # for instance in instances: + # opsz_val = instance.coordinates["opsz"] + # name = nametable.getName(instance.subfamilyNameID, 3, 1, 1033).toUnicode() + # name = name.replace("Italic", "").strip() + # if name == "": + # name = "Regular" + # inst = { + # "name": name, + # "value": opsz_val, + # } + # if int(opsz_val) == val_min: + # inst["flags"] = 0 + # inst["linkedValue"] = val_max + # else: + # inst["linkedValue"] = val_min + # results.append(inst) + + # return results + + +def build_wght_axis_values(ttfont): + results = [] + nametable = ttfont['name'] + instances = ttfont['fvar'].instances + has_bold = any([True for i in instances if i.coordinates['wght'] == 700]) + for instance in instances: + wght_val = instance.coordinates["wght"] + name = nametable.getName(instance.subfamilyNameID, 3, 1, 1033).toUnicode() + #print(nametable.getName(instance.postscriptNameID, 3, 1, 1033).toUnicode()) + name = name.replace("Italic", "").strip() + if name == "": + name = "Regular" + inst = { + "name": name, + "nominalValue": wght_val, + } + if inst["nominalValue"] == 400: + inst["flags"] = 0x2 + results.append(inst) + + # Dynamically generate rangeMinValues and rangeMaxValues + entries = [results[0]["nominalValue"]] + \ + [i["nominalValue"] for i in results] + \ + [results[-1]["nominalValue"]] + for i, entry in enumerate(results): + entry["rangeMinValue"] = (entries[i] + entries[i+1]) / 2 + entry["rangeMaxValue"] = (entries[i+1] + entries[i+2]) / 2 + + # Format 2 doesn't support linkedValues so we have to append another + # Axis Value (format 3) for Reg which does support linkedValues + if has_bold: + inst = { + "name": "Regular", + "value": 400, + "flags": 0x2, + "linkedValue": 700 + } + results.append(inst) + return results + + +def update_nametable(ttfont): + """ + - Add nameID 25 + - Update fvar instance names and add fvar instance postscript names + """ + is_italic = font_is_italic(ttfont) + has_mac_names = font_has_mac_names(ttfont) + + # Add nameID 25 + # https://docs.microsoft.com/en-us/typography/opentype/spec/name#name-ids + vf_ps_name = _add_nameid_25(ttfont, is_italic, has_mac_names) + + nametable = ttfont['name'] + instances = ttfont["fvar"].instances + + print("%d instances of %s:" % (len(instances), "Italic" if is_italic else "Roman")) + + # find opsz max + opsz_val_max = 0.0 + for instance in instances: + opsz_val = instance.coordinates["opsz"] + if opsz_val_max == 0.0 or opsz_val > opsz_val_max: + opsz_val_max = opsz_val + + # Update fvar instances + i = 0 + for inst in instances: + inst_name = nametable.getName(inst.subfamilyNameID, 3, 1, 1033).toUnicode() + print("instance %2d" % i, inst_name) + i += 1 + + # Update instance subfamilyNameID + if is_italic: + inst_name = inst_name.strip() + inst_name = inst_name.replace("Regular Italic", "Italic") + inst_name = inst_name.replace("Italic", "").strip() + inst_name = inst_name + " Italic" + ttfont['name'].setName(inst_name, inst.subfamilyNameID, 3, 1, 0x409) + if has_mac_names: + ttfont['name'].setName(inst_name, inst.subfamilyNameID, 1, 0, 0) + + # Add instance psName + ps_name = vf_ps_name + remove_whitespace(inst_name) + ps_name_id = ttfont['name'].addName(ps_name) + inst.postscriptNameID = ps_name_id + + +def _add_nameid_25(ttfont, is_italic, has_mac_names): + name = ttfont['name'].getName(16, 3, 1, 1033) or \ + ttfont['name'].getName(1, 3, 1, 1033).toUnicode() + # if is_italic: + # name = f"{name}Italic" + # else: + # name = f"{name}Roman" + # ttfont['name'].setName(name, 25, 3, 1, 1033) + if is_italic: + name = f"{name}Italic" + ttfont['name'].setName(name, 25, 3, 1, 1033) + if has_mac_names: + ttfont['name'].setName(name, 25, 1, 0, 0) + return name + + +def main(): + parser = argparse.ArgumentParser( + formatter_class=argparse.RawDescriptionHelpFormatter, + description=__doc__ + ) + parser.add_argument("fonts", nargs="+", help=( + "Paths to font files. Fonts must be part of the same family." + ) + ) + args = parser.parse_args() + paths = args.fonts + + # This monstrosity exists so we don't break the v1 api. + italic_font = None + if len(paths) > 2: + raise Exception( + "Can only add STAT tables to a max of two fonts. " + "Run gftools fix-vf-meta --help for usage instructions" + ) + elif len(paths) == 2: + if "Italic" in paths[0]: + tmp = paths[0] + paths[0] = paths[1] + paths[1] = tmp + elif "Italic" not in paths[1]: + raise Exception("No Italic font found!") + roman_font = TTFont(paths[0]) + italic_font = TTFont(paths[1]) + else: + roman_font = TTFont(paths[0]) + + update_nametable(roman_font) + if italic_font: + update_nametable(italic_font) + + build_stat(roman_font, italic_font) + + roman_font.save(paths[0] + "-fixed.ttf") + if italic_font: + italic_font.save(paths[1] + "-fixed.ttf") + + # roman_font.save(paths[0]) + # if italic_font: + # italic_font.save(paths[1]) + + +if __name__ == "__main__": + main() -- cgit v1.2.3