#!/usr/bin/env python3 # coding=utf8 import sys import os.path import fontforge ###### Some helpers def is_panose_monospaced(font): """ Check if the font's Panose flags say it is monospaced """ # https://forum.high-logic.com/postedfiles/Panose.pdf panose = list(font.os2_panose) if panose[0] < 2 or panose[0] > 5: return -1 # invalid Panose info panose_mono = ((panose[0] == 2 and panose[3] == 9) or (panose[0] == 3 and panose[3] == 3)) return 1 if panose_mono else 0 def is_monospaced(font): """ Check if a font is probably monospaced """ # Some fonts lie (or have not any Panose flag set), spot check monospaced: width = -1 width_mono = True for glyph in [ 0x49, 0x4D, 0x57, 0x61, 0x69, 0x2E ]: # wide and slim glyphs 'I', 'M', 'W', 'a', 'i', '.' if not glyph in font: # A 'strange' font, believe Panose return is_panose_monospaced(font) == 1 # print(" -> {} {}".format(glyph, font[glyph].width)) if width < 0: width = font[glyph].width continue if font[glyph].width != width: # Exception for fonts like Code New Roman Regular or Hermit Light/Bold: # Allow small 'i' and dot to be smaller than normal # I believe the source fonts are buggy if glyph in [ 0x69, 0x2E ]: if width > font[glyph].width: continue (xmin, _, xmax, _) = font[glyph].boundingBox() if width > xmax - xmin: continue width_mono = False break # We believe our own check more then Panose ;-D return width_mono def get_advance_width(font, extended, minimum): """ Get the maximum/minimum advance width in the extended range """ width = 0 if extended: end = 0x17f else: end = 0x07e for glyph in range(0x21, end): if not glyph in font: continue if glyph in range(0x7F, 0xBF): continue # ignore special characters like '1/4' etc if width == 0: width = font[glyph].width continue if not minimum and width < font[glyph].width: width = font[glyph].width elif minimum and width > font[glyph].width: width = font[glyph].width return width ###### Let's go! if len(sys.argv) < 2: print('Usage: {} font_name [font_name ...]\n'.format(sys.argv[0])) sys.exit(1) print('Examining {} font files'.format(len(sys.argv) - 1)) for filename in sys.argv[1:]: fullfile = os.path.basename(filename) fname = os.path.splitext(fullfile)[0] font = fontforge.open(filename, 1) panose = list(font.os2_panose) mono = is_panose_monospaced(font) if mono == -1: monotxt = 'unknown' elif mono == 0: monotxt = 'false' elif mono == 1: monotxt = 'true' else: monotxt = '???' width = -1 widthmono = True widthmono = is_monospaced(font) if (mono == 0 and widthmono) or (mono == 1 and not widthmono): print('LIES {} (width {} / {} - {}) Panose says "monospace {}" {}'.format(fname, get_advance_width(font, False, True), get_advance_width(font, False, False), get_advance_width(font, True, False), monotxt, panose)) else: print('MONO {} (width {} / {} - {}) Panose says "monospace {}" {}'.format(fname, get_advance_width(font, False, True), get_advance_width(font, False, False), get_advance_width(font, True, False), monotxt, panose)) if not widthmono: print('NOT MONO {} (width {} / {} - {}) Panose says "monospace {}" {}'.format(fname, get_advance_width(font, False, True), get_advance_width(font, False, False), get_advance_width(font, True, False), monotxt, panose)) font.close()