#!/usr/bin/env python
# -*- coding: ISO-8859-1 -*-
# Preprocessor for serto font for the use with LaTeX.
# Copyright 2001-2013 by Johannes Heinecke
# you can use this and change it as you wish, under the condition
# that the original copyright line is not deleted
# Last changes:
# 2 Nov 2003: major reconstruction, use of three fontfiles
# 12 July 2003: correction on stretch
# 29 September 2007: Possibility to typeset two identical letters without a qusshaya
# 31 March 2013: add SERTOFONTDIR environment variable
FONTFILESERTO="serto.font" # specify absolute path
FONTFILECHALD="assyr.font"
#FONTFILEESTR="estrangelo.font"
# use the environment variable SERTOFONTDIR to specify the directory of the *.font files
import re, sys, string, os.path
# translating syriac unicode points to serto codings
class Letter:
def __init__(self,
coding, # what coding to use in .ptex-file
name, # name of the letter
isolated, # what character to take in isolated usage
initial, # what character to take in word-initial usage
medial, # what character to take in word-medial usage
final, # what character to take in word-final usage
link): # does it link to the following (# link 0: next letter is initial, 1: next letter is medial, 2: ignore, 8: character is a superscript symbol 9: character is a subscript symbol
self.coding = coding
self.name = name
self.isolated = map(int, isolated.split('+'))
self.initial = map(int, initial.split('+'))
self.medial = map(int, medial.split('+'))
self.final = map(int, final.split('+'))
self.link = int(link)
if self.isolated[0] == -1: self.isolated = None
if self.initial[0] == -1: self.initial = None
if self.medial[0] == -1: self.medial = None
if self.final[0] == -1: self.final = None
#sys.stderr.write("%s:%s-%s-%s-%s\n" \
# % (name,self.isolated,self.initial,self.medial,self.final))
def getcontext(self, ctx):
if ctx == 0: return self.isolated[0]
elif ctx == 1: return self.initial[0]
elif ctx == 2: return self.medial[0]
elif ctx == 3: return self.final[0]
def getChar(self, ctx):
if ctx == 0:
if not self.isolated: return ['']
return map(lambda x: "%c" % x, self.isolated)
#return int(self.isolated[0])
elif ctx == 1:
if not self.initial: return ['']
return map(lambda x: "%c" % x, self.initial)
#return int(self.initial[0])
elif ctx == 2:
#sys.stderr.write("MEDIAL %s\n" % self.medial)
if not self.medial:
#sys.stderr.write("NONE:e\n")
return ['']
return map(lambda x: "%c" % x, self.medial)
#return int(self.medial[0])
elif ctx == 3:
if not self.final: return ['']
return map(lambda x: "%c" % x, self.final)
#return int(self.final[0])
class Serto:
def __init__(self, elatex=0):
self.elatex=elatex # eLaTeX needs \TeXXeTstate=1
self.tabelle = {} # style: {"_d": Letter-Object}
self.transtabelle = {} # style: "_d": \d{d}
self.usingUTF8 = False
self.inlineS = re.compile("()(.*?)()")
self.inlineT = re.compile("()(.*?)()")
self.inlineST = re.compile("()(.*?)()")
self.inlineC = re.compile("()(.*?)()")
self.inlineCT = re.compile("()(.*?)()")
self.inlineE = re.compile("()(.*?)()")
self.inlineET = re.compile("()(.*?)()")
self.tabelle["serto"] = {}
self.transtabelle["serto"] = {}
self.readfont(FONTFILESERTO,
self.tabelle["serto"],
self.transtabelle["serto"])
#print self.tabelle["serto"]
self.tabelle["chaldean"] = {}
self.transtabelle["chaldean"] = {}
self.readfont(FONTFILECHALD,
self.tabelle["chaldean"],
self.transtabelle["chaldean"])
self.UnicodeTable = { 0x0710: "'",
0x0712: "b",
0x0713: "g",
0x0714: "g", #gamal garshuni
0x0715: "d",
0x0717: "h",
0x0718: "w",
0x0719: "z",
0x071a: ".h",
0x071b: ".t",
0x071c: ".t", # teth garshuni
0x071d: "y",
0x071f: "k",
0x0720: "l",
0x0721: "m",
0x0722: "n",
0x0723: "s",
0x0724: "s", # final semkath
0x0725: "`",
0x0726: "p",
0x0728: ".s",
0x0729: "q",
0x072a: "r",
0x072b: "^s",
0x072c: "t",
0x0308: "P", # syame
0x0730: "a",
0x0731: "A",
0x0732: ":a",
0x0733: "=a",
0x0734: "=A",
0x0735: ":=a",
0x0736: "e",
0x0737: "E",
0x0738: ":e",
0x0739: ":e",
0x073a: "i",
0x073b: "I",
0x073c: ":i",
0x073d: "u",
0x073e: "U",
0x073f: ":u",
0x0740: ":=a",
0x0741: "*",
0x0742: "+",
#punctuation listed in unicode not completed implemented in serto
0x0700: ".:.",
0x0701: ".",
0x0702: ".",
0x0703: ":",
0x0704: ":",
0x0705: ":",
0x0706: ":",
0x0707: ":",
0x0708: ":",
0x0709: ":",
0x070D: ".X.",
}
#print "zzzzzz",UnicodeTable
def readfont(self, filename, tabelle, transtabelle):
dirname = os.environ.get("SERTOFONTDIR")
if not dirname:
dirname = os.path.dirname(sys.argv[0])
#print "eeee", dirname
fp = open("%s/%s" % (dirname, filename), "r")
lines = fp.readlines()
fp.close()
#self.tabelle = {} # "_d": (isol, init, med, fin, link)
#self.tabelle = {} # "_d": Letter-Object
#self.transtabelle = {} # "_d": \d{d}
self.fontname = ""
status = "syriac"
self.errct = 0
for z in lines:
if len(z) < 2:
continue
if z[0] == "#":
if z[:6] == "#FONT:":
a = string.split(z)
self.fontname = string.strip(a[1])
#self.textframe.thetext.config(font=self.fontname)
#self.testlabel.config(font=self.fontname)
elif z[:7] == "#TRANS:":
status = "transliterate"
continue
felder = string.split(z)
if status == "syriac":
if len(felder) < 7:
self.errct = self.errct + 1
print "ERROR:", z
else:
#print z, int(felder[2])
#self.tabelle[felder[0]] = (int(felder[2]),
# int(felder[3]),
# int(felder[4]),
# int(felder[5]),
# int(felder[6]))
tabelle[felder[0]] = Letter(felder[0],
felder[1],
felder[2],
felder[3],
felder[4],
felder[5],
felder[6])
else:
if len(felder) < 2:
#print "WARNING:", z
transtabelle[felder[0]] = felder[0]
else:
transtabelle[felder[0]] = felder[1]
def tokenize(self, str, xlen, style="serto"):
ix = 0
self.tokens = []
self.digits = []
number = 0 #
while(ix < xlen): #for ix in range(xlen):
#print "IX", ix, str
if str[ix] == "\\":
command = "\\"
ix = ix + 1
while(ix < xlen):
if not str[ix] in string.letters:
break
else:
command = command + str[ix]
ix = ix + 1
self.tokens.append(command)
elif str[ix] in "{}":
self.tokens.append(str[ix])
ix = ix + 1
else:
for ll in range(5, 0, -1):
if self.tabelle[style].has_key(str[ix:ix+ll]):
if ll == 1 and str[ix:ix+ll] in "aeiou" \
and (len(self.tokens) == 0 \
or self.tokens[-1] == "~"):
#self.tokens.append("'" + str[ix:ix+ll])
self.tokens.extend(["'", str[ix:ix+ll]])
#pass
else:
if len(self.tokens) \
and str[ix:ix+ll] == self.tokens[-1] \
and self.tabelle[style][str[ix:ix+ll]].link != 3 \
and str[ix:ix+ll] not in ["~", "0", "1",
"2", "3", "4",
"5", "6", "7",
"8", "9", "--"]:
# insert shadda
self.tokens.append("Q")
else:
self.tokens.append(str[ix:ix+ll])
ix = ix + ll
break # for-loop
else:
ix = ix + 1
#print "TOKENS",self.tokens
def transtokenize(self, str, xlen, style="serto"):
ix = 0
self.tokens = []
self.digits = []
number = 0 #
while(ix < xlen): #for ix in range(xlen):
#print "IX", ix,
for ll in range(5, 0, -1):
if self.transtabelle[style].has_key(str[ix:ix+ll]):
if ll == 1 and str[ix:ix+ll] in "aeiou" \
and (len(self.tokens) == 0 \
or self.tokens[-1] == "~"):
self.tokens.append("'" + str[ix:ix+ll])
#self.tokens.extend(["'", str[ix:ix+ll]])
else:
#if len(self.tokens) \
# and str[ix:ix+ll] == self.tokens[-1] \
# and self.tabelle[str[ix:ix+ll]][4] != 3 \
# and str[ix:ix+ll] not in ["~", "0", "1", "2", "3", "4",
# "5", "6", "7", "8", "9"]:
# """insert shadda"""
# self.tokens.append("Q")
#else:
self.tokens.append(str[ix:ix+ll])
ix = ix + ll
break # for-loop
else:
ix = ix + 1
#print "TRANSTOKENS",self.tokens
def transliterate(self, syrisch, style="serto"):
if self.usingUTF8:
line = unicode(syrisch, "utf8")
newline = ""
for c in line:
#print "eee %x" % ord(c), self.UnicodeTable.has_key(ord(c)),
#print c.encode("utf8")
sertocode = self.UnicodeTable.get(ord(c), c)
newline += sertocode
#print "[%s]" % sertocode, newline
syrisch = newline
syrisch = string.replace(syrisch, " ", "~")
self.transtokenize(syrisch, len(syrisch), style)
#self.err("TOKEN %s" %self.tokens)
ret = []
oldtok = ""
for tok in self.tokens:
if tok == "~": # blank
ret.append(" ")
#elif tok == "Q": # shadda
#ret.append(ret[-1])
#elif tok == "+": # soft sign under begadkefat
#if len(ret):
# ret[-1] = self.spec.get(oldtok+tok, oldtok+tok)
else:
ret.append(self.transtabelle[style].get(tok, tok))
#oldtok = tok
return string.join(ret, "")
def syriacise(self, style="serto"):
# replace tokens by serto letters, take into account context
ix = 0
out = []
digits = []
self.maxlen = len(self.tokens)
#sys.stderr.write("%s\n" % self.tokens)
number = 0
for i in range(self.maxlen):
if self.tokens[i][0] in "\\{}":
out.append(self.tokens[i])
elif self.tabelle[style][self.tokens[i]].medial == -1:
#print "skipping letter"
continue
else:
form = self.context(i, style=style)
if self.tokens[i] in ["0", "1", "2", "3", "4",
"5", "6", "7", "8", "9"]:
number = 1
digits.append(chr(self.tabelle[style][self.tokens[i]].getcontext(form)))
else:
if number == 1:
number = 0
digits.reverse()
out.extend(digits)
digits = []
#out.append(chr(self.tabelle[self.tokens[i]][form]))
#out.append("%c" %(self.tabelle[self.tokens[i]].getcontext(form)))
for c in self.tabelle[style][self.tokens[i]].getChar(form):
#sys.stderr.write("LETTER: %s\n" % c)
out.append(c)
#print self.tokens[i], form, self.tabelle[self.tokens[i]][form]
if number:
number = 0
digits.reverse()
out.extend(digits)
#for i in out: print "%d" % ord(i),
#print
#if not self.elatex:
# out.reverse()
# This kills empty letters, caused by -1 in .font-file
return string.join(out, "")
def context(self, ix, style="serto"):
"""returns 0 if letter is isolated
1 if letter is initial
2 if letter is medial
3 if letter is final"""
if self.before(ix, style) and self.next(ix, style):
return 2
elif self.before(ix, style) and not self.next(ix, style):
return 3
elif not self.before(ix, style) and self.next(ix, style):
return 1
else:
return 0
def next(self, ix, style="serto"):
"""returns 1 if next token is a letter"""
for i in range(ix+1, self.maxlen):
if self.tokens[i][0] in "\\{}":
return 0
elif self.tabelle[style][self.tokens[i]].link in [2,3]:
continue
elif self.tokens[i] not in ["~", "!", ",", ".", ";", "?"] :
return 1
else:
return 0
return 0
def before(self, ix, style="serto"):
"""returns 1 if preceding token is a letter"""
for i in range(ix-1, -1, -1):
if self.tokens[i][0] in "\\{}":
return 0
elif self.tabelle[style][self.tokens[i]].link == 2:
continue
elif self.tokens[i] != "~":
if self.tabelle[style][self.tokens[i]].link == 0:
return 0
else:
return 1
else:
return 0
return 0
def convert(self, transcript, style="serto"):
# interface function
# dummy blank
if self.usingUTF8:
line = unicode(transcript, "utf8")
newline = ""
for c in line:
#sys.stderr.write( "eee %d %s\n" % (ord(c), self.UnicodeTable.has_key(ord(c))))
#sys.stderr.write( c.encode("utf8") + "\n")
sertocode = self.UnicodeTable.get(ord(c), c)
newline += sertocode
#sys.stderr.write( "[%s]\n" % sertocode)
transcript = newline.encode("utf8")
transcript = string.replace(transcript, " ", "~")
self.tokenize(transcript, len(transcript), style)
return self.syriacise(style)
def texify(self, word, style="serto"):
res = []
for ll in serto.convert(word, style):
# ll: position of current syriac character in font table
#sys.stdout.write("LETTER: 0x%x\n" % (ord(ll)))
#sys.stderr.write("LETTER: %s\n" % ord(ll))
if ord(ll) < 16:
#print "WWWWWWWWW", len(res), res
if len(res):
res[-1] = "\\uppersyriac{%d}{%s}" % (ord(ll), res[-1])
else:
res.append("\\uppersyriac{%d}{A}" % (ord(ll))) # A: Olaf
elif ord(ll) < 32:
if len(res):
res[-1] = "\\lowersyriac{%d}{%s}" % (ord(ll), res[-1])
else:
res.append("\\lowersyriac{%d}{A}" % (ord(ll)))
elif ord(ll) < 127 and ord(ll) not in [34,35,36,37,38,95]:
res.append(ll)
else:
# special (active) TeX-characters, charactes > 127
res.append("\\char%d{}" % ord(ll))
if not self.elatex:
res.reverse()
return string.join(res, "")
def inlineserto(self, matchobject):
return "{\\serto\\beginR %s\\endR}" % self.texify(matchobject.group(2))
def inlinechaldean(self, matchobject):
return "{\\assyr\\beginR %s\\endR}" \
% self.texify(matchobject.group(2), "chaldean")
def inlinetrans(self, matchobject):
return "\\emph{%s}" % self.transliterate(matchobject.group(2))
def inlinesertotrans(self, matchobject):
return "{\\serto\\beginR %s\\endR} \\emph{%s}" \
% (self.texify(matchobject.group(2)),
self.transliterate(matchobject.group(2)))
def inlinechaldeantrans(self, matchobject):
return "{\\assyr\\beginR %s\\endR} \\emph{%s}" \
% (self.texify(matchobject.group(2), "chaldean"),
self.transliterate(matchobject.group(2), "chaldean"))
def err(self, s):
sys.stderr.write(s + "\n")
#-------------------------------------------------------
if __name__ == "__main__":
sys.stderr.write("serto - LaTeX - preprocessor\n(c) Johannes Heinecke\n")
if len(sys.argv) < 2:
sys.stderr.write("usage:\n serto.py [-o] inputfile\n")
sys.stderr.write(" -o: for usage with an older version of LaTeX which cannot typeset right-to-left scripts elatex\n\n")
sys.exit(1)
else:
sys.stderr.write("\n")
import getopt
elatex = 1
optlist,comargs = getopt.getopt(sys.argv[1:], "")
for (o,a) in optlist:
if o == "-o":
elatex = 0
serto = Serto(elatex=elatex)
fp = open(comargs[0])
#mode = "latin"
mode = ["latin"]
z = fp.readline()
while (z):
#print "LINE", z,
if z.find("\usepackage[utf8]{inputenc}") > -1:
serto.usingUTF8 = True
#print 'QQQ',z, mode
if z[:-1] == "":
# must be on a single line (will be deleted)
if not elatex:
sys.stderr.write("using without the -e option (and elatex) may not work!\n")
#mode = "serto"
mode.append("serto")
print '{\\serto\\beginR %'
elif string.strip(z[:-1]) == "":
#mode = "latin"
del mode[-1]
#print '\\endR}%' # causes problems in last line
print '}%'
elif z[:-1] == "":
# must be on a single line (will be deleted)
if not elatex:
sys.stderr.write("using without the -e option (and elatex) may not work!\n")
#mode = "chaldean"
mode.append("chaldean")
print '{\\assyr\\beginR %'
elif string.strip(z[:-1]) == "":
#mode = "latin"
del mode[-1]
#print '\\endR}%' # causes problems in last line
print '}%'
elif z[:-1] == "":
#mode = "trans"
mode.append("trans")
print '{\\it %'
elif string.strip(z[:-1]) == "":
#mode = "latin"
del mode[-1]
print '}%'
# elif z[:-1] == "":
# mode = "sertotrans"
# print '{\\serto\\beginR %'
#
# elif string.strip(z[:-1]) == "":
# mode = "latin"
# print '\\endR}%'
else:
#print "mmm", mode, z
if mode[-1] == "latin":
#sys.stdout.write(serto.inlineS.sub(serto.inlineserto, z))
a = serto.inlineS.sub(serto.inlineserto, z)
b = serto.inlineT.sub(serto.inlinetrans, a)
c = serto.inlineST.sub(serto.inlinesertotrans, b)
d = serto.inlineC.sub(serto.inlinechaldean, c)
e = serto.inlineCT.sub(serto.inlinechaldeantrans, d)
sys.stdout.write(e)
elif mode[-1] == "trans":
print serto.transliterate(z)
else:
if z[:-1] == "": print "\n\\beginR",
else:
print serto.texify(z, mode[-1])
#print "rrrrr", mode
z = fp.readline()
fp.close()
sys.exit(serto.errct)