Skip to content

Commit

Permalink
Fix Pierre's issue in Asana by copying metrics
Browse files Browse the repository at this point in the history
Metrics are now copied from the monospace font into the newly created
font. This may not be the cleanest solution, but it ensures that the
monospacified font does not cause the line height to change. A manual
fix for Asana Math (the font this was originally reported for) is to
copy the Typographic OS/2 metrics into the HHEA metrics. Following
FontForge's recommendations (setting the HHEA metrics to the Win Ascent
and Descent) does not work (it makes the line height huge, which isn't
visible in Emacs 25+ (it ignores it following a bug report of a few
months ago, but it is visible and annoying in Emacs ≤ 24, and LibreOffice)
  • Loading branch information
cpitclaudel committed Dec 8, 2015
1 parent 512a077 commit 7374562
Show file tree
Hide file tree
Showing 18 changed files with 169 additions and 13 deletions.
6 changes: 4 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ CONSOLAS=/home/clement/.fonts/Microsoft/Consolas-Fixed.ttf

XITS=/home/clement/.fonts/maths/xits-math.otf
STIX=/home/clement/.fonts/maths/STIXMath-Regular.otf
ASANA=/home/clement/.fonts/maths/Asana-Math-Tr.otf
ASANA=/usr/share/fonts/truetype/asana-math/Asana-Math.otf
SYMBOLA=/usr/share/fonts/truetype/ttf-ancient-scripts/Symbola605.ttf
LATINMODERN=/home/clement/.fonts/maths/latinmodern-math.otf
TEXGYRESCHOLA=/home/clement/.fonts/maths/texgyreschola-math.otf
Expand All @@ -19,13 +19,15 @@ ttf: monospacifier.py
./monospacifier.py \
--references ./sources/references/* \
--inputs ./sources/inputs/* \
--save-to ./fonts 2>&1 # --merge
--save-to ./fonts --copy-metrics # 2>&1 # --merge

prepare:
echo "References"
rm ./sources/references/*
cp $(CONSOLAS) ./sources/references/Consolas.ttf
cp $(DEJAVU) ./sources/references/DejaVuSansMono.ttf
echo "Inputs"
rm ./sources/inputs/*
cp $(XITS) ./sources/inputs/XITSMath.otf
cp $(STIX) ./sources/inputs/STIXMath.otf
cp $(ASANA) ./sources/inputs/AsanaMath.otf
Expand Down
21 changes: 14 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
# monospacifier.py

A great way to increase the Unicode coverage of your favorite programming font.
*A great way to increase the Unicode coverage of your favorite programming font.*

![default vs monospacified](demo/symbola-loop.gif)

## Details
`monospacifier.py` adjusts every character of your favorite variable-width font to match a reference monospace font. The result is a good fallback font for characters not covered by the reference: the result is a font setup with good Unicode coverage, without breaking indentation.

`monospacifier.py` adjusts every character of your favourite variable-pitch font to match the width of a reference monospace font. The result is an good fallback font to use for characters not covered by the reference font. The final combination (original monospace font + monospacified font as fallback) has good Unicode coverage, and does not break indentation.

`monospacifier.py` includes multiple scaling algorithms. They are all pretty basic; this approach won't work well for anything but a fallback font. The most advanced one (demoed) sets the bounding box of each glyph appropriately, and slightly compresses wide characters to not spill too much from that bounding box. This preserves ratios (so ↦ and ⟼ are still distinguishable), while ensuring that each character occupies one "screen cell". Of course, two consecutive wide symbols will overlap.
![default vs monospacified](demo/symbola-loop.gif)

## Pre-monospacified fonts (monospace fonts with good Unicode coverage)

Expand Down Expand Up @@ -56,3 +52,14 @@ Please submit recipes for other editors or operating systems!
![inconsistent fallbacks](demo/original.png) ![consistent fallback](demo/symbola.png) ![monospacified fallback](demo/symbola-monospacified.png)

Monospace font + default fallbacks — Monospace font + original Symbola — Monospace font + Monospacified Symbola

## Usage

## Details

* For help, run `./monospacifier.py -h`
* For examples of use, see the Makefile (I use it to generate the files listed here)

`monospacifier.py` includes multiple scaling algorithms (only one is exposed on the CLI). They are all rather basic, so don;t expect this program to create anything except a decent fallback font.

The most advanced algorithm (demoed) sets the bounding box of each glyph appropriately (to match the most common width in the monospace font), and slightly compresses wide characters to reduce bleeding (wide glyphs will overlap with neighboring characters), while preserving distinctions between long and short glyphs (so ↦ and ⟼ are still distinguishable). Then (conditional on the `--copy-metrics` flag), `monospacifier.py` adjusts the metrics of the newly created font to match those of the reference (this fixes a number of issues that I don't understand well, in particular with `hhea_descent` and `os2_typodescent` metrics; if you have a clue about this, please do get in touch by opening an issue).
Binary file modified fonts/Asana_monospacified_for_Consolas.ttf
Binary file not shown.
Binary file modified fonts/Asana_monospacified_for_DejaVuSansMono.ttf
Binary file not shown.
Binary file modified fonts/FreeSerif_monospacified_for_Consolas.ttf
Binary file not shown.
Binary file modified fonts/FreeSerif_monospacified_for_DejaVuSansMono.ttf
Binary file not shown.
Binary file modified fonts/LatinModernMath_monospacified_for_Consolas.ttf
Binary file not shown.
Binary file modified fonts/LatinModernMath_monospacified_for_DejaVuSansMono.ttf
Binary file not shown.
Binary file modified fonts/STIXMath_monospacified_for_Consolas.ttf
Binary file not shown.
Binary file modified fonts/STIXMath_monospacified_for_DejaVuSansMono.ttf
Binary file not shown.
Binary file modified fonts/Symbola_monospacified_for_Consolas.ttf
Binary file not shown.
Binary file modified fonts/Symbola_monospacified_for_DejaVuSansMono.ttf
Binary file not shown.
Binary file modified fonts/TeXGyreScholaMath_monospacified_for_Consolas.ttf
Binary file not shown.
Binary file modified fonts/TeXGyreScholaMath_monospacified_for_DejaVuSansMono.ttf
Binary file not shown.
Binary file modified fonts/XITSMath_monospacified_for_Consolas.ttf
Binary file not shown.
Binary file modified fonts/XITSMath_monospacified_for_DejaVuSansMono.ttf
Binary file not shown.
122 changes: 122 additions & 0 deletions metrics
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# activeLayer
# bitmapSizes
# changed
# cidcopyright
# cidfamilyname
# cidfontname
# cidfullname
# cidordering
# cidregistry
# cidsubfont
# cidsubfontcnt
# cidsubfontnames
# cidsupplement
# cidversion
# cidweight
# comment
# copyright
# cvt
# default_base_filename
# design_size
# em
# encoding
# familyname
# fondname
# fontlog
# fontname
# fullname
# gasp
# gasp_version
# gpos_lookups
# gsub_lookups
# guide
# hasvmetrics
# head_optimized_for_cleartype
# horizontalBaseline
# is_cid
# is_quadratic
# iscid
# isnew
# italicangle
# layer_cnt
# layers
# loadState
# macstyle
# markClasses
# math
# maxp_FDEFs
# maxp_IDEFs
# maxp_maxStackDepth
# maxp_storageCnt
# maxp_twilightPtCnt
# maxp_zones
# multilayer
# onlybitmaps
# os2_codepages
# os2_family_class
# os2_fstype
# os2_panose
# os2_unicoderanges
# os2_use_typo_metrics
# os2_vendor
# os2_version
# os2_weight
# os2_weight_width_slope_only
# path
# persistant
# persistent
# private
# privateState
# selection
# sfd_path
# sfntRevision
# sfnt_names
# size_feature
# strokedfont
# strokewidth
# temporary
# texparameters
# uniqueid
# upos
# userdata
# uwidth
# version
# verticalBaseline
# vertical_origin
# weight
# woffMajor
# woffMetadata
# woffMinor
# xuid
ascent
capHeight
descent
hhea_ascent
hhea_ascent_add
hhea_descent
hhea_descent_add
hhea_linegap
os2_capheight
os2_strikeypos
os2_strikeysize
os2_subxoff
os2_subxsize
os2_subyoff
os2_subysize
os2_supxoff
os2_supxsize
os2_supyoff
os2_supysize
os2_typoascent
os2_typoascent_add
os2_typodescent
os2_typodescent_add
os2_typolinegap
os2_width
os2_winascent
os2_winascent_add
os2_windescent
os2_windescent_add
os2_xheight
vhea_linegap
xHeight
33 changes: 29 additions & 4 deletions monospacifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,23 @@ def scale(self, glyph):
GlyphScaler.set_width(glyph, self.cell_width)

class FontScaler(object):
# METRICS = ["ascent", "capHeight", "descent", "hhea_ascent", "hhea_ascent_add",
# "hhea_descent", "hhea_descent_add", "hhea_linegap", "os2_capheight",
# "os2_strikeypos", "os2_strikeysize", "os2_subxoff", "os2_subxsize",
# "os2_subyoff", "os2_subysize", "os2_supxoff", "os2_supxsize", "os2_supyoff",
# "os2_supysize", "os2_typoascent", "os2_typoascent_add", "os2_typodescent",
# "os2_typodescent_add", "os2_typolinegap", "os2_width", "os2_winascent",
# "os2_winascent_add", "os2_windescent", "os2_windescent_add", "os2_xheight",
# "vhea_linegap", "xHeight"]
METRICS = ["ascent", "descent", "hhea_ascent", "hhea_ascent_add",
"hhea_descent", "hhea_descent_add", "hhea_linegap", "os2_capheight",
"os2_strikeypos", "os2_strikeysize", "os2_subxoff", "os2_subxsize",
"os2_subyoff", "os2_subysize", "os2_supxoff", "os2_supxsize", "os2_supyoff",
"os2_supysize", "os2_typoascent", "os2_typoascent_add", "os2_typodescent",
"os2_typodescent_add", "os2_typolinegap", "os2_width", "os2_winascent",
"os2_winascent_add", "os2_windescent", "os2_windescent_add", "os2_xheight",
"vhea_linegap"]

def __init__(self, path):
self.font = fontforge.open(path) # Prints a few warnings
self.renamed = False
Expand Down Expand Up @@ -155,6 +172,10 @@ def scale_glyphs(self, scaler):
# counter[glyph.width] += 1
# print("> Final width distribution: {}".format(", ".join(map(str, counter.most_common(10)))))

def copy_metrics(self, reference):
for metric in FontScaler.METRICS:
setattr(self.font, metric, getattr(reference, metric))

def write(self, name):
"""
Save font to NAME.
Expand All @@ -174,7 +195,7 @@ def plot_widths(glyphs):
def fname(path):
return os.path.splitext(os.path.basename(path))[0]

def make_monospace(reference, fallback, gscaler, save_to):
def make_monospace(reference, fallback, gscaler, save_to, copy_metrics):
fontname = "{}_monospacified_for_{}".format(cleanup_font_name(fallback.fontname), cleanup_font_name(reference.fontname))
familyname = "{} monospacified for {}".format(cleanup_font_name(fallback.familyname), cleanup_font_name(reference.familyname))
fullname = "{} monospacified for {}".format(cleanup_font_name(fallback.fullname), cleanup_font_name(reference.fullname))
Expand All @@ -189,6 +210,8 @@ def make_monospace(reference, fallback, gscaler, save_to):

fscaler.font.em = reference.em # Adjust em size (number of internal units per em)
fscaler.scale_glyphs(gscaler)
if copy_metrics:
fscaler.copy_metrics(reference)
fscaler.write(destination)

return destination
Expand Down Expand Up @@ -228,9 +251,11 @@ def parse_arguments():
help="Where to save the newly generated monospace fonts. Defaults to current directory.")
parser.add_argument('--merge', action='store_const', const=True, default=False,
help="Whether to create copies of the reference font, extended with monospacified glyphs of the inputs.")
parser.add_argument('--copy-metrics', action='store_const', const=True, default=False,
help="Whether to apply the metrics of the reference font to the new font.")
return parser.parse_args()

def process_fonts(ref_paths, fnt_paths, save_to, merge):
def process_fonts(ref_paths, fnt_paths, save_to, merge, copy_metrics):
for ref in ref_paths:
reference = fontforge.open(ref)
ref_width = FontScaler.most_common_width(reference)
Expand All @@ -239,7 +264,7 @@ def process_fonts(ref_paths, fnt_paths, save_to, merge):
fallback = fontforge.open(fnt)
print(">>> - Monospacifying {}".format(fallback.familyname))
gscaler = StretchingGlyphScaler(ref_width, FontScaler.average_width(fallback))
path = make_monospace(reference, fallback, gscaler, save_to)
path = make_monospace(reference, fallback, gscaler, save_to, copy_metrics)
if merge:
monospacified = fontforge.open(path)
print(">>> - Merging with {}".format(monospacified.familyname))
Expand All @@ -250,7 +275,7 @@ def main():
args = parse_arguments()
# del args.inputs[1:]
# del args.references[1:]
results = list(process_fonts(args.references, args.inputs, args.save_to, args.merge))
results = list(process_fonts(args.references, args.inputs, args.save_to, args.merge, args.copy_metrics))

tabdata = {}
for ref, fnt, ttf in results:
Expand Down

0 comments on commit 7374562

Please sign in to comment.