#!/bin/env python
__copyright__ =  """
Copyright (c) 2006, 2007, 2008, 2009 Adobe Systems Incorporated

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""

__doc__ = """
autohint. Wrapper for the Adobe auto-hinting C program. The autohintexe program
works on only one glyph at a time, expressed in the old 'bez' format, a
bezier-curve language much like PostScript Type 1, but with text operators and
coordinates.

The autohint script uses Just von Rossum's fontTools library to read and
write a font file. It extracts the PostScript T2 charstring program for
each selected glyph in turn. It converts this into a 'bez language
file, and calls the autohintexe program to work on this file. autohintexe returns a
hinted glyph in the form of a 'bez' file, and autohint converts this
back into Type2 and sticks it back into the font.  autohintexe will, if
allowed, change the outline to do things like put points at extrema.

In order to work on Type 1 font files as well as the OpenType/CFF fonts
supported directly by the fontTools library,  'autohint' uses the 'tx'
tool to convert the font to a 'CFF' font program, and then builds a
partial OpenType font for the fontTools to work with.
"""

__usage__ = """
autohint  AutoHinting program v1.26 Jan 13 2009
autohint -h
autohint -u
autohint [-g <glyph list>] [-gf <filename>] [-xg <glyph list>] [-xgf <filename>] [-cf path] [-a] [-logOnly] [-r] [-q] [-c] [-nf] [-ns] [-nb] [-o <output font path>]  font-path

Auto-hinting program for PostScript and OpenType/CFF fonts.
Copyright (c) 2006, 2007, 2008 Adobe Systems Incorporated
"""

__help__ = __usage__ + """

Takes a  list fonts, and an optional list of glyphs, and hints the
fonts. If the list of glyphs is supplied, the hinting is limited to the
specified glyphs.

Note that the hinting is better if the font's global alignment zones are
set usefully; at the very least, you should have entered values that
capture capital height, x height, ascender and descender heights, and
ascender and descender overshoot. The reports provided by the stemHist
tool are useful for choosing these.

By default, autothint will hint all glyphs in the font. Options allow you to
specify a sub-set of the glyphs for hinting.

Options:

-h	Print help

-u	Print usage

-g <glyphID1>,<glyphID2>,...,<glyphIDn>   Hint only the specified list
	of glyphs. Note that all glyph swill be written to the output file.
	The list must be comma-delimited. The glyph ID's may be glyphID's,
	glyph names, or glyph CID's. If the latter, the CID value must be
	prefixed with the string "/". There must be no white-space in the
	glyph list. Examples: autohint -g A,B,C,68 myFont autohint -g /1030,
	/434, /1535, 68 myCIDFont

	A range of glyphs may be specified by providing two names separated
	only by a hyphen: autohint -g zero-nine,onehalf myFont Note that the
	range will be resolved by expanding the glyph indices (GID)'s, not
	by alphabetic names.

-gf <file name>   Hint only the list of glyphs contained in the
	specified file, The file must contain a comma-delimited list of
	glyph identifiers. Any number of space, tab, and new-line characters
	are permitted between glyph names and commas.
	
-xg, -xgf  Same as -g and -gf, but will exclude the specified glyphs from hinting.

-cf <path>	AC will try and add counter hints to only a short hard-coded list of glyphs:
	vertical counters:  "m", "M", "T", "ellipsis"
	horizontal counters:  "element", "equivalence", "notelement", "divide".
	Counter hints work to keep open the space between stems open and equal in size.
	
	To extend this list, use the option "-cf" followed by a path to a text file.
	The text file must contain one record per line. A  record references one
	glyph, and should consist of  a single letter V or H, to indicate whether
	the counters should be vertical or horizontal, followd by a space or tab,
	followed by a glyph name. A maximum of 64 glyph names can be added to either
	the vertical or horizontal list. Example:
	   V	ffi
	   V	ffl
	   V	f_f_j
	
	Alternatively, if there is a file named "fontinfo" in the same directory as
	the source font, this script will look in that file for lines with the format:
	   VCounterChar ( <glyph name1>  <glyph name2> ... )
	   HCounterChar ( <glyph name1>  <glyph name2> ... )
	and add the referenced glyphs to the counter hint lists. Example line:
	   VCounterChar (ffi ffl f_f_j)
	
-logOnly  Do not change any outlines, but report warnings for all selected
	glyphs, including those already hinted. -q is ignored, -a is implied.
	
-q   quiet: will suppress comments from the auto-hinting library about
	recommended glyph outline changes.

-c   Permit changes to the glyph outline. When this is turned on , the autohint
	program will fix issues like not having a point at vertical and horizontal
	extremes.

-nf   Suppress generation of flex commands.

-ns   Suppress hint substitution. Do this only if you really want the
	smallest possible font file. This will use only one set of hints for
	the entire glyph.

-nb   Allow font to have to no stem widths or blue values specified.
	Without this option, autohint will complain and quit.

-o <output font path> Write hinted font to path. If not specified, then
	autohint will write the hinted output to the original font path
	name.

-hf Use history file. Will create it if it does not already exist.

-a   Hint all selected glyphs unconditionally. Has effect only if the history
	file is being used.

-r   re-hint glyphs, even if they already have hint info and are not referenced
	in the history file. Has effect only if the history file is being used.

autohint can maintain a history file, which allows you to avoid hinting glyphs
that have already been auto-hinted or manually hinted. When this is in use,
autohint will by default hint only  those glyphs that are not
already hinted, and also those glyphs which are hinted, but whose outlines have
changed since the last time autohint was run. autohint knows whether an outline
has changed by storing the outline in the history file whenever the glyph is
hinted, and then consulting the history file when it is asked to hint the glyph
again. By default, autohint does not maintain or use the history file, but this
can be turned on with an option.

When used, the history file is named "<PostScriptName>.plist", in the same
location as the parent font file. For each glyph, autohint stores a simplified
version of the outline coordinates. If this entry is missing for a glyph and the
glyph has hints, then autohint assumes it was manually hinted, and will by
default not hint it again. If the file is missing, autohint will assume that all
the glyphs were manually hinted, and you will have to use the option -a or -r to
hint any glyphs.


"""

# Methods:

# Parse args. If glyphlist is from file, read in entire file as single string,
# and remove all whitespace, then parse out glyph-names and GID's.

# For each font name:
#    use fontTools library to open font and extract CFF table. 
#    If error, skip font and report error.
#    filter specified glyph list, if any, with list of glyphs in the font.
#    open font plist file, if any. If not, create empty font plist.
#    build alignment zone string
#    for identifier in glyph-list:
#        Get T2 charstring for glyph from parent font CFF table. If not present, report and skip.
#        get new alignment zone string if FD array index (which font dict is used) has changed.
#        Convert to bez time
#        Build autohint point list string, used to tell if glyph has been changed since the last time it was hinted.
#        If requested, check against plist dict, and skip if glyph is 
#        already hinted or is manually hinted.
#        Call auto-hint library on bez string.
#        If change to the point list is permitted and happened, rebuild
#        autohint point list string.
#        Convert bez string to T2 charstring, and update parent font CFF.
#        add glyph hint entry to plist file
#    save font plist file.

import sys
import os
import re
import time
#from GSttLib import TTFont, getTableModule
from GSttLib import TTFont, getTableModule

#from GSttLib import TTFont, getTableModule
#from GSttLib import getTableModule
import plistlib
import warnings
warnings.simplefilter("ignore", RuntimeWarning) # supress waring about use of os.tempnam().

kACIDKey = "AutoHintKey"
# If we are not running from FDK.py, look down one level
# for the C library.
g = globals()
if (not "frozen" in g) and ("__file__" in g):
    ACLibPath = os.path.dirname(__file__)
    if os.name == 'nt':
            ACLibPath = os.path.join(ACLibPath, os.name)
    else:
            ACLibPath = os.path.join(ACLibPath, "osx")
    sys.path.append(ACLibPath)

from BezTools import *
import FDKUtils

kFontPlistSuffix  = ".plist"
kBlueValueKeys = [
			"BaselineOvershoot", # 0
			"BaselineYCoord", #1
			"CapHeight", #2
			"CapOvershoot", #3
			"LcHeight", #4
			"LcOvershoot", #5
			"AscenderHeight", #6
			"AscenderOvershoot", #7
			"FigHeight", #8
			"FigOvershoot", #9
			"Height5", #10
			"Height5Overshoot", #11
			"Height6", #12
			"Height6Overshoot", #13
			]

kOtherBlueValueKeys = [
			"Baseline5Overshoot", #0
			"Baseline5", #1
			"Baseline6Overshoot", #2
			"Baseline6", #3
			"SuperiorOvershoot", #4
			"SuperiorBaseline", #5
			"OrdinalOvershoot", #6
			"OrdinalBaseline", #7
			"DescenderOvershoot", #8
			"DescenderHeight", # 9
			]


class ACOptions:
	def __init__(self):
		self.inputPath = None
		self.outputPath = None
		self.glyphList = []
		self.excludeGlyphList = 0
		self.usePlistFile = 0
		self.hintAll = 0
		self.rehint = 0
		self.verbose = 1
		self.allowChanges = 0
		self.noFlex = 0
		self.noHintSub = 0
		self.allow_no_blues = 0
		self.hCounterGlyphs = []
		self.vCounterGlyphs = []
		self.counterHintFile = None
		self.logOnly = 0
		self.debug = 0
		
class ACOptionParseError(KeyError):
	pass

class ACFontError(KeyError):
	pass

class ACHintError(KeyError):
	pass

class FDKEnvironmentError(AttributeError):
	pass

def logMsg(*args):
	return
	for s in args:
		print s

def ACreport(*args):
	# long function used by the hinting library
	for arg in args:
		print arg,
	if arg[-1] != os.linesep:
		print

def CheckEnvironment():
	txPath = 'tx'
	txError = 0
	pipe = os.popen("'%s/%s' -u 2>&1" % (sys.path[0], txPath))
	report = pipe.read()
	pipe.close()
	if "Copyright" not in report:
			txError = 1
	
	if  txError:
		logMsg("Please re-install the FDK. The executable directory \"%s\" is missing the tool: < %s >." % (txPath ))
		logMsg("or the files referenced by the shell script is missing.")
		raise FDKEnvironmentError

	return txPath

def expandNames(glyphName):
	glyphRange = glyphName.split("-")
	if len(glyphRange) > 1:
		g1 = expandNames(glyphRange[0])
		g2 =  expandNames(glyphRange[1])
		glyphName =  "%s-%s" % (g1, g2)

	elif glyphName[0] == "/":
		glyphName = "cid" + glyphName[1:].zfill(5)

	elif glyphName.startswith("cid") and (len(glyphName) < 8):
		return "cid" + glyphName[3:].zfill(5)

	return glyphName

def parseGlyphListArg(glyphString):
	glyphString = re.sub(r"[ \t\r\n,]+",  ",",  glyphString)
	glyphList = glyphString.split(",")
	glyphList = map(expandNames, glyphList)
	glyphList =  filter(None, glyphList)
	return glyphList


def parseCounterHintData(path):
	hCounterGlyphList = []
	vCounterGlyphList = []
	gf = file(path, "rt")
	data = gf.read()
	gf.close()
	lines = re.findall(r"([^\r\n]+)", data)
	# strip blank and comment lines
	lines = filter(lambda line: re.sub(r"#.+", "", line), lines)
	lines = filter(lambda line: line.strip(), lines)
	for line in lines:
		fields = line.split()
		if (len(fields) != 2) or (fields[0] not in ["V", "v", "H", "h"]) :
			print "\tError: could not process counter hint line '%s' in file %s. Doesn't look like V or H followed by a tab or space, and then a glyph name." % (line, path)
		elif  fields[0] in ["V", "v"]:
			vCounterGlyphList.append(fields[1])
		else:
			hCounterGlyphList.append(fields[1])
	return hCounterGlyphList, vCounterGlyphList
	
	

def checkFontinfoFile(options):
	# Check if there ia a makeotf fontinfo file in the input font directory. If so, 
	# get any Vcounter or HCouunter glyphs from it.
	srcFontInfo = os.path.dirname(options.inputPath)
	srcFontInfo = os.path.join(srcFontInfo, "fontinfo")
	if os.path.exists(srcFontInfo):
		fi = open(srcFontInfo, "rU")
		data = fi.read()
		fi.close()
		counterGlyphLists = re.findall(r"([VH])CounterChars\s+\(\s*([^\)\r\n]+)\)", data)
		for entry in counterGlyphLists:
			glyphList = entry[1].split()
			if glyphList:
				if entry[0] == "V":
					options.vCounterGlyphs.extend(glyphList)
				else:
					options.hCounterGlyphs.extend(glyphList)
					
		if 	options.vCounterGlyphs or options.hCounterGlyphs:
			options.counterHintFile = srcFontInfo

def getOptions():
	options = ACOptions()
	i = 1
	numOptions = len(sys.argv)
	while i < numOptions:
		arg = sys.argv[i]
		if options.inputPath:
			raise ACOptionParseError("Option Error: All options must preceed the  input font path <%s>." % arg) 

		if arg == "-h":
			logMsg(__help__)
			command = "autohintexe -v"
			pipe = os.popen(command)
			report = pipe.read()
			pipe.close()
			#logMsg( report)
			raise ACOptionParseError
		elif arg == "-u":
			logMsg(__usage__)
			command = "autohintexe -v"
			pipe = os.popen(command)
			report = pipe.read()
			pipe.close()
			#logMsg( report)
			raise ACOptionParseError
		elif arg == "-hf":
			options.usePlistFile = 1
		elif arg == "-a":
			options.hintAll = 1
		elif arg == "-r":
			options.rehint = 1
		elif arg == "-q":
			options.verbose = 0
		elif arg == "-c":
			options.allowChanges = 1
		elif arg == "-nf":
			options.noFlex = 1
		elif arg == "-ns":
			options.noHintSub = 1
		elif arg == "-nb":
			options.allow_no_blues = 1
		elif arg in ["-xg", "-g"]:
			if arg == "-xg":
				options.excludeGlyphList = 1
			i = i +1
			glyphString = sys.argv[i]
			if glyphString[0] == "-":
				raise ACOptionParseError("Option Error: it looks like the first item in the glyph list following '-g' is another option.") 
			options.glyphList += parseGlyphListArg(glyphString)
		elif arg in ["-xgf", "-gf"]:
			if arg == "-xgf":
				options.excludeGlyphList = 1
			i = i +1
			filePath = sys.argv[i]
			if filePath[0] == "-":
				raise ACOptionParseError("Option Error: it looks like the the glyph list file following '-gf' is another option.") 
			try:
				gf = file(filePath, "rt")
				glyphString = gf.read()
				gf.close()
			except (IOError,OSError):
				raise ACOptionParseError("Option Error: could not open glyph list file <%s>." %  filePath) 
			options.glyphList += parseGlyphListArg(glyphString)
		elif arg == "-cf":
			i = i +1
			filePath = sys.argv[i]
			if filePath[0] == "-":
				raise ACOptionParseError("Option Error: it looks like the the counter hint glyph list file following '-cf' is another option.") 
			try:
				options.counterHintFile = filePath
				options.hCounterGlyphs, options.vCounterGlyphs = parseCounterHintData(filePath)
			except (IOError,OSError):
				raise ACOptionParseError("Option Error: could not open counter hint glyph list  file <%s>." %  filePath) 
		elif arg == "-logOnly":
			options.logOnly = 1
		elif arg == "-o":
			i = i +1
			options.outputPath = sys.argv[i]
		elif arg == "-d":
			options.debug = 1
		elif arg[0] == "-":
			raise ACOptionParseError("Option Error: Unknown option <%s>." %  arg) 
		else:
			options.inputPath = arg
		i  += 1
	if not options.inputPath:
		raise ACOptionParseError("Option Error: You must provide a font file path.") 
	if not options.outputPath:
		options.outputPath = options.inputPath

	if not os.path.exists(options.inputPath):
		raise ACOptionParseError("Option Error: The input font file path %s' does not exist." % (options.inputPath)) 

	checkFontinfoFile(options)
	
	if options.logOnly:
		options.verbose = 1
		options.hintAll = 1
		
	return options


def getGlyphID(glyphTag, fontGlyphList):
	glyphID = None
	try:
		glyphID = int(glyphTag)
		glyphName = fontGlyphList[glyphID]
	except IndexError:
		pass
	except ValueError:
		try:
			glyphID = fontGlyphList.index(glyphTag)
		except IndexError:
			pass
		except ValueError:
			pass
	return glyphID

def getGlyphNames(glyphTag, fontGlyphList, fontFileName):
	glyphNameList = []
	rangeList = glyphTag.split("-")
	prevGID = getGlyphID(rangeList[0], fontGlyphList)
	if prevGID == None:
		if len(rangeList) > 1:
			logMsg( "\tWarning: glyph ID <%s> in range %s from glyph selection list option is not in font. <%s>." % (rangeList[0], glyphTag, fontFileName))
		else:
			logMsg( "\tWarning: glyph ID <%s> from glyph selection list option is not in font. <%s>." % (rangeList[0], fontFileName))
		return None
	glyphNameList.append(fontGlyphList[prevGID])

	for glyphTag2 in rangeList[1:]:
		gid = getGlyphID(glyphTag2, fontGlyphList)
		if gid == None:
			logMsg( "\tWarning: glyph ID <%s> in range %s from glyph selection list option is not in font. <%s>." % (glyphTag2, glyphTag, fontFileName))
			return None
		for i in range(prevGID+1, gid+1):
			glyphNameList.append(fontGlyphList[i])
		prevGID = gid

	return glyphNameList

def filterGlyphList(options, fontGlyphList, fontFileName):
	# Return the list of glyphs which are in the intersection of the argument list and the glyphs in the font
	# Complain about glyphs in the argument list which are not in the font.
	if not options.glyphList:
		glyphList = fontGlyphList
	else:
		# expand ranges:
		glyphList = []
		for glyphTag in options.glyphList:
			glyphNames = getGlyphNames(glyphTag, fontGlyphList, fontFileName)
			if glyphNames != None:
				glyphList.extend(glyphNames)
		if options.excludeGlyphList:
			newList = filter(lambda name: name not in glyphList, fontGlyphList)
			glyphList = newList
	return glyphList



def  openFontPlistFile(psName, dirPath):
	# Find or create the plist file. This hold a Python dictionary in repr() form,
	# key: glyph name, value: outline point list
	# This is used to determine which glyphs are manually hinted, and which have changed since the last
	# hint pass. 
	fontPlist = None
	filePath = None
	isNewPlistFile = 1
	pPath1 = os.path.join(dirPath, psName + kFontPlistSuffix)
	if  os.path.exists(pPath1):
		filePath = pPath1
	else: # Crude approach to file length limitations. Since Adobe keeps face info in separate directories, I don't worry about name collisions.
		pPath2 = os.path.join(dirPath, psName[:-len(kFontPlistSuffix)] + kFontPlistSuffix)
		if os.path.exists(pPath2):
			filePath = pPath2
	if not filePath:
		filePath = pPath1
	else:
		try:
			fontPlist = plistlib.Plist.fromFile(filePath)
			isNewPlistFile = 0
		except (IOError, OSError):
			raise ACFontError("\tError: font plist file exists, but coud not be read <%s>." % filePath)		
		except:
			raise ACFontError("\tError: font plist file exists, but coud not be parsed <%s>." % filePath)
		
	if  fontPlist == None:
		fontPlist =  plistlib.Plist()
	if not fontPlist.has_key(kACIDKey):
		fontPlist[kACIDKey] = {}
	return fontPlist, filePath, isNewPlistFile

def getLanguageGroup(pTopDict, fdIndex):
	if  hasattr(pTopDict, "FDArray"):
		pDict = pTopDict.FDArray[fdIndex]
	else:
		pDict = pTopDict
	privateDict = pDict.Private
	if  hasattr(privateDict, "LanguageGroup"):
		lg = privateDict.LanguageGroup
	else:
		lg = 0
	return 0
	


def getFontInfo(pTopDict, fontPSName, options, fdIndex = 0):
	# The AC library needs the global font hint zones and standard stem widths. Format them
	# into a single  text string.
	# The text format is arbitrary, inherited from very old software, but there is no real need to change it.
	if  hasattr(pTopDict, "FDArray"):
		pDict = pTopDict.FDArray[fdIndex]
	else:
		pDict = pTopDict
	privateDict = pDict.Private

  	fontinfo=[]
	fontinfo.append("OrigEmSqUnits")
	upm = int(1/pDict.FontMatrix[0])
	fontinfo.append(str(upm) ) # OrigEmSqUnits

	fontinfo.append("FontName")
	if  hasattr(pTopDict, "FontName"):
		fontinfo.append(pDict.FontName ) # FontName
	else:
		fontinfo.append(fontPSName )

	if not hasattr(privateDict, "BlueValues"):
		if options.allow_no_blues:
			privateDict.BlueValues = [ -250, -250, 1100, 1100 ] #  relatively safe values
		else:
			raise	ACFontError("Error: font has no BlueValues array!")
	
	numBlueValues = len(privateDict.BlueValues)
	if numBlueValues < 4:
		raise	ACFontError("Error: font must have at least four values in it's BlueValues array for AC to work!")
	blueValues = privateDict.BlueValues[:]
	# The first pair only is a bottom zone,, where the first value is the overshoot position;
	# the rest are top zones, and second value of the pair is the overshoot position.
	blueValues[0] = blueValues[0] - blueValues[1]
	for i in range(3, numBlueValues,2):
		blueValues[i] = blueValues[i] - blueValues[i-1]
		
	blueValues = map(str, blueValues)
	numBlueValues = min(numBlueValues, len(kBlueValueKeys))
	for i in range(numBlueValues):
		fontinfo.append(kBlueValueKeys[i])
		fontinfo.append(blueValues[i])

	#print numBlueValues
	#for i in range(0, len(fontinfo),2):
	#	print fontinfo[i], fontinfo[i+1]

	if hasattr(privateDict, "OtherBlues"):
		# For all OtherBlues, the pairs are bottom zones, and the first value of each pair is the overshoot position.
		i = 0
		numBlueValues = len(privateDict.OtherBlues)
		blueValues = privateDict.OtherBlues[:]
		blueValues.sort()
		for i in range(0, numBlueValues,2):
			blueValues[i] = blueValues[i] - blueValues[i+1]
		blueValues = map(str, blueValues)
		numBlueValues = min(numBlueValues, len(kOtherBlueValueKeys))
		for i in range(numBlueValues):
			fontinfo.append(kOtherBlueValueKeys[i])
			fontinfo.append(blueValues[i]) 
	
	if hasattr(privateDict, "StemSnapV"):
		vstems = privateDict.StemSnapV
	elif hasattr(privateDict, "StdVW"):
		vstems = [privateDict.StdVW]
	else:
		if options.allow_no_blues:
			vstems =  [1]
		else:
			raise	ACFontError("Error: font has neither StemSnapV nor StdVW!")
	vstems.sort()
	if (len(vstems) == 0) or ((len(vstems) == 1) and (vstems[0] < 1) ):
		vstems =  ["1"] # dummy value that will allow PyAC to run
		logMsg("Warning: There is no value or 0 value for DominantV.")
	vstems = repr(vstems)
	fontinfo.append("DominantV")
	fontinfo.append(vstems)
	fontinfo.append("StemSnapV")
	fontinfo.append(vstems)

	if hasattr(privateDict, "StemSnapH"):
		hstems = privateDict.StemSnapH
	elif hasattr(privateDict, "StdHW"):
		hstems = [privateDict.StdHW]
	else:
		if options.allow_no_blues:
			hstems = [1]
		else:
			raise	ACFontError("Error: font has neither StemSnapH nor StdHW!")
	hstems.sort()
	if (len(hstems) == 0) or ((len(hstems) == 1) and (hstems[0] < 1) ):
		hstems =  [1] # dummy value that will allow PyAC to run
		logMsg("Warning: There is no value or 0 value for DominantH.")
	hstems = repr(hstems)
	fontinfo.append("DominantH")
	fontinfo.append(hstems)
	fontinfo.append("StemSnapH")
	fontinfo.append(hstems)
	fontinfo.append("FlexOK")
	if options.noFlex:
		fontinfo.append("false")
	else:
		fontinfo.append("true")
	fontinfo.append(" ")

	# Add candidate lists for counter hints, if any.
	if options.vCounterGlyphs:
		temp = " ".join(options.vCounterGlyphs)
		fontinfo.append("VCounterChars")
		fontinfo.append("( %s )" % (temp))
	if options.hCounterGlyphs:
		temp = " ".join(options.vCounterGlyphs)
		fontinfo.append("HCounterChars")
		fontinfo.append("( %s )" % (temp))
	fontinfoStr = " ".join(fontinfo) + os.linesep
	return fontinfoStr

flexPatthern = re.compile(r"preflx1[^f]+preflx2[\r\n](-*\d+\s+-*\d+\s+-*\d+\s+-*\d+\s+-*\d+\s+-*\d+\s+)(-*\d+\s+-*\d+\s+-*\d+\s+-*\d+\s+-*\d+\s+-*\d+\s+).+?flx([\r\n])",  re.DOTALL)
commentPattern = re.compile(r"[^\r\n]*%[^\r\n]*[\r\n]")
hintGroupPattern = re.compile(r"beginsubr.+?newcolors[\r\n]", re.DOTALL)
whiteSpacePattern = re.compile(r"\s+", re.DOTALL)

def makeACIdentifier(bezText):
	# Get rid of all the hint operators and their args 
	# collapse flex to just the two rct's
	bezText = commentPattern.sub("", bezText)
	bezText = hintGroupPattern.sub("", bezText)
	bezText = flexPatthern.sub( "\1 rct\3\2 rct\3", bezText)
	bezText = whiteSpacePattern.sub("", bezText)
	return bezText

def openFile(path, txPath):
	# If input font is  CFF or PS, build a dummy ttFont in memory for use by autohint.
	# return ttFont, and flag if is a real OTF font Return flag is 0 if OTF, 1 if CFF, and 2 if PS/
	fontType  = 0 # OTF
	tempPath = os.path.dirname(path)
	tempPathCFF  = os.path.join(tempPath, "temp.ac.cff")
	try:
		ff = file(path, "rb")
		data = ff.read(10)
		ff.close()
	except (IOError, OSError):
		logMsg("Failed to open and read font file %s." % path)

	if data[:4] == "OTTO": # it is an OTF font, can process file directly
		try:
			ttFont = TTFont(path)
		except (IOError, OSError):
			raise ACFontError("Error opening or reading from font file <%s>." % path)
		except TTLibError:
			raise ACFontError("Error parsing font file <%s>." % path)
		
		try:
			cffTable = ttFont["CFF "]
		except KeyError:
			raise ACFontError("Error: font is not a CFF font <%s>." % fontFileName)

		return ttFont, fontType

	# It is not an OTF file.
	if (data[0] == '\1') and (data[1] == '\0'): # CFF file
		fontType = 1
		tempPathCFF = path
	elif not "%" in data:
		#not a PS file either
		logMsg("Font file must be a PS, CFF or OTF  fontfile: %s." % path)
		raise ACFontError("Font file must be PS, CFF or OTF file: %s." % path)

	else:  # It is a PS file. Convert to CFF.	
		fontType =  2
		command="'%s/%s'  -cff +b  \"%s\" \"%s\" 2>&1" % (sys.path[0], txPath, path, tempPathCFF)
		pipe = os.popen(command)
		report = pipe.read()
		#print "report", report
		pipe.close()
		if "fatal" in report:
			logMsg( command )
			logMsg("Attempted to convert font %s  from PS to a temporary CFF data file." % path)
			logMsg(report)
			raise ACFontError("Failed to convert PS font %s to a temp CFF font." % path)

	# now package the CFF font as an OTF font for use by autohint.
	#print "tempPathCFF", tempPathCFF
	ff = file(tempPathCFF, "rb")
	data = ff.read()
	ff.close()
	try:
		ttFont = TTFont()
		cffModule = getTableModule('CFF ')
		cffTable = cffModule.table_C_F_F_('CFF ')
		ttFont['CFF '] = cffTable
		cffTable.decompile(data, ttFont)
	except:
		import traceback
		traceback.print_exc()
		logMsg("Attempted to read font %s  as CFF." % path)
		raise ACFontError("Error parsing font file <%s>." % fontFileName)
	return ttFont, fontType


def saveFontFile(ttFont, inputPath, outputPath, fontType, txPath):
	overwriteOriginal = 0
	if inputPath == outputPath:
		overwriteOriginal = 1
	tempPath = os.path.dirname(inputPath)
	tempPath = os.path.join(tempPath, "temp.ac.cff")

	if fontType == 0: # OTF
		if overwriteOriginal:
			ttFont.save(tempPath)
			ttFont.close()
			if os.path.exists(inputPath):
				try:
					os.remove(inputPath)
					os.rename(tempPath, inputPath)
				except (OSError, IOError):
					logMsg("Error: could not overwrite original font file path '%s'. Hinted font file path is '%s'." % (inputPath, tempPath))
		else:
			ttFont.save(outputPath)
			ttFont.close()

	else:
		data = ttFont["CFF "].compile(ttFont)
		if fontType == 1: # CFF
			if overwriteOriginal:
				tf = file(tempPath, "wb")
				tf.write(data)
				tf.close()
				os.rename(tempPath, inputPath)
			else:
				tf = file(outputPath, "wb")
				tf.write(data)
				tf.close()

		elif  fontType == 2: # PS.
			tf = file(tempPath, "wb")
			tf.write(data)
			tf.close()
			if overwriteOriginal:
				command="'%s/%s'  -t1 \"%s\" \"%s\" 2>&1" % (sys.path[0], txPath, tempPath, inputPath)
			else:
				command="'%s/%s'  -t1 \"%s\" \"%s\" 2>&1" % (sys.path[0], txPath, tempPath, outputPath)
			pipe = os.popen(command)
			report = pipe.read()
			pipe.close()
			#logMsg(report)
			if "fatal" in report:
				raise IOError("Failed to convert hinted font temp file with tx %s" % tempPath)
			if overwriteOriginal:
				os.remove(tempPath)
			# remove temp file left over from openFile.
			if os.path.exists(tempPath):
				try:
					os.remove(tempPath)
				except (IOError,OSError):
					logMsg("Error: could not delete temporary font file path '%s'." % (tempPath))
					

def hintFile(options, txPath):
	#    use fontTools library to open font and extract CFF table. 
	#    If error, skip font and report error.
	path = options.inputPath
	fontFileName = os.path.basename(path)
	logMsg("Hinting font %s. Start time: %s." % (path, time.asctime()))

	ttFont, fontType = openFile(path, txPath)
	fontGlyphList = ttFont.getGlyphOrder()
	
	try:
		cffTable = ttFont["CFF "]
	except KeyError:
		raise ACFontError("Error: font is not a CFF font <%s>." % fontFileName)

	#   filter specified list, if any, with font list.
	glyphList = filterGlyphList(options, fontGlyphList, fontFileName)
	if not glyphList:
		raise ACFontError("Error: selected glyph list is empty for font <%s>." % fontFileName)

	tempBaseName = os.tempnam()
	tempBez = tempBaseName + ".bez"
	tempBezNew = tempBez + ".new"
	tempFI = tempBaseName + ".fi"
	
	#print "tempBaseName", tempBaseName
	psName = cffTable.cff.fontNames[0]
	
	if (not options.logOnly) and options.usePlistFile:
		fontPlist, fontPlistFilePath, isNewPlistFile = openFontPlistFile(psName, os.path.dirname(path))
		if isNewPlistFile and  not (options.hintAll or options.rehint):
			logMsg("No hint info plist file was found, so all glyphs are unknown to autohint. To hint all glyphs, run autohint again with option -a to hint all glyphs unconditionally.")
			logMsg("Done with font %s. End time: %s." % (path, time.asctime()))
			return

	# Check counter glyphs, if any.
	if options.hCounterGlyphs or options.vCounterGlyphs:
		missingList = filter(lambda name: name not in fontGlyphList, options.hCounterGlyphs + options.vCounterGlyphs)
		if missingList:
			logMsg( "\tError: glyph named in counter hint list file '%s' are not in font: %s" % (options.counterHintFile, missingList) )

	#    build alignment zone string
	topDict =  cffTable.cff.topDictIndex[0]
	fontinfo = getFontInfo(topDict, psName, options)
	# print "___fontinfo:", fontinfo
	fp = open(tempFI, "wt")
	fp.write(fontinfo)
	fp.close()
	lg = getLanguageGroup(topDict, 0)
	useStem3 = 0 == lg
	
	#    for identifier in glyph-list:
	# 	Get charstring.
	charStrings = topDict.CharStrings
	charStringIndex = charStrings.charStringsIndex
	removeHints = 1
	isCID = hasattr(topDict, "FDSelect")
	lastFDIndex = 0
	reportCB = ACreport
	anyGlyphChanged = 0
	pListChanged = 0
	if isCID:
		options.noFlex = 1
		
	if options.verbose:
		verboseArg = ""
	else:
		verboseArg = " -q" 
		dotCount = 0
		curTime = time.time()

	if options.allowChanges:
		suppressEditArg = ""
	else:
		suppressEditArg = " -e"

	if options.noHintSub:
		supressHintSubArg = " -n"
	else:
		supressHintSubArg = ""

		
	for name in glyphList:
		prevACIdentifier = None
		# get new fontinfo string if FD array index has changed, as
		# as each FontDict has different alignment zones.
		if isCID:
			gid = ttFont.getGlyphID(name)
			fdIndex = topDict.FDSelect[gid]
			if not fdIndex == lastFDIndex:
				lastFDIndex = fdIndex
				fontinfo = getFontInfo(topDict, psName, options, fdIndex)
				fp = open(tempFI, "wt")
				fp.write(fontinfo)
				fp.close()
				lg = getLanguageGroup(topDict, fdIndex)
				useStem3 = 0 == lg
		else:
			gid = charStrings.charStrings[name]

		# 	Convert to bez format
		t2CharString = charStringIndex[gid]
		try:
			bezString, hasHints, t2Wdth = convertT2GlyphToBez(t2CharString, removeHints)
			#bezString = "%% %s%s" % (name, os.linesep) + bezString
		except SEACError:
			logMsg( "\tSkipping %s; can't process 'seac' composite glyphs." % (name) )
			continue # skip 'seac' composite glyphs.
		# 	Build autohint point list identifier
		ACidentifier = "" # makeACIdentifier(bezString)
		if len(bezString) < 5:
			# skip empty glyphs.
			continue

		# if "mt" not in ACidentifier:
		# 	# skip empty glyphs.
		# 	continue
		
		oldBezString = ""
		oldHintBezString = ""
		
		if (not options.logOnly) and options.usePlistFile:
			# If the glyph is not in the plist file, then we skip it unless kReHintUnknown is set.
			# If the glyph is in the plist file and the outline has changed, we hint it. 
			try:
				(prevACIdentifier, ACtime, oldBezString, oldHintBezString) =  fontPlist[kACIDKey][name]
			except KeyError:
				pListChanged = 1 # Didn't hane an entry in tepList file, so we will add one.
				if hasHints and not (options.rehint):
					# Glyphs is hinted, but not referenced in the plist file. Skip it unless options.rehint is se
					if  not isNewPlistFile:
						# Comment only if there is a plist file; otherwise, we'd be complaining for almost every glyph.
						logMsg("\t%s Skipping glyph - it has hints, but it is not in the hint info plist file." % name)
					continue
			
			# if prevACIdentifier and (prevACIdentifier == ACidentifier): # there is an entry, in the plist file and it matches what's in the font.
			# 	if hasHints and (not options.hintAll):
			# 		continue
			else:
				pListChanged = 1
		
		if options.verbose:
			logMsg("Hinting %s." %name)
		else:
			newTime = time.time()
			if (newTime - curTime) > 1:
				#print ".",
				sys.stdout.flush()
				curTime = newTime
				dotCount +=1
			if dotCount > 40:
				dotCount = 0
				#print ""

		anyGlyphChanged = 1
		# 	Call auto-hint library on bez string.
		bp = open(tempBez, "wt")
		bp.write(bezString)
		bp.close()
		
		# print "oldBezString", oldBezString
		#print ""
		#print "bezString", bezString
		
		if oldBezString != "" and oldBezString == bezString:
			newBezString = oldHintBezString
			#print "Use cached Outline:", oldHintBezString
		else:
			if os.path.exists(tempBezNew):
				os.remove(tempBezNew)
			#print "__oldHintBezString", oldHintBezString
			# test ???
			#bp = open(tempBezNew, "wt")
			#bp.write(bezString)
			#bp.close()
			# test ???
			#print "Hint Test"
			command = "'%s/autohintexe' %s%s%s -s .new -f \"%s\" \"%s\"" % (sys.path[0], verboseArg, suppressEditArg, supressHintSubArg, tempFI, tempBez)
			
			#if  options.debug:
			# print "__command:", command
			# test ????
			p = os.popen(command)
			log = p.read()
			p.close()
			# test ????
			#if log:
			#	print log

			if os.path.exists(tempBezNew):
				bp = open(tempBezNew, "rt")
				newBezString = bp.read()
				bp.close()
				if options.debug:
					print "Wrote AC fontinfo data file to", tempFI
					print "Wrote AC output bez file to", tempBezNew
				else:
					os.remove(tempBezNew)
			else:
				newBezString = None
		
		#print "newBezString", newBezString
		
		if not newBezString:
			print "Error - failure in processing outline data"
			continue
			
		if not (("ry" in newBezString[:200]) or ("rb" in newBezString[:200]) or ("rm" in newBezString[:200]) or ("rv" in newBezString[:200])):
			print "No hints added!"

		if options.logOnly:
			continue
			
		# 	Convert bez to charstring, and update CFF.
		t2Program = [t2Wdth] + convertBezToT2(newBezString,  useStem3)
		if t2Program:
			t2CharString.program = t2Program
		else:
			logMsg("\t%s Skipping glyph - error in processing hinted outline." % (name))
			continue
		
		
		
		if options.usePlistFile:
			bezString, hasHints, t2Wdth = convertT2GlyphToBez(t2CharString, 1)
			
			#print "bezString", bezString
			#print "hasHints", hasHints
			#print "t2Wdth", t2Wdth
			ACidentifier = "" #makeACIdentifier(bezString)
			# add glyph hint entry to plist file
			# if options.allowChanges:
			# 	if prevACIdentifier and (prevACIdentifier != ACidentifier):
			# 		logMsg("\t%s Glyph outline changed" % name)
			
			fontPlist[kACIDKey][name] = (ACidentifier, time.asctime(), bezString, newBezString )
			

	if not options.verbose:
		print "" # print final new line after progress dots.

	if  options.debug:
		print "Wrote input AC bez file to", tempBez
	else:
		if os.path.exists(tempBez):
			os.remove(tempBez)
		if os.path.exists(tempBezNew):
			os.remove(tempBezNew)
		if os.path.exists(tempFI):
			os.remove(tempFI)
		
	if not options.logOnly:
		if anyGlyphChanged:
			logMsg("Saving font file with new hints..." + time.asctime())
			saveFontFile(ttFont, options.inputPath , options.outputPath , fontType, txPath)
		else:
			if options.usePlistFile:
				if options.rehint:
					logMsg("No new hints. All glyphs had hints that matched the hint record file %s." % (fontPlistFilePath))
				else:
					logMsg("No new hints. All glyphs were already hinted.")
			else:
				logMsg("No glyphs were hinted.")
	if options.usePlistFile and (anyGlyphChanged or pListChanged):
		#  save font plist file.
		fontPlist.write(fontPlistFilePath)
	
	logMsg("Done with font %s. End time: %s." % (path, time.asctime()))

def main():

	try:
		txPath = CheckEnvironment()
	except FDKEnvironmentError,e:
		logMsg(e)
		return

	try:
		options = getOptions()
	except ACOptionParseError,e:
		logMsg(e)
		return

	# verify that all files exist.
	if not os.path.isfile(options.inputPath):
		logMsg("File does not exist: <%s>." % options.inputPath)
	else:
		try:
			hintFile(options, txPath)
		except (ACFontError),e:
			logMsg("\t%s" % e)
	return


if __name__=='__main__':
	main()
	

