update glyphinfo

This commit is contained in:
Rasmus Andersson 2018-09-03 16:06:04 -07:00
parent 6785f6ea1c
commit 4ab36d3e3b
4 changed files with 79 additions and 176 deletions

View file

@ -55,6 +55,12 @@ body.single #glyphs {
color: white; color: white;
text-align: center; text-align: center;
font-size:1em; font-size:1em;
white-space: pre;
overflow:hidden;
cursor: default;
}
body.single .glyph .names {
display: none;
} }
.glyph:hover .name { .glyph:hover .name {
color: rgba(0,0,0,0.8); color: rgba(0,0,0,0.8);

View file

@ -326,7 +326,7 @@ function render() {
// glyphInfo: { // glyphInfo: {
// "glyphs": [ // "glyphs": [
// [name :string, unicode? :int|null, unicodeName? :string, color? :string|null], // [name :string, unicode? :int|null, unicodeName? :string, mtime? :string, color? :string|null],
// ["A", 65, "LATIN CAPITAL LETTER A", "#dbeaf7"], // ["A", 65, "LATIN CAPITAL LETTER A", "#dbeaf7"],
// ... // ...
// ], // ],
@ -405,7 +405,8 @@ function renderGlyphGraphicG(g, lastGlyphName, lastGlyphEl, singleGlyph) {
name: name, name: name,
unicode: g[1], unicode: g[1],
unicodeName: g[2], unicodeName: g[2],
color: g[3], // mtime: g[3],
color: g[4],
// These are all in 1:1 UPM (not scaled) // These are all in 1:1 UPM (not scaled)
advance: metrics.advance, advance: metrics.advance,

View file

@ -3,6 +3,7 @@ from __future__ import print_function
import sys, os import sys, os
from os.path import dirname, abspath, join as pjoin from os.path import dirname, abspath, join as pjoin
import subprocess import subprocess
import time
# patch PYTHONPATH to include $BASEDIR/build/venv/python/site-packages # patch PYTHONPATH to include $BASEDIR/build/venv/python/site-packages
BASEDIR = abspath(pjoin(dirname(__file__), os.pardir, os.pardir)) BASEDIR = abspath(pjoin(dirname(__file__), os.pardir, os.pardir))
@ -34,5 +35,27 @@ def getVersion():
return _version return _version
_local_tz_offs = None
def getLocalTimeZoneOffset(): # in seconds from UTC
# seriously ugly hack to get timezone offset in Python
global _local_tz_offs
if _local_tz_offs is None:
tzname = time.strftime("%Z", time.localtime())
s = time.strftime('%z', time.strptime(tzname, '%Z'))
i = 0
neg = False
if s[0] == '-':
neg = True
i = 1
elif s[0] == '+':
i = 1
h = int(s[i:i+2])
m = int(s[i+2:])
_local_tz_offs = ((h * 60) + m) * 60
if neg:
_local_tz_offs = -_local_tz_offs
return _local_tz_offs
# update environment to include $VENVDIR/bin # update environment to include $VENVDIR/bin
os.environ['PATH'] = os.path.join(VENVDIR, 'bin') + ':' + os.environ['PATH'] os.environ['PATH'] = os.path.join(VENVDIR, 'bin') + ':' + os.environ['PATH']

View file

@ -4,129 +4,20 @@
# Grab http://www.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt # Grab http://www.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt
# #
from __future__ import print_function from __future__ import print_function
import os, sys, json, re
import os, sys
from os.path import dirname, basename, abspath, relpath, join as pjoin
sys.path.append(abspath(pjoin(dirname(__file__), 'tools')))
from common import BASEDIR, getVersion, getLocalTimeZoneOffset
import json, re
import time
from argparse import ArgumentParser from argparse import ArgumentParser
from robofab.objects.objectsRF import OpenFont
from collections import OrderedDict from collections import OrderedDict
from unicode_util import parseUnicodeDataFile
from ConfigParser import RawConfigParser from ConfigParser import RawConfigParser
# from robofab.objects.objectsRF import OpenFont
from unicode_util import parseUnicodeDataFile
BASEDIR = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir)) from defcon import Font
# Regex matching "default" glyph names, like "uni2043" and "u01C5"
uniNameRe = re.compile(r'^u(?:ni)([0-9A-F]{4,8})$')
def unicodeForDefaultGlyphName(glyphName):
m = uniNameRe.match(glyphName)
if m is not None:
try:
return int(m.group(1), 16)
except:
pass
return None
def loadAGL(filename): # -> { 2126: 'Omega', ... }
m = {}
with open(filename, 'r') as f:
for line in f:
# Omega;2126
# dalethatafpatah;05D3 05B2 # higher-level combinations; ignored
line = line.strip()
if len(line) > 0 and line[0] != '#':
name, uc = tuple([c.strip() for c in line.split(';')])
if uc.find(' ') == -1:
# it's a 1:1 mapping
m[int(uc, 16)] = name
return m
def loadLocalNamesDB(fonts, agl, diacriticComps):
uc2names = None # { 2126: ['Omega', ...], ...}
allNames = OrderedDict() # {'Omega':True, ...}
for font in fonts:
_uc2names = font.getCharacterMapping() # { 2126: ['Omega', ...], ...}
if uc2names is None:
uc2names = _uc2names
else:
for uc, _names in _uc2names.iteritems():
names = uc2names.setdefault(uc, [])
for name in _names:
if name not in names:
names.append(name)
for g in font:
allNames.setdefault(g.name, True)
# agl { 2126: 'Omega', ...} -> { 'Omega': [2126, ...], ...}
aglName2Ucs = {}
for uc, name in agl.iteritems():
aglName2Ucs.setdefault(name, []).append(uc)
for glyphName, comp in diacriticComps.iteritems():
aglUCs = aglName2Ucs.get(glyphName)
if aglUCs is None:
uc = unicodeForDefaultGlyphName(glyphName)
if uc is not None:
glyphName2 = agl.get(uc)
if glyphName2 is not None:
glyphName = glyphName2
names = uc2names.setdefault(uc, [])
if glyphName not in names:
names.append(glyphName)
allNames.setdefault(glyphName, True)
else:
allNames.setdefault(glyphName, True)
for uc in aglUCs:
names = uc2names.get(uc, [])
if glyphName not in names:
names.append(glyphName)
uc2names[uc] = names
name2ucs = {} # { 'Omega': [2126, ...], ...}
for uc, names in uc2names.iteritems():
for name in names:
name2ucs.setdefault(name, set()).add(uc)
return uc2names, name2ucs, allNames
def canonicalGlyphName(glyphName, uc2names):
uc = unicodeForDefaultGlyphName(glyphName)
if uc is not None:
names = uc2names.get(uc)
if names is not None and len(names) > 0:
return names[0]
return glyphName
def parseGlyphComposition(composite):
c = composite.split("=")
d = c[1].split("/")
glyphName = d[0]
if len(d) == 1:
offset = [0, 0]
else:
offset = [int(i) for i in d[1].split(",")]
accentString = c[0]
accents = accentString.split("+")
baseName = accents.pop(0)
accentNames = [i.split(":") for i in accents]
return (glyphName, baseName, accentNames, offset)
def loadGlyphCompositions(filename): # { glyphName => (baseName, accentNames, offset) }
compositions = OrderedDict()
with open(filename, 'r') as f:
for line in f:
line = line.strip()
if len(line) > 0 and line[0] != '#':
glyphName, baseName, accentNames, offset = parseGlyphComposition(line)
compositions[glyphName] = (baseName, accentNames, offset)
return compositions
def rgbaToCSSColor(r=0, g=0, b=0, a=1): def rgbaToCSSColor(r=0, g=0, b=0, a=1):
@ -134,7 +25,7 @@ def rgbaToCSSColor(r=0, g=0, b=0, a=1):
if a == 1: if a == 1:
return '#%02x%02x%02x' % (R,G,B) return '#%02x%02x%02x' % (R,G,B)
else: else:
return 'rgba(%d,%d,%d,%f)' % (R,G,B,a) return 'rgba(%d,%d,%d,%g)' % (R,G,B,a)
def unicodeName(cp): def unicodeName(cp):
@ -146,6 +37,14 @@ def unicodeName(cp):
return None return None
# YYYY/MM/DD HH:mm:ss => YYYY-MM-DDTHH:mm:ssZ (local time -> UTC)
def localDateTimeToUTCStr(localstr, pattern='%Y/%m/%d %H:%M:%S'):
t = time.strptime(localstr, pattern)
ts = time.mktime(t) - getLocalTimeZoneOffset()
return time.strftime('%Y-%m-%dT%H:%M:%SZ', time.localtime(ts))
def main(): def main():
argparser = ArgumentParser( argparser = ArgumentParser(
description='Generate info on name, unicodes and color mark for all glyphs') description='Generate info on name, unicodes and color mark for all glyphs')
@ -158,18 +57,6 @@ def main():
'fontPaths', metavar='<ufofile>', type=str, nargs='+', help='UFO fonts to update') 'fontPaths', metavar='<ufofile>', type=str, nargs='+', help='UFO fonts to update')
args = argparser.parse_args() args = argparser.parse_args()
markLibKey = 'com.typemytype.robofont.mark'
srcDir = os.path.join(BASEDIR, 'src')
# load fontbuild config
config = RawConfigParser(dict_type=OrderedDict)
configFilename = os.path.join(srcDir, 'fontbuild.cfg')
config.read(configFilename)
deleteNames = set()
for sectionName, value in config.items('glyphs'):
if sectionName == 'delete':
deleteNames = set(value.split())
fontPaths = [] fontPaths = []
for fontPath in args.fontPaths: for fontPath in args.fontPaths:
@ -179,75 +66,61 @@ def main():
else: else:
fontPaths.append(fontPath) fontPaths.append(fontPath)
fonts = [OpenFont(fontPath) for fontPath in args.fontPaths] fonts = [Font(fontPath) for fontPath in args.fontPaths]
agl = loadAGL(os.path.join(srcDir, 'glyphlist.txt')) # { 2126: 'Omega', ... }
diacriticComps = loadGlyphCompositions(os.path.join(srcDir, 'diacritics.txt'))
uc2names, name2ucs, allNames = loadLocalNamesDB(fonts, agl, diacriticComps)
ucd = {} ucd = {}
if args.ucdFile: if args.ucdFile:
ucd = parseUnicodeDataFile(args.ucdFile) ucd = parseUnicodeDataFile(args.ucdFile)
glyphorder = OrderedDict() glyphs = [] # contains final glyph data printed as JSON
with open(os.path.join(os.path.dirname(args.fontPaths[0]), 'glyphorder.txt'), 'r') as f:
for name in f.read().splitlines():
if len(name) and name[0] != '#':
glyphorder[name] = True
for name in diacriticComps.iterkeys():
glyphorder[name] = True
glyphNames = glyphorder.keys()
visitedGlyphNames = set() visitedGlyphNames = set()
glyphs = []
for font in fonts: for font in fonts:
for name, v in glyphorder.iteritems(): glyphorder = font.lib['public.glyphOrder']
if name in deleteNames: for name in glyphorder:
continue
if name in visitedGlyphNames: if name in visitedGlyphNames:
continue continue
g = None g = font[name]
ucs = []
try:
g = font[name]
ucs = g.unicodes
except:
ucs = name2ucs.get(name)
if ucs is None:
continue
# color
color = None color = None
if g is not None and markLibKey in g.lib: if 'public.markColor' in g.lib:
# TODO: translate from (r,g,b,a) to #RRGGBB (skip A) rgba = [float(c.strip()) for c in g.lib['public.markColor'].strip().split(',')]
rgba = g.lib[markLibKey] color = rgbaToCSSColor(*rgba)
if isinstance(rgba, list) or isinstance(rgba, tuple):
color = rgbaToCSSColor(*rgba) # mtime
elif name in diacriticComps: mtime = None
color = '<derived>' if 'com.schriftgestaltung.Glyphs.lastChange' in g.lib:
datetimestr = g.lib['com.schriftgestaltung.Glyphs.lastChange']
mtime = localDateTimeToUTCStr(datetimestr)
# name[, unicode[, unicodeName[, color]]] # name[, unicode[, unicodeName[, color]]]
glyph = None
ucs = g.unicodes
if len(ucs): if len(ucs):
for uc in ucs: for uc in ucs:
ucName = unicodeName(ucd.get(uc)) ucName = unicodeName(ucd.get(uc))
if not ucName and uc >= 0xE000 and uc <= 0xF8FF: if not ucName and uc >= 0xE000 and uc <= 0xF8FF:
ucName = '[private use %04X]' % uc ucName = '[private use %04X]' % uc
if color: if color:
glyph = [name, uc, ucName, color] glyph = [name, uc, ucName, mtime, color]
elif mtime:
glyph = [name, uc, ucName, mtime]
elif ucName: elif ucName:
glyph = [name, uc, ucName] glyph = [name, uc, ucName]
else: else:
glyph = [name, uc] glyph = [name, uc]
glyphs.append(glyph)
else: else:
glyph = [name, None, None, color] if color else [name] if color:
glyphs.append(glyph) glyph = [name, None, None, mtime, color]
elif mtime:
glyph = [name, None, None, mtime]
else:
glyph = [name]
glyphs.append(glyph)
visitedGlyphNames.add(name) visitedGlyphNames.add(name)
print('{"glyphs":[') print('{"glyphs":[')