Skip to content

Make proportional font outputs work (when the MONO axis is set to <1) #25

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ You can also follow the instructions below. I have tried to make them pretty gra

In a terminal, use `cd` to get to a folder you want this project in. Then, clone the repo and move into it:

```
```sh
git clone https://github.com/arrowtype/recursive-code-config.git
cd recursive-code-config
```
Expand All @@ -58,7 +58,7 @@ cd recursive-code-config

Then, set up the venv and install requirements:

```bash
```sh
python3 -m venv venv # make a virtual environment called "venv"
source venv/bin/activate # activate the virtual environment
pip install -r requirements.txt # install dependencies
Expand All @@ -68,7 +68,7 @@ pip install -r requirements.txt # install dependencies

Setting up the venv and install requirements is slightly different in Windows, in my testing. Navigate to the project in a terminal, and then use the following commands:

```bash
```sh
py -m venv venv # make a virtual environment called "venv"
venv\Scripts\activate # activate the virtual environment
pip install -r requirements.txt # install dependencies
Expand All @@ -78,7 +78,7 @@ pip install -r requirements.txt # install dependencies

This file uses YAML. Hopefully, it is fairly self-explanatory. If not, file an issue and someone will hopefully help out!

First, specify the family name you want (e.g. `Rec Mono Custom`).
First, specify the family name you want (e.g. `Rec Code Custom`).

Then, specify axis values you want for Regular, Italic, Bold, & Bold Italic fonts.

Expand Down Expand Up @@ -121,7 +121,7 @@ source venv/bin/activate # activate the virtual environment if you haven
python3 scripts/instantiate-code-fonts.py
```

It will build & output fonts to a folder like `RecMono-Custom` (this is affected by whatever custom name you give fonts in config.yaml).
It will build & output fonts to a folder like `RecCode-Custom` (this is affected by whatever custom name you give fonts in config.yaml).

#### 3. Install the fonts and activate the ligatures!

Expand Down
10 changes: 5 additions & 5 deletions config.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Configure your own custom Rec Mono for Code font
# Configure your own custom Rec Code for Code font

#
# /$$$$$$ /$$
Expand All @@ -16,11 +16,11 @@
# -----------------------------------------------------------------------------
# Family Name

# The name you want after "Rec Mono" in your custom fonts.
# Example: "Custom" will yield the family "Rec Mono Custom".
# The name you want after "Rec Code" in your custom fonts.
# Example: "Custom" will yield the family "Rec Code Custom".
# Keep under 13 characters in length to avoid potential OS bugs.

Family Name: Custom
Family Name Suffix: Custom

# -----------------------------------------------------------------------------
# Font Styles
Expand Down Expand Up @@ -71,7 +71,7 @@ Fonts:
Code Ligatures: True

# Include font features to freeze in stylistic options. Copy them below to use.
# See README for details.
# See the project README for details.

# These options only have an affect at CRSV<=0.5 (Roman/normal styles)
# ss01 # Single-story a
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
6 changes: 3 additions & 3 deletions premade-configs/casual.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Configuration for Rec Mono Casual
# Configuration for Rec Code Casual
# Here as an example, or to tweak & use if you wish to
# See README for details.
# See the project README and top-level config.yaml for details.

Family Name: Casual
Family Name Suffix: Casual

Fonts:
Regular:
Expand Down
6 changes: 3 additions & 3 deletions premade-configs/duotone.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Configuration for Rec Mono Duotone
# Configuration for Rec Code Duotone
# Here as an example, or to tweak & use if you wish to
# See README for details.
# See the project README and top-level config.yaml for details.

Family Name: Duotone
Family Name Suffix: Duotone

Fonts:
Regular:
Expand Down
6 changes: 3 additions & 3 deletions premade-configs/linear.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Configuration for Rec Mono Linear
# Configuration for Rec Code Linear
# Here as an example, or to tweak & use if you wish to
# See README for details.
# See the project README and top-level config.yaml for details.

Family Name: Linear
Family Name Suffix: Linear

