summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/doc/fonts/amiri/tools
diff options
context:
space:
mode:
authorKarl Berry <karl@freefriends.org>2011-12-28 23:43:16 +0000
committerKarl Berry <karl@freefriends.org>2011-12-28 23:43:16 +0000
commit009b91d976b3b16341a11461f1cc41f072bdaffa (patch)
treed7446021a90d29b822a25080f7ac4a4de7acdc4a /Master/texmf-dist/doc/fonts/amiri/tools
parentadc3eeeb7c123ebed03ccd63d8a90adc5ed692d6 (diff)
amiri 0.101 (27dec11)
git-svn-id: svn://tug.org/texlive/trunk@24961 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/doc/fonts/amiri/tools')
-rwxr-xr-xMaster/texmf-dist/doc/fonts/amiri/tools/build.py291
-rwxr-xr-xMaster/texmf-dist/doc/fonts/amiri/tools/runtest.py41
2 files changed, 225 insertions, 107 deletions
diff --git a/Master/texmf-dist/doc/fonts/amiri/tools/build.py b/Master/texmf-dist/doc/fonts/amiri/tools/build.py
index c72eb81c706..7bef39cb1a5 100755
--- a/Master/texmf-dist/doc/fonts/amiri/tools/build.py
+++ b/Master/texmf-dist/doc/fonts/amiri/tools/build.py
@@ -14,12 +14,17 @@
# <http://creativecommons.org/publicdomain/zero/1.0/>.
import fontforge
+import psMat
import sys
import os
import getopt
-import tempfile
+from tempfile import mkstemp
+from fontTools.ttLib import TTFont
def genCSS(font, base):
+ """Generates a CSS snippet for webfont usage based on:
+ http://www.fontspring.com/blog/the-new-bulletproof-font-face-syntax"""
+
style = ("slanted" in font.fullname.lower()) and "oblique" or "normal"
weight = font.os2_weight
family = font.familyname + "Web"
@@ -38,6 +43,9 @@ def genCSS(font, base):
return css
def cleanAnchors(font):
+ """Removes anchor classes (and associated lookups) that are used only
+ internally for building composite glyph."""
+
klasses = (
"Dash",
"DigitAbove",
@@ -47,6 +55,7 @@ def cleanAnchors(font):
"DotBelow",
"DotBelowAlt",
"DotHmaza",
+ "HamzaBelow",
"HighHamza",
"MarkDotAbove",
"MarkDotBelow",
@@ -60,7 +69,8 @@ def cleanAnchors(font):
"TashkilBelowDot",
"TwoDotsAbove",
"TwoDotsBelow",
- "TwoDotsBelowAlt"
+ "TwoDotsBelowAlt",
+ "VAbove",
)
for klass in klasses:
@@ -68,19 +78,44 @@ def cleanAnchors(font):
lookup = font.getLookupOfSubtable(subtable)
font.removeLookup(lookup)
-def cleanUnused(font):
- for glyph in font.glyphs():
- # glyphs colored yellow are pending removal, so we remove them from the
- # final font.
- if glyph.color == 0xffff00:
- font.removeGlyph(glyph)
+def flattenNestedReferences(font, ref, new_transform=None):
+ """Flattens nested references by replacing them with the ultimate reference
+ and applying any transformation matrices involved, so that the final font
+ has only simple composite glyphs. This to work around what seems to be an
+ Apple bug that results in ignoring transformation matrix of nested
+ references."""
+
+ name = ref[0]
+ transform = ref[1]
+ glyph = font[name]
+ new_ref = []
+ if glyph.references and glyph.foreground.isEmpty():
+ for nested_ref in glyph.references:
+ for i in flattenNestedReferences(font, nested_ref, transform):
+ new_ref.append(i)
+ else:
+ if new_transform:
+ matrix = psMat.compose(transform, new_transform)
+ new_ref.append((name, matrix))
+ else:
+ new_ref.append(ref)
+
+ return new_ref
def validateGlyphs(font):
- flipped_ref = 0x10
+ """Fixes some common FontForge validation warnings, currently handles:
+ * wrong direction
+ * flipped references
+ * missing points at extrema
+ In addition to flattening nested references."""
+
wrong_dir = 0x8
+ flipped_ref = 0x10
missing_extrema = 0x20
for glyph in font.glyphs():
state = glyph.validate(True)
+ refs = []
+
if state & flipped_ref:
glyph.unlinkRef()
glyph.correctDirection()
@@ -89,12 +124,26 @@ def validateGlyphs(font):
if state & missing_extrema:
glyph.addExtrema("all")
+ for ref in glyph.references:
+ for i in flattenNestedReferences(font, ref):
+ refs.append(i)
+ if refs:
+ glyph.references = refs
+
def setVersion(font, version):
- font.version = "%07.3f" %float(version)
- font.appendSFNTName("Arabic (Egypt)", "Version", "إصدارة %s" %font.version.replace(".", ","))
+ font.version = "%07.3f" % float(version)
+ for name in font.sfnt_names:
+ if name[0] == "Arabic (Egypt)" and name[1] == "Version":
+ font.appendSFNTName(name[0], name[1],
+ name[2].replace("VERSION", font.version.replace(".", "\xD9\xAB")))
def mergeFeatures(font, feafile):
- oldfea = tempfile.mkstemp(suffix='.fea')[1]
+ """Merges feature file into the font while making sure mark positioning
+ lookups (already in the font) come after kerning lookups (from the feature
+ file), which is required by Uniscribe to get correct mark positioning for
+ kerned glyphs."""
+
+ oldfea = mkstemp(suffix='.fea')[1]
font.generateFeatureFile(oldfea)
for lookup in font.gpos_lookups:
@@ -104,22 +153,19 @@ def mergeFeatures(font, feafile):
font.mergeFeature(oldfea)
os.remove(oldfea)
-def stripLocalName(font):
- for name in font.sfnt_names:
- if name[0] != "English (US)" and name[1] in ("Family", "Fullname"):
- font.appendSFNTName(name[0], name[1], None)
+def makeCss(infiles, outfile):
+ """Builds a CSS file for the entire font family."""
-def makeCss(infile, outfile):
- text = ""
+ css = ""
- for f in infile.split():
+ for f in infiles.split():
base = os.path.splitext(os.path.basename(f))[0]
font = fontforge.open(f)
- text += genCSS(font, base)
+ css += genCSS(font, base)
font.close()
out = open(outfile, "w")
- out.write(text)
+ out.write(css)
out.close()
def generateFont(font, outfile, hack=False):
@@ -129,7 +175,7 @@ def generateFont(font, outfile, hack=False):
# ff takes long to write the file, so generate to tmp file then rename
# it to keep fontview happy
import subprocess
- tmpout = tempfile.mkstemp(dir=".", suffix=os.path.basename(outfile))[1]
+ tmpout = mkstemp(dir=".", suffix=os.path.basename(outfile))[1]
font.generate(tmpout, flags=flags)
#os.rename(tmpout, outfile) # file monitor will not see this, why?
p = subprocess.Popen("cat %s > %s" %(tmpout, outfile), shell=True)
@@ -139,25 +185,115 @@ def generateFont(font, outfile, hack=False):
font.generate(outfile, flags=flags)
font.close()
+def drawOverUnderline(glyph, pos, thickness, width):
+ pen = glyph.glyphPen()
+
+ pen.moveTo((-50, pos))
+ pen.lineTo((-50, pos + thickness))
+ pen.lineTo((width + 50, pos + thickness))
+ pen.lineTo((width + 50, pos))
+ pen.closePath()
+
+def makeOverUnderline(font):
+ # test string:
+ # صِ̅فْ̅ ̅خَ̅ل̅قَ̅ ̅بًّ̅ صِ̲فْ̲ ̲خَ̲ل̲قَ̲ ̲بِ̲
+
+ thickness = font.uwidth # underline width (thickness)
+ o_pos = font.os2_typoascent
+ u_pos = font.upos - thickness # underline pos
+ minwidth = 100.0
+
+ widths = {}
+
+ for glyph in font.glyphs():
+ if glyph.glyphclass == 'baseglyph':
+ width = round(glyph.width/100) * 100
+ width = width > minwidth and width or minwidth
+ if not width in widths:
+ widths[width] = []
+ widths[width].append(glyph.glyphname)
+
+ o_encoded = font.createChar(0x0305, 'uni0305')
+ u_encoded = font.createChar(0x0332, 'uni0332')
+ o_encoded.width = u_encoded.width = 0
+ o_encoded.glyphclass = u_encoded.glyphclass = 'mark'
+ drawOverUnderline(o_encoded, o_pos, thickness, 500)
+ drawOverUnderline(u_encoded, u_pos, thickness, 500)
+
+ o_base = font.createChar(-1, 'uni0305.0')
+ u_base = font.createChar(-1, 'uni0332.0')
+ o_base.width = u_base.width = 0
+ o_base.glyphclass = u_base.glyphclass = 'baseglyph'
+ drawOverUnderline(o_base, o_pos, thickness, 500)
+ drawOverUnderline(u_base, u_pos, thickness, 500)
+
+ font.addLookup('mark hack', 'gsub_single', (), (("mark",(("arab",("dflt")),)),), font.gsub_lookups[-1])
+ font.addLookupSubtable('mark hack', 'mark hack 1')
+
+ o_encoded.addPosSub('mark hack 1', o_base.glyphname)
+ u_encoded.addPosSub('mark hack 1', u_base.glyphname)
+
+ context_lookup_name = 'OverUnderLine'
+ font.addLookup(context_lookup_name, 'gsub_contextchain', ('ignore_marks'), (("mark",(("arab",("dflt")),)),), font.gsub_lookups[-1])
+
+ for width in sorted(widths.keys()):
+ o_name = 'uni0305.%d' % width
+ u_name = 'uni0332.%d' % width
+ o_glyph = font.createChar(-1, o_name)
+ u_glyph = font.createChar(-1, u_name)
+
+ o_glyph.glyphclass = u_glyph.glyphclass = 'mark'
+
+ drawOverUnderline(o_glyph, o_pos, thickness, width)
+ drawOverUnderline(u_glyph, u_pos, thickness, width)
+
+ single_lookup_name = str(width)
+
+ font.addLookup(single_lookup_name, 'gsub_single', (), (), font.gsub_lookups[-1])
+ font.addLookupSubtable(single_lookup_name, single_lookup_name + '1')
+
+ o_base.addPosSub(single_lookup_name + '1', o_name)
+ u_base.addPosSub(single_lookup_name + '1', u_name)
+
+ rule = '| [%s] [%s %s] @<%s> | ' %(" ".join(widths[width]), o_base.glyphname, u_base.glyphname, single_lookup_name)
+
+ font.addContextualSubtable(context_lookup_name, context_lookup_name + str(width), 'coverage', rule)
+
def makeWeb(infile, outfile):
"""If we are building a web version then try to minimise file size"""
- from fontTools.ttLib import TTFont
- # suppress noisy DeprecationWarnings in fontTools
- import warnings
- warnings.filterwarnings("ignore",category=DeprecationWarning)
+ # "short-post" generates a post table without glyph names to save some KBs
+ # since glyph names are only needed for PDF's as readers use them to
+ # "guess" characters when copying text, which is of little use in web fonts.
+ flags = ("opentype", "short-post")
- font = TTFont(infile, recalcBBoxes=0)
+ font = fontforge.open(infile)
- # internal glyph names are useless on the web, so force a format 3 post
- # table
- post = font['post']
- post.formatType = 3.0
- post.glyphOrder = None
- del(post.extraNames)
- del(post.mapping)
+ # removed compatibility glyphs that of little use on the web
+ compat_ranges = (
+ (0xfb50, 0xfbb1),
+ (0xfbd3, 0xfd3d),
+ (0xfd50, 0xfdf9),
+ (0xfdfc, 0xfdfc),
+ (0xfe70, 0xfefc),
+ )
- # 'name' table is a bit bulky, and of almost no use in for web fonts,
+ for glyph in font.glyphs():
+ for i in compat_ranges:
+ start = i[0]
+ end = i[1]
+ if start <= glyph.unicode <= end:
+ font.removeGlyph(glyph)
+ break
+
+ tmpfont = mkstemp(suffix=os.path.basename(outfile))[1]
+ font.generate(tmpfont, flags=flags)
+ font.close()
+
+ # now open in fontTools
+ font = TTFont(tmpfont, recalcBBoxes=0)
+
+ # our 'name' table is a bit bulky, and of almost no use in for web fonts,
# so we strip all unnecessary entries.
name = font['name']
names = []
@@ -185,10 +321,7 @@ def makeWeb(infile, outfile):
name.names = names
- # dummy DSIG is useless here too
- del(font['DSIG'])
-
- # FFTM is FontForge specific, remove too
+ # FFTM is FontForge specific, remove it
del(font['FFTM'])
# force compiling GPOS/GSUB tables by fontTools, saves few tens of KBs
@@ -198,8 +331,9 @@ def makeWeb(infile, outfile):
font.save(outfile)
font.close()
+ os.remove(tmpfont)
+
def makeSlanted(infile, outfile, slant):
- import psMat
import math
font = fontforge.open(infile)
@@ -212,18 +346,18 @@ def makeSlanted(infile, outfile, slant):
# fix metadata
font.italicangle = slant
- font.fontname = font.fontname.replace("Regular", "Slanted")
font.fullname += " Slanted"
- font.appendSFNTName("Arabic (Egypt)", "SubFamily", "مائل")
+ if font.weight == "Bold":
+ font.fontname = font.fontname.replace("Bold", "BoldSlanted")
+ font.appendSFNTName("Arabic (Egypt)", "SubFamily", "عريض مائل")
+ font.appendSFNTName("English (US)", "SubFamily", "Bold Slanted")
+ else:
+ font.fontname = font.fontname.replace("Regular", "Slanted")
+ font.appendSFNTName("Arabic (Egypt)", "SubFamily", "مائل")
-def makeSfd(infile, outfile, version):
- font = fontforge.open(infile)
- if version:
- setVersion(font, version)
- font.save(outfile)
- font.close()
+ generateFont(font, outfile)
-def makeDesktop(infile, outfile, feafile, version, nolocalname):
+def makeDesktop(infile, outfile, version):
font = fontforge.open(infile)
if version:
@@ -235,31 +369,27 @@ def makeDesktop(infile, outfile, feafile, version, nolocalname):
# fix some common font issues
validateGlyphs(font)
- # remove unused glyphs
- cleanUnused(font)
-
- if nolocalname:
- stripLocalName(font)
-
- if feafile:
+ if font.sfd_path:
+ feafile = os.path.splitext(font.sfd_path)[0] + '.fea'
mergeFeatures(font, feafile)
+ #makeOverUnderline(font)
+
generateFont(font, outfile, True)
-def usage(code):
+def usage(extramessage, code):
+ if extramessage:
+ print extramessage
+
message = """Usage: %s OPTIONS...
Options:
--input=FILE file name of input font
--output=FILE file name of output font
--version=VALUE set font version to VALUE
- --feature-file=FILE optional feature file
--slant=VALUE autoslant
--css output is a CSS file
- --sfd output is a SFD file
--web output is web version
- --desktop output is desktop version
- --no-localised-name strip out localised font name
-h, --help print this message and exit
""" % os.path.basename(sys.argv[0])
@@ -271,54 +401,39 @@ if __name__ == "__main__":
try:
opts, args = getopt.gnu_getopt(sys.argv[1:],
"h",
- ["help","input=","output=", "feature-file=", "version=", "slant=", "css", "web", "desktop", "sfd", "no-localised-name"])
+ ["help","input=","output=", "version=", "slant=", "css", "web"])
except getopt.GetoptError, err:
- print str(err)
- usage(-1)
+ usage(str(err), -1)
infile = None
outfile = None
- feafile = None
version = None
slant = False
css = False
web = False
- desktop = False
- sfd = False
- nolocalname = False
for opt, arg in opts:
if opt in ("-h", "--help"):
- usage(0)
+ usage("", 0)
elif opt == "--input": infile = arg
elif opt == "--output": outfile = arg
- elif opt == "--feature-file": feafile = arg
elif opt == "--version": version = arg
elif opt == "--slant": slant = float(arg)
elif opt == "--css": css = True
elif opt == "--web": web = True
- elif opt == "--desktop": desktop = True
- elif opt == "--sfd": sfd = True
- elif opt == "--no-localised-name": nolocalname = True
if not infile:
- print "No input file"
- usage(-1)
+ usage("No input file", -1)
if not outfile:
- print "No output file"
- usage(-1)
+ usage("No output file", -1)
if css:
makeCss(infile, outfile)
-
- if sfd:
- makeSfd(infile, outfile, version)
-
- if slant:
- makeSlanted(infile, outfile, slant)
-
- if web:
+ elif web:
makeWeb(infile, outfile)
-
- if desktop:
- makeDesktop(infile, outfile, feafile, version, nolocalname)
+ elif slant:
+ makeSlanted(infile, outfile, slant)
+ else:
+ if not version:
+ usage("No version specified", -1)
+ makeDesktop(infile, outfile, version)
diff --git a/Master/texmf-dist/doc/fonts/amiri/tools/runtest.py b/Master/texmf-dist/doc/fonts/amiri/tools/runtest.py
index fe14f279766..4ea7ff34076 100755
--- a/Master/texmf-dist/doc/fonts/amiri/tools/runtest.py
+++ b/Master/texmf-dist/doc/fonts/amiri/tools/runtest.py
@@ -16,11 +16,11 @@ def runHB(row, font):
process = subprocess.Popen(args, stdout=subprocess.PIPE)
return process.communicate()[0].strip()
-def runTest(reader, font):
+def runTest(test, font):
count = 0
failed = {}
passed = []
- for row in reader:
+ for row in test:
count += 1
row[4] = ('\\' in row[4]) and row[4].decode('unicode-escape') or row[4]
text = row[4]
@@ -33,9 +33,9 @@ def runTest(reader, font):
return passed, failed
-def initTest(reader, font):
+def initTest(test, font):
out = ""
- for row in reader:
+ for row in test:
result = runHB(row, font)
out += "%s;%s\n" %(";".join(row), result)
@@ -52,28 +52,31 @@ if __name__ == '__main__':
for arg in args:
testname = arg
- testfd = open(testname, 'r')
- fontname = testfd.readline().strip("# \n")
- reader = csv.reader(testfd, delimiter=';')
+ reader = csv.reader(open(testname), delimiter=';')
+
+ test = []
+ for row in reader:
+ test.append(row)
if init:
outname = testname+".test"
outfd = open(outname, "w")
outfd.write("# %s\n" %fontname)
- outfd.write(initTest(reader, fontname))
+ outfd.write(initTest(test, fontname))
outfd.close()
sys.exit(0)
- passed, failed = runTest(reader, fontname)
- message = "%s: %d passed, %d failed" %(os.path.basename(testname), len(passed), len(failed))
+ for style in ('regular', 'bold', 'slanted', 'boldslanted'):
+ fontname = 'amiri-%s.ttf' % style
+ passed, failed = runTest(test, fontname)
+ message = "%s: font '%s', %d passed, %d failed" %(os.path.basename(testname),
+ fontname, len(passed), len(failed))
- if failed:
- print message
- for test in failed:
- print test
- print "string: \t", failed[test][0]
- print "reference:\t", failed[test][1]
- print "result: \t", failed[test][2]
- sys.exit(1)
- else:
print message
+ if failed:
+ for test in failed:
+ print test
+ print "string: \t", failed[test][0]
+ print "reference:\t", failed[test][1]
+ print "result: \t", failed[test][2]
+ sys.exit(1)