예제 #1
0
    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)
예제 #2
0
 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()
예제 #3
0
파일: build.py 프로젝트: qirh/amiri
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
예제 #4
0
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
예제 #5
0
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()
예제 #6
0
 def __init__(self):
     self._file = StringIO()
     self._writer = XMLWriter(self._file, encoding="utf-8")
예제 #7
0
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()
예제 #8
0
 def __init__(self):
     self._file = StringIO()
     self._writer = XMLWriter(self._file, encoding="utf-8")
예제 #9
0
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()
예제 #10
0
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()
예제 #11
0
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"))
예제 #12
0
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)