Fonts:
Regular:
Expand Down
40 changes: 40 additions & 0 deletions premade-configs/sans-duo.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Configuration for Rec Code Sans Duo
# Here as an example, or to tweak & use if you wish to
# See the project README and top-level config.yaml for details.

Family Name Suffix: Sans Duo

Fonts:
Regular:
MONO: 0
CASL: 0
wght: 400
slnt: 0
CRSV: 0
Italic:
MONO: 0
CASL: 1
wght: 400
slnt: -10
CRSV: 1
Bold:
MONO: 0
CASL: 0
wght: 750
slnt: 0
CRSV: 0
Bold Italic:
MONO: 0
CASL: 1
wght: 750
slnt: -10
CRSV: 1

Code Ligatures: True

Features:
- ss03 # Simplified f
- ss05 # Simplified l
- ss08 # Serifless L and Z
- ss09 # Simplified 6 and 9
- ss12 # Simplified @
6 changes: 3 additions & 3 deletions premade-configs/semicasual.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Configuration for Rec Mono SemiCasual
# Configuration for Rec Code SemiCasual
# Here as an example, or to tweak & use if you wish to
# See README for details.
# See the project README and top-level config.yaml for details.

Family Name: Semicasual
Family Name Suffix: Semicasual

Fonts:
Regular:
Expand Down
119 changes: 87 additions & 32 deletions scripts/dlig2calt.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""
A script to change dlig features to calt features,
to make code ligatures on by default in Rec Mono for Code.
to make code ligatures on by default in Rec Code for Code.
"""

from fontTools import ttLib
Expand All @@ -9,68 +9,123 @@
from fontTools.pens.ttGlyphPen import TTGlyphPen
from argparse import ArgumentParser
import pathops
import subprocess
import os

def simpleDlig2calt(fontPath, inplace=False):
"""
A simple way to change a "dlig" feature to "calt."

# codeLigs = {} # probably not needed
Assumes the fontPath hasn’t already been converted to a TTX file,
as that would confuse the variables below.
"""

def dlig2calt(fontPath, inplace=False):
# convert font’s GSUB table to a TTX file
subprocess.run(["ttx", "-t", "GSUB", fontPath])

# make the path for a ttx
ttxPath = fontPath.replace(".ttf",".ttx")

# Read in the TTX file
with open(ttxPath, 'r', encoding='utf-8') as file:
ttxData = file.read()

# Replace the target string to update the dlig feature tags to calt feature tags
ttxData = ttxData.replace('"dlig"', '"calt"')

# Write the TTX file out again
with open(ttxPath, 'w', encoding='utf-8') as file:
file.write(ttxData)

# merge TTX back into font:
# ttx -m fontname.ttf fontname.ttx

# save font
if inplace:
subprocess.run(["ttx", "-f", "-m", fontPath, ttxPath])
print("\nCode ligatures are now under the calt feature and on by default.\n")
else:
newFontPath = fontPath.replace('.ttf','.calt_ligs.ttf')
subprocess.run(["ttx", "-o", newFontPath, "-m", fontPath, ttxPath])
print("Saved font with feature 'dlig' changed to 'calt' at ", newFontPath)

# clean up the temporary TTX file
os.remove(ttxPath)


def makeCodeLigsMonospace(fontPath, inplace=False):
"""
Takes code ligatures with different widths, and makes them all exactly one monospace character wide.

The extra width goes over the left bound, and the space is made up for by a newly-created "LIG" glyph.
"""

font = ttLib.TTFont(fontPath)

unitWidth = font['hmtx']['space'][0] # 600 for most monospace fonts w/ UPM=1000
# set unit width / tabular width ... assumes the "=" symbol will have a tabular width for any code font
# 600 for most monospace fonts w/ UPM=1000
unitWidth = font['hmtx']['equal'][0]

# make "LIG" glyph
# create the "LIG" glyph to fill in gaps for code ligatures made into single-unit-wide glyphs
font['glyf'].__setitem__('LIG', font['glyf']['space'])

font['hmtx'].__setitem__('LIG', font['hmtx']['space'])

# set /LIG glyph width to /equal width, not /space, to allow proportional fonts
font['glyf']._setCoordinates('LIG', [(0,0), (600,0), (0, 0), (0, 0)], font['hmtx'].metrics)


# update code ligature widths to be single units with left overhang
for glyphName in font.getGlyphNames():
if font['hmtx'][glyphName][0] > unitWidth:

# set width to space (e.g. 600), then offset left side to be negative
oldWidth = font['hmtx'][glyphName][0]
oldLSB = font['hmtx'][glyphName][1]
widthDiff = oldWidth - unitWidth
newLSB = oldLSB - widthDiff
font['hmtx'].__setitem__(glyphName, (unitWidth, newLSB))
# only apply this to code ligatures... leave other glyphs as-is, in case they are intentionally proportional (i.e. MONO != 1)
if ".code" in glyphName:

# Adjust coordinates in glyf table
coords = font['glyf']._getCoordinatesAndControls(glyphName, font['hmtx'].metrics)[0]
phantoms = font['glyf']._getPhantomPoints(glyphName, font['hmtx'].metrics)
# set width to equal sign (e.g. 600), then offset left side to be negative
oldWidth = font['hmtx'][glyphName][0]
oldLSB = font['hmtx'][glyphName][1]
widthDiff = oldWidth - unitWidth
newLSB = oldLSB - widthDiff
font['hmtx'].__setitem__(glyphName, (unitWidth, newLSB))

# take off last four items of coords to allow adjusted phantoms to be handled separately, then combined
coords = coords[:len(coords)-4]
# Adjust coordinates in glyf table
coords = font['glyf']._getCoordinatesAndControls(glyphName, font['hmtx'].metrics)[0]
phantoms = font['glyf']._getPhantomPoints(glyphName, font['hmtx'].metrics)

adjustedCoords = [(x-widthDiff, y) for x, y in coords]
adjustedPhantoms = [(0,0), (600,0), phantoms[-2], phantoms[-1]]
# take off last four items of coords to allow adjusted phantoms to be handled separately, then combined
coords = coords[:len(coords)-4]

newCoords = adjustedCoords+adjustedPhantoms
font['glyf']._setCoordinates(glyphName, newCoords, font['hmtx'].metrics)
adjustedCoords = [(x-widthDiff, y) for x, y in coords]
adjustedPhantoms = [(0,0), (600,0), phantoms[-2], phantoms[-1]]

newCoords = adjustedCoords+adjustedPhantoms
font['glyf']._setCoordinates(glyphName, newCoords, font['hmtx'].metrics)

# add new feature code, using calt rather than dlig
builder.addOpenTypeFeatures(font,"font-data/features/calt-generated--code_fonts_only.fea")


# save font
if inplace:
font.save(fontPath)
print("\nCode ligatures are now on by default.\n")
else:
newPath = fontPath.replace('.ttf','.calt_ligs.ttf')
font.save(newPath)
print("Saved font with feature 'dlig' changed to 'calt' at ", newPath)
fontPath = fontPath.replace('.ttf','.calt_ligs.ttf')
font.save(fontPath)
print("Saved font with feature 'dlig' changed to 'calt' at ", fontPath)


def main():
description = "Change dlig features to calt features."
parser = ArgumentParser(description=description)
parser.add_argument('font', nargs=1)
parser.add_argument('--inplace', action='store_true')
args = parser.parse_args()

dlig2calt(args.font[0], args.inplace)
description = "Change dlig features to calt features."
parser = ArgumentParser(description=description)
parser.add_argument('font', nargs=1)
parser.add_argument('--inplace', action='store_true')
parser.add_argument('--mono', '-m', action='store_true')
args = parser.parse_args()

if args.mono:
makeCodeLigsMonospace(args.font[0], args.inplace)
else:
simpleDlig2calt(args.font[0], args.inplace)


if __name__ == '__main__':
Expand Down
Loading