def __init__(self, filename, features, version): self._font = fontforge.open(filename) self._version = version self._features = StringIO() if features: preprocessor = Preprocessor() for d in ("italic", "sans", "display", "math"): if d in filename.lower(): preprocessor.define(d.upper()) with open(features) as f: preprocessor.parse(f) preprocessor.write(self._features)
def __init__(self, text, glyph_set): feature_file = StringIO(text) parser_ = parser.Parser(feature_file, glyph_set, followIncludes=False) self._doc = parser_.parse() self.statements = self._doc.statements self._lines = text.splitlines(True) # keepends=True self._build_end_locations()
def generateFeatures(font, args): """Generates feature text by merging feature file with mark positioning lookups (already in the font) and making sure they come after kerning lookups (from the feature file), which is required by Uniscribe to get correct mark positioning for kerned glyphs.""" oldfea = "" for lookup in font.gpos_lookups: oldfea += generateFeatureString(font, lookup) for lookup in font.gpos_lookups + font.gsub_lookups: font.removeLookup(lookup) # open feature file and insert the generated GPOS features in place of the # placeholder text with open(args.features) as f: o = StringIO() preprocessor = Preprocessor() if args.quran: preprocessor.define("QURAN") elif args.slant: preprocessor.define("ITALIC") preprocessor.parse(f) preprocessor.write(o) fea_text = tounicode(o.getvalue(), "utf-8") fea_text = fea_text.replace("{%anchors%}", oldfea) bases = [g.glyphname for g in font.glyphs() if g.glyphclass != "mark"] marks = [g.glyphname for g in font.glyphs() if g.glyphclass == "mark"] carets = {g.glyphname: g.lcarets for g in font.glyphs() if any(g.lcarets)} gdef = [] gdef.append("@GDEFBase = [%s];" % " ".join(bases)) gdef.append("@GDEFMark = [%s];" % " ".join(marks)) gdef.append("table GDEF {") gdef.append(" GlyphClassDef @GDEFBase, , @GDEFMark, ;") for k, v in carets.items(): gdef.append(" LigatureCaretByPos %s %s;" % (k, " ".join(map(str, v)))) gdef.append("} GDEF;") fea_text += "\n".join(gdef) return fea_text
def _executeCodeInNamespace(code, namespace): """ Execute the code in the given namespace. """ # This was adapted from DrawBot's scriptTools.py. saveStdout = sys.stdout saveStderr = sys.stderr tempStdout = StringIO() tempStderr = StringIO() try: sys.stdout = tempStdout sys.stderr = tempStderr try: code = compile(code, "", "exec", 0) except: traceback.print_exc(0) else: try: exec(code, namespace) except: etype, value, tb = sys.exc_info() if tb.tb_next is not None: tb = tb.tb_next traceback.print_exception(etype, value, tb) etype = value = tb = None finally: sys.stdout = saveStdout sys.stderr = saveStderr output = tempStdout.getvalue() errors = tempStderr.getvalue() return output, errors
def pformat_tti(program, preserve=True): from fontTools.ttLib.tables.ttProgram import _pushCountPat assembly = program.getAssembly(preserve=preserve) stream = StringIO() i = 0 indent = 0 nInstr = len(assembly) while i < nInstr: instr = assembly[i] if _unindentRE.match(instr): indent -= 1 stream.write(' ' * indent) stream.write(instr) stream.write("\n") m = _pushCountPat.match(instr) i = i + 1 if m: nValues = int(m.group(1)) line = [] j = 0 for j in range(nValues): if j and not (j % 25): stream.write(' '.join(line)) stream.write("\n") line = [] line.append(assembly[i + j]) stream.write(' ' * indent) stream.write(' '.join(line)) stream.write("\n") i = i + j + 1 if _indentRE.match(instr): indent += 1 return stream.getvalue()
def __init__(self): self._file = StringIO() self._writer = XMLWriter(self._file, encoding="utf-8")
class Logger(object): def __init__(self): self._file = StringIO() self._writer = XMLWriter(self._file, encoding="utf-8") def __del__(self): self._writer = None self._file.close() def logStart(self): self._writer.begintag("xml") def logEnd(self): self._writer.endtag("xml") def logMainSettings(self, glyphNames, script, langSys): self._writer.begintag("initialSettings") self._writer.newline() self._writer.simpletag("string", value=" ".join(glyphNames)) self._writer.newline() self._writer.simpletag("script", value=script) self._writer.newline() self._writer.simpletag("langSys", value=langSys) self._writer.newline() self._writer.endtag("initialSettings") self._writer.newline() def logTableStart(self, table): name = table.__class__.__name__ self._writer.begintag("table", name=name) self._writer.newline() self.logTableFeatureStates(table) def logTableEnd(self): self._writer.endtag("table") def logTableFeatureStates(self, table): self._writer.begintag("featureStates") self._writer.newline() for tag in sorted(table.getFeatureList()): state = table.getFeatureState(tag) self._writer.simpletag("feature", name=tag, state=int(state)) self._writer.newline() self._writer.endtag("featureStates") self._writer.newline() def logApplicableLookups(self, table, lookups): self._writer.begintag("applicableLookups") self._writer.newline() if lookups: order = [] last = None for tag, lookup in lookups: if tag != last: if order: self._logLookupList(last, order) order = [] last = tag index = table.LookupList.Lookup.index(lookup) order.append(index) self._logLookupList(last, order) self._writer.endtag("applicableLookups") self._writer.newline() def _logLookupList(self, tag, lookups): lookups = " ".join([str(i) for i in lookups]) self._writer.simpletag("lookups", feature=tag, indices=lookups) self._writer.newline() def logProcessingStart(self): self._writer.begintag("processing") self._writer.newline() def logProcessingEnd(self): self._writer.endtag("processing") self._writer.newline() def logLookupStart(self, table, tag, lookup): index = table.LookupList.Lookup.index(lookup) self._writer.begintag("lookup", feature=tag, index=index) self._writer.newline() def logLookupEnd(self): self._writer.endtag("lookup") self._writer.newline() def logSubTableStart(self, lookup, subtable): index = lookup.SubTable.index(subtable) lookupType = subtable.__class__.__name__ self._writer.begintag("subTable", index=index, type=lookupType) self._writer.newline() def logSubTableEnd(self): self._writer.endtag("subTable") self._writer.newline() def logGlyphRecords(self, glyphRecords): for r in glyphRecords: self._writer.simpletag("glyphRecord", name=r.glyphName, xPlacement=r.xPlacement, yPlacement=r.yPlacement, xAdvance=r.xAdvance, yAdvance=r.yAdvance) self._writer.newline() def logInput(self, processed, unprocessed): self._writer.begintag("input") self._writer.newline() self._writer.begintag("processed") self._writer.newline() self.logGlyphRecords(processed) self._writer.endtag("processed") self._writer.newline() self._writer.begintag("unprocessed") self._writer.newline() self.logGlyphRecords(unprocessed) self._writer.endtag("unprocessed") self._writer.newline() self._writer.endtag("input") self._writer.newline() def logOutput(self, processed, unprocessed): self._writer.begintag("output") self._writer.newline() self._writer.begintag("processed") self._writer.newline() self.logGlyphRecords(processed) self._writer.endtag("processed") self._writer.newline() self._writer.begintag("unprocessed") self._writer.newline() self.logGlyphRecords(unprocessed) self._writer.endtag("unprocessed") self._writer.newline() self._writer.endtag("output") self._writer.newline() def logResults(self, processed): self._writer.begintag("results") self._writer.newline() self.logGlyphRecords(processed) self._writer.endtag("results") self._writer.newline() def getText(self): return self._file.getvalue()
def pformat_tti(program, preserve=True): from fontTools.ttLib.tables.ttProgram import _pushCountPat assembly = program.getAssembly(preserve=preserve) stream = StringIO() i = 0 indent = 0 nInstr = len(assembly) while i < nInstr: instr = assembly[i] if _unindentRE.match(instr): indent -= 1 stream.write(" " * indent) stream.write(instr) stream.write("\n") m = _pushCountPat.match(instr) i = i + 1 if m: nValues = int(m.group(1)) line = [] j = 0 for j in range(nValues): if j and not (j % 25): stream.write(" ".join(line)) stream.write("\n") line = [] line.append(assembly[i + j]) stream.write(" " * indent) stream.write(" ".join(line)) stream.write("\n") i = i + j + 1 if _indentRE.match(instr): indent += 1 return stream.getvalue()
class Font: def __init__(self, filename, features, version): self._font = fontforge.open(filename) self._version = version self._features = StringIO() if features: preprocessor = Preprocessor() for d in ("italic", "sans", "display", "math"): if d in filename.lower(): preprocessor.define(d.upper()) with open(features) as f: preprocessor.parse(f) preprocessor.write(self._features) def _save_features(self, filename): font = self._font features = self._features with NamedTemporaryFile(suffix=".fea", mode="rt") as temp: font.generateFeatureFile(temp.name) lines = temp.readlines() for line in lines: if not line.startswith("languagesystem"): features.write(line) for lookup in font.gpos_lookups + font.gsub_lookups: font.removeLookup(lookup) with open(filename, "wt") as f: f.write(features.getvalue()) def _cleanup_glyphs(self): font = self._font for glyph in font.glyphs(): glyph.unlinkRef() if glyph.unlinkRmOvrlpSave: glyph.removeOverlap() glyph.correctDirection() def _update_metadata(self): version = self._version font = self._font year = datetime.date.today().year font.copyright = (u"Copyright © 2012-%s " % year + u"The Libertinus Project Authors.") font.version = version # Override the default which includes the build date font.appendSFNTName( "English (US)", "UniqueID", "%s;%s;%s" % (version, font.os2_vendor, font.fontname)) font.appendSFNTName("English (US)", "Vendor URL", "https://github.com/alif-type/libertinus") def _draw_over_under_line(self, name, widths): font = self._font bbox = font[name].boundingBox() pos = bbox[1] height = bbox[-1] - bbox[1] for width in sorted(widths): glyph = font.createChar(-1, "%s.%d" % (name, width)) glyph.width = 0 glyph.glyphclass = "mark" pen = glyph.glyphPen() pen.moveTo((-25 - width, pos)) pen.lineTo((-25 - width, pos + height)) pen.lineTo((25, pos + height)) pen.lineTo((25, pos)) pen.closePath() def _make_over_under_line(self): font = self._font minwidth = 50 bases = [n for n in ("uni0305", "uni0332") if n in font] if not bases: return # Collect glyphs grouped by their widths rounded by minwidth, we will # use them to decide the widths of over/underline glyphs we will draw widths = {} for glyph in font.glyphs(): if glyph.glyphclass != 'mark' and glyph.width > 0: width = round(glyph.width / minwidth) * minwidth width = max(width, minwidth) if width not in widths: widths[width] = [] widths[width].append(glyph.glyphname) for name in bases: self._draw_over_under_line(name, widths) dirname = os.path.dirname(font.path) fea = [] fea.append("feature mark {") fea.append(" @OverSet = [%s];" % " ".join(bases)) fea.append(" lookupflag UseMarkFilteringSet @OverSet;") for width in sorted(widths): # For each width group we create an over/underline glyph with the # same width, and add a contextual substitution lookup to use it # when an over/underline follows any glyph in this group replacements = ['%s.%d' % (name, width) for name in bases] fea.append(" sub [%s] [%s]' by [%s];" % (" ".join( widths[width]), " ".join(bases), " ".join(replacements))) fea.append("} mark;") self._features.write("\n".join(fea)) def generate(self, output, output_features): self._update_metadata() self._cleanup_glyphs() self._make_over_under_line() self._save_features(output_features) self._font.generate(output, flags=("opentype"))
class Font: def __init__(self, filename, features, version): self._font = fontforge.open(filename) self._version = version self._features = StringIO() if features: preprocessor = Preprocessor() for d in ("italic", "sans", "display", "math"): if d in filename.lower(): preprocessor.define(d.upper()) with open(features) as f: preprocessor.parse(f) preprocessor.write(self._features) def _merge_features(self): with NamedTemporaryFile(suffix=".fea", mode="w") as temp: temp.write(self._features.getvalue()) temp.flush() self._font.mergeFeature(temp.name) def _cleanup_glyphs(self): font = self._font for glyph in font.glyphs(): glyph.unlinkRef() if glyph.unlinkRmOvrlpSave: glyph.removeOverlap() glyph.correctDirection() glyph.autoHint() def _update_metadata(self): version = self._version font = self._font year = datetime.date.today().year font.copyright = (u"Copyright © 2012-%s " % year + u"The Libertinus Project Authors.") font.version = version # Override the default which includes the build date font.appendSFNTName( "English (US)", "UniqueID", "%s;%s;%s" % (version, font.os2_vendor, font.fontname)) font.appendSFNTName("English (US)", "Vendor URL", "https://github.com/alif-type/libertinus") def _draw_over_under_line(self, name, widths): font = self._font bbox = font[name].boundingBox() pos = bbox[1] height = bbox[-1] - bbox[1] for width in sorted(widths): glyph = font.createChar(-1, "%s.%d" % (name, width)) glyph.width = 0 glyph.glyphclass = "mark" pen = glyph.glyphPen() pen.moveTo((-25 - width, pos)) pen.lineTo((-25 - width, pos + height)) pen.lineTo((25, pos + height)) pen.lineTo((25, pos)) pen.closePath() def _make_over_under_line(self): font = self._font minwidth = 50 bases = [n for n in ("uni0305", "uni0332") if n in font] if not bases: return # Collect glyphs grouped by their widths rounded by minwidth, we will # use them to decide the widths of over/underline glyphs we will draw widths = {} for glyph in font.glyphs(): if glyph.glyphclass != 'mark' and glyph.width > 0: width = round(glyph.width / minwidth) * minwidth width = max(width, minwidth) if width not in widths: widths[width] = [] widths[width].append(glyph.glyphname) for name in bases: self._draw_over_under_line(name, widths) dirname = os.path.dirname(font.path) fea = [] fea.append("feature mark {") fea.append(" @OverSet = [%s];" % " ".join(bases)) fea.append(" lookupflag UseMarkFilteringSet @OverSet;") for width in sorted(widths): # For each width group we create an over/underline glyph with the # same width, and add a contextual substitution lookup to use it # when an over/underline follows any glyph in this group replacements = ['%s.%d' % (name, width) for name in bases] fea.append(" sub [%s] [%s]' by [%s];" % (" ".join( widths[width]), " ".join(bases), " ".join(replacements))) fea.append("} mark;") self._features.write("\n".join(fea)) def generate(self, output): self._update_metadata() self._cleanup_glyphs() self._make_over_under_line() self._merge_features() self._font.generate(output, flags=("opentype")) font = TTFont(output) # https://github.com/fontforge/fontforge/pull/3235 # fontDirectionHint is deprecated and must be set to 2 font["head"].fontDirectionHint = 2 # unset bits 6..10 font["head"].flags &= ~0x7e0 options = subset.Options() options.set(layout_features='*', name_IDs='*', notdef_outline=True, glyph_names=True, recalc_average_width=True, drop_tables=["FFTM"]) unicodes = font["cmap"].getBestCmap().keys() subsetter = subset.Subsetter(options=options) subsetter.populate(unicodes=unicodes) subsetter.subset(font) font.save(output)