#!/usr/bin/python
# -*- encoding: UTF-8 -*-
# otf2gdl

import fontforge
import re
import string
import sys
import codecs
import gdldef
import unicodedata

lang = {
	"dflt": "",
	"AFK ": "af", 
	"AZE ": "az",
	"CAT ": "ca",	
	"CRT ": "crh",
	"CSY ": "cs",
	"DAN ": "da",
	"DEU ": "de",
	"ELL ": "el",
	"ENG ": "en",
	"EO  ": "eo",
	"ESP ": "es",
	"FIN ": "fi",
	"FRA ": "fr",
	"HUN ": "hu",
	"MOL ": "none",
	"NLD ": "nl",
	"ITA ": "it",
	"LTZ ": "lb",
	"PLK ": "pl",
	"PTG ": "pt",
	"ROM ": "ro",
	"RUS ": "ru",
	"SLV ": "sl",
	"SRP ": "sr",
	"SRPL": "sh",
	"SVE ": "sv",
	"TRK ": "tr"
}

if len(sys.argv) == 1:
	print "otf2gdl - OpenType to Graphite GDL converter"
	print "Usage: otf2gdl [-e] font.otf [font.gdl]"
	print "Options: -e: extended font with heavy asterisk for footnote numbering"
	sys.exit(1)

extended = False
params = sys.argv[1:]
if params[0] == "-e":
	extended = True
	params = params[1:]

print params[0]
font=fontforge.open(params[0])

if len(params) == 2:
	out = codecs.open(params[1], "wb", encoding="UTF-8")
else:
	out = codecs.open(re.sub("[.][^.]*$", "", params[0]) + ".gdl", "wb", encoding="UTF-8")

kern_classes = []	# declaration of kerning classes
kern_pos = []		# position table of kerning data

def kerning(kerningclass, subtable):
	global kern_classes, kern_pos
	l = len(kerningclass[0])
	r = len(kerningclass[1])
	kern_classes = get_kern_classes(kern_classes, "kernl", subtable, kerningclass[0])
	kern_classes = get_kern_classes(kern_classes, "kernr", subtable, kerningclass[1])
	data = kerningclass[2]
	for i in range(0, len(data)):
		if data[i] <> 0:
			kern_pos.append("kernr_" + subtable + "_" + str(i % r) + " { kern.x += " + str(data[i]) + "m } / kernl_" + subtable + "_" + str(i / r) + " _ ;")

def get_kern_classes(array, name, subtable, data):
	for i in range(1, len(data)):
		array.append(name + "_" + subtable + "_" + str(i) + " = p" + str(data[i]) + ";")
	return array

# convert kerning data to GDL

# print >>out, font.getLookupSubtableAnchorClasses(font.getLookupSubtables(font.gsub_lookups[1])[0])

features = []
letters = set() # set of letters
numbers = set() # set of numbers
lookups = {} # substitutions
lookupl = {} # ligatures
dotlessi = False
Libertine_It = False
Qu_sc = False
f_f_j = False
Germandbls_alt = False
f_corr = "f_corr = p(\"f\");"
ff_corr = "ff_corr = p(\"f_f\")"
onefifth = False
glyphs_uni = set()
cpsp = {}
cpsp_table = ""

k = 0

for i in font.gpos_lookups:
	if i[1:5] == "kern":
		try:
			kerning(font.getKerningClass(font.getLookupSubtables(i)[0]), str(k))
			k = k + 1
		except:
			pass
	elif i[1:5] == "cpsp":
		cpsp_table = font.getLookupSubtables(i)[0]

def lookup_glyph(name, l):
	if l[1] == "Substitution":
		try:
			lookups[l[0]][0].append(name)
			lookups[l[0]][1].append(l[2])
		except:
			lookups[l[0]] = ([name], [l[2]])
		if l[0][1:5] == "salt":
			n = str(len(lookups[l[0]][0]))
			if len(n) == 1:
				m = "0" + n
			lookups["'sa" + m + "' Stylistic alternative"] = ([name], [l[2]])
#			salt.append(name)
#			salt2.append(l[2])
	elif l[1] == "Ligature":
		try:
			lookupl[l[0]][0].append(name)
			lookupl[l[0]][1].append(l[2:])
		except:
			lookupl	[l[0]] = ([name], [l[2:]])
	elif l[1] == "Position" and l[0] == cpsp_table:
		try:
			cpsp["cpsp" + str(l[4])].append(name) 
		except:
			cpsp["cpsp" + str(l[4])] = [name]

if extended and (params[1].find("Libertine_It") > -1):
	Libertine_It = True
	letters = letters | set(["uniE0F7", "uniE0F8", "uniE033", "uniE037", "uni00AD"])

if extended and params[1].find("Libertine_BI") > -1:
	letters = letters | set(["uniE033", "uniE037"])

if extended and params[1].find("Libertine_Bd") > -1:
	letters = letters | set(["uniE033"])

for i in font.glyphs():
	a =  i.getPosSub("*")
	glyphs_uni = glyphs_uni | set([i.glyphname])
	if len(a) > 0:
		for j in a:
			lookup_glyph(i.glyphname, j)
	if i.glyphname == "dotlessi":
		dotlessi = True
	if i.glyphname == "f.short":
		f_corr = "f_corr = p(\"f.short\");"
	if i.glyphname == "f_f.short":
		ff_corr = "ff_corr = p(\"f_f.short\");"
	if i.glyphname == "Q_u.sc":
		Qu_sc = True
	if i.glyphname == "Germandbls.alt":
		Germandbls_alt = True
	if i.glyphname == "f_f_j" or i.unicode == 57395:
		f_f_j = True
#	if i.unicode == 57591:
#		g_y = True
	if i.glyphname == "onefifth":
		onefifth = True
	if i.glyphname[:3] == "uni":
		glyphs_uni = glyphs_uni | set([i.glyphname.replace("uni","0x").lower()])
	try:
		uni = fontforge.unicodeFromName(i.glyphname)
		glyphs_uni = glyphs_uni | set([hex(uni)])
		if re.match("[L].*", unicodedata.category(unichr(uni))):
			letters = letters | set([i.glyphname])
	except:
		if re.sub("[.]alt$", "", i.glyphname) in letters: # add J.alt, etc. glyphs to letters
			letters = letters | set([i.glyphname])
	if re.sub("[.].*", "", i.glyphname) in ["zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"]:
			numbers = numbers | set([i.glyphname])

#print letters

def get_languages(lookupinfo):
	l = []
	for i in str(lookupinfo).split("'"):
		if re.match("[A-Z]{3} ", i) or i == "dflt":
			l.append(i)
	return set(l)

languages = set()	# all OpenType language codes used by the font
for i in font.gsub_lookups:
	languages = languages | get_languages(font.getLookupInfo(i))

# printing GDL source

print >>out, "#include \"stddef.gdh\""
print >>out, "#define p postscript"
print >>out, "#define LG_HU 0x040d"
print >>out, "table(feature)"
feat = []

def feathelp(feat):
	if not (re.match("ss[0-2][0-9]", feat[1:5]) or (feat[1:3] == "sa" and feat[4] > "0" and feat[4] <= "9")):
		return ""
	f = []
	s = []
	if feat in lookups:
		f = lookups[feat][0]
	elif feat in lookupl:
		f = lookupl[feat][0]
	for j in f:	
		s.append(j)
#		if fontforge.unicodeFromName(j) > -1:
#			s.append(unichr(fontforge.unicodeFromName(j)))
	if len(s) > 0:
		if len(s) > 3:
			s[3] = u"…"
			s[3] = "..."
			s = s[:4]
		return " (" + string.join(s, ", ") + ")"
	return ""


LinDia = "" # Default ss01 table of Linux Libertine for German ÖÜÄ
SMCPfix = "" # short fix for ligatures with small caps

for i in sorted(lookups.keys() + lookupl.keys()):
	l = i[1:5]
	if l == "ss01" and font.fullname.find("Linux ") > -1: 
		LinDia = "ss01 = 0;"
	if l == "liga":
		for j in lookupl[i][0]:
			letters = letters | set([j])
	if  l[1:4] == "ss0":
		letters = letters | set(lookups[i][1])
	if l == "smcp":
		letters = letters | set(lookups[i][1])
		SMCPfix = " && !smcp"
	if not l in feat:

		if l == "liga" or (l == "ss01" and font.fullname.find("Linux ") > -1): # German umlaut variants are default only for German
			print >>out, l + " { id = \"" + l + "\"; default = 1; name.LG_USENG = \"" + re.sub(" lookup.*$", "", i) + feathelp(i) + "\"}"
		elif l == "frac" and extended:
			print >>out, """frac { id = "frac"; name.LG_USENG="'frac' Diagonal and nut Fractions"; settings { 
	none { value = 0; name.LG_USENG = "None"; }
	diagonal { value = 1; name.LG_USENG = "Diagonal fractions"; }
	nut { value = 2;  name.LG_USENG = "Nut fractions"; }
}}"""
		else:
			print >>out, l + " { id = \"" + l + "\"; name.LG_USENG = \"" + re.sub(" lookup.*$", "", i) + feathelp(i) + "\"}"
		feat.append(l)

if len(cpsp) > 0:
	print >>out, "cpsp { id = \"cpsp\"; name.LG_USENG=\"'cpsp' Capital spacing\"; }"

print >>out, gdldef.feat

if extended:
	print >>out, gdldef.feat_linlib
	# add smcp for Linux Libertine Bold Italic
	if (params[1].find("BI") > -1):
		print >>out, gdldef.feat_linlibBI

print >>out, "endtable;"


print >>out, "table(language)"
for i in lang:
	if i == "DEU " and font.fullname.find("Linux ") > -1: # German umlaut variants are default only for German
		print >>out, i, "{ languages = (\"" + lang[i] + "\");", LinDia, " lng = ", i, " }"
	elif i == "HUN " or i == "FRA ": # French spacing before "!", "?", ";" and ":"
		print >>out, i, "{ languages = (\"" + lang[i] + "\"); frsp = 1; lng = ", i, " }"
	elif i != "dflt":
		print >>out, i, "{ languages = (\"" + lang[i] + "\"); lng = ", i, " }"
print >>out, "endtable;"

print >>out, "table(glyph)"
print >>out, gdldef.glyph
print >>out, f_corr
print >>out, ff_corr
cnum = []
cnum1 = []
cnum5 = []
for i in lookups:
	l = re.sub("[^a-zA-Z0-9]", "_", i)
	# fix for missing dotless i
	if dotlessi and i[1:5] == "smcp" and not "dotlessi" in lookups[i][0]:
		lookups[i][0].append("dotlessi")
		lookups[i][1].append("i.sc")
	# fix for Linux Libertine (bad old-style conversion in Bold Italic):
	if i[1:5] == "onum" and not "six.oldstyle" in lookups[i][1]:
		lookups[i][1][lookups[i][0].index("six")] = "six.oldstyle"
		lookups[i][1][lookups[i][0].index("seven")] = "seven.oldstyle"
		lookups[i][1][lookups[i][0].index("eight")] = "eight.oldstyle"
		lookups[i][1][lookups[i][0].index("nine")] = "nine.oldstyle"
		lookups[i] = (tuple(list(lookups[i][0]) + ["zero.slashfitted", "zero.fitted", "one.fitted", "two.fitted", "three.fitted", "four.fitted", "five.fitted", "six.fitted", "seven.fitted", "eight.fitted", "nine.fitted", "Euro.fitted"]), lookups[i][1])
		lookups[i] = (lookups[i][0], tuple(list(lookups[i][1]) + ["zero.oldstyle", "zero.oldstyle", "one.oldstyle", "two.oldstyle", "three.oldstyle", "four.oldstyle", "five.oldstyle", "six.oldstyle", "seven.oldstyle", "eight.oldstyle", "nine.oldstyle", "Euro"]))
	# fix for Linux Libertine (digit 8 in pnum, correct data needs for the feature "arti"):
	if i[1:5] == "pnum" and "zero.taboldstyle" in lookups[i][0] and "eight.oldstyle" in lookups[i][0]:
		idx = lookups[i][0].index("eight.oldstyle")
		lookups[i][0][idx] = "eight.taboldstyle"
		lookups[i][1][idx] = "eight.oldstyle"


	print >>out, "class_" + l + "_1 = p", str(tuple(lookups[i][0])).replace("'", "\"").replace(",)", ")"), ";"
	print >>out, "class_" + l + "_2 = p", str(tuple(lookups[i][1])).replace("'", "\"").replace(",)", ")"), ";"
	if i[1:5] == "sups":
		print >>out, "cidx2 = class_" + l + "_2;"
	if i[1:5] == "sinf":
		print >>out, "cidx3 = class_" + l + "_2;"
	if i[1:5] == "onum" or i[1:5] == "pnum" or i[1:5] == "zero": # all numerical glyphs
		cnum.append("class_" + l + "_2")
		try:
			cnum1.append(lookups[i][1][lookups[i][0].index("one")])
			cnum5.append(lookups[i][1][lookups[i][0].index("five")])
			cnum1.append(lookups[i][1][lookups[i][0].index("one.fitted")])
			cnum5.append(lookups[i][1][lookups[i][0].index("five.fitted")])		
		except:
			pass

if len(cnum): 
	print >>out, "add = (dd, " + string.join(cnum, ",") + ");".replace(",\" )", ")")
	print >>out, "ad1 = p(\"one\", \"" + string.join(cnum1, "\", \"") + "\");".replace(",\" )", ")")
	print >>out, "ad5 = p(\"five\", \"" + string.join(cnum5, "\", \"") + "\");".replace(",\" )", ")")
else:
	print >>out, "add = (dd)";
	print >>out, "ad1 = p(\"one\")"
	print >>out, "ad5 = p(\"five\")"
print >>out, "numbers = p(\"" + string.join(numbers, "\", \"") + "\");"
print >>out, "letters = p(\"" + string.join(letters, "\", \"") + "\");"
print >>out, """
csc123 = letters;
csc123lig = letters;
csc123ligd = (letters, U+002D);
csc123ligdash = (letters, U+002D);
csc123liga = (csc123ligd, U+0020);
"""
if "uni2731" in glyphs_uni or extended:
	print >>out, "asterisk = U+2731;" 
else:
	print >>out, "asterisk = p(\"asterisk\");" 
if "dagger" in glyphs_uni:
	print >>out, "dagger = U+2020;" 
else:
	print >>out, "dagger = U+002B;" 
if "daggerdbl" in glyphs_uni:
	print >>out, "daggerdbl = U+2021;" 
else:
	print >>out, "daggerdbl = U+0023;" 

for i in kern_classes:
	a = str(i).replace("'", "\"").replace(",)", ")").replace(", <NULL>", "")
	#if Libertine_It kerning extended for ligature fj, ffj, gj, gy and ij
	if Libertine_It and ((a.find("kernl") > -1 and a.find("\"j\"") > -1) or (a.find("kernr") > -1 and a.find("\"i\"") > -1)):
		a = a.replace(")", ", \"uni0133\")")
	if Libertine_It and ((a.find("kernl") > -1 and a.find("\"y\"") > -1) or (a.find("kernr") > -1 and a.find("\"g\"") > -1)):
		a = a.replace(")", ", \"uniE0F7\")")
	if Libertine_It and ((a.find("kernl") > -1 and a.find("\"j\"") > -1) or (a.find("kernr") > -1 and a.find("\"g\"") > -1)):
		a = a.replace(")", ", \"uniE0F8\")")
	if Libertine_It and (a.find("kernr") > -1 and a.find("\"f\"") > -1):
		a = a.replace(")", ", \"uniE033\", \"uniE037\")")  # XXX f_j is missing from OpenType kerning table of Linux Libertine
	print >>out, a

for i in cpsp:
	print >>out, i, "= p(\"" + string.join(cpsp[i], "\", \"") + "\");"
if len(cpsp) > 0:
	print >>out, "cpsp_all = (", string.join(cpsp.keys(), ",") + ");"

if extended:
	# correction for Linux Libertine Bold Italic small caps
	if (params[1].find("BI") > -1):
		print >>out, gdldef.glyph_linlib.replace("..U+E0B5", "..U+E0B3, U+021B, U+0219")
	else:
		print >>out, gdldef.glyph_linlib

print >>out, "endtable;"

print >>out, "table(position)"

for i in cpsp:
#	print >>out, "if (cpsp || caps == 2)", i, "{ kern.x += ", i[4:] + "; user2 = true } / cpsp_all ^ _ { user2 == false }; endif;"
	print >>out, "if (cpsp || caps == 2)", i, "{ shift.x += 8; advance.x += 20; user2 = true } / ANY ^ _ { user2 == false }; endif;"
#	print >>out, "if (cpsp || caps == 2)", i, "{ shift.x += 250; advance.x += 500; user2 = true } / U+0020 ^ _ { user2 == false }; endif;"
	print >>out, "if (cpsp || caps == 2)", i, "{ shift.x += 8; advance.x += 20; user2 = true } / _ { user2 == false }; endif;"


print >>out, gdldef.kern
if extended:
	print >>out, gdldef.kern_linlib
for i in kern_pos:
	print >>out, i

print >>out, "endtable;"

def lang_condition(l):
	try:
		l2 = get_languages(font.getLookupInfo(font.getLookupOfSubtable(l)))
	except: # sa01..
		return ""
	if languages == l2 or len(l2) == 0:
		return ""
	if "dflt" in l2:
		return " && !(lng==" + string.join ((languages-l2)-set(["dflt"]), " || lng ==") + ")"
	return " && (lng==" + string.join(l2, " || lng	==") + ")"

print >>out, "table(sub) { MaxBackup = 100; MaxRuleLoop = 200 }"

for i in lookups:
	c = re.sub("[^a-zA-Z0-9]", "_", i)
	l = i[1:5]
	if (l == "case"):
		# feature case is default, when the text is uppercased by caps == 2
		print >>out, "if (" + l + " || caps==2" + lang_condition(i) + ")"
	else:
		print >>out, "if (" + l + lang_condition(i) + ")"
	if l == "smcp" or l == "sups" or l == "onum" or l == "pnum":
		print >>out, "class_" + c + "_1 > class_" + c + "_2 / ^_;"
	elif extended:
		print >>out, "if (caps == 0) class_" + c + "_1 > class_" + c + "_2 / ^_; endif;"
		print >>out, "class_" + c + "_1 > class_" + c + "_2;"
	else:
		print >>out, "class_" + c + "_1 > class_" + c + "_2 / ^_;"		
	if l == "onum" or l == "pnum" or  l == "zero":
		print >>out, "if (arti) class_" + c + "_1 > class_" + c + "_2 / ^ ANY _; endif;"
	if l == "fina":
		print >>out, "class_" + c + "_1 > @1 / _ letters;"
	print >>out, "endif;"
	if l == "sups":
		print >>out, "if (texm) U+005E class_" + c + "_1 > _ class_" + c + "_2 / ^_; endif;"
	if l == "sinf":
		print >>out, "if (texm) U+005F class_" + c + "_1 > _ class_" + c + "_2 / ^_; endif;"

def check(st):
	a = st.splitlines(True)
	for i in range(0, len(a)):
		if a[i].find(".superior") > -1:
			if not (set(re.sub("[^ .a-z]", "", a[i][a[i].find(">"):].replace("p(","")).lower().split()) <= glyphs_uni):
				a[i] = "//" + a[i]
	for i in range(0, len(a)):
		if a[i].find("// CHECK") > 0 or re.match("U\\+005C", a[i]):
			g = re.sub("U\+0*", "0x", re.sub("[^ \+a-zA-Z0-9]", " ",a[i][a[i].find(">"):].replace("// CHECK",""))).lower().split()
			if not set(g) <= glyphs_uni:
				a[i] = "//" + a[i]
	return string.join(a)

print >>out, check(gdldef.sub)

if extended:
	if onefifth:
		print >>out, """
if (frac==1)
p("one") slashes p("five") > p("onefifth") / ^_ _ _ ;
p("two") slashes p("five") > p("twofifths") / ^_ _ _ ;
p("three") slashes p("five") > p("threefifths") / ^_ _ _ ;
p("four") slashes p("five") > p("fourfifths") / ^_ _ _ ;
p("one") slashes p("six") > p("onesixth") / ^_ _ _ ;
p("five") slashes p("six") > p("fivesixths") / ^_ _ _ ;
p("one") slashes p("eight") > p("oneeighth") / ^_ _ _ ;
p("three") slashes p("eight") > p("threeeighths") / ^_ _ _ ;
p("five") slashes p("eight") > p("fiveeighths") / ^_ _ _ ;
p("seven") slashes p("eight") > p("seveneighths") / ^_ _ _ ;
p("one") slashes > @2 @3 / dd ^_ _ ;
p("one") slashes > p("onenumerator") / ^_ _ ;
endif;
"""
	if Libertine_It:
		print >>out, """
if (lng == NLD && caps != 2)
	if (dlig)
		p("b") p("i") p("j") p("o") p("u") p("x") _ > @1 @2 ZWSP @3 @4 @5 @6 / ^_ _ _ _ _ _ _;
		p("b") p("i") p("j") p("e") p("c") p("t") p("i") p("e") _ > @1 @2 ZWSP @3 @4 @5 @6 @7 @8 / ^ _ _ _ _ _ _ _ _ _;
		p("i") p("j") > U+133 _;
	endif;
endif;
"""
	print >>out, gdldef.sub_linlib
	# set smcp for Linux Libertine Bold Italic
	if (params[1].find("BI") > -1):
		print >>out, gdldef.sub_linlibBI
		SMCPfix = " && !smcp"
	if Qu_sc:
		print >>out, "if (liga && smcp && !c2sc) p(\"Q\") p(\"u\") > _ p(\"Q_u.sc\"); endif;"
#		print >>out, "if (liga && !c2sc && caps != 2) p(\"Q\") p(\"u\") > _ p(\"Q_u\"); endif;"
	if Germandbls_alt:
		print >>out, "if (ss03 && c2sc) p(\"Germandbls.alt\") > p(\"germandbls.scalt\"); endif;"
#	if extended and g_y:
#		print >>out, "p(\"g\") p(\"y\") > _ U+E0F7;"
#		print >>out, "if (lng == HUN && liga && caps == 0) p(\"g\") p(\"y\") > _ U+E0F7; endif;"

def addliga(pr, pr2):
	pr = pr + "if (caps == 1) " + pr2.replace("/", "/ ANY")
	pr = pr + "else if (caps == 3) " + pr2.replace("/", "/ csc123")
	pr = pr + "else if (caps == 4) " + pr2.replace("/", "/ csc123ligd") + "else if (caps == 0) " + pr2 + " endif;"
	return pr

for i in lookupl:
	c = re.sub("[^a-zA-Z0-9]", "_", i)
	l = i[1:5] + SMCPfix # short fix for the different priorities
	print >>out, "if (" + l + lang_condition(i) + ")"
	for j in range(0, len(lookupl[i][0])):
		diflen = len(lookupl[i][1][j])
		pr = "p(\"" + string.join(lookupl[i][1][j], "\") p(\"") + "\") > p(\"" + lookupl[i][0][j] +  "\") / ^" + "_ " * diflen + ";"
		if lookupl[i][0][j] in ["exclamdbl", "question_question", "exclam_question", "question_exclam"]:
			pr = "if (!frsp) " + pr + "endif;"
		elif extended:
			pr = addliga("", pr)
			pr = pr + "if (caps > 0) p(\"Q\") p(\"u\") > p(\"q\") p(\"u\") / ANY _ _; p(\"Q\") p(\"u\") > p(\"Q_u\") / ^_ _; endif;"
#			pr = pr + "p(\"q\") p(\"u\") > p(\"Q_u\") / ^_ _;"
			if lookupl[i][0][j] == "f_k": # and f_f_j:
				pr = addliga(pr, "p(\"f\") p(\"j\") > U+E037 / ^_ _;")
				pr = addliga(pr, "p(\"f\") p(\"f\") p(\"j\") > U+E033 / ^_ _ _;")
				if (Libertine_It):
					# pr = addliga(pr, "p(\"g\") p(\"j\") > _ U+E0F8;")
					pr = pr + "if (lng==NLD) p(\"J\") > p(\"J.alt\"); endif;" 
					pr = pr + "if (lng==HUN)" + addliga("", "p(\"g\") p(\"y\") > _ U+E0F7 / ^_ _;") + "endif;" 
					pr = addliga(pr, "p(\"g\") p(\"j\") > _ U+E0F8 / ^_ _;") 
			if lookupl[i][0][j] == "c_k" or lookupl[i][0][j] == "c_h" or lookupl[i][0][j] == "t_z":
				pr = "if (lng == DEU)" + pr + "endif;"
				if Libertine_It and i[1:5] == "dlig":
					pr = pr + "if (lng==NLD)" + addliga("", "p(\"i\") p(\"j\") > _ U+0133;") + "endif;" 
		print >>out, pr
	print >>out, "endif;"

print >>out, "endtable;"

