diff options
Diffstat (limited to 'Master/texmf-dist/doc/fonts/amiri/tools/build.py')
-rwxr-xr-x | Master/texmf-dist/doc/fonts/amiri/tools/build.py | 291 |
1 files changed, 203 insertions, 88 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) |