summaryrefslogtreecommitdiff
path: root/fonts/utilities/otfinst/otfinst.py
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
committerNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
commite0c6872cf40896c7be36b11dcc744620f10adf1d (patch)
tree60335e10d2f4354b0674ec22d7b53f0f8abee672 /fonts/utilities/otfinst/otfinst.py
Initial commit
Diffstat (limited to 'fonts/utilities/otfinst/otfinst.py')
-rw-r--r--fonts/utilities/otfinst/otfinst.py748
1 files changed, 748 insertions, 0 deletions
diff --git a/fonts/utilities/otfinst/otfinst.py b/fonts/utilities/otfinst/otfinst.py
new file mode 100644
index 0000000000..cf79ab3ccd
--- /dev/null
+++ b/fonts/utilities/otfinst/otfinst.py
@@ -0,0 +1,748 @@
+#!/usr/bin/env python
+
+# otfinst version 1.0.3 2 January 2007
+# http://www.ece.ucdavis.edu/~jowens/code/otfinst/
+# Formerly known as otftex_install
+#
+# usage: As arguments, takes OTF font files or directories where the
+# OTF font files are located. Configuration is within this file.
+
+# Copyright (c) 2005, 2006, John Owens <jowens at ece.ucdavis.edu>,
+# University of California, Davis. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# Neither the name of the University of California nor the names of
+# its contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission. THIS
+# SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+import sys, os, re, math, time, datetime, operator, glob
+
+#######################################
+# BEGIN USER-SETTABLE VARIABLES
+#######################################
+
+# which encoding are we using?
+encoding = 'LY1'
+
+# "encoding" (above) should have a dict entry in "encodings" that specifies
+# the two-letter berry designation for the encoding and the command-line
+# options to otftotfm that work for the encoding
+encodings = { 'LY1':
+ { 'berryencoding': '8y',
+ 'commandline': '-e texnansx',
+ },
+ }
+
+# kpsewhich will look up the right TEXMFHOME directory, but within that,
+# where do local fonts go?
+localfontsdir = '/tex/latex/localfonts/'
+
+# which OpenType options do we support in this script?
+# add more to optionList if you want the script to support them
+optionList = [
+ # in optionList, each element is itself a list
+ # we generate options by always picking one choice from each element
+ # and taking all permutations of those choices
+ ['kern'], # always selected
+ ['liga'],
+ ['lnum', 'onum', 'swsh', 'sinf', 'sups', 'pnum', 'fakenum'], # one of these
+ ['smcp', ''], # either smcp or nothing
+ ]
+
+# categorize the OpenType options above
+# globalOptionList contains options that we should apply to all fonts
+globalOptionList = ['kern', 'liga']
+# newFontFamilyOptionList has options that create new font families
+newFontFamilyOptionList = ['lnum', 'onum', 'swsh', 'sinf', 'sups', 'pnum',
+ 'fakenum']
+# weightOptionsList contains options that modify the weight
+weightOptionList = []
+# widthOptionsList contains options that modify the width
+widthOptionList = []
+# variantOptionsList contains options that modify the variant
+variantOptionList = ['smcp']
+# fakeOptionList contains options that aren't real OpenType options
+fakeOptionList = [
+ 'fakenum' # fakenum is if no ?num is available
+ ]
+
+# options and their Berry encodings
+# contains both:
+# - features (4 letters) and their mappings (lnum, onum, smcp)
+# (found in otfinfo --features)
+# - subfamily attributes (italic, bold, etc.)
+# (found in otfinfo --info, subfamily field)
+optionMappings = {
+ # OpenType command line options
+ 'lnum' : 'x',
+ 'onum' : 'j',
+ 'fakenum' : '', # we manually put this in if lnum and
+ # onum are not present so we get
+ # something out
+ 'swsh' : 'w',
+ 'sinf' : '0',
+ 'sups' : '1',
+ 'pnum' : '2',
+ 'smcp' : 'sc', # also a variant
+ # weights
+ 'thin' : 'ul',
+ 'extralight' : 'el',
+ 'light' : 'l',
+ 'book' : 'm',
+ 'medium' : 'mb',
+ 'demibold' : 'db',
+ 'semibold' : 'sb',
+ 'bold' : 'b',
+ 'black' : 'eb',
+ 'heavy' : 'eb',
+ # widths
+ 'condensed' : 'c',
+ 'cond' : 'c',
+ 'narrow' : 'n',
+ 'semicondensed' : 'sc',
+ 'semiextended' : 'sx',
+ 'extended' : 'x',
+ # variants
+ 'italic' : 'it',
+ 'slanted' : 'sl',
+ 'oblique' : 'sl',
+ 'outline' : 'ol',
+ # regular is the default
+ 'regular' : '',
+ }
+
+# each font has a 3-letter designation
+# it's the first 3 letters from
+# http://www.tex.ac.uk/tex-archive/info/fontname/adobe.map
+# font names are what's returned from "otfinfo --family fontfile.otf"
+berryname = { 'Minion Pro' : 'pmn',
+ 'Myriad Pro' : 'pmy',
+ 'Adobe Garamond Pro' : 'pag',
+ 'Adobe Caslon Pro' : 'pac',
+ 'Adobe Jenson Pro' : 'paj',
+ 'Utopia Std' : 'put',
+ 'Warnock Pro' : 'pwp',
+ 'Kepler Std' : 'pkp',
+ 'Prestige Elite Std' : 'ppe',
+ 'Calibri' : 'jk0',
+ 'Cambria' : 'jk1',
+ 'Candara' : 'jk2',
+ 'Consolas' : 'jk3',
+ 'Constantia' : 'jk4',
+ 'Corbel' : 'jk5',
+ 'Arno Pro' : 'pa0',
+ 'ScalaSansPro' : '0sa',
+ 'Hypatia Sans Pro' : 'phy',
+ }
+
+# if you would like to scale the font by default, set it here as a float
+scaled = { 'Minion Pro' : 1.0,
+ }
+
+#######################################
+# END USER-SETTABLE VARIABLES
+#######################################
+
+#######################################
+# BEGIN UTILITY FUNCTIONS
+#######################################
+
+def letterize(s):
+ s = s.replace('0', 'zero')
+ s = s.replace('1', 'one')
+ s = s.replace('2', 'two')
+ s = s.replace('3', 'three')
+ s = s.replace('4', 'four')
+ s = s.replace('5', 'five')
+ s = s.replace('6', 'six')
+ s = s.replace('7', 'seven')
+ s = s.replace('8', 'eight')
+ s = s.replace('9', 'nine')
+ return s
+
+glyfRE = re.compile("glyf")
+CFFRE = re.compile("CFF")
+# User can redefine if necessary; used to prune candidate list of fonts
+def isValidFontFile(filename):
+ # return os.system("otfinfo --info -q %s" % filename) == 0
+ fields = os.popen("otfinfo -qt '%s'" % filename, 'r').readlines()
+ # otfinfo -qt returns the fields in the font file; "glyf" means TrueType
+ # and "CFF" means PostScript
+ for line in fields:
+ if glyfRE.search(line):
+ print "%s (TTF)" % filename
+ return True
+ if CFFRE.search(line):
+ print "%s (PS)" % filename
+ return True
+ return False
+
+# Eddie Kohler's shell script for determining OTF type
+# otfflavor () {
+# # Returns 0 for not OTF, 1 for CFF-flavored, 2 for TTF-flavored
+# otfinfo -qt $1 | awk '/glyf/ { exit 2 }
+# /CFF/ { exit 1 }'
+# }
+
+def generateFontfiles(l):
+ # Get all candidate files. This works as follows:
+ # 1) glob the input list, resulting in a list
+ # 2)a) if anything in that list is a file, add it to the fontfiles list
+ # 2)b) if anything in that list is a dir, add its subfiles to
+ # the fontfiles list
+ # start off assuming everything is a dir
+ gl = []
+ fontfiles = []
+ for item in l:
+ gl += glob.glob('%s*' % item)
+ for item in gl:
+ if os.path.isdir(item):
+ for file in os.listdir(item):
+ fontfiles.append(os.path.join(item, file))
+ if os.path.isfile(item):
+ fontfiles.append(item)
+ print "Installing following font files:"
+ fontfiles = filter(isValidFontFile, fontfiles)
+ return fontfiles
+
+# http://www.microsoft.com/typography/otspec/recom.htm
+# We recommend using name ID's 8-12, to identify manufacturer,
+# designer, description, URL of the vendor, and URL of the designer.
+# URL's must contain the protocol of the site: for example, http:// or
+# mailto: or ftp://. The OpenType font properties extension can
+# enumerate this information to the users.
+#
+# The Subfamily string in the 'name' table should be used for variants
+# of weight (ultra light to extra black) and style (oblique/italic or
+# not). So, for example, the full font name of "Helvetica Narrow
+# Italic" should be defined as Family name "Helvetica Narrow" and
+# Subfamily "Italic". This is so that Windows can group the standard
+# four weights of a font in a reasonable fashion for
+# non-typographically aware applications which only support
+# combinations of "bold" and "italic."
+#
+# The Full font name string usually contains a concatenation of
+# strings 1 and 2. However, if the font is 'Regular' as indicated in
+# string 2, then use only the family name contained in string 1. This
+# is the font name that Windows will expose to users.
+
+otfinfoversionRE = re.compile("([0-9]+\.[0-9]+)")
+otfinfoRE = re.compile("^(.*):\s+(.*)$")
+urlRE = re.compile("(\w*)\.(com|net|org|edu|de|fr|co\.uk)")
+opticalRE = re.compile("size range \(([\d.]+) pt, ([\d.]+) pt\]")
+
+# list-to-string and string-to-list primitives (useful when we want to
+# index into a hash with a list - we use a string instead)
+sep = '-'
+def l2s(l):
+ return sep.join(l)
+
+def s2l(s):
+ if not s:
+ return []
+ return s.split(sep)
+
+# otfinfo requires version 2.38 or higher of otfinfo (which supports --info)
+def checkOTFInfoVersion():
+ otfiv = os.popen("otfinfo --version").read()
+ m = otfinfoversionRE.search(otfiv)
+ if float(m.group(1)) < 2.38:
+ sys.stderr.write("Your otfinfo version is currently %s.\nPlease upgrade your otfinfo version to at least 2.38.\n" % m.group(1))
+ sys.exit()
+
+# given a font file, populates fonthash for that font file
+# calls classifyFont on each font file too
+def addToFonthash(font):
+ postScriptName = os.popen("otfinfo --postscript-name '%s'" % font).read()[:-1]
+ fonthash[postScriptName] = {}
+ fonthash[postScriptName]['filename'] = font
+ info = os.popen("otfinfo --info '%s'" % font).read()[:-1]
+ for line in info.split('\n'):
+ m = otfinfoRE.search(line)
+ if m:
+ if (m.group(1) == 'Family') or (m.group(1) == 'Preferred family'):
+ # 'preferred family' preferred over 'family'; relies on it coming last
+ # which it should under otfinfo
+ fonthash[postScriptName]['family'] = m.group(2)
+ elif (m.group(1) == 'Subfamily') or (m.group(1) == 'Preferred subfamily'):
+ # 'preferred subfamily' preferred over 'subfamily'; relies on
+ # it coming last which it should under otfinfo
+ # this RE separates both 'Aa Bb' and 'AaBb' into ['aa', 'bb']
+ fonthash[postScriptName]['subfamily'] = \
+ re.sub("(?<=[a-z])(?=[A-Z])", " ", m.group(2)).lower().split()
+ if 'roman' in fonthash[postScriptName]['subfamily']:
+ # replace 'roman' with 'regular'
+ roman_id = fonthash[postScriptName]['subfamily'].index('roman')
+ fonthash[postScriptName]['subfamily'][roman_id] = 'regular'
+ while 'extra' in fonthash[postScriptName]['subfamily']:
+ # combine 'extra' with next element, delete 'extra'
+ extra_id = fonthash[postScriptName]['subfamily'].index('extra')
+ fonthash[postScriptName]['subfamily'][extra_id+1] = \
+ 'extra' + fonthash[postScriptName]['subfamily'][extra_id+1]
+ fonthash[postScriptName]['subfamily'].remove('extra')
+ elif m.group(1) == 'Vendor URL':
+ fonthash[postScriptName]['vendor'] = urlRE.search(m.group(2)).group(1)
+ if 'vendor' not in fonthash[postScriptName].keys():
+ fonthash[postScriptName]['vendor'] = 'generic'
+ features = os.popen("otfinfo --features '%s'" % font).read()[:-1]
+ opticalSize = os.popen("otfinfo --optical-size '%s'" % font).read()[:-1]
+ m = opticalRE.search(opticalSize)
+ if m:
+ # convert from OTF's (exclusive, inclusive] to TeX's [inclusive, exclusive)
+ # OTF measures in decipoints, so add 0.1 to each size
+ fonthash[postScriptName]['opticalSize'] = [str(float(m.group(1)) + 0.1),
+ str(float(m.group(2)) + 0.1)]
+ else:
+ fonthash[postScriptName]['opticalSize'] = []
+ fonthash[postScriptName]['weight'] = []
+ fonthash[postScriptName]['width'] = []
+ fonthash[postScriptName]['variant'] = []
+ for s in fonthash[postScriptName]['subfamily']:
+ if s in ['regular', 'thin', 'extralight', 'light', 'book', 'medium', 'regular', 'demibold', 'semibold', 'bold', 'heavy', 'black']:
+ fonthash[postScriptName]['weight'].append(s)
+ if s in ['regular', 'condensed', 'cond', 'semicondensed', 'narrow', 'semiextended', 'extended']:
+ fonthash[postScriptName]['width'].append(s)
+ if s in ['regular', 'italic', 'slanted', 'oblique', 'outline']:
+ fonthash[postScriptName]['variant'].append(s)
+ fonthash[postScriptName]['weight'].sort()
+ fonthash[postScriptName]['width'].sort()
+ fonthash[postScriptName]['variant'].sort()
+ fonthash[postScriptName]['weight'] = l2s(fonthash[postScriptName]['weight'])
+ fonthash[postScriptName]['width'] = l2s(fonthash[postScriptName]['width'])
+ fonthash[postScriptName]['variant'] = l2s(fonthash[postScriptName]['variant'])
+ numFeatureCount = 0
+ fonthash[postScriptName]['features'] = []
+ for line in features.split('\n'):
+ if line[1:4] == 'num':
+ numFeatureCount += 1
+ fonthash[postScriptName]['features'].append(line[0:4])
+ if numFeatureCount == 0:
+ fonthash[postScriptName]['features'].append('fakenum')
+ # if no other ?num are present, add 'fakenum'
+ classifyFont(postScriptName)
+
+# populates classifiedFonts from fonthash
+def classifyFont(postScriptName):
+ # classifiedFonts[postScriptName][weight][width][variant] contains list of opticals
+ family = fonthash[postScriptName]['family']
+ weight = fonthash[postScriptName]['weight']
+ width = fonthash[postScriptName]['width']
+ variant = fonthash[postScriptName]['variant']
+ if family not in classifiedFonts.keys():
+ classifiedFonts[family] = {}
+ if weight not in classifiedFonts[family].keys():
+ classifiedFonts[family][weight] = {}
+ if width not in classifiedFonts[family][weight].keys():
+ classifiedFonts[family][weight][width] = {}
+ if variant not in classifiedFonts[family][weight][width].keys():
+ classifiedFonts[family][weight][width][variant] = []
+ classifiedFonts[family][weight][width][variant].append([postScriptName, fonthash[postScriptName]['opticalSize']])
+
+# generates Berry series from list of series options
+def buildSeries(weight, width):
+ seriesopts = unique(s2l(weight) + s2l(width))
+ if l2s(seriesopts) == 'regular' or not seriesopts:
+ return 'm'
+ series = ''
+ for s in seriesopts:
+ series += optionMappings[s]
+ return series
+
+# generates Berry shape from list of shape options
+def buildShape(variant):
+ shapeopts = unique(s2l(variant))
+ if l2s(shapeopts) == 'regular' or not shapeopts:
+ return 'n'
+ shape = ''
+ if ('italic' in shapeopts) and ('smcp' in shapeopts):
+ shapeopts.remove('italic')
+ shapeopts.remove('smcp')
+ shape += 'si'
+ for s in shapeopts:
+ shape += optionMappings[s]
+ return shape
+
+# populates explodedFonts from classifiedFonts
+def populateFontDataStructures():
+ for family in classifiedFonts.keys():
+ for weight in classifiedFonts[family].keys():
+ for width in classifiedFonts[family][weight].keys():
+ for variant in classifiedFonts[family][weight][width].keys():
+ for font in classifiedFonts[family][weight][width][variant]:
+ name = font[0]
+ myOptionList = []
+ for o in optionList:
+ opt = []
+ for oo in o:
+ if not oo or oo in fonthash[name]['features']:
+ opt.append(oo)
+ if opt:
+ myOptionList.append(opt)
+ for o in breakout(myOptionList):
+ xfamily = [ fonthash[name]['family'] ]
+ xweight = weight
+ xwidth = width
+ xvariant = variant
+ cmdlineoptions = []
+ for oo in o:
+ if oo in globalOptionList and oo not in fakeOptionList:
+ cmdlineoptions.append(oo)
+ if oo in newFontFamilyOptionList and oo not in fakeOptionList:
+ xfamily.append(oo)
+ if oo in weightOptionList and oo not in fakeOptionList:
+ xweight = l2s(s2l(weight) + [oo])
+ cmdlineoptions.append(oo)
+ if oo in widthOptionList and oo not in fakeOptionList:
+ xwidth = l2s(s2l(width) + [oo])
+ cmdlineoptions.append(oo)
+ if oo in variantOptionList and oo not in fakeOptionList:
+ xvariant = l2s(s2l(variant) + [oo])
+ cmdlineoptions.append(oo)
+ xfamily = l2s(xfamily)
+ # print name, xfamily, xseries, xshape, cmdlineoptions
+ # print l2s([encoding, name] + s2l(xfamily)[1:] + cmdlineoptions),
+ # for f in (s2l(xfamily)[1:] + cmdlineoptions):
+ # print '-f%s' % f,
+ # print
+ # print xfamily, '|', xweight, '|', xwidth, '|', buildSeries(xweight, xwidth), '|', xvariant, '|', buildShape(xvariant), '|', name
+ series = buildSeries(xweight, xwidth)
+ shape = buildShape(xvariant)
+ optical = l2s(fonthash[name]['opticalSize'])
+ if xfamily not in explodedFonts.keys():
+ explodedFonts[xfamily] = {}
+ if series not in explodedFonts[xfamily].keys():
+ explodedFonts[xfamily][series] = {}
+ if shape not in explodedFonts[xfamily][series].keys():
+ explodedFonts[xfamily][series][shape] = {}
+ if optical not in explodedFonts[xfamily][series][shape].keys():
+ explodedFonts[xfamily][series][shape][optical] = {}
+ explodedFonts[xfamily][series][shape][optical]['filename'] = fonthash[name]['filename']
+ explodedFonts[xfamily][series][shape][optical]['family'] = fonthash[name]['family']
+ explodedFonts[xfamily][series][shape][optical]['vendor'] = fonthash[name]['vendor']
+ explodedFonts[xfamily][series][shape][optical]['fontname'] = l2s([encoding, name] + s2l(xfamily)[1:] + cmdlineoptions)
+ explodedFonts[xfamily][series][shape][optical]['cmdlineoptions'] = ''
+ for f in (s2l(xfamily)[1:] + cmdlineoptions):
+ explodedFonts[xfamily][series][shape][optical]['cmdlineoptions'] += '-f%s ' % f
+
+# generates .fd files for each font family, .sty file for the typeface
+# does the right thing if multiple typefaces/font families
+# requires that explodedFonts is populated
+def generateFDandSTY():
+ styfiles = {}
+ for family in explodedFonts.keys():
+ fontbase = s2l(family)[0]
+ fontoptions = s2l(family)[1:]
+ if fontbase in styfiles.keys():
+ styfiles[fontbase].append(fontoptions)
+ else:
+ styfiles[fontbase] = [ fontoptions ]
+ fdfilebase = encoding.lower()
+ if fontbase not in berryname.keys():
+ sys.stderr.write("Error: The three-letter Berry ID for font '%s' must be\n added to the 'berryname' hash. Please add it as '%s': 'xxx',\n replacing 'xxx' with the three-letter Berry ID.\n" % (fontbase, fontbase))
+ sys.exit()
+ fontname = berryname[fontbase]
+ for o in fontoptions:
+ fontname += optionMappings[o]
+ fdfilebase += fontname
+ print "Generating", (fdfilebase + ".fd")
+ texmfhome = filenameInLocaldir(fdfilebase + ".fd")
+ print texmfhome
+ fddest = open(texmfhome, 'w')
+ print >> fddest, ("%% Autogenerated by %s on %s" %
+ (sys.argv[0],
+ datetime.date.today().strftime("%Y/%m/%d")))
+ longsuffix = fontoptions
+ print >> fddest, ("\ProvidesFile{%s.fd}[%s %s/%s]" %
+ (fdfilebase, datetime.date.today().strftime("%Y/%m/%d"),
+ encoding, family))
+ # example of scaling from Helvetica:
+ # \expandafter\ifx\csname Hv@scale\endcsname\relax
+ # \let\Hv@@scale\@empty
+ # \else
+ # \edef\Hv@@scale{s*[\csname Hv@scale\endcsname]}%
+ # \fi
+ print >> fddest, r"\expandafter\ifx\csname %s@scaled\endcsname\relax" % letterize(berryname[fontbase])
+ if fontbase in scaled.keys():
+ print >> fddest, r" \edef\%s@scaled{s*[%f]}%%" % (letterize(berryname[fontbase]), scaled[fontbase])
+ else:
+ print >> fddest, r" \let\%s@scaled\@empty" % letterize(berryname[fontbase])
+ print >> fddest, r"\fi"
+ print >> fddest, "\n\\DeclareFontFamily{%s}{%s}{}" % (encoding, fontname)
+ for se in explodedFonts[family].keys():
+ for sh in explodedFonts[family][se].keys():
+ print >> fddest, ("\\DeclareFontShape{%s}{%s}{%s}{%s}{" %
+ (encoding, fontname, se, sh))
+ opticalkeys = explodedFonts[family][se][sh].keys()
+ sz = len(opticalkeys)
+ if sz > 1:
+ # sort by size range
+ opticalkeys.sort(lambda a,b: cmp(float(a.split('-')[0]),
+ float(b.split('-')[0])))
+ # then lop off the lowest start point and highest end point
+ for op in opticalkeys:
+ if sz > 1:
+ range = op
+ if range == opticalkeys[0]:
+ range = '-' + range.split('-')[1]
+ elif range == opticalkeys[-1]:
+ range = range.split('-')[0] + '-'
+ print >> fddest, " <%s> " % range,
+ else:
+ print >> fddest, " <-> ",
+ print >> fddest, "\%s@scaled " % letterize(berryname[fontbase]),
+ print >> fddest, "%s" % explodedFonts[family][se][sh][op]['fontname']
+ # example command line:
+ # otftotfm -a -e texnansx --no-create /Volumes/Adobe\ Type\ Classics/Western\ Fonts/Adobe\ Caslon\ Pro/ACaslonPro-Regular.otf -fkern -fliga LY1--AdobeCaslonPro-Regular
+
+ installcommands.append("otftotfm --no-updmap -a %s --typeface '%s' --vendor '%s' '%s' %s %s" %
+ (encodings[encoding]['commandline'],
+ niceFontName(explodedFonts[family][se][sh][op]['family']),
+ explodedFonts[family][se][sh][op]['vendor'],
+ explodedFonts[family][se][sh][op]['filename'],
+ explodedFonts[family][se][sh][op]['cmdlineoptions'],
+ explodedFonts[family][se][sh][op]['fontname']))
+ print >> fddest, "}{}"
+ # rest of fd part:
+ # - substitutes slanted with italic if slanted not available
+ # - substitutes italic with slanted if italic not available
+ # - substitutes bold extended with bold if bold extended not available
+ # - substitutes bold extended slanted with bxit if bxsl not available
+ for sh in [ ['sl', 'it'], ['it', 'sl'] ]:
+ # sh[0] is replacement sh, sh[1] is old sh
+ for se in explodedFonts[family].keys():
+ if sh[0] in explodedFonts[family][se].keys() and sh[1] not in explodedFonts[family][se].keys():
+ print >> fddest, ("\\DeclareFontShape{%s}{%s}{%s}{%s}{" %
+ (encoding, fontname, se, sh[1]))
+ print >> fddest, " <-> sub * %s/%s/%s" % (fontname, se, sh[0])
+ print >> fddest, "}{}"
+
+ if 'b' in explodedFonts[family].keys() and 'bx' not in explodedFonts[family].keys():
+ se = 'b'
+ for sh in explodedFonts[family][se]:
+ print >> fddest, ("\\DeclareFontShape{%s}{%s}{%s}{%s}{" %
+ (encoding, fontname, 'bx', sh))
+ print >> fddest, " <-> sub * %s/%s/%s" % (fontname, se, sh)
+ print >> fddest, "}{}"
+
+ sh = 'sl'
+ if 'it' in explodedFonts[family][se].keys() and sh not in explodedFonts[family][se].keys():
+ print >> fddest, ("\\DeclareFontShape{%s}{%s}{%s}{%s}{" %
+ (encoding, fontname, 'bx', sh))
+ print >> fddest, " <-> sub * %s/%s/%s" % (fontname, se, 'it')
+ print >> fddest, "}{}"
+ print >> fddest, "\n\\endinput"
+ fddest.close()
+
+ for fontbase in styfiles.keys():
+ styoptions = styfiles[fontbase]
+ print styoptions
+ styname = niceFontName(fontbase)
+ filename = filenameInLocaldir(styname + '.sty')
+ stydest = open(filename, 'w')
+ print >> stydest, ("%% Autogenerated by %s on %s" %
+ (sys.argv[0],
+ datetime.date.today().strftime("%Y/%m/%d")))
+ print >> stydest, r"\NeedsTeXFormat{LaTeX2e}"
+ print >> stydest, (r"\ProvidesPackage{%s}[%s %s]" %
+ (styname, datetime.date.today().strftime("%Y/%m/%d"),
+ fontbase))
+ print >> stydest, r"\RequirePackage[%s]{fontenc}" % encoding
+ print >> stydest, r"\RequirePackage{textcomp}"
+ print >> stydest, r"\RequirePackage{xkeyval}"
+ print >> stydest, r"\RequirePackage{nfssext}"
+ seensups = 0
+ seensinf = 0
+ for o in styoptions:
+ if 'sups' in o:
+ seensups = 1
+ if 'sinf' in o:
+ seensinf = 1
+ if seensups:
+ print >> stydest, r"\def\@makefnmark{\hbox{\sustyle\@thefnmark}}"
+ if seensinf:
+ print >> stydest, r"\providecommand*{\textfrac}[2]{%"
+ print >> stydest, r" \textsu{#1}%"
+ print >> stydest, r" \textfractionsolidus"
+ print >> stydest, r" \textin{#2}}"
+ str = r"\define@key{%s}{scaled}" % letterize(berryname[fontbase])
+ if berryname[fontbase] in scaled.keys():
+ str += "[%s]" % scaled[berryname[fontbase]]
+ else:
+ str += "[1.0]"
+ str += "{\def\%s@scaled{s*[#1]}}" % letterize(berryname[fontbase])
+ print >> stydest, str
+ str = r"\define@key{%s}{family}[rm]{\def\%s@family{#1}}" % (letterize(berryname[fontbase]), letterize(berryname[fontbase]))
+ print >> stydest, str
+ print >> stydest, r"\DeclareOption*{%"
+ print >> stydest, r" \begingroup"
+ print >> stydest, r" \edef\x{\endgroup"
+ print >> stydest, r" \noexpand\setkeys{%s}{\CurrentOption}}%%" % letterize(berryname[fontbase])
+ print >> stydest, r" \x}"
+
+ onumprinted = False
+ lnumprinted = False
+ # change this if you don't want oldstyle to be the default
+ oldstyleisdefault = True
+ styledefaultprinted = False
+ numoptions = ''
+ for o in styoptions:
+ if not onumprinted and 'onum' in o:
+ if oldstyleisdefault == True:
+ print >> stydest, (r"\newcommand*{\%s@style}{%s}" %
+ (letterize(berryname[fontbase]),
+ optionMappings['onum']))
+ styledefaultprinted = True
+ numoptions += r"\DeclareOption{oldstyle}{%" + "\n"
+ numoptions += (r" \renewcommand*{\%s@style}{%s}%%" %
+ (letterize(berryname[fontbase]), optionMappings['onum']))
+ numoptions += "\n}\n"
+ onumprinted = True
+ if not lnumprinted and 'lnum' in o:
+ if oldstyleisdefault == False:
+ print >> stydest, (r"\newcommand*{\%s@style}{%s}" %
+ (letterize(berryname[fontbase]),
+ optionMappings['lnum']))
+ styledefaultprinted = True
+ numoptions += r"\DeclareOption{lining}{%" + "\n"
+ numoptions += (r" \renewcommand*{\%s@style}{%s}%%" %
+ (letterize(berryname[fontbase]), optionMappings['lnum']))
+ numoptions += "\n}\n"
+ lnumprinted = True
+ if not lnumprinted and not onumprinted:
+ print >> stydest, (r"\newcommand*{\%s@style}{%s}" %
+ (letterize(berryname[fontbase]),
+ optionMappings['fakenum']))
+ print >> stydest, numoptions
+
+ print >> stydest, (r"\newcommand*{\%s@default}{%%" %
+ letterize(berryname[fontbase]))
+ print >> stydest, (r" \renewcommand*{\rmdefault}{%s\%s@style}%%" %
+ (berryname[fontbase], letterize(berryname[fontbase])))
+ print >> stydest, "}\n"
+
+ for o in ['rm', 'sf', 'tt']:
+ print >> stydest, (r"\DeclareOption{%s}{%%" % o)
+ print >> stydest, (r" \renewcommand*{\%s@default}{}%%" %
+ letterize(berryname[fontbase]))
+ print >> stydest, (r" \renewcommand*{\%sdefault}{%s\%s@style}%%" %
+ (o, berryname[fontbase],
+ letterize(berryname[fontbase])))
+ print >> stydest, "}\n"
+
+ print >> stydest, r"\ProcessOptions*"
+ print >> stydest, r"\%s@default" % letterize(berryname[fontbase])
+ print >> stydest, r"\endinput"
+ stydest.close()
+
+# given installcommands list, runs all commands in that list
+def executeCommands():
+ dryrun = False # change to true to show commands but not run them
+ installcommands[-1] = installcommands[-1].replace(" --no-updmap", "")
+ for cmd in installcommands:
+ print cmd
+ if not dryrun:
+ os.system(cmd)
+ # typically these two need to be called at the end to clean up
+ if not dryrun:
+ os.system("texhash")
+ os.system("updmap --syncwithtrees")
+
+# break out all possibilities from a list
+# breakout(['a', 'b'], ['c', '']) => [['a', 'c'], ['a'], ['b', 'c'], ['b']]
+# Filters empty values out of output lists.
+# breakout by Doug Zongker (thanks Doug!)
+def breakout(lyst):
+ if not lyst:
+ yield []
+ else:
+ for i in lyst[0]:
+ if i:
+ for j in breakout(lyst[1:]):
+ yield [i] + j
+ else:
+ for j in breakout(lyst[1:]):
+ yield j
+
+def filenameInLocaldir(str):
+ return (os.popen("kpsewhich -expand-path='$TEXMFHOME'",
+ 'r').readline()[0:-1] +
+ localfontsdir + str)
+
+removeproRE = re.compile(" pro")
+removestdRE = re.compile(" std")
+def niceFontName(str):
+ return removestdRE.sub('', removeproRE.sub('', str.lower())).replace(' ', '')
+
+# stable
+def unique(s):
+ u = {}
+ ss = []
+ for x in s:
+ if x not in u.keys():
+ ss.append(x)
+ u[x] = 1
+ return ss
+
+#######################################
+# END UTILITY FUNCTIONS
+#######################################
+
+#######################################
+# BEGIN GLOBAL VARIABLES
+# DON'T TOUCH THESE
+#######################################
+
+# contains otftotfm commands to send to the os
+installcommands = []
+
+# indexed by font file name, contains (flat) info about font file:
+# filename, family, subfamily, vendor, opticalSize, series, shape, features
+fonthash = {}
+
+# indexed by [family][series][shape], contains list of optical variants
+# for each [family][series][shape]
+classifiedFonts = {}
+
+# indexed by [family][series][shape][optical], contains info for each optical
+# variant:
+# filename, vendor, fontname, cmdlineoptions
+explodedFonts = {}
+
+#######################################
+# END GLOBAL VARIABLES
+#######################################
+
+#######################################
+# BEGIN PROGRAM
+#######################################
+
+checkOTFInfoVersion()
+for font in generateFontfiles(sys.argv[1:]):
+ addToFonthash(font)
+populateFontDataStructures()
+generateFDandSTY()
+executeCommands()
+
+#######################################
+# END PROGRAM
+#######################################