예제 #1
0
def test_check_unique_glyphnames():
  """ Font contains unique glyph names? """
  import io
  from fontbakery.profiles.universal import com_google_fonts_check_unique_glyphnames as check

  test_font_path = TEST_FILE("nunito/Nunito-Regular.ttf")
  test_font = TTFont(test_font_path)
  status, _ = list(check(test_font))[-1]
  assert status == PASS

  # Fonttools renames duplicate glyphs with #1, #2, ... on load.
  # Code snippet from https://github.com/fonttools/fonttools/issues/149.
  glyph_names = test_font.getGlyphOrder()
  glyph_names[2] = glyph_names[3]
  # Load again, we changed the font directly.
  test_font = TTFont(test_font_path)
  test_font.setGlyphOrder(glyph_names)
  test_font['post']  # Just access the data to make fonttools generate it.
  test_file = io.BytesIO()
  test_font.save(test_file)
  test_font = TTFont(test_file)
  status, message = list(check(test_font))[-1]
  assert status == FAIL
  assert "space" in message

  # Upgrade to post format 3.0 and roundtrip data to update TTF object.
  test_font = TTFont(test_font_path)
  test_font.setGlyphOrder(glyph_names)
  test_font["post"].formatType = 3.0
  test_file = io.BytesIO()
  test_font.save(test_file)
  test_font = TTFont(test_file)
  status, message = list(check(test_font))[-1]
  assert status == SKIP
예제 #2
0
def _layoutEngineOTLTablesRepresentationFactory(layoutEngine):
    font = layoutEngine.font
    ret = dict()
    glyphOrder = sorted(font.keys())
    if font.features.text:
        otf = TTFont()
        otf.setGlyphOrder(glyphOrder)
        # compile with feaLib + markWriter/kernWriter
        try:
            compiler = FeatureCompiler(font, otf)
            compiler.postProcess = lambda: None
            compiler.compile()
            for name in ("GDEF", "GSUB", "GPOS"):
                if name in otf:
                    table = otf[name].compile(otf)
                    value = hb.Blob.create_for_array(table,
                                                     HB.MEMORY_MODE_READONLY)
                    ret[name] = value
        except Exception:
            # TODO: handle this in the UI
            import traceback

            print(traceback.format_exc(5))
            # discard tables from incompletely parsed feature text
            ret = dict()
    return ret, glyphOrder
예제 #3
0
def test_check_unique_glyphnames():
    """ Font contains unique glyph names? """
    check = CheckTester(universal_profile,
                        "com.google.fonts/check/unique_glyphnames")

    ttFont = TTFont(TEST_FILE("nunito/Nunito-Regular.ttf"))
    assert_PASS(check(ttFont))

    # Fonttools renames duplicate glyphs with #1, #2, ... on load.
    # Code snippet from https://github.com/fonttools/fonttools/issues/149.
    glyph_names = ttFont.getGlyphOrder()
    glyph_names[2] = glyph_names[3]

    # Load again, we changed the font directly.
    ttFont = TTFont(TEST_FILE("nunito/Nunito-Regular.ttf"))
    ttFont.setGlyphOrder(glyph_names)
    ttFont['post']  # Just access the data to make fonttools generate it.
    _file = io.BytesIO()
    _file.name = ttFont.reader.file.name
    ttFont.save(_file)
    ttFont = TTFont(_file)
    message = assert_results_contain(check(ttFont), FAIL,
                                     "duplicated-glyph-names")
    assert "space" in message

    # Upgrade to post format 3.0 and roundtrip data to update TTF object.
    ttFont = TTFont(TEST_FILE("nunito/Nunito-Regular.ttf"))
    ttFont.setGlyphOrder(glyph_names)
    ttFont["post"].formatType = 3.0
    _file = io.BytesIO()
    _file.name = ttFont.reader.file.name
    ttFont.save(_file)
    ttFont = TTFont(_file)
    assert_SKIP(check(ttFont))
예제 #4
0
def makeTTFont():
    glyphs = """
        .notdef space slash fraction semicolon period comma ampersand
        quotedblleft quotedblright quoteleft quoteright
        zero one two three four five six seven eight nine
        zero.oldstyle one.oldstyle two.oldstyle three.oldstyle
        four.oldstyle five.oldstyle six.oldstyle seven.oldstyle
        eight.oldstyle nine.oldstyle onequarter onehalf threequarters
        onesuperior twosuperior threesuperior ordfeminine ordmasculine
        A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
        a b c d e f g h i j k l m n o p q r s t u v w x y z
        A.sc B.sc C.sc D.sc E.sc F.sc G.sc H.sc I.sc J.sc K.sc L.sc M.sc
        N.sc O.sc P.sc Q.sc R.sc S.sc T.sc U.sc V.sc W.sc X.sc Y.sc Z.sc
        A.alt1 A.alt2 A.alt3 B.alt1 B.alt2 B.alt3 C.alt1 C.alt2 C.alt3
        a.alt1 a.alt2 a.alt3 a.end b.alt c.mid d.alt d.mid
        e.begin e.mid e.end m.begin n.end s.end z.end
        Eng Eng.alt1 Eng.alt2 Eng.alt3
        A.swash B.swash C.swash D.swash E.swash F.swash G.swash H.swash
        I.swash J.swash K.swash L.swash M.swash N.swash O.swash P.swash
        Q.swash R.swash S.swash T.swash U.swash V.swash W.swash X.swash
        Y.swash Z.swash
        f_l c_h c_k c_s c_t f_f f_f_i f_f_l f_i o_f_f_i s_t f_i.begin
        a_n_d T_h T_h.swash germandbls ydieresis yacute breve
        grave acute dieresis macron circumflex cedilla umlaut ogonek caron
        damma hamza sukun kasratan lam_meem_jeem noon.final noon.initial
        by feature lookup sub table uni0327 uni0328 e.fina
    """.split()
    glyphs.extend("cid{:05d}".format(cid) for cid in range(800, 1001 + 1))
    font = TTFont()
    font.setGlyphOrder(glyphs)
    return font
예제 #5
0
    def __init__(self, ufo, ttFont=None, glyphSet=None, **kwargs):
        """
        Args:
          ufo: an object representing a UFO (defcon.Font or equivalent)
            containing the features source data.
          ttFont: a fontTools TTFont object where the generated OpenType
            tables are added. If None, an empty TTFont is used, with
            the same glyph order as the ufo object.
          glyphSet: a (optional) dict containing pre-processed copies of
            the UFO glyphs.
        """
        self.ufo = ufo

        if ttFont is None:
            from fontTools.ttLib import TTFont

            from ufo2ft.util import makeOfficialGlyphOrder

            ttFont = TTFont()
            ttFont.setGlyphOrder(makeOfficialGlyphOrder(ufo))
        self.ttFont = ttFont

        glyphOrder = ttFont.getGlyphOrder()
        if glyphSet is not None:
            assert set(glyphOrder) == set(glyphSet.keys())
        else:
            glyphSet = ufo
        self.glyphSet = OrderedDict((gn, glyphSet[gn]) for gn in glyphOrder)
예제 #6
0
def makeTTFont():
    glyphs = """
        .notdef space slash fraction semicolon period comma ampersand
        quotedblleft quotedblright quoteleft quoteright
        zero one two three four five six seven eight nine
        zero.oldstyle one.oldstyle two.oldstyle three.oldstyle
        four.oldstyle five.oldstyle six.oldstyle seven.oldstyle
        eight.oldstyle nine.oldstyle onequarter onehalf threequarters
        onesuperior twosuperior threesuperior ordfeminine ordmasculine
        A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
        a b c d e f g h i j k l m n o p q r s t u v w x y z
        A.sc B.sc C.sc D.sc E.sc F.sc G.sc H.sc I.sc J.sc K.sc L.sc M.sc
        N.sc O.sc P.sc Q.sc R.sc S.sc T.sc U.sc V.sc W.sc X.sc Y.sc Z.sc
        A.alt1 A.alt2 A.alt3 B.alt1 B.alt2 B.alt3 C.alt1 C.alt2 C.alt3
        a.alt1 a.alt2 a.alt3 a.end b.alt c.mid d.alt d.mid
        e.begin e.mid e.end m.begin n.end s.end z.end
        Eng Eng.alt1 Eng.alt2 Eng.alt3
        A.swash B.swash C.swash D.swash E.swash F.swash G.swash H.swash
        I.swash J.swash K.swash L.swash M.swash N.swash O.swash P.swash
        Q.swash R.swash S.swash T.swash U.swash V.swash W.swash X.swash
        Y.swash Z.swash
        f_l c_h c_k c_s c_t f_f f_f_i f_f_l f_i o_f_f_i s_t f_i.begin
        a_n_d T_h T_h.swash germandbls ydieresis yacute breve
        grave acute dieresis macron circumflex cedilla umlaut ogonek caron
        damma hamza sukun kasratan lam_meem_jeem noon.final noon.initial
    """.split()
    font = TTFont()
    font.setGlyphOrder(glyphs)
    return font
def _layoutEngineOTLTablesRepresentationFactory(layoutEngine):
    import os
    import tempfile
    font = layoutEngine.font
    gdef = gsub = gpos = None
    if font.features.text:
        otf = TTFont()
        otf.setGlyphOrder(sorted(font.keys()))
        # XXX hack around fontTools only reading from disk
        fd, feaPath = tempfile.mkstemp()
        f = open(feaPath, "w")
        f.write(font.features.text)
        f.close()
        # compile with fontTools
        try:
            addOpenTypeFeatures(feaPath, otf)
        except:
            import traceback
            print(traceback.format_exc(5))
        finally:
            os.close(fd)
            os.remove(feaPath)
        if "GDEF" in otf:
            gdef = otf["GDEF"]
        if "GSUB" in otf:
            gsub = otf["GSUB"]
        if "GPOS" in otf:
            gpos = otf["GPOS"]
    return gdef, gsub, gpos
예제 #8
0
def test_check_unique_glyphnames():
    """ Font contains unique glyph names? """
    import io
    from fontbakery.profiles.universal import com_google_fonts_check_unique_glyphnames as check

    test_font_path = TEST_FILE("nunito/Nunito-Regular.ttf")
    test_font = TTFont(test_font_path)
    status, _ = list(check(test_font))[-1]
    assert status == PASS

    # Fonttools renames duplicate glyphs with #1, #2, ... on load.
    # Code snippet from https://github.com/fonttools/fonttools/issues/149.
    glyph_names = test_font.getGlyphOrder()
    glyph_names[2] = glyph_names[3]
    # Load again, we changed the font directly.
    test_font = TTFont(test_font_path)
    test_font.setGlyphOrder(glyph_names)
    test_font['post']  # Just access the data to make fonttools generate it.
    test_file = io.BytesIO()
    test_font.save(test_file)
    test_font = TTFont(test_file)
    status, message = list(check(test_font))[-1]
    assert status == FAIL
    assert "space" in message

    # Upgrade to post format 3.0 and roundtrip data to update TTF object.
    test_font = TTFont(test_font_path)
    test_font.setGlyphOrder(glyph_names)
    test_font["post"].formatType = 3.0
    test_file = io.BytesIO()
    test_font.save(test_file)
    test_font = TTFont(test_file)
    status, message = list(check(test_font))[-1]
    assert status == SKIP
예제 #9
0
    def check_mti_file(self, name, tableTag=None):

        xml_expected_path = self.getpath("%s.ttx" % name + ('.'+tableTag if tableTag is not None else ''))
        with open(xml_expected_path, 'rt', encoding="utf-8") as xml_expected_file:
            xml_expected = xml_expected_file.read()

        font = self.create_font()

        with open(self.getpath("%s.txt" % name), 'rt', encoding="utf-8") as f:
            table = mtiLib.build(f, font, tableTag=tableTag)

        if tableTag is not None:
            self.assertEqual(tableTag, table.tableTag)
        tableTag = table.tableTag

        # Make sure it compiles.
        blob = table.compile(font)

        # Make sure it decompiles.
        decompiled = table.__class__()
        decompiled.decompile(blob, font)

        # XML from built object.
        writer = XMLWriter(StringIO())
        writer.begintag(tableTag); writer.newline()
        table.toXML(writer, font)
        writer.endtag(tableTag); writer.newline()
        xml_built = writer.file.getvalue()

        # XML from decompiled object.
        writer = XMLWriter(StringIO())
        writer.begintag(tableTag); writer.newline()
        decompiled.toXML(writer, font)
        writer.endtag(tableTag); writer.newline()
        xml_binary = writer.file.getvalue()

        self.expect_ttx(xml_binary,   xml_built, fromfile='decompiled',      tofile='built')
        self.expect_ttx(xml_expected, xml_built, fromfile=xml_expected_path, tofile='built')

        from fontTools.misc import xmlReader
        f = StringIO()
        f.write(xml_expected)
        f.seek(0)
        font2 = TTFont()
        font2.setGlyphOrder(font.getGlyphOrder())
        reader = xmlReader.XMLReader(f, font2)
        reader.read(rootless=True)

        # XML from object read from XML.
        writer = XMLWriter(StringIO())
        writer.begintag(tableTag); writer.newline()
        font2[tableTag].toXML(writer, font)
        writer.endtag(tableTag); writer.newline()
        xml_fromxml = writer.file.getvalue()

        self.expect_ttx(xml_expected, xml_fromxml, fromfile=xml_expected_path, tofile='fromxml')
예제 #10
0
    def check_mti_file(self, name, tableTag=None):

        xml_expected_path = self.getpath("%s.ttx" % name + ('.'+tableTag if tableTag is not None else ''))
        with open(xml_expected_path, 'rt', encoding="utf-8") as xml_expected_file:
            xml_expected = xml_expected_file.read()

        font = self.create_font()

        with open(self.getpath("%s.txt" % name), 'rt', encoding="utf-8") as f:
            table = mtiLib.build(f, font, tableTag=tableTag)

        if tableTag is not None:
            self.assertEqual(tableTag, table.tableTag)
        tableTag = table.tableTag

        # Make sure it compiles.
        blob = table.compile(font)

        # Make sure it decompiles.
        decompiled = table.__class__()
        decompiled.decompile(blob, font)

        # XML from built object.
        writer = XMLWriter(StringIO(), newlinestr='\n')
        writer.begintag(tableTag); writer.newline()
        table.toXML(writer, font)
        writer.endtag(tableTag); writer.newline()
        xml_built = writer.file.getvalue()

        # XML from decompiled object.
        writer = XMLWriter(StringIO(), newlinestr='\n')
        writer.begintag(tableTag); writer.newline()
        decompiled.toXML(writer, font)
        writer.endtag(tableTag); writer.newline()
        xml_binary = writer.file.getvalue()

        self.expect_ttx(xml_binary,   xml_built, fromfile='decompiled',      tofile='built')
        self.expect_ttx(xml_expected, xml_built, fromfile=xml_expected_path, tofile='built')

        from fontTools.misc import xmlReader
        f = StringIO()
        f.write(xml_expected)
        f.seek(0)
        font2 = TTFont()
        font2.setGlyphOrder(font.getGlyphOrder())
        reader = xmlReader.XMLReader(f, font2)
        reader.read(rootless=True)

        # XML from object read from XML.
        writer = XMLWriter(StringIO(), newlinestr='\n')
        writer.begintag(tableTag); writer.newline()
        font2[tableTag].toXML(writer, font)
        writer.endtag(tableTag); writer.newline()
        xml_fromxml = writer.file.getvalue()

        self.expect_ttx(xml_expected, xml_fromxml, fromfile=xml_expected_path, tofile='fromxml')
예제 #11
0
 def test_decompile_empty_table(self):
     font = TTFont()
     glyphNames = [".notdef", "space"]
     font.setGlyphOrder(glyphNames)
     font["loca"] = newTable("loca")
     font["loca"].locations = [0] * (len(glyphNames) + 1)
     font["glyf"] = newTable("glyf")
     font["glyf"].decompile(b"\x00", font)
     self.assertEqual(len(font["glyf"]), 2)
     self.assertEqual(font["glyf"][".notdef"].numberOfContours, 0)
     self.assertEqual(font["glyf"]["space"].numberOfContours, 0)
예제 #12
0
 def test_getPhantomPoints(self):
     # https://github.com/fonttools/fonttools/issues/2295
     font = TTFont()
     glyphNames = [".notdef"]
     font.setGlyphOrder(glyphNames)
     font["loca"] = newTable("loca")
     font["loca"].locations = [0] * (len(glyphNames) + 1)
     font["glyf"] = newTable("glyf")
     font["glyf"].decompile(b"\x00", font)
     font["hmtx"] = newTable("hmtx")
     font["hmtx"].metrics = {".notdef": (100, 0)}
     font["head"] = newTable("head")
     font["head"].unitsPerEm = 1000
     self.assertEqual(font["glyf"].getPhantomPoints(".notdef", font, 0),
                      [(0, 0), (100, 0), (0, 0), (0, -1000)])
예제 #13
0
파일: svg.py 프로젝트: mavit/nanoemoji
def _ensure_groups_grouped_in_glyph_order(
    color_glyphs: MutableMapping[str, ColorGlyph],
    ttfont: ttLib.TTFont,
    reuse_groups: Tuple[Tuple[str, ...]],
):
    # svg requires glyphs in same doc have sequential gids; reshuffle to make this true
    glyph_order = ttfont.getGlyphOrder()[:-len(color_glyphs)]
    gid = len(glyph_order)
    for group in reuse_groups:
        for glyph_name in group:
            color_glyphs[glyph_name] = color_glyphs[glyph_name]._replace(
                glyph_id=gid)
            gid += 1
        glyph_order.extend(group)
    ttfont.setGlyphOrder(glyph_order)
예제 #14
0
def test_setGlyphOrder_also_updates_glyf_glyphOrder():
    # https://github.com/fonttools/fonttools/issues/2060#issuecomment-1063932428
    font = TTFont()
    font.importXML(os.path.join(DATA_DIR, "TestTTF-Regular.ttx"))
    current_order = font.getGlyphOrder()

    assert current_order == font["glyf"].glyphOrder

    new_order = list(current_order)
    while new_order == current_order:
        random.shuffle(new_order)

    font.setGlyphOrder(new_order)

    assert font.getGlyphOrder() == new_order
    assert font["glyf"].glyphOrder == new_order
예제 #15
0
def makeTTFont():
    glyphs = (
        ".notdef space slash fraction "
        "zero one two three four five six seven eight nine "
        "zero.oldstyle one.oldstyle two.oldstyle three.oldstyle "
        "four.oldstyle five.oldstyle six.oldstyle seven.oldstyle "
        "eight.oldstyle nine.oldstyle onehalf "
        "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z "
        "a b c d e f g h i j k l m n o p q r s t u v w x y z "
        "A.sc B.sc C.sc D.sc E.sc F.sc G.sc H.sc I.sc J.sc K.sc L.sc M.sc "
        "N.sc O.sc P.sc Q.sc R.sc S.sc T.sc U.sc V.sc W.sc X.sc Y.sc Z.sc "
        "A.alt1 A.alt2 A.alt3 B.alt1 B.alt2 B.alt3 C.alt1 C.alt2 C.alt3 "
        "d.alt n.end s.end "
        "c_h c_k c_s c_t f_f f_f_i f_i f_l o_f_f_i "
    ).split()
    font = TTFont()
    font.setGlyphOrder(glyphs)
    return font
예제 #16
0
def makeTTFont():
    glyphs = (
        ".notdef space slash fraction "
        "zero one two three four five six seven eight nine "
        "zero.oldstyle one.oldstyle two.oldstyle three.oldstyle "
        "four.oldstyle five.oldstyle six.oldstyle seven.oldstyle "
        "eight.oldstyle nine.oldstyle onehalf "
        "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z "
        "a b c d e f g h i j k l m n o p q r s t u v w x y z "
        "A.sc B.sc C.sc D.sc E.sc F.sc G.sc H.sc I.sc J.sc K.sc L.sc M.sc "
        "N.sc O.sc P.sc Q.sc R.sc S.sc T.sc U.sc V.sc W.sc X.sc Y.sc Z.sc "
        "A.alt1 A.alt2 A.alt3 B.alt1 B.alt2 B.alt3 C.alt1 C.alt2 C.alt3 "
        "d.alt n.end s.end "
        "f_l c_h c_k c_s c_t f_f f_f_i f_f_l f_i o_f_f_i s_t "
        "grave acute dieresis macron circumflex cedilla umlaut ogonek caron "
        "damma hamza sukun kasratan lam_meem_jeem  ").split()
    font = TTFont()
    font.setGlyphOrder(glyphs)
    return font
예제 #17
0
def _layoutEngineOTLTablesRepresentationFactory(layoutEngine):
    font = layoutEngine.font
    gdef = gsub = gpos = None
    if font.features.text:
        otf = TTFont()
        otf.setGlyphOrder(sorted(font.keys()))
        # compile with fontTools
        try:
            addOpenTypeFeaturesFromString(otf, font.features.text)
        except:
            import traceback
            print(traceback.format_exc(5))
        if "GDEF" in otf:
            gdef = otf["GDEF"]
        if "GSUB" in otf:
            gsub = otf["GSUB"]
        if "GPOS" in otf:
            gpos = otf["GPOS"]
    return gdef, gsub, gpos
예제 #18
0
def test_max_ctx_calc_features():
    glyphs = '.notdef space A B C a b c'.split()
    features = """
    lookup GSUB_EXT useExtension {
        sub a by b;
    } GSUB_EXT;

    lookup GPOS_EXT useExtension {
        pos a b -10;
    } GPOS_EXT;

    feature sub1 {
        sub A by a;
        sub A B by b;
        sub A B C by c;
        sub [A B] C by c;
        sub [A B] C [A B] by c;
        sub A by A B;
        sub A' C by A B;
        sub a' by b;
        sub a' b by c;
        sub a from [A B C];
        rsub a by b;
        rsub a' by b;
        rsub a b' by c;
        rsub a b' c by A;
        rsub [a b] c' by A;
        rsub [a b] c' [a b] by B;
        lookup GSUB_EXT;
    } sub1;

    feature pos1 {
        pos A 20;
        pos A B -50;
        pos A B' 10 C;
        lookup GPOS_EXT;
    } pos1;
    """
    font = TTFont()
    font.setGlyphOrder(glyphs)
    addOpenTypeFeaturesFromString(font, features)

    assert maxCtxFont(font) == 3
예제 #19
0
def _layoutEngineOTLTablesRepresentationFactory(layoutEngine):
    font = layoutEngine.font
    gdef = gsub = gpos = None
    if font.features.text:
        otf = TTFont()
        otf.setGlyphOrder(sorted(font.keys()))
        # compile with fontTools
        try:
            addOpenTypeFeaturesFromString(otf, font.features.text)
        except:
            import traceback
            print(traceback.format_exc(5))
        if "GDEF" in otf:
            gdef = otf["GDEF"]
        if "GSUB" in otf:
            gsub = otf["GSUB"]
        if "GPOS" in otf:
            gpos = otf["GPOS"]
    return gdef, gsub, gpos
예제 #20
0
def test_max_ctx_calc_features():
    glyphs = '.notdef space A B C a b c'.split()
    features = """
    lookup GSUB_EXT useExtension {
        sub a by b;
    } GSUB_EXT;

    lookup GPOS_EXT useExtension {
        pos a b -10;
    } GPOS_EXT;

    feature sub1 {
        sub A by a;
        sub A B by b;
        sub A B C by c;
        sub [A B] C by c;
        sub [A B] C [A B] by c;
        sub A by A B;
        sub A' C by A B;
        sub a' by b;
        sub a' b by c;
        sub a from [A B C];
        rsub a by b;
        rsub a' by b;
        rsub a b' by c;
        rsub a b' c by A;
        rsub [a b] c' by A;
        rsub [a b] c' [a b] by B;
        lookup GSUB_EXT;
    } sub1;

    feature pos1 {
        pos A 20;
        pos A B -50;
        pos A B' 10 C;
        lookup GPOS_EXT;
    } pos1;
    """
    font = TTFont()
    font.setGlyphOrder(glyphs)
    addOpenTypeFeaturesFromString(font, features)

    assert maxCtxFont(font) == 3
예제 #21
0
def makeTTFont():
    glyphs = (
        ".notdef space slash fraction "
        "zero one two three four five six seven eight nine "
        "zero.oldstyle one.oldstyle two.oldstyle three.oldstyle "
        "four.oldstyle five.oldstyle six.oldstyle seven.oldstyle "
        "eight.oldstyle nine.oldstyle onehalf "
        "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z "
        "a b c d e f g h i j k l m n o p q r s t u v w x y z "
        "A.sc B.sc C.sc D.sc E.sc F.sc G.sc H.sc I.sc J.sc K.sc L.sc M.sc "
        "N.sc O.sc P.sc Q.sc R.sc S.sc T.sc U.sc V.sc W.sc X.sc Y.sc Z.sc "
        "A.alt1 A.alt2 A.alt3 B.alt1 B.alt2 B.alt3 C.alt1 C.alt2 C.alt3 "
        "d.alt n.end s.end "
        "f_l c_h c_k c_s c_t f_f f_f_i f_f_l f_i o_f_f_i s_t "
        "grave acute dieresis macron circumflex cedilla umlaut ogonek caron "
        "damma hamza sukun kasratan lam_meem_jeem  "
    ).split()
    font = TTFont()
    font.setGlyphOrder(glyphs)
    return font
예제 #22
0
def _ensure_groups_grouped_in_glyph_order(
    color_glyphs: MutableMapping[str, ColorGlyph],
    color_glyph_order: Sequence[str],
    ttfont: ttLib.TTFont,
    reuse_groups: Tuple[Tuple[str, ...]],
):
    # svg requires glyphs in same doc have sequential gids; reshuffle to make this true.

    # Changing the order of glyphs in a TTFont requires that all tables that use
    # glyph indexes have been fully decompiled (loaded with lazy=False).
    # Cf. https://github.com/fonttools/fonttools/issues/2060
    _ensure_ttfont_fully_decompiled(ttfont)

    # The glyph names in the TTFont may have been dropped (post table 3.0), so the
    # names we see after decompiling the TTFont are made up and likely different
    # from the input color glyph names. We only want to reorder the glyphs while
    # keeping the existing names, we can't change order and rename at the same time
    # or else tables that contain mappings keyed by glyph name would blow up.
    # Thus, we need to match the old and current names by their position in the
    # font's current glyph order: i.e. we assume all color glyphs are placed at the
    # END of the glyph order.
    current_glyph_order = ttfont.getGlyphOrder()
    current_color_glyph_names = current_glyph_order[-len(color_glyphs):]
    assert len(color_glyph_order) == len(current_color_glyph_names)
    rename_map = {
        color_glyph_order[i]: current_color_glyph_names[i]
        for i in range(len(color_glyph_order))
    }

    glyph_order = current_glyph_order[:-len(color_glyphs)]
    gid = len(glyph_order)
    for group in reuse_groups:
        for glyph_name in group:
            color_glyphs[glyph_name] = color_glyphs[glyph_name]._replace(
                glyph_id=gid)
            gid += 1
        glyph_order.extend(rename_map[g] for g in group)
    ttfont.setGlyphOrder(glyph_order)
예제 #23
0
class OutlineOTFCompiler(object):

    """
    This object will create a bare-bones OTF-CFF containing
    outline data and not much else. The only external
    method is :meth:`ufo2fdk.tools.outlineOTF.compile`.

    When creating this object, you must provide a *font*
    object and a *path* indicating where the OTF should
    be saved. Optionally, you can provide a *glyphOrder*
    list of glyph names indicating the order of the glyphs
    in the font.
    """

    def __init__(self, font, path, glyphOrder=None):
        self.ufo = font
        self.path = path
        self.log = []
        # make any missing glyphs and store them locally
        missingRequiredGlyphs = self.makeMissingRequiredGlyphs()
        # make a dict of all glyphs
        self.allGlyphs = {}
        for glyph in font:
            self.allGlyphs[glyph.name] = glyph
        self.allGlyphs.update(missingRequiredGlyphs)
        # store the glyph order
        if glyphOrder is None:
            glyphOrder = sorted(self.allGlyphs.keys())
        self.glyphOrder = self.makeOfficialGlyphOrder(glyphOrder)
        # make a reusable bounding box
        self.fontBoundingBox = tuple([_roundInt(i) for i in self.makeFontBoundingBox()])
        # make a reusable character mapping
        self.unicodeToGlyphNameMapping = self.makeUnicodeToGlyphNameMapping()

    # -----------
    # Main Method
    # -----------

    def compile(self):
        """
        Compile the OTF.
        """
        self.otf = TTFont(sfntVersion="OTTO")
        # populate basic tables
        self.setupTable_head()
        self.setupTable_hhea()
        self.setupTable_hmtx()
        self.setupTable_name()
        self.setupTable_maxp()
        self.setupTable_cmap()
        self.setupTable_OS2()
        self.setupTable_post()
        self.setupTable_CFF()
        self.setupOtherTables()
        # write the file
        self.otf.save(self.path)
        # discard the object
        self.otf.close()
        del self.otf

    # -----
    # Tools
    # -----

    def makeFontBoundingBox(self):
        """
        Make a bounding box for the font.

        **This should not be called externally.** Subclasses
        may override this method to handle the bounds creation
        in a different way if desired.
        """
        return getFontBounds(self.ufo)

    def makeUnicodeToGlyphNameMapping(self):
        """
        Make a ``unicode : glyph name`` mapping for the font.

        **This should not be called externally.** Subclasses
        may override this method to handle the mapping creation
        in a different way if desired.
        """
        mapping = {}
        for glyphName, glyph in self.allGlyphs.items():
            unicodes = glyph.unicodes
            for uni in unicodes:
                mapping[uni] = glyphName
        return mapping

    def makeMissingRequiredGlyphs(self):
        """
        Add space and .notdef to the font if they are not present.

        **This should not be called externally.** Subclasses
        may override this method to handle the glyph creation
        in a different way if desired.
        """
        glyphs = {}
        font = self.ufo
        unitsPerEm = _roundInt(getAttrWithFallback(font.info, "unitsPerEm"))
        ascender = _roundInt(getAttrWithFallback(font.info, "ascender"))
        descender = _roundInt(getAttrWithFallback(font.info, "descender"))
        defaultWidth = _roundInt(unitsPerEm * 0.5)
        if ".notdef" not in self.ufo:
            glyphs[".notdef"] = StubGlyph(name=".notdef", width=defaultWidth, unitsPerEm=unitsPerEm, ascender=ascender, descender=descender)
        if "space" not in self.ufo:
            glyphs["space"] = StubGlyph(name="space", width=defaultWidth, unitsPerEm=unitsPerEm, ascender=ascender, descender=descender, unicodes=[32])
        return glyphs

    def makeOfficialGlyphOrder(self, glyphOrder):
        """
        Make a the final glyph order.

        **This should not be called externally.** Subclasses
        may override this method to handle the order creation
        in a different way if desired.
        """
        allGlyphs = self.allGlyphs
        orderedGlyphs = [".notdef", "space"]
        for glyphName in glyphOrder:
            if glyphName in [".notdef", "space"]:
                continue
            orderedGlyphs.append(glyphName)
        for glyphName in sorted(allGlyphs.keys()):
            if glyphName not in orderedGlyphs:
                orderedGlyphs.append(glyphName)
        return orderedGlyphs

    def getCharStringForGlyph(self, glyph, private, globalSubrs):
        """
        Get a Type2CharString for the *glyph*

        **This should not be called externally.** Subclasses
        may override this method to handle the charstring creation
        in a different way if desired.
        """
        width = glyph.width
        # subtract the nominal width
        postscriptNominalWidthX = getAttrWithFallback(self.ufo.info, "postscriptNominalWidthX")
        if postscriptNominalWidthX:
            width = width - postscriptNominalWidthX
        # round
        width = _roundInt(width)
        pen = T2CharStringPen(width, self.allGlyphs)
        glyph.draw(pen)
        charString = pen.getCharString(private, globalSubrs)
        return charString

    # --------------
    # Table Builders
    # --------------

    def setupTable_head(self):
        """
        Make the head table.

        **This should not be called externally.** Subclasses
        may override or supplement this method to handle the
        table creation in a different way if desired.
        """
        self.otf["head"] = head = newTable("head")
        font = self.ufo
        head.checkSumAdjustment = 0
        head.tableVersion = 1.0
        versionMajor = getAttrWithFallback(font.info, "versionMajor")
        versionMinor = getAttrWithFallback(font.info, "versionMinor") * .001
        head.fontRevision = versionMajor + versionMinor
        head.magicNumber = 0x5F0F3CF5
        # upm
        head.unitsPerEm = int(round(getAttrWithFallback(font.info, "unitsPerEm")))
        # times
        head.created = dateStringToTimeValue(getAttrWithFallback(font.info, "openTypeHeadCreated")) - mac_epoch_diff
        head.modified = dateStringToTimeValue(dateStringForNow()) - mac_epoch_diff
        # bounding box
        xMin, yMin, xMax, yMax = self.fontBoundingBox
        head.xMin = int(math.floor(xMin))
        head.yMin = int(math.floor(yMin))
        head.xMax = int(math.ceil(xMax))
        head.yMax = int(math.ceil(yMax))
        # style mapping
        styleMapStyleName = getAttrWithFallback(font.info, "styleMapStyleName")
        macStyle = []
        if styleMapStyleName == "bold":
            macStyle = [0]
        elif styleMapStyleName == "bold italic":
            macStyle = [0, 1]
        elif styleMapStyleName == "italic":
            macStyle = [1]
        head.macStyle = intListToNum(macStyle, 0, 16)
        # misc
        head.flags = intListToNum(getAttrWithFallback(font.info, "openTypeHeadFlags"), 0, 16)
        head.lowestRecPPEM = _roundInt(getAttrWithFallback(font.info, "openTypeHeadLowestRecPPEM"))
        head.fontDirectionHint = 2
        head.indexToLocFormat = 0
        head.glyphDataFormat = 0

    def setupTable_name(self):
        """
        Make the name table.

        **This should not be called externally.** Subclasses
        may override or supplement this method to handle the
        table creation in a different way if desired.
        """
        self.otf["name"] = newTable("name")

    def setupTable_maxp(self):
        """
        Make the maxp table.

        **This should not be called externally.** Subclasses
        may override or supplement this method to handle the
        table creation in a different way if desired.
        """
        self.otf["maxp"] = maxp = newTable("maxp")
        maxp.tableVersion = 0x00005000

    def setupTable_cmap(self):
        """
        Make the cmap table.

        **This should not be called externally.** Subclasses
        may override or supplement this method to handle the
        table creation in a different way if desired.
        """
        from fontTools.ttLib.tables._c_m_a_p import cmap_format_4

        nonBMP = dict((k, v) for k, v in self.unicodeToGlyphNameMapping.items() if k > 65535)
        if nonBMP:
            mapping = dict((k, v) for k, v in self.unicodeToGlyphNameMapping.items() if k <= 65535)
        else:
            mapping = dict(self.unicodeToGlyphNameMapping)
        # mac
        cmap4_0_3 = cmap_format_4(4)
        cmap4_0_3.platformID = 0
        cmap4_0_3.platEncID = 3
        cmap4_0_3.language = 0
        cmap4_0_3.cmap = mapping
        # windows
        cmap4_3_1 = cmap_format_4(4)
        cmap4_3_1.platformID = 3
        cmap4_3_1.platEncID = 1
        cmap4_3_1.language = 0
        cmap4_3_1.cmap = mapping
        # store
        self.otf["cmap"] = cmap = newTable("cmap")
        cmap.tableVersion = 0
        cmap.tables = [cmap4_0_3, cmap4_3_1]
        # If we have glyphs outside Unicode BMP, we must set another
        # subtable that can hold longer codepoints for them.
        if nonBMP:
            from fontTools.ttLib.tables._c_m_a_p import cmap_format_12
            nonBMP.update(mapping)
            # mac
            cmap12_0_4 = cmap_format_12(12)
            cmap12_0_4.platformID = 0
            cmap12_0_4.platEncID = 4
            cmap12_0_4.language = 0
            cmap12_0_4.cmap = nonBMP
            # windows
            cmap12_3_10 = cmap_format_12(12)
            cmap12_3_10.platformID = 3
            cmap12_3_10.platEncID = 10
            cmap12_3_10.language = 0
            cmap12_3_10.cmap = nonBMP
            # update tables registry
            cmap.tables = [cmap4_0_3, cmap4_3_1, cmap12_0_4, cmap12_3_10]
        cmap.tables.sort()

    def setupTable_OS2(self):
        """
        Make the OS/2 table.

        **This should not be called externally.** Subclasses
        may override or supplement this method to handle the
        table creation in a different way if desired.
        """
        self.otf["OS/2"] = os2 = newTable("OS/2")
        font = self.ufo
        os2.version = 0x0004
        # average glyph width
        widths = [glyph.width for glyph in self.allGlyphs.values() if glyph.width > 0]
        os2.xAvgCharWidth = _roundInt(sum(widths) / len(widths))
        # weight and width classes
        os2.usWeightClass = getAttrWithFallback(font.info, "openTypeOS2WeightClass")
        os2.usWidthClass = getAttrWithFallback(font.info, "openTypeOS2WidthClass")
        # embedding
        os2.fsType = intListToNum(getAttrWithFallback(font.info, "openTypeOS2Type"), 0, 16)
        # subscript
        v = getAttrWithFallback(font.info, "openTypeOS2SubscriptXSize")
        if v is None:
            v = 0
        os2.ySubscriptXSize = _roundInt(v)
        v = getAttrWithFallback(font.info, "openTypeOS2SubscriptYSize")
        if v is None:
            v = 0
        os2.ySubscriptYSize = _roundInt(v)
        v = getAttrWithFallback(font.info, "openTypeOS2SubscriptXOffset")
        if v is None:
            v = 0
        os2.ySubscriptXOffset = _roundInt(v)
        v = getAttrWithFallback(font.info, "openTypeOS2SubscriptYOffset")
        if v is None:
            v = 0
        os2.ySubscriptYOffset = _roundInt(v)
        # superscript
        v = getAttrWithFallback(font.info, "openTypeOS2SuperscriptXSize")
        if v is None:
            v = 0
        os2.ySuperscriptXSize = _roundInt(v)
        v = getAttrWithFallback(font.info, "openTypeOS2SuperscriptYSize")
        if v is None:
            v = 0
        os2.ySuperscriptYSize = _roundInt(v)
        v = getAttrWithFallback(font.info, "openTypeOS2SuperscriptXOffset")
        if v is None:
            v = 0
        os2.ySuperscriptXOffset = _roundInt(v)
        v = getAttrWithFallback(font.info, "openTypeOS2SuperscriptYOffset")
        if v is None:
            v = 0
        os2.ySuperscriptYOffset = _roundInt(v)
        # strikeout
        v = getAttrWithFallback(font.info, "openTypeOS2StrikeoutSize")
        if v is None:
            v = 0
        os2.yStrikeoutSize = _roundInt(v)
        v = getAttrWithFallback(font.info, "openTypeOS2StrikeoutPosition")
        if v is None:
            v = 0
        os2.yStrikeoutPosition = _roundInt(v)
        # family class
        os2.sFamilyClass = 0  # XXX not sure how to create the appropriate value
        # panose
        data = getAttrWithFallback(font.info, "openTypeOS2Panose")
        panose = Panose()
        panose.bFamilyType = data[0]
        panose.bSerifStyle = data[1]
        panose.bWeight = data[2]
        panose.bProportion = data[3]
        panose.bContrast = data[4]
        panose.bStrokeVariation = data[5]
        panose.bArmStyle = data[6]
        panose.bLetterForm = data[7]
        panose.bMidline = data[8]
        panose.bXHeight = data[9]
        os2.panose = panose
        # Unicode ranges
        uniRanges = getAttrWithFallback(font.info, "openTypeOS2UnicodeRanges")
        os2.ulUnicodeRange1 = intListToNum(uniRanges, 0, 32)
        os2.ulUnicodeRange2 = intListToNum(uniRanges, 32, 32)
        os2.ulUnicodeRange3 = intListToNum(uniRanges, 64, 32)
        os2.ulUnicodeRange4 = intListToNum(uniRanges, 96, 32)
        # codepage ranges
        codepageRanges = getAttrWithFallback(font.info, "openTypeOS2CodePageRanges")
        os2.ulCodePageRange1 = intListToNum(codepageRanges, 0, 32)
        os2.ulCodePageRange2 = intListToNum(codepageRanges, 32, 32)
        # vendor id
        os2.achVendID = _ignoreASCII(getAttrWithFallback(font.info, "openTypeOS2VendorID"))
        # vertical metrics
        os2.sxHeight = _roundInt(getAttrWithFallback(font.info, "xHeight"))
        os2.sCapHeight = _roundInt(getAttrWithFallback(font.info, "capHeight"))
        os2.sTypoAscender = _roundInt(getAttrWithFallback(font.info, "openTypeOS2TypoAscender"))
        os2.sTypoDescender = _roundInt(getAttrWithFallback(font.info, "openTypeOS2TypoDescender"))
        os2.sTypoLineGap = _roundInt(getAttrWithFallback(font.info, "openTypeOS2TypoLineGap"))
        os2.usWinAscent = _roundInt(getAttrWithFallback(font.info, "openTypeOS2WinAscent"))
        os2.usWinDescent = _roundInt(getAttrWithFallback(font.info, "openTypeOS2WinDescent"))
        # style mapping
        selection = list(getAttrWithFallback(font.info, "openTypeOS2Selection"))
        styleMapStyleName = getAttrWithFallback(font.info, "styleMapStyleName")
        if styleMapStyleName == "regular":
            selection.append(6)
        elif styleMapStyleName == "bold":
            selection.append(5)
        elif styleMapStyleName == "italic":
            selection.append(0)
        elif styleMapStyleName == "bold italic":
            selection += [0, 5]
        os2.fsSelection = intListToNum(selection, 0, 16)
        # characetr indexes
        unicodes = [i for i in self.unicodeToGlyphNameMapping.keys() if i is not None]
        if unicodes:
            minIndex = min(unicodes)
            maxIndex = max(unicodes)
        else:
            # the font may have *no* unicode values
            # (it really happens!) so there needs
            # to be a fallback. use space for this.
            minIndex = 0x0020
            maxIndex = 0x0020
        if maxIndex > 0xFFFF:
            # the spec says that 0xFFFF should be used
            # as the max if the max exceeds 0xFFFF
            maxIndex = 0xFFFF
        os2.fsFirstCharIndex = minIndex
        os2.fsLastCharIndex = maxIndex
        os2.usBreakChar = 32
        os2.usDefaultChar = 0
        # maximum contextual lookup length
        os2.usMaxContex = 0

    def setupTable_hmtx(self):
        """
        Make the hmtx table.

        **This should not be called externally.** Subclasses
        may override or supplement this method to handle the
        table creation in a different way if desired.
        """
        self.otf["hmtx"] = hmtx = newTable("hmtx")
        hmtx.metrics = {}
        for glyphName, glyph in self.allGlyphs.items():
            width = glyph.width
            left = 0
            if len(glyph) or len(glyph.components):
                left = glyph.leftMargin
            if left is None:
                left = 0
            hmtx[glyphName] = (_roundInt(width), _roundInt(left))

    def setupTable_hhea(self):
        """
        Make the hhea table.

        **This should not be called externally.** Subclasses
        may override or supplement this method to handle the
        table creation in a different way if desired.
        """
        self.otf["hhea"] = hhea = newTable("hhea")
        font = self.ufo
        hhea.tableVersion = 0x00010000
        # vertical metrics
        hhea.ascent = _roundInt(getAttrWithFallback(font.info, "openTypeHheaAscender"))
        hhea.descent = _roundInt(getAttrWithFallback(font.info, "openTypeHheaDescender"))
        hhea.lineGap = _roundInt(getAttrWithFallback(font.info, "openTypeHheaLineGap"))
        # horizontal metrics
        widths = []
        lefts = []
        rights = []
        extents = []
        for glyph in self.allGlyphs.values():
            left = glyph.leftMargin
            right = glyph.rightMargin
            if left is None:
                left = 0
            if right is None:
                right = 0
            widths.append(glyph.width)
            lefts.append(left)
            rights.append(right)
            bounds = glyph.bounds
            if bounds is not None:
                xMin, yMin, xMax, yMax = bounds
            else:
                xMin = 0
                xMax = 0
            extent = left + (xMax - xMin)  # equation from spec for calculating xMaxExtent: Max(lsb + (xMax - xMin))
            extents.append(extent)
        hhea.advanceWidthMax = _roundInt(max(widths))
        hhea.minLeftSideBearing = _roundInt(min(lefts))
        hhea.minRightSideBearing = _roundInt(min(rights))
        hhea.xMaxExtent = _roundInt(max(extents))
        # misc
        hhea.caretSlopeRise = getAttrWithFallback(font.info, "openTypeHheaCaretSlopeRise")
        hhea.caretSlopeRun = getAttrWithFallback(font.info, "openTypeHheaCaretSlopeRun")
        hhea.caretOffset = _roundInt(getAttrWithFallback(font.info, "openTypeHheaCaretOffset"))
        hhea.reserved0 = 0
        hhea.reserved1 = 0
        hhea.reserved2 = 0
        hhea.reserved3 = 0
        hhea.metricDataFormat = 0
        # glyph count
        hhea.numberOfHMetrics = len(self.allGlyphs)

    def setupTable_post(self):
        """
        Make the post table.

        **This should not be called externally.** Subclasses
        may override or supplement this method to handle the
        table creation in a different way if desired.
        """
        self.otf["post"] = post = newTable("post")
        font = self.ufo
        post.formatType = 3.0
        # italic angle
        italicAngle = getAttrWithFallback(font.info, "italicAngle")
        post.italicAngle = italicAngle
        # underline
        underlinePosition = getAttrWithFallback(font.info, "postscriptUnderlinePosition")
        if underlinePosition is None:
            underlinePosition = 0
        post.underlinePosition = _roundInt(underlinePosition)
        underlineThickness = getAttrWithFallback(font.info, "postscriptUnderlineThickness")
        if underlineThickness is None:
            underlineThickness = 0
        post.underlineThickness = _roundInt(underlineThickness)
        # determine if the font has a fixed width
        post.isFixedPitch = getAttrWithFallback(font.info, "postscriptIsFixedPitch")
        # misc
        post.minMemType42 = 0
        post.maxMemType42 = 0
        post.minMemType1 = 0
        post.maxMemType1 = 0

    def setupTable_CFF(self):
        """
        Make the CFF table.

        **This should not be called externally.** Subclasses
        may override or supplement this method to handle the
        table creation in a different way if desired.
        """
        self.otf["CFF "] = cff = newTable("CFF ")
        cff = cff.cff
        # set up the basics
        cff.major = 1
        cff.minor = 0
        cff.hdrSize = 4
        cff.offSize = 4
        cff.fontNames = []
        strings = IndexedStrings()
        cff.strings = strings
        private = PrivateDict(strings=strings)
        private.rawDict.update(private.defaults)
        globalSubrs = GlobalSubrsIndex(private=private)
        topDict = TopDict(GlobalSubrs=globalSubrs, strings=strings)
        topDict.Private = private
        charStrings = topDict.CharStrings = CharStrings(file=None, charset=None,
            globalSubrs=globalSubrs, private=private, fdSelect=None, fdArray=None)
        charStrings.charStringsAreIndexed = True
        topDict.charset = []
        charStringsIndex = charStrings.charStringsIndex = SubrsIndex(private=private, globalSubrs=globalSubrs)
        cff.topDictIndex = topDictIndex = TopDictIndex()
        topDictIndex.append(topDict)
        topDictIndex.strings = strings
        cff.GlobalSubrs = globalSubrs
        # populate naming data
        info = self.ufo.info
        psName = getAttrWithFallback(info, "postscriptFontName")
        cff.fontNames.append(psName)
        topDict = cff.topDictIndex[0]
        topDict.version = "%d.%d" % (getAttrWithFallback(info, "versionMajor"), getAttrWithFallback(info, "versionMinor"))
        trademark = getAttrWithFallback(info, "trademark")
        if trademark:
            trademark = normalizeStringForPostscript(trademark.replace(u"\u00A9", "Copyright"))
        if trademark != self.ufo.info.trademark:
            self.log.append("[Warning] The trademark was normalized for storage in the CFF table and consequently some characters were dropped: '%s'" % trademark)
        if trademark is None:
            trademark = ""
        topDict.Notice = trademark
        copyright = getAttrWithFallback(info, "copyright")
        if copyright:
            copyright = normalizeStringForPostscript(copyright.replace(u"\u00A9", "Copyright"))
        if copyright != self.ufo.info.copyright:
            self.log.append("[Warning] The copyright was normalized for storage in the CFF table and consequently some characters were dropped: '%s'" % copyright)
        if copyright is None:
            copyright = ""
        topDict.Copyright = copyright
        topDict.FullName = getAttrWithFallback(info, "postscriptFullName")
        topDict.FamilyName = getAttrWithFallback(info, "openTypeNamePreferredFamilyName")
        topDict.Weight = getAttrWithFallback(info, "postscriptWeightName")
        topDict.FontName = getAttrWithFallback(info, "postscriptFontName")
        # populate various numbers
        topDict.isFixedPitch = getAttrWithFallback(info, "postscriptIsFixedPitch")
        topDict.ItalicAngle = getAttrWithFallback(info, "italicAngle")
        underlinePosition = getAttrWithFallback(info, "postscriptUnderlinePosition")
        if underlinePosition is None:
            underlinePosition = 0
        topDict.UnderlinePosition = _roundInt(underlinePosition)
        underlineThickness = getAttrWithFallback(info, "postscriptUnderlineThickness")
        if underlineThickness is None:
            underlineThickness = 0
        topDict.UnderlineThickness = _roundInt(underlineThickness)
        # populate font matrix
        unitsPerEm = _roundInt(getAttrWithFallback(info, "unitsPerEm"))
        topDict.FontMatrix = [1.0 / unitsPerEm, 0, 0, 1.0 / unitsPerEm, 0, 0]
        # populate the width values
        defaultWidthX = _roundInt(getAttrWithFallback(info, "postscriptDefaultWidthX"))
        if defaultWidthX:
            private.rawDict["defaultWidthX"] = defaultWidthX
        nominalWidthX = _roundInt(getAttrWithFallback(info, "postscriptNominalWidthX"))
        if nominalWidthX:
            private.rawDict["nominalWidthX"] = nominalWidthX
        # populate hint data
        blueFuzz = _roundInt(getAttrWithFallback(info, "postscriptBlueFuzz"))
        blueShift = _roundInt(getAttrWithFallback(info, "postscriptBlueShift"))
        blueScale = getAttrWithFallback(info, "postscriptBlueScale")
        forceBold = getAttrWithFallback(info, "postscriptForceBold")
        blueValues = getAttrWithFallback(info, "postscriptBlueValues")
        if isinstance(blueValues, list):
            blueValues = [_roundInt(i) for i in blueValues]
        otherBlues = getAttrWithFallback(info, "postscriptOtherBlues")
        if isinstance(otherBlues, list):
            otherBlues = [_roundInt(i) for i in otherBlues]
        familyBlues = getAttrWithFallback(info, "postscriptFamilyBlues")
        if isinstance(familyBlues, list):
            familyBlues = [_roundInt(i) for i in familyBlues]
        familyOtherBlues = getAttrWithFallback(info, "postscriptFamilyOtherBlues")
        if isinstance(familyOtherBlues, list):
            familyOtherBlues = [_roundInt(i) for i in familyOtherBlues]
        stemSnapH = getAttrWithFallback(info, "postscriptStemSnapH")
        if isinstance(stemSnapH, list):
            stemSnapH = [_roundInt(i) for i in stemSnapH]
        stemSnapV = getAttrWithFallback(info, "postscriptStemSnapV")
        if isinstance(stemSnapV, list):
            stemSnapV = [_roundInt(i) for i in stemSnapV]
        # only write the blues data if some blues are defined.
        if (blueValues or otherBlues):
            private.rawDict["BlueFuzz"] = blueFuzz
            private.rawDict["BlueShift"] = blueShift
            private.rawDict["BlueScale"] = blueScale
            private.rawDict["ForceBold"] = forceBold
            private.rawDict["BlueValues"] = blueValues
            private.rawDict["OtherBlues"] = otherBlues
            private.rawDict["FamilyBlues"] = familyBlues
            private.rawDict["FamilyOtherBlues"] = familyOtherBlues
        # only write the stems if both are defined.
        if (stemSnapH and stemSnapV):
            private.rawDict["StemSnapH"] = stemSnapH
            private.rawDict["StdHW"] = stemSnapH[0]
            private.rawDict["StemSnapV"] = stemSnapV
            private.rawDict["StdVW"] = stemSnapV[0]
        # populate glyphs
        for glyphName in self.glyphOrder:
            glyph = self.allGlyphs[glyphName]
            charString = self.getCharStringForGlyph(glyph, private, globalSubrs)
            # add to the font
            exists = glyphName in charStrings
            if exists:
                # XXX a glyph already has this name. should we choke?
                glyphID = charStrings.charStrings[glyphName]
                charStringsIndex.items[glyphID] = charString
            else:
                charStringsIndex.append(charString)
                glyphID = len(topDict.charset)
                charStrings.charStrings[glyphName] = glyphID
                topDict.charset.append(glyphName)
        topDict.FontBBox = self.fontBoundingBox
        # write the glyph order
        self.otf.setGlyphOrder(self.glyphOrder)

    def setupOtherTables(self):
        """
        Make the other tables. The default implementation does nothing.

        **This should not be called externally.** Subclasses
        may override this method to add other tables to the
        font if desired.
        """
        pass
예제 #24
0
def make_font(feature_source, fea_type='fea'):
    """Return font with GSUB compiled from given source.

    Adds a bunch of filler tables so the font can be saved if needed, for
    debugging purposes.
    """

    # copied from fontTools' feaLib/builder_test.
    glyphs = """
        .notdef space slash fraction semicolon period comma ampersand
        quotedblleft quotedblright quoteleft quoteright
        zero one two three four five six seven eight nine
        zero.oldstyle one.oldstyle two.oldstyle three.oldstyle
        four.oldstyle five.oldstyle six.oldstyle seven.oldstyle
        eight.oldstyle nine.oldstyle onequarter onehalf threequarters
        onesuperior twosuperior threesuperior ordfeminine ordmasculine
        A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
        a b c d e f g h i j k l m n o p q r s t u v w x y z
        A.sc B.sc C.sc D.sc E.sc F.sc G.sc H.sc I.sc J.sc K.sc L.sc M.sc
        N.sc O.sc P.sc Q.sc R.sc S.sc T.sc U.sc V.sc W.sc X.sc Y.sc Z.sc
        A.alt1 A.alt2 A.alt3 B.alt1 B.alt2 B.alt3 C.alt1 C.alt2 C.alt3
        a.alt1 a.alt2 a.alt3 a.end b.alt c.mid d.alt d.mid
        e.begin e.mid e.end m.begin n.end s.end z.end
        Eng Eng.alt1 Eng.alt2 Eng.alt3
        A.swash B.swash C.swash D.swash E.swash F.swash G.swash H.swash
        I.swash J.swash K.swash L.swash M.swash N.swash O.swash P.swash
        Q.swash R.swash S.swash T.swash U.swash V.swash W.swash X.swash
        Y.swash Z.swash
        f_l c_h c_k c_s c_t f_f f_f_i f_f_l f_i o_f_f_i s_t f_i.begin
        a_n_d T_h T_h.swash germandbls ydieresis yacute breve
        grave acute dieresis macron circumflex cedilla umlaut ogonek caron
        damma hamza sukun kasratan lam_meem_jeem noon.final noon.initial
        by feature lookup sub table
    """.split()
    font = TTFont()
    font.setGlyphOrder(glyphs)
    glyph_order = font.getGlyphOrder()

    font['cmap'] = cmap = newTable('cmap')
    table = cmap_format_4(4)
    table.platformID = 3
    table.platEncID = 1
    table.language = 0
    table.cmap = {AGL2UV[n]: n for n in glyph_order if n in AGL2UV}
    cmap.tableVersion = 0
    cmap.tables = [table]

    font['glyf'] = glyf = newTable('glyf')
    glyf.glyphs = {}
    glyf.glyphOrder = glyph_order
    for name in glyph_order:
        pen = TTGlyphPen(None)
        glyf[name] = pen.glyph()

    font['head'] = head = newTable('head')
    head.tableVersion = 1.0
    head.fontRevision = 1.0
    head.flags = head.checkSumAdjustment = head.magicNumber =\
        head.created = head.modified = head.macStyle = head.lowestRecPPEM =\
        head.fontDirectionHint = head.indexToLocFormat =\
        head.glyphDataFormat =\
        head.xMin = head.xMax = head.yMin = head.yMax = 0
    head.unitsPerEm = 1000

    font['hhea'] = hhea = newTable('hhea')
    hhea.tableVersion = 0x00010000
    hhea.ascent = hhea.descent = hhea.lineGap =\
        hhea.caretSlopeRise = hhea.caretSlopeRun = hhea.caretOffset =\
        hhea.reserved0 = hhea.reserved1 = hhea.reserved2 = hhea.reserved3 =\
        hhea.metricDataFormat = hhea.advanceWidthMax = hhea.xMaxExtent =\
        hhea.minLeftSideBearing = hhea.minRightSideBearing =\
        hhea.numberOfHMetrics = 0

    font['hmtx'] = hmtx = newTable('hmtx')
    hmtx.metrics = {}
    for name in glyph_order:
        hmtx[name] = (600, 50)

    font['loca'] = newTable('loca')

    font['maxp'] = maxp = newTable('maxp')
    maxp.tableVersion = 0x00005000
    maxp.numGlyphs = 0

    font['post'] = post = newTable('post')
    post.formatType = 2.0
    post.extraNames = []
    post.mapping = {}
    post.glyphOrder = glyph_order
    post.italicAngle = post.underlinePosition = post.underlineThickness =\
        post.isFixedPitch = post.minMemType42 = post.maxMemType42 =\
        post.minMemType1 = post.maxMemType1 = 0

    if fea_type == 'fea':
        addOpenTypeFeaturesFromString(font, feature_source)
    elif fea_type == 'mti':
        font['GSUB'] = mtiLib.build(UnicodeIO(feature_source), font)

    return font
예제 #25
0
class WOFF2Writer(SFNTWriter):

    flavor = "woff2"

    def __init__(self,
                 file,
                 numTables,
                 sfntVersion="\000\001\000\000",
                 flavor=None,
                 flavorData=None):
        if not haveBrotli:
            print(
                'The WOFF2 encoder requires the Brotli Python extension, available at:\n'
                'https://github.com/google/brotli',
                file=sys.stderr)
            raise ImportError("No module named brotli")

        self.file = file
        self.numTables = numTables
        self.sfntVersion = Tag(sfntVersion)
        self.flavorData = flavorData or WOFF2FlavorData()

        self.directoryFormat = woff2DirectoryFormat
        self.directorySize = woff2DirectorySize
        self.DirectoryEntry = WOFF2DirectoryEntry

        self.signature = Tag("wOF2")

        self.nextTableOffset = 0
        self.transformBuffer = BytesIO()

        self.tables = OrderedDict()

        # make empty TTFont to store data while normalising and transforming tables
        self.ttFont = TTFont(recalcBBoxes=False, recalcTimestamp=False)

    def __setitem__(self, tag, data):
        """Associate new entry named 'tag' with raw table data."""
        if tag in self.tables:
            raise TTLibError("cannot rewrite '%s' table" % tag)
        if tag == 'DSIG':
            # always drop DSIG table, since the encoding process can invalidate it
            self.numTables -= 1
            return

        entry = self.DirectoryEntry()
        entry.tag = Tag(tag)
        entry.flags = getKnownTagIndex(entry.tag)
        # WOFF2 table data are written to disk only on close(), after all tags
        # have been specified
        entry.data = data

        self.tables[tag] = entry

    def close(self):
        """ All tags must have been specified. Now write the table data and directory.
		"""
        if len(self.tables) != self.numTables:
            raise TTLibError("wrong number of tables; expected %d, found %d" %
                             (self.numTables, len(self.tables)))

        if self.sfntVersion in ("\x00\x01\x00\x00", "true"):
            isTrueType = True
        elif self.sfntVersion == "OTTO":
            isTrueType = False
        else:
            raise TTLibError(
                "Not a TrueType or OpenType font (bad sfntVersion)")

        # The WOFF2 spec no longer requires the glyph offsets to be 4-byte aligned.
        # However, the reference WOFF2 implementation still fails to reconstruct
        # 'unpadded' glyf tables, therefore we need to 'normalise' them.
        # See:
        # https://github.com/khaledhosny/ots/issues/60
        # https://github.com/google/woff2/issues/15
        if isTrueType:
            self._normaliseGlyfAndLoca(padding=4)
        self._setHeadTransformFlag()

        # To pass the legacy OpenType Sanitiser currently included in browsers,
        # we must sort the table directory and data alphabetically by tag.
        # See:
        # https://github.com/google/woff2/pull/3
        # https://lists.w3.org/Archives/Public/public-webfonts-wg/2015Mar/0000.html
        # TODO(user): remove to match spec once browsers are on newer OTS
        self.tables = OrderedDict(sorted(self.tables.items()))

        self.totalSfntSize = self._calcSFNTChecksumsLengthsAndOffsets()

        fontData = self._transformTables()
        compressedFont = brotli.compress(fontData, mode=brotli.MODE_FONT)

        self.totalCompressedSize = len(compressedFont)
        self.length = self._calcTotalSize()
        self.majorVersion, self.minorVersion = self._getVersion()
        self.reserved = 0

        directory = self._packTableDirectory()
        self.file.seek(0)
        self.file.write(pad(directory + compressedFont, size=4))
        self._writeFlavorData()

    def _normaliseGlyfAndLoca(self, padding=4):
        """ Recompile glyf and loca tables, aligning glyph offsets to multiples of
		'padding' size. Update the head table's 'indexToLocFormat' accordingly while
		compiling loca.
		"""
        if self.sfntVersion == "OTTO":
            return

        # make up glyph names required to decompile glyf table
        self._decompileTable('maxp')
        numGlyphs = self.ttFont['maxp'].numGlyphs
        glyphOrder = ['.notdef'
                      ] + ["glyph%.5d" % i for i in range(1, numGlyphs)]
        self.ttFont.setGlyphOrder(glyphOrder)

        for tag in ('head', 'loca', 'glyf'):
            self._decompileTable(tag)
        self.ttFont['glyf'].padding = padding
        for tag in ('glyf', 'loca'):
            self._compileTable(tag)

    def _setHeadTransformFlag(self):
        """ Set bit 11 of 'head' table flags to indicate that the font has undergone
		a lossless modifying transform. Re-compile head table data."""
        self._decompileTable('head')
        self.ttFont['head'].flags |= (1 << 11)
        self._compileTable('head')

    def _decompileTable(self, tag):
        """ Fetch table data, decompile it, and store it inside self.ttFont. """
        tag = Tag(tag)
        if tag not in self.tables:
            raise TTLibError("missing required table: %s" % tag)
        if self.ttFont.isLoaded(tag):
            return
        data = self.tables[tag].data
        if tag == 'loca':
            tableClass = WOFF2LocaTable
        elif tag == 'glyf':
            tableClass = WOFF2GlyfTable
        else:
            tableClass = getTableClass(tag)
        table = tableClass(tag)
        self.ttFont.tables[tag] = table
        table.decompile(data, self.ttFont)

    def _compileTable(self, tag):
        """ Compile table and store it in its 'data' attribute. """
        self.tables[tag].data = self.ttFont[tag].compile(self.ttFont)

    def _calcSFNTChecksumsLengthsAndOffsets(self):
        """ Compute the 'original' SFNT checksums, lengths and offsets for checksum
		adjustment calculation. Return the total size of the uncompressed font.
		"""
        offset = sfntDirectorySize + sfntDirectoryEntrySize * len(self.tables)
        for tag, entry in self.tables.items():
            data = entry.data
            entry.origOffset = offset
            entry.origLength = len(data)
            if tag == 'head':
                entry.checkSum = calcChecksum(data[:8] + b'\0\0\0\0' +
                                              data[12:])
            else:
                entry.checkSum = calcChecksum(data)
            offset += (entry.origLength + 3) & ~3
        return offset

    def _transformTables(self):
        """Return transformed font data."""
        for tag, entry in self.tables.items():
            if tag in woff2TransformedTableTags:
                data = self.transformTable(tag)
            else:
                data = entry.data
            entry.offset = self.nextTableOffset
            entry.saveData(self.transformBuffer, data)
            self.nextTableOffset += entry.length
        self.writeMasterChecksum()
        fontData = self.transformBuffer.getvalue()
        return fontData

    def transformTable(self, tag):
        """Return transformed table data."""
        if tag not in woff2TransformedTableTags:
            raise TTLibError("Transform for table '%s' is unknown" % tag)
        if tag == "loca":
            data = b""
        elif tag == "glyf":
            for tag in ('maxp', 'head', 'loca', 'glyf'):
                self._decompileTable(tag)
            glyfTable = self.ttFont['glyf']
            data = glyfTable.transform(self.ttFont)
        else:
            raise NotImplementedError
        return data

    def _calcMasterChecksum(self):
        """Calculate checkSumAdjustment."""
        tags = list(self.tables.keys())
        checksums = []
        for i in range(len(tags)):
            checksums.append(self.tables[tags[i]].checkSum)

        # Create a SFNT directory for checksum calculation purposes
        self.searchRange, self.entrySelector, self.rangeShift = getSearchRange(
            self.numTables, 16)
        directory = sstruct.pack(sfntDirectoryFormat, self)
        tables = sorted(self.tables.items())
        for tag, entry in tables:
            sfntEntry = SFNTDirectoryEntry()
            sfntEntry.tag = entry.tag
            sfntEntry.checkSum = entry.checkSum
            sfntEntry.offset = entry.origOffset
            sfntEntry.length = entry.origLength
            directory = directory + sfntEntry.toString()

        directory_end = sfntDirectorySize + len(
            self.tables) * sfntDirectoryEntrySize
        assert directory_end == len(directory)

        checksums.append(calcChecksum(directory))
        checksum = sum(checksums) & 0xffffffff
        # BiboAfba!
        checksumadjustment = (0xB1B0AFBA - checksum) & 0xffffffff
        return checksumadjustment

    def writeMasterChecksum(self):
        """Write checkSumAdjustment to the transformBuffer."""
        checksumadjustment = self._calcMasterChecksum()
        self.transformBuffer.seek(self.tables['head'].offset + 8)
        self.transformBuffer.write(struct.pack(">L", checksumadjustment))

    def _calcTotalSize(self):
        """Calculate total size of WOFF2 font, including any meta- and/or private data."""
        offset = self.directorySize
        for entry in self.tables.values():
            offset += len(entry.toString())
        offset += self.totalCompressedSize
        offset = (offset + 3) & ~3
        offset = self._calcFlavorDataOffsetsAndSize(offset)
        return offset

    def _calcFlavorDataOffsetsAndSize(self, start):
        """Calculate offsets and lengths for any meta- and/or private data."""
        offset = start
        data = self.flavorData
        if data.metaData:
            self.metaOrigLength = len(data.metaData)
            self.metaOffset = offset
            self.compressedMetaData = brotli.compress(data.metaData,
                                                      mode=brotli.MODE_TEXT)
            self.metaLength = len(self.compressedMetaData)
            offset += self.metaLength
        else:
            self.metaOffset = self.metaLength = self.metaOrigLength = 0
            self.compressedMetaData = b""
        if data.privData:
            # make sure private data is padded to 4-byte boundary
            offset = (offset + 3) & ~3
            self.privOffset = offset
            self.privLength = len(data.privData)
            offset += self.privLength
        else:
            self.privOffset = self.privLength = 0
        return offset

    def _getVersion(self):
        """Return the WOFF2 font's (majorVersion, minorVersion) tuple."""
        data = self.flavorData
        if data.majorVersion is not None and data.minorVersion is not None:
            return data.majorVersion, data.minorVersion
        else:
            # if None, return 'fontRevision' from 'head' table
            if 'head' in self.tables:
                return struct.unpack(">HH", self.tables['head'].data[4:8])
            else:
                return 0, 0

    def _packTableDirectory(self):
        """Return WOFF2 table directory data."""
        directory = sstruct.pack(self.directoryFormat, self)
        for entry in self.tables.values():
            directory = directory + entry.toString()
        return directory

    def _writeFlavorData(self):
        """Write metadata and/or private data using appropiate padding."""
        compressedMetaData = self.compressedMetaData
        privData = self.flavorData.privData
        if compressedMetaData and privData:
            compressedMetaData = pad(compressedMetaData, size=4)
        if compressedMetaData:
            self.file.seek(self.metaOffset)
            assert self.file.tell() == self.metaOffset
            self.file.write(compressedMetaData)
        if privData:
            self.file.seek(self.privOffset)
            assert self.file.tell() == self.privOffset
            self.file.write(privData)

    def reordersTables(self):
        return True
예제 #26
0
class OTFPostProcessor(object):
    """Does some post-processing operations on a compiled OpenType font, using
    info from the source UFO where necessary.
    """
    def __init__(self, otf, ufo):
        self.ufo = ufo
        stream = BytesIO()
        otf.save(stream)
        stream.seek(0)
        self.otf = TTFont(stream)
        self._postscriptNames = ufo.lib.get('public.postscriptNames')

    def process(self, useProductionNames=True, optimizeCff=True):
        if useProductionNames:
            self._rename_glyphs_from_ufo()
        if optimizeCff and 'CFF ' in self.otf:
            from compreffor import compress
            compress(self.otf)
        return self.otf

    def _rename_glyphs_from_ufo(self):
        """Rename glyphs using ufo.lib.public.postscriptNames in UFO."""

        rename_map = {g.name: self._build_production_name(g) for g in self.ufo}
        # .notdef may not be present in the original font
        rename_map[".notdef"] = ".notdef"
        rename = lambda names: [rename_map[n] for n in names]

        self.otf.setGlyphOrder(rename(self.otf.getGlyphOrder()))
        if 'CFF ' in self.otf:
            cff = self.otf['CFF '].cff.topDictIndex[0]
            char_strings = cff.CharStrings.charStrings
            cff.CharStrings.charStrings = {
                rename_map.get(n, n): v
                for n, v in char_strings.items()
            }
            cff.charset = rename(cff.charset)

    def _build_production_name(self, glyph):
        """Build a production name for a single glyph."""

        # use PostScript names from UFO lib if available
        if self._postscriptNames:
            production_name = self._postscriptNames.get(glyph.name)
            return production_name if production_name else glyph.name

        # use name derived from unicode value
        unicode_val = glyph.unicode
        if glyph.unicode is not None:
            return '%s%04X' % ('u' if unicode_val > 0xffff else 'uni',
                               unicode_val)

        # use production name + last (non-script) suffix if possible
        parts = glyph.name.rsplit('.', 1)
        if len(parts) == 2 and parts[0] in self.ufo:
            return '%s.%s' % (self._build_production_name(
                self.ufo[parts[0]]), parts[1])

        # use ligature name, making sure to look up components with suffixes
        parts = glyph.name.split('.', 1)
        if len(parts) == 2:
            liga_parts = ['%s.%s' % (n, parts[1]) for n in parts[0].split('_')]
        else:
            liga_parts = glyph.name.split('_')
        if len(liga_parts) > 1 and all(n in self.ufo for n in liga_parts):
            unicode_vals = [self.ufo[n].unicode for n in liga_parts]
            if all(v and v <= 0xffff for v in unicode_vals):
                return 'uni' + ''.join('%04X' % v for v in unicode_vals)
            return '_'.join(
                self._build_production_name(self.ufo[n]) for n in liga_parts)

        return glyph.name
예제 #27
0
def font():
    font = TTFont()
    font.setGlyphOrder([".notdef"] + ["glyph%05d" % i for i in range(1, 30)])
    return font
예제 #28
0
파일: outlineOTF.py 프로젝트: yphc/ufo2fdk
class OutlineOTFCompiler(object):
    """
    This object will create a bare-bones OTF-CFF containing
    outline data and not much else. The only external
    method is :meth:`ufo2fdk.tools.outlineOTF.compile`.

    When creating this object, you must provide a *font*
    object and a *path* indicating where the OTF should
    be saved. Optionally, you can provide a *glyphOrder*
    list of glyph names indicating the order of the glyphs
    in the font.
    """
    def __init__(self, font, path, glyphOrder=None):
        self.ufo = font
        self.path = path
        self.log = []
        # make any missing glyphs and store them locally
        missingRequiredGlyphs = self.makeMissingRequiredGlyphs()
        # make a dict of all glyphs
        self.allGlyphs = {}
        for glyph in font:
            self.allGlyphs[glyph.name] = glyph
        self.allGlyphs.update(missingRequiredGlyphs)
        # store the glyph order
        if glyphOrder is None:
            glyphOrder = sorted(self.allGlyphs.keys())
        self.glyphOrder = self.makeOfficialGlyphOrder(glyphOrder)
        # make a reusable bounding box
        self.fontBoundingBox = tuple(
            [_roundInt(i) for i in self.makeFontBoundingBox()])
        # make a reusable character mapping
        self.unicodeToGlyphNameMapping = self.makeUnicodeToGlyphNameMapping()

    # -----------
    # Main Method
    # -----------

    def compile(self):
        """
        Compile the OTF.
        """
        self.otf = TTFont(sfntVersion="OTTO")
        # populate basic tables
        self.setupTable_head()
        self.setupTable_hhea()
        self.setupTable_hmtx()
        self.setupTable_name()
        self.setupTable_maxp()
        self.setupTable_cmap()
        self.setupTable_OS2()
        self.setupTable_post()
        self.setupTable_CFF()
        self.setupOtherTables()
        # write the file
        self.otf.save(self.path)
        # discard the object
        self.otf.close()
        del self.otf

    # -----
    # Tools
    # -----

    def makeFontBoundingBox(self):
        """
        Make a bounding box for the font.

        **This should not be called externally.** Subclasses
        may override this method to handle the bounds creation
        in a different way if desired.
        """
        return getFontBounds(self.ufo)

    def makeUnicodeToGlyphNameMapping(self):
        """
        Make a ``unicode : glyph name`` mapping for the font.

        **This should not be called externally.** Subclasses
        may override this method to handle the mapping creation
        in a different way if desired.
        """
        mapping = {}
        for glyphName, glyph in self.allGlyphs.items():
            unicodes = glyph.unicodes
            for uni in unicodes:
                mapping[uni] = glyphName
        return mapping

    def makeMissingRequiredGlyphs(self):
        """
        Add space and .notdef to the font if they are not present.

        **This should not be called externally.** Subclasses
        may override this method to handle the glyph creation
        in a different way if desired.
        """
        glyphs = {}
        font = self.ufo
        unitsPerEm = _roundInt(getAttrWithFallback(font.info, "unitsPerEm"))
        ascender = _roundInt(getAttrWithFallback(font.info, "ascender"))
        descender = _roundInt(getAttrWithFallback(font.info, "descender"))
        defaultWidth = _roundInt(unitsPerEm * 0.5)
        if ".notdef" not in self.ufo:
            glyphs[".notdef"] = StubGlyph(name=".notdef",
                                          width=defaultWidth,
                                          unitsPerEm=unitsPerEm,
                                          ascender=ascender,
                                          descender=descender)
        if "space" not in self.ufo:
            glyphs["space"] = StubGlyph(name="space",
                                        width=defaultWidth,
                                        unitsPerEm=unitsPerEm,
                                        ascender=ascender,
                                        descender=descender,
                                        unicodes=[32])
        return glyphs

    def makeOfficialGlyphOrder(self, glyphOrder):
        """
        Make a the final glyph order.

        **This should not be called externally.** Subclasses
        may override this method to handle the order creation
        in a different way if desired.
        """
        allGlyphs = self.allGlyphs
        orderedGlyphs = [".notdef", "space"]
        for glyphName in glyphOrder:
            if glyphName in [".notdef", "space"]:
                continue
            orderedGlyphs.append(glyphName)
        for glyphName in sorted(self.allGlyphs.keys()):
            if glyphName not in orderedGlyphs:
                orderedGlyphs.append(glyphName)
        return orderedGlyphs

    def getCharStringForGlyph(self, glyph, private, globalSubrs):
        """
        Get a Type2CharString for the *glyph*

        **This should not be called externally.** Subclasses
        may override this method to handle the charstring creation
        in a different way if desired.
        """
        width = glyph.width
        # subtract the nominal width
        postscriptNominalWidthX = getAttrWithFallback(
            self.ufo.info, "postscriptNominalWidthX")
        if postscriptNominalWidthX:
            width = width - postscriptNominalWidthX
        # round
        width = _roundInt(width)
        pen = T2CharStringPen(width, self.allGlyphs)
        glyph.draw(pen)
        charString = pen.getCharString(private, globalSubrs)
        return charString

    # --------------
    # Table Builders
    # --------------

    def setupTable_head(self):
        """
        Make the head table.

        **This should not be called externally.** Subclasses
        may override or supplement this method to handle the
        table creation in a different way if desired.
        """
        self.otf["head"] = head = newTable("head")
        font = self.ufo
        head.checkSumAdjustment = 0
        head.tableVersion = 1.0
        versionMajor = getAttrWithFallback(font.info, "versionMajor")
        versionMinor = getAttrWithFallback(font.info, "versionMinor") * .001
        head.fontRevision = versionMajor + versionMinor
        head.magicNumber = 0x5F0F3CF5
        # upm
        head.unitsPerEm = getAttrWithFallback(font.info, "unitsPerEm")
        # times
        head.created = dateStringToTimeValue(
            getAttrWithFallback(font.info,
                                "openTypeHeadCreated")) - mac_epoch_diff
        head.modified = dateStringToTimeValue(
            dateStringForNow()) - mac_epoch_diff
        # bounding box
        xMin, yMin, xMax, yMax = self.fontBoundingBox
        head.xMin = xMin
        head.yMin = yMin
        head.xMax = xMax
        head.yMax = yMax
        # style mapping
        styleMapStyleName = getAttrWithFallback(font.info, "styleMapStyleName")
        macStyle = []
        if styleMapStyleName == "bold":
            macStyle = [0]
        elif styleMapStyleName == "bold italic":
            macStyle = [0, 1]
        elif styleMapStyleName == "italic":
            macStyle = [1]
        head.macStyle = intListToNum(macStyle, 0, 16)
        # misc
        head.flags = intListToNum(
            getAttrWithFallback(font.info, "openTypeHeadFlags"), 0, 16)
        head.lowestRecPPEM = _roundInt(
            getAttrWithFallback(font.info, "openTypeHeadLowestRecPPEM"))
        head.fontDirectionHint = 2
        head.indexToLocFormat = 0
        head.glyphDataFormat = 0

    def setupTable_name(self):
        """
        Make the name table.

        **This should not be called externally.** Subclasses
        may override or supplement this method to handle the
        table creation in a different way if desired.
        """
        self.otf["name"] = newTable("name")

    def setupTable_maxp(self):
        """
        Make the maxp table.

        **This should not be called externally.** Subclasses
        may override or supplement this method to handle the
        table creation in a different way if desired.
        """
        self.otf["maxp"] = maxp = newTable("maxp")
        maxp.tableVersion = 0x00005000

    def setupTable_cmap(self):
        """
        Make the cmap table.

        **This should not be called externally.** Subclasses
        may override or supplement this method to handle the
        table creation in a different way if desired.
        """
        from fontTools.ttLib.tables._c_m_a_p import cmap_format_4

        nonBMP = dict((k, v)
                      for k, v in self.unicodeToGlyphNameMapping.items()
                      if k > 65535)
        if nonBMP:
            mapping = dict((k, v)
                           for k, v in self.unicodeToGlyphNameMapping.items()
                           if k <= 65535)
        else:
            mapping = dict(self.unicodeToGlyphNameMapping)
        # mac
        cmap4_0_3 = cmap_format_4(4)
        cmap4_0_3.platformID = 0
        cmap4_0_3.platEncID = 3
        cmap4_0_3.language = 0
        cmap4_0_3.cmap = mapping
        # windows
        cmap4_3_1 = cmap_format_4(4)
        cmap4_3_1.platformID = 3
        cmap4_3_1.platEncID = 1
        cmap4_3_1.language = 0
        cmap4_3_1.cmap = mapping
        # store
        self.otf["cmap"] = cmap = newTable("cmap")
        cmap.tableVersion = 0
        cmap.tables = [cmap4_0_3, cmap4_3_1]
        # If we have glyphs outside Unicode BMP, we must set another
        # subtable that can hold longer codepoints for them.
        if nonBMP:
            from fontTools.ttLib.tables._c_m_a_p import cmap_format_12
            nonBMP.update(mapping)
            # mac
            cmap12_0_4 = cmap_format_12(12)
            cmap12_0_4.platformID = 0
            cmap12_0_4.platEncID = 4
            cmap12_0_4.language = 0
            cmap12_0_4.cmap = nonBMP
            # windows
            cmap12_3_10 = cmap_format_12(12)
            cmap12_3_10.platformID = 3
            cmap12_3_10.platEncID = 10
            cmap12_3_10.language = 0
            cmap12_3_10.cmap = nonBMP
            # update tables registry
            cmap.tables = [cmap4_0_3, cmap4_3_1, cmap12_0_4, cmap12_3_10]

    def setupTable_OS2(self):
        """
        Make the OS/2 table.

        **This should not be called externally.** Subclasses
        may override or supplement this method to handle the
        table creation in a different way if desired.
        """
        self.otf["OS/2"] = os2 = newTable("OS/2")
        font = self.ufo
        os2.version = 0x0004
        # average glyph width
        widths = [
            glyph.width for glyph in self.allGlyphs.values() if glyph.width > 0
        ]
        os2.xAvgCharWidth = _roundInt(sum(widths) / len(widths))
        # weight and width classes
        os2.usWeightClass = getAttrWithFallback(font.info,
                                                "openTypeOS2WeightClass")
        os2.usWidthClass = getAttrWithFallback(font.info,
                                               "openTypeOS2WidthClass")
        # embedding
        os2.fsType = intListToNum(
            getAttrWithFallback(font.info, "openTypeOS2Type"), 0, 16)
        # subscript
        v = getAttrWithFallback(font.info, "openTypeOS2SubscriptXSize")
        if v is None:
            v = 0
        os2.ySubscriptXSize = _roundInt(v)
        v = getAttrWithFallback(font.info, "openTypeOS2SubscriptYSize")
        if v is None:
            v = 0
        os2.ySubscriptYSize = _roundInt(v)
        v = getAttrWithFallback(font.info, "openTypeOS2SubscriptXOffset")
        if v is None:
            v = 0
        os2.ySubscriptXOffset = _roundInt(v)
        v = getAttrWithFallback(font.info, "openTypeOS2SubscriptYOffset")
        if v is None:
            v = 0
        os2.ySubscriptYOffset = _roundInt(v)
        # superscript
        v = getAttrWithFallback(font.info, "openTypeOS2SuperscriptXSize")
        if v is None:
            v = 0
        os2.ySuperscriptXSize = _roundInt(v)
        v = getAttrWithFallback(font.info, "openTypeOS2SuperscriptYSize")
        if v is None:
            v = 0
        os2.ySuperscriptYSize = _roundInt(v)
        v = getAttrWithFallback(font.info, "openTypeOS2SuperscriptXOffset")
        if v is None:
            v = 0
        os2.ySuperscriptXOffset = _roundInt(v)
        v = getAttrWithFallback(font.info, "openTypeOS2SuperscriptYOffset")
        if v is None:
            v = 0
        os2.ySuperscriptYOffset = _roundInt(v)
        # strikeout
        v = getAttrWithFallback(font.info, "openTypeOS2StrikeoutSize")
        if v is None:
            v = 0
        os2.yStrikeoutSize = _roundInt(v)
        v = getAttrWithFallback(font.info, "openTypeOS2StrikeoutPosition")
        if v is None:
            v = 0
        os2.yStrikeoutPosition = _roundInt(v)
        # family class
        os2.sFamilyClass = 0  # XXX not sure how to create the appropriate value
        # panose
        data = getAttrWithFallback(font.info, "openTypeOS2Panose")
        panose = Panose()
        panose.bFamilyType = data[0]
        panose.bSerifStyle = data[1]
        panose.bWeight = data[2]
        panose.bProportion = data[3]
        panose.bContrast = data[4]
        panose.bStrokeVariation = data[5]
        panose.bArmStyle = data[6]
        panose.bLetterForm = data[7]
        panose.bMidline = data[8]
        panose.bXHeight = data[9]
        os2.panose = panose
        # Unicode ranges
        uniRanges = getAttrWithFallback(font.info, "openTypeOS2UnicodeRanges")
        os2.ulUnicodeRange1 = intListToNum(uniRanges, 0, 32)
        os2.ulUnicodeRange2 = intListToNum(uniRanges, 32, 32)
        os2.ulUnicodeRange3 = intListToNum(uniRanges, 64, 32)
        os2.ulUnicodeRange4 = intListToNum(uniRanges, 96, 32)
        # codepage ranges
        codepageRanges = getAttrWithFallback(font.info,
                                             "openTypeOS2CodePageRanges")
        os2.ulCodePageRange1 = intListToNum(codepageRanges, 0, 32)
        os2.ulCodePageRange2 = intListToNum(codepageRanges, 32, 32)
        # vendor id
        os2.achVendID = str(
            getAttrWithFallback(font.info, "openTypeOS2VendorID").decode(
                "ascii", "ignore"))
        # vertical metrics
        os2.sxHeight = _roundInt(getAttrWithFallback(font.info, "xHeight"))
        os2.sCapHeight = _roundInt(getAttrWithFallback(font.info, "capHeight"))
        os2.sTypoAscender = _roundInt(
            getAttrWithFallback(font.info, "openTypeOS2TypoAscender"))
        os2.sTypoDescender = _roundInt(
            getAttrWithFallback(font.info, "openTypeOS2TypoDescender"))
        os2.sTypoLineGap = _roundInt(
            getAttrWithFallback(font.info, "openTypeOS2TypoLineGap"))
        os2.usWinAscent = _roundInt(
            getAttrWithFallback(font.info, "openTypeOS2WinAscent"))
        os2.usWinDescent = _roundInt(
            getAttrWithFallback(font.info, "openTypeOS2WinDescent"))
        # style mapping
        selection = list(getAttrWithFallback(font.info,
                                             "openTypeOS2Selection"))
        styleMapStyleName = getAttrWithFallback(font.info, "styleMapStyleName")
        if styleMapStyleName == "regular":
            selection.append(6)
        elif styleMapStyleName == "bold":
            selection.append(5)
        elif styleMapStyleName == "italic":
            selection.append(0)
        elif styleMapStyleName == "bold italic":
            selection += [0, 5]
        os2.fsSelection = intListToNum(selection, 0, 16)
        # characetr indexes
        unicodes = [
            i for i in self.unicodeToGlyphNameMapping.keys() if i is not None
        ]
        if unicodes:
            minIndex = min(unicodes)
            maxIndex = max(unicodes)
        else:
            # the font may have *no* unicode values
            # (it really happens!) so there needs
            # to be a fallback. use space for this.
            minIndex = 0x0020
            maxIndex = 0x0020
        if maxIndex > 0xFFFF:
            # the spec says that 0xFFFF should be used
            # as the max if the max exceeds 0xFFFF
            maxIndex = 0xFFFF
        os2.fsFirstCharIndex = minIndex
        os2.fsLastCharIndex = maxIndex
        os2.usBreakChar = 32
        os2.usDefaultChar = 0
        # maximum contextual lookup length
        os2.usMaxContex = 0

    def setupTable_hmtx(self):
        """
        Make the hmtx table.

        **This should not be called externally.** Subclasses
        may override or supplement this method to handle the
        table creation in a different way if desired.
        """
        self.otf["hmtx"] = hmtx = newTable("hmtx")
        hmtx.metrics = {}
        for glyphName, glyph in self.allGlyphs.items():
            width = glyph.width
            left = 0
            if len(glyph) or len(glyph.components):
                left = glyph.leftMargin
            if left is None:
                left = 0
            hmtx[glyphName] = (_roundInt(width), _roundInt(left))

    def setupTable_hhea(self):
        """
        Make the hhea table.

        **This should not be called externally.** Subclasses
        may override or supplement this method to handle the
        table creation in a different way if desired.
        """
        self.otf["hhea"] = hhea = newTable("hhea")
        font = self.ufo
        hhea.tableVersion = 1.0
        # vertical metrics
        hhea.ascent = _roundInt(
            getAttrWithFallback(font.info, "openTypeHheaAscender"))
        hhea.descent = _roundInt(
            getAttrWithFallback(font.info, "openTypeHheaDescender"))
        hhea.lineGap = _roundInt(
            getAttrWithFallback(font.info, "openTypeHheaLineGap"))
        # horizontal metrics
        widths = []
        lefts = []
        rights = []
        extents = []
        for glyph in self.allGlyphs.values():
            left = glyph.leftMargin
            right = glyph.rightMargin
            if left is None:
                left = 0
            if right is None:
                right = 0
            widths.append(glyph.width)
            lefts.append(left)
            rights.append(right)
            # robofab
            if hasattr(glyph, "box"):
                bounds = glyph.box
            # others
            else:
                bounds = glyph.bounds
            if bounds is not None:
                xMin, yMin, xMax, yMax = bounds
            else:
                xMin = 0
                xMax = 0
            extent = left + (
                xMax - xMin
            )  # equation from spec for calculating xMaxExtent: Max(lsb + (xMax - xMin))
            extents.append(extent)
        hhea.advanceWidthMax = _roundInt(max(widths))
        hhea.minLeftSideBearing = _roundInt(min(lefts))
        hhea.minRightSideBearing = _roundInt(min(rights))
        hhea.xMaxExtent = _roundInt(max(extents))
        # misc
        hhea.caretSlopeRise = getAttrWithFallback(
            font.info, "openTypeHheaCaretSlopeRise")
        hhea.caretSlopeRun = getAttrWithFallback(font.info,
                                                 "openTypeHheaCaretSlopeRun")
        hhea.caretOffset = _roundInt(
            getAttrWithFallback(font.info, "openTypeHheaCaretOffset"))
        hhea.reserved0 = 0
        hhea.reserved1 = 0
        hhea.reserved2 = 0
        hhea.reserved3 = 0
        hhea.metricDataFormat = 0
        # glyph count
        hhea.numberOfHMetrics = len(self.allGlyphs)

    def setupTable_post(self):
        """
        Make the post table.

        **This should not be called externally.** Subclasses
        may override or supplement this method to handle the
        table creation in a different way if desired.
        """
        self.otf["post"] = post = newTable("post")
        font = self.ufo
        post.formatType = 3.0
        # italic angle
        italicAngle = getAttrWithFallback(font.info, "italicAngle")
        post.italicAngle = italicAngle
        # underline
        underlinePosition = getAttrWithFallback(font.info,
                                                "postscriptUnderlinePosition")
        if underlinePosition is None:
            underlinePosition = 0
        post.underlinePosition = _roundInt(underlinePosition)
        underlineThickness = getAttrWithFallback(
            font.info, "postscriptUnderlineThickness")
        if underlineThickness is None:
            underlineThickness = 0
        post.underlineThickness = _roundInt(underlineThickness)
        # determine if the font has a fixed width
        post.isFixedPitch = getAttrWithFallback(font.info,
                                                "postscriptIsFixedPitch")
        # misc
        post.minMemType42 = 0
        post.maxMemType42 = 0
        post.minMemType1 = 0
        post.maxMemType1 = 0

    def setupTable_CFF(self):
        """
        Make the CFF table.

        **This should not be called externally.** Subclasses
        may override or supplement this method to handle the
        table creation in a different way if desired.
        """
        self.otf["CFF "] = cff = newTable("CFF ")
        cff = cff.cff
        # set up the basics
        cff.major = 1
        cff.minor = 0
        cff.hdrSize = 4
        cff.offSize = 4
        cff.fontNames = []
        strings = IndexedStrings()
        cff.strings = strings
        private = PrivateDict(strings=strings)
        private.rawDict.update(private.defaults)
        globalSubrs = GlobalSubrsIndex(private=private)
        topDict = TopDict(GlobalSubrs=globalSubrs, strings=strings)
        topDict.Private = private
        charStrings = topDict.CharStrings = CharStrings(
            file=None,
            charset=None,
            globalSubrs=globalSubrs,
            private=private,
            fdSelect=None,
            fdArray=None)
        charStrings.charStringsAreIndexed = True
        topDict.charset = []
        charStringsIndex = charStrings.charStringsIndex = SubrsIndex(
            private=private, globalSubrs=globalSubrs)
        cff.topDictIndex = topDictIndex = TopDictIndex()
        topDictIndex.append(topDict)
        topDictIndex.strings = strings
        cff.GlobalSubrs = globalSubrs
        # populate naming data
        info = self.ufo.info
        psName = getAttrWithFallback(info, "postscriptFontName")
        cff.fontNames.append(psName)
        topDict = cff.topDictIndex[0]
        topDict.version = "%d.%d" % (getAttrWithFallback(
            info, "versionMajor"), getAttrWithFallback(info, "versionMinor"))
        trademark = getAttrWithFallback(info, "trademark")
        if trademark:
            trademark = normalizeStringForPostscript(
                trademark.replace(u"\u00A9", "Copyright"))
        if trademark != self.ufo.info.trademark:
            self.log.append(
                "[Warning] The trademark was normalized for storage in the CFF table and consequently some characters were dropped: '%s'"
                % trademark)
        if trademark is None:
            trademark = ""
        topDict.Notice = trademark
        copyright = getAttrWithFallback(info, "copyright")
        if copyright:
            copyright = normalizeStringForPostscript(
                copyright.replace(u"\u00A9", "Copyright"))
        if copyright != self.ufo.info.copyright:
            self.log.append(
                "[Warning] The copyright was normalized for storage in the CFF table and consequently some characters were dropped: '%s'"
                % copyright)
        if copyright is None:
            copyright = ""
        topDict.Copyright = copyright
        topDict.FullName = getAttrWithFallback(info, "postscriptFullName")
        topDict.FamilyName = getAttrWithFallback(
            info, "openTypeNamePreferredFamilyName")
        topDict.Weight = getAttrWithFallback(info, "postscriptWeightName")
        topDict.FontName = getAttrWithFallback(info, "postscriptFontName")
        # populate various numbers
        topDict.isFixedPitch = getAttrWithFallback(info,
                                                   "postscriptIsFixedPitch")
        topDict.ItalicAngle = getAttrWithFallback(info, "italicAngle")
        underlinePosition = getAttrWithFallback(info,
                                                "postscriptUnderlinePosition")
        if underlinePosition is None:
            underlinePosition = 0
        topDict.UnderlinePosition = _roundInt(underlinePosition)
        underlineThickness = getAttrWithFallback(
            info, "postscriptUnderlineThickness")
        if underlineThickness is None:
            underlineThickness = 0
        topDict.UnderlineThickness = _roundInt(underlineThickness)
        # populate font matrix
        unitsPerEm = _roundInt(getAttrWithFallback(info, "unitsPerEm"))
        topDict.FontMatrix = [1.0 / unitsPerEm, 0, 0, 1.0 / unitsPerEm, 0, 0]
        # populate the width values
        defaultWidthX = _roundInt(
            getAttrWithFallback(info, "postscriptDefaultWidthX"))
        if defaultWidthX:
            private.rawDict["defaultWidthX"] = defaultWidthX
        nominalWidthX = _roundInt(
            getAttrWithFallback(info, "postscriptNominalWidthX"))
        if nominalWidthX:
            private.rawDict["nominalWidthX"] = nominalWidthX
        # populate hint data
        blueFuzz = _roundInt(getAttrWithFallback(info, "postscriptBlueFuzz"))
        blueShift = _roundInt(getAttrWithFallback(info, "postscriptBlueShift"))
        blueScale = getAttrWithFallback(info, "postscriptBlueScale")
        forceBold = getAttrWithFallback(info, "postscriptForceBold")
        blueValues = getAttrWithFallback(info, "postscriptBlueValues")
        if isinstance(blueValues, list):
            blueValues = [_roundInt(i) for i in blueValues]
        otherBlues = getAttrWithFallback(info, "postscriptOtherBlues")
        if isinstance(otherBlues, list):
            otherBlues = [_roundInt(i) for i in otherBlues]
        familyBlues = getAttrWithFallback(info, "postscriptFamilyBlues")
        if isinstance(familyBlues, list):
            familyBlues = [_roundInt(i) for i in familyBlues]
        familyOtherBlues = getAttrWithFallback(info,
                                               "postscriptFamilyOtherBlues")
        if isinstance(familyOtherBlues, list):
            familyOtherBlues = [_roundInt(i) for i in familyOtherBlues]
        stemSnapH = getAttrWithFallback(info, "postscriptStemSnapH")
        if isinstance(stemSnapH, list):
            stemSnapH = [_roundInt(i) for i in stemSnapH]
        stemSnapV = getAttrWithFallback(info, "postscriptStemSnapV")
        if isinstance(stemSnapV, list):
            stemSnapV = [_roundInt(i) for i in stemSnapV]
        # only write the blues data if some blues are defined.
        if (blueValues or otherBlues):
            private.rawDict["BlueFuzz"] = blueFuzz
            private.rawDict["BlueShift"] = blueShift
            private.rawDict["BlueScale"] = blueScale
            private.rawDict["ForceBold"] = forceBold
            private.rawDict["BlueValues"] = blueValues
            private.rawDict["OtherBlues"] = otherBlues
            private.rawDict["FamilyBlues"] = familyBlues
            private.rawDict["FamilyOtherBlues"] = familyOtherBlues
        # only write the stems if both are defined.
        if (stemSnapH and stemSnapV):
            private.rawDict["StemSnapH"] = stemSnapH
            private.rawDict["StdHW"] = stemSnapH[0]
            private.rawDict["StemSnapV"] = stemSnapV
            private.rawDict["StdVW"] = stemSnapV[0]
        # populate glyphs
        for glyphName in self.glyphOrder:
            glyph = self.allGlyphs[glyphName]
            unicodes = glyph.unicodes
            charString = self.getCharStringForGlyph(glyph, private,
                                                    globalSubrs)
            # add to the font
            exists = charStrings.has_key(glyphName)
            if exists:
                # XXX a glyph already has this name. should we choke?
                glyphID = charStrings.charStrings[glyphName]
                charStringsIndex.items[glyphID] = charString
            else:
                charStringsIndex.append(charString)
                glyphID = len(topDict.charset)
                charStrings.charStrings[glyphName] = glyphID
                topDict.charset.append(glyphName)
        topDict.FontBBox = self.fontBoundingBox
        # write the glyph order
        self.otf.setGlyphOrder(self.glyphOrder)

    def setupOtherTables(self):
        """
        Make the other tables. The default implementation does nothing.

        **This should not be called externally.** Subclasses
        may override this method to add other tables to the
        font if desired.
        """
        pass
예제 #29
0
def makeLookup1():
    # make a variation of the shell TTX data
    f = open(shellSourcePath)
    ttxData = f.read()
    f.close()
    ttxData = ttxData.replace("__familyName__", "gsubtest-lookup1")
    tempShellSourcePath = shellSourcePath + ".temp"
    f = open(tempShellSourcePath, "wb")
    f.write(ttxData)
    f.close()
    
    # compile the shell
    shell = TTFont(sfntVersion="OTTO")
    shell.importXML(tempShellSourcePath)
    shell.save(shellTempPath)
    os.remove(tempShellSourcePath)
    
    # load the shell
    shell = TTFont(shellTempPath)
    
    # grab the PASS and FAIL data
    hmtx = shell["hmtx"]
    glyphSet = shell.getGlyphSet()
    
    failGlyph = glyphSet["F"]
    failGlyph.decompile()
    failGlyphProgram = list(failGlyph.program)
    failGlyphMetrics = hmtx["F"]
    
    passGlyph = glyphSet["P"]
    passGlyph.decompile()
    passGlyphProgram = list(passGlyph.program)
    passGlyphMetrics = hmtx["P"]
    
    # grab some tables
    hmtx = shell["hmtx"]
    cmap = shell["cmap"]
    
    # start the glyph order
    existingGlyphs = [".notdef", "space", "F", "P"]
    glyphOrder = list(existingGlyphs)
    
    # start the CFF
    cff = shell["CFF "].cff
    globalSubrs = cff.GlobalSubrs
    topDict = cff.topDictIndex[0]
    topDict.charset = existingGlyphs
    private = topDict.Private
    charStrings = topDict.CharStrings
    charStringsIndex = charStrings.charStringsIndex
    
    features = sorted(mapping)

    # build the outline, hmtx and cmap data
    cp = baseCodepoint
    for index, tag in enumerate(features):
    
    	# tag.pass
    	glyphName = "{0!s}.pass".format(tag)
    	glyphOrder.append(glyphName)
    	addGlyphToCFF(
    		glyphName=glyphName,
    		program=passGlyphProgram,
    		private=private,
    		globalSubrs=globalSubrs,
    		charStringsIndex=charStringsIndex,
    		topDict=topDict,
            charStrings=charStrings
    	)
    	hmtx[glyphName] = passGlyphMetrics
     
    	for table in cmap.tables:
    		if table.format == 4:
    			table.cmap[cp] = glyphName
    		else:
    			raise NotImplementedError, "Unsupported cmap table format: {0:d}".format(table.format)
    	cp += 1
    
    	# tag.fail
    	glyphName = "{0!s}.fail".format(tag)
    	glyphOrder.append(glyphName)
    	addGlyphToCFF(
    		glyphName=glyphName,
    		program=failGlyphProgram,
    		private=private,
    		globalSubrs=globalSubrs,
    		charStringsIndex=charStringsIndex,
    		topDict=topDict,
            charStrings=charStrings
    	)
    	hmtx[glyphName] = failGlyphMetrics
     
    	for table in cmap.tables:
    		if table.format == 4:
    			table.cmap[cp] = glyphName
    		else:
    			raise NotImplementedError, "Unsupported cmap table format: {0:d}".format(table.format)

        # bump this up so that the sequence is the same as the lookup 3 font
    	cp += 3

    # set the glyph order
    shell.setGlyphOrder(glyphOrder)
    
    # start the GSUB
    shell["GSUB"] = newTable("GSUB")
    gsub = shell["GSUB"].table = GSUB()
    gsub.Version = 1.0
    
    # make a list of all the features we will make
    featureCount = len(features)
    
    # set up the script list
    scriptList = gsub.ScriptList = ScriptList()
    scriptList.ScriptCount = 1
    scriptList.ScriptRecord = []
    scriptRecord = ScriptRecord()
    scriptList.ScriptRecord.append(scriptRecord)
    scriptRecord.ScriptTag = "DFLT"
    script = scriptRecord.Script = Script()
    defaultLangSys = script.DefaultLangSys = DefaultLangSys()
    defaultLangSys.FeatureCount = featureCount
    defaultLangSys.FeatureIndex = range(defaultLangSys.FeatureCount)
    defaultLangSys.ReqFeatureIndex = 65535
    defaultLangSys.LookupOrder = None
    script.LangSysCount = 0
    script.LangSysRecord = []
    
    # set up the feature list
    featureList = gsub.FeatureList = FeatureList()
    featureList.FeatureCount = featureCount
    featureList.FeatureRecord = []
    for index, tag in enumerate(features):
        # feature record
        featureRecord = FeatureRecord()
        featureRecord.FeatureTag = tag
        feature = featureRecord.Feature = Feature()
        featureList.FeatureRecord.append(featureRecord)
        # feature
        feature.FeatureParams = None
        feature.LookupCount = 1
        feature.LookupListIndex = [index]
    
    # write the lookups
    lookupList = gsub.LookupList = LookupList()
    lookupList.LookupCount = featureCount
    lookupList.Lookup = []
    for tag in features:
        # lookup
        lookup = Lookup()
        lookup.LookupType = 1
        lookup.LookupFlag = 0
        lookup.SubTableCount = 1
        lookup.SubTable = []
        lookupList.Lookup.append(lookup)
        # subtable
        subtable = SingleSubst()
        subtable.Format = 2
        subtable.LookupType = 1
        subtable.mapping = {
            "{0!s}.pass".format(tag) : "{0!s}.fail".format(tag),
            "{0!s}.fail".format(tag) : "{0!s}.pass".format(tag),
        }
        lookup.SubTable.append(subtable)
    
    path = outputPath % 1 + ".otf"
    if os.path.exists(path):
    	os.remove(path)
    shell.save(path)
    
    # get rid of the shell
    if os.path.exists(shellTempPath):
        os.remove(shellTempPath)
예제 #30
0
def make_font(feature_source, fea_type="fea"):
    """Return font with GSUB compiled from given source.

    Adds a bunch of filler tables so the font can be saved if needed, for
    debugging purposes.
    """

    # copied from fontTools' feaLib/builder_test.
    glyphs = """
        .notdef space slash fraction semicolon period comma ampersand
        quotedblleft quotedblright quoteleft quoteright
        zero one two three four five six seven eight nine
        zero.oldstyle one.oldstyle two.oldstyle three.oldstyle
        four.oldstyle five.oldstyle six.oldstyle seven.oldstyle
        eight.oldstyle nine.oldstyle onequarter onehalf threequarters
        onesuperior twosuperior threesuperior ordfeminine ordmasculine
        A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
        a b c d e f g h i j k l m n o p q r s t u v w x y z
        A.sc B.sc C.sc D.sc E.sc F.sc G.sc H.sc I.sc J.sc K.sc L.sc M.sc
        N.sc O.sc P.sc Q.sc R.sc S.sc T.sc U.sc V.sc W.sc X.sc Y.sc Z.sc
        A.alt1 A.alt2 A.alt3 B.alt1 B.alt2 B.alt3 C.alt1 C.alt2 C.alt3
        a.alt1 a.alt2 a.alt3 a.end b.alt c.mid d.alt d.mid
        e.begin e.mid e.end m.begin n.end s.end z.end
        Eng Eng.alt1 Eng.alt2 Eng.alt3
        A.swash B.swash C.swash D.swash E.swash F.swash G.swash H.swash
        I.swash J.swash K.swash L.swash M.swash N.swash O.swash P.swash
        Q.swash R.swash S.swash T.swash U.swash V.swash W.swash X.swash
        Y.swash Z.swash
        f_l c_h c_k c_s c_t f_f f_f_i f_f_l f_i o_f_f_i s_t f_i.begin
        a_n_d T_h T_h.swash germandbls ydieresis yacute breve
        grave acute dieresis macron circumflex cedilla umlaut ogonek caron
        damma hamza sukun kasratan lam_meem_jeem noon.final noon.initial
        by feature lookup sub table
    """.split()
    font = TTFont()
    font.setGlyphOrder(glyphs)
    glyph_order = font.getGlyphOrder()

    font["cmap"] = cmap = newTable("cmap")
    table = cmap_format_4(4)
    table.platformID = 3
    table.platEncID = 1
    table.language = 0
    table.cmap = {AGL2UV[n]: n for n in glyph_order if n in AGL2UV}
    cmap.tableVersion = 0
    cmap.tables = [table]

    font["glyf"] = glyf = newTable("glyf")
    glyf.glyphs = {}
    glyf.glyphOrder = glyph_order
    for name in glyph_order:
        pen = TTGlyphPen(None)
        glyf[name] = pen.glyph()

    font["head"] = head = newTable("head")
    head.tableVersion = 1.0
    head.fontRevision = 1.0
    head.flags = (
        head.checkSumAdjustment
    ) = (
        head.magicNumber
    ) = (
        head.created
    ) = (
        head.modified
    ) = (
        head.macStyle
    ) = (
        head.lowestRecPPEM
    ) = (
        head.fontDirectionHint
    ) = (
        head.indexToLocFormat
    ) = head.glyphDataFormat = head.xMin = head.xMax = head.yMin = head.yMax = 0
    head.unitsPerEm = 1000

    font["hhea"] = hhea = newTable("hhea")
    hhea.tableVersion = 0x00010000
    hhea.ascent = (
        hhea.descent
    ) = (
        hhea.lineGap
    ) = (
        hhea.caretSlopeRise
    ) = (
        hhea.caretSlopeRun
    ) = (
        hhea.caretOffset
    ) = (
        hhea.reserved0
    ) = (
        hhea.reserved1
    ) = (
        hhea.reserved2
    ) = (
        hhea.reserved3
    ) = (
        hhea.metricDataFormat
    ) = (
        hhea.advanceWidthMax
    ) = (
        hhea.xMaxExtent
    ) = hhea.minLeftSideBearing = hhea.minRightSideBearing = hhea.numberOfHMetrics = 0

    font["hmtx"] = hmtx = newTable("hmtx")
    hmtx.metrics = {}
    for name in glyph_order:
        hmtx[name] = (600, 50)

    font["loca"] = newTable("loca")

    font["maxp"] = maxp = newTable("maxp")
    maxp.tableVersion = 0x00005000
    maxp.numGlyphs = 0

    font["post"] = post = newTable("post")
    post.formatType = 2.0
    post.extraNames = []
    post.mapping = {}
    post.glyphOrder = glyph_order
    post.italicAngle = (
        post.underlinePosition
    ) = (
        post.underlineThickness
    ) = (
        post.isFixedPitch
    ) = post.minMemType42 = post.maxMemType42 = post.minMemType1 = post.maxMemType1 = 0

    if fea_type == "fea":
        addOpenTypeFeaturesFromString(font, feature_source)
    elif fea_type == "mti":
        font["GSUB"] = mtiLib.build(UnicodeIO(feature_source), font)

    return font
예제 #31
0
class OTFPostProcessor(object):
    """Does some post-processing operations on a compiled OpenType font, using
    info from the source UFO where necessary.
    """

    def __init__(self, otf, ufo):
        self.ufo = ufo
        stream = BytesIO()
        otf.save(stream)
        stream.seek(0)
        self.otf = TTFont(stream)

    def process(self, useProductionNames=True, optimizeCff=True):
        if useProductionNames:
            self._rename_glyphs_from_ufo()
        if optimizeCff and 'CFF ' in self.otf:
            from compreffor import Compreffor
            comp = Compreffor(self.otf)
            comp.compress()
        return self.otf

    def _rename_glyphs_from_ufo(self):
        """Rename glyphs using glif.lib.public.postscriptNames in UFO."""

        rename_map = {
            g.name: self._build_production_name(g) for g in self.ufo}
        # .notdef may not be present in the original font
        rename_map[".notdef"] = ".notdef"
        rename = lambda names: [rename_map[n] for n in names]

        self.otf.setGlyphOrder(rename(self.otf.getGlyphOrder()))
        if 'CFF ' in self.otf:
            cff = self.otf['CFF '].cff.topDictIndex[0]
            char_strings = cff.CharStrings.charStrings
            cff.CharStrings.charStrings = {
                rename_map.get(n, n): v for n, v in char_strings.items()}
            cff.charset = rename(cff.charset)

    def _build_production_name(self, glyph):
        """Build a production name for a single glyph."""

        # use name from Glyphs source if available
        production_name = glyph.lib.get('public.postscriptName')
        if production_name:
            return production_name

        # use name derived from unicode value
        unicode_val = glyph.unicode
        if glyph.unicode is not None:
            return '%s%04X' % (
                'u' if unicode_val > 0xffff else 'uni', unicode_val)

        # use production name + last (non-script) suffix if possible
        parts = glyph.name.rsplit('.', 1)
        if len(parts) == 2 and parts[0] in self.ufo:
            return '%s.%s' % (
                self._build_production_name(self.ufo[parts[0]]), parts[1])

        # use ligature name, making sure to look up components with suffixes
        parts = glyph.name.split('.', 1)
        if len(parts) == 2:
            liga_parts = ['%s.%s' % (n, parts[1]) for n in parts[0].split('_')]
        else:
            liga_parts = glyph.name.split('_')
        if len(liga_parts) > 1 and all(n in self.ufo for n in liga_parts):
            unicode_vals = [self.ufo[n].unicode for n in liga_parts]
            if all(v and v <= 0xffff for v in unicode_vals):
                return 'uni' + ''.join('%04X' % v for v in unicode_vals)
            return '_'.join(
                self._build_production_name(self.ufo[n]) for n in liga_parts)

        return glyph.name
예제 #32
0
def test_max_ctx_calc_no_features():
    font = TTFont()
    assert maxCtxFont(font) == 0
    font.setGlyphOrder(['.notdef'])
    addOpenTypeFeaturesFromString(font, '')
    assert maxCtxFont(font) == 0
예제 #33
0
파일: woff2.py 프로젝트: Moscarda/fonttools
class WOFF2Writer(SFNTWriter):

	flavor = "woff2"

	def __init__(self, file, numTables, sfntVersion="\000\001\000\000",
		         flavor=None, flavorData=None):
		if not haveBrotli:
			print('The WOFF2 encoder requires the Brotli Python extension, available at:\n'
				  'https://github.com/google/brotli', file=sys.stderr)
			raise ImportError("No module named brotli")

		self.file = file
		self.numTables = numTables
		self.sfntVersion = Tag(sfntVersion)
		self.flavorData = flavorData or WOFF2FlavorData()

		self.directoryFormat = woff2DirectoryFormat
		self.directorySize = woff2DirectorySize
		self.DirectoryEntry = WOFF2DirectoryEntry

		self.signature = Tag("wOF2")

		self.nextTableOffset = 0
		self.transformBuffer = BytesIO()

		self.tables = OrderedDict()

		# make empty TTFont to store data while normalising and transforming tables
		self.ttFont = TTFont(recalcBBoxes=False, recalcTimestamp=False)

	def __setitem__(self, tag, data):
		"""Associate new entry named 'tag' with raw table data."""
		if tag in self.tables:
			raise TTLibError("cannot rewrite '%s' table" % tag)
		if tag == 'DSIG':
			# always drop DSIG table, since the encoding process can invalidate it
			self.numTables -= 1
			return

		entry = self.DirectoryEntry()
		entry.tag = Tag(tag)
		entry.flags = getKnownTagIndex(entry.tag)
		# WOFF2 table data are written to disk only on close(), after all tags
		# have been specified
		entry.data = data

		self.tables[tag] = entry

	def close(self):
		""" All tags must have been specified. Now write the table data and directory.
		"""
		if len(self.tables) != self.numTables:
			raise TTLibError("wrong number of tables; expected %d, found %d" % (self.numTables, len(self.tables)))

		if self.sfntVersion in ("\x00\x01\x00\x00", "true"):
			isTrueType = True
		elif self.sfntVersion == "OTTO":
			isTrueType = False
		else:
			raise TTLibError("Not a TrueType or OpenType font (bad sfntVersion)")

		# The WOFF2 spec no longer requires the glyph offsets to be 4-byte aligned.
		# However, the reference WOFF2 implementation still fails to reconstruct
		# 'unpadded' glyf tables, therefore we need to 'normalise' them.
		# See:
		# https://github.com/khaledhosny/ots/issues/60
		# https://github.com/google/woff2/issues/15
		if isTrueType:
			self._normaliseGlyfAndLoca(padding=4)
		self._setHeadTransformFlag()

		# To pass the legacy OpenType Sanitiser currently included in browsers,
		# we must sort the table directory and data alphabetically by tag.
		# See:
		# https://github.com/google/woff2/pull/3
		# https://lists.w3.org/Archives/Public/public-webfonts-wg/2015Mar/0000.html
		# TODO(user): remove to match spec once browsers are on newer OTS
		self.tables = OrderedDict(sorted(self.tables.items()))

		self.totalSfntSize = self._calcSFNTChecksumsLengthsAndOffsets()

		fontData = self._transformTables()
		compressedFont = brotli.compress(fontData, mode=brotli.MODE_FONT)

		self.totalCompressedSize = len(compressedFont)
		self.length = self._calcTotalSize()
		self.majorVersion, self.minorVersion = self._getVersion()
		self.reserved = 0

		directory = self._packTableDirectory()
		self.file.seek(0)
		self.file.write(pad(directory + compressedFont, size=4))
		self._writeFlavorData()

	def _normaliseGlyfAndLoca(self, padding=4):
		""" Recompile glyf and loca tables, aligning glyph offsets to multiples of
		'padding' size. Update the head table's 'indexToLocFormat' accordingly while
		compiling loca.
		"""
		if self.sfntVersion == "OTTO":
			return

		# make up glyph names required to decompile glyf table
		self._decompileTable('maxp')
		numGlyphs = self.ttFont['maxp'].numGlyphs
		glyphOrder = ['.notdef'] + ["glyph%.5d" % i for i in range(1, numGlyphs)]
		self.ttFont.setGlyphOrder(glyphOrder)

		for tag in ('head', 'loca', 'glyf'):
			self._decompileTable(tag)
		self.ttFont['glyf'].padding = padding
		for tag in ('glyf', 'loca'):
			self._compileTable(tag)

	def _setHeadTransformFlag(self):
		""" Set bit 11 of 'head' table flags to indicate that the font has undergone
		a lossless modifying transform. Re-compile head table data."""
		self._decompileTable('head')
		self.ttFont['head'].flags |= (1 << 11)
		self._compileTable('head')

	def _decompileTable(self, tag):
		""" Fetch table data, decompile it, and store it inside self.ttFont. """
		tag = Tag(tag)
		if tag not in self.tables:
			raise TTLibError("missing required table: %s" % tag)
		if self.ttFont.isLoaded(tag):
			return
		data = self.tables[tag].data
		if tag == 'loca':
			tableClass = WOFF2LocaTable
		elif tag == 'glyf':
			tableClass = WOFF2GlyfTable
		else:
			tableClass = getTableClass(tag)
		table = tableClass(tag)
		self.ttFont.tables[tag] = table
		table.decompile(data, self.ttFont)

	def _compileTable(self, tag):
		""" Compile table and store it in its 'data' attribute. """
		self.tables[tag].data = self.ttFont[tag].compile(self.ttFont)

	def _calcSFNTChecksumsLengthsAndOffsets(self):
		""" Compute the 'original' SFNT checksums, lengths and offsets for checksum
		adjustment calculation. Return the total size of the uncompressed font.
		"""
		offset = sfntDirectorySize + sfntDirectoryEntrySize * len(self.tables)
		for tag, entry in self.tables.items():
			data = entry.data
			entry.origOffset = offset
			entry.origLength = len(data)
			if tag == 'head':
				entry.checkSum = calcChecksum(data[:8] + b'\0\0\0\0' + data[12:])
			else:
				entry.checkSum = calcChecksum(data)
			offset += (entry.origLength + 3) & ~3
		return offset

	def _transformTables(self):
		"""Return transformed font data."""
		for tag, entry in self.tables.items():
			if tag in woff2TransformedTableTags:
				data = self.transformTable(tag)
			else:
				data = entry.data
			entry.offset = self.nextTableOffset
			entry.saveData(self.transformBuffer, data)
			self.nextTableOffset += entry.length
		self.writeMasterChecksum()
		fontData = self.transformBuffer.getvalue()
		return fontData

	def transformTable(self, tag):
		"""Return transformed table data."""
		if tag not in woff2TransformedTableTags:
			raise TTLibError("Transform for table '%s' is unknown" % tag)
		if tag == "loca":
			data = b""
		elif tag == "glyf":
			for tag in ('maxp', 'head', 'loca', 'glyf'):
				self._decompileTable(tag)
			glyfTable = self.ttFont['glyf']
			data = glyfTable.transform(self.ttFont)
		else:
			raise NotImplementedError
		return data

	def _calcMasterChecksum(self):
		"""Calculate checkSumAdjustment."""
		tags = list(self.tables.keys())
		checksums = []
		for i in range(len(tags)):
			checksums.append(self.tables[tags[i]].checkSum)

		# Create a SFNT directory for checksum calculation purposes
		self.searchRange, self.entrySelector, self.rangeShift = getSearchRange(self.numTables, 16)
		directory = sstruct.pack(sfntDirectoryFormat, self)
		tables = sorted(self.tables.items())
		for tag, entry in tables:
			sfntEntry = SFNTDirectoryEntry()
			sfntEntry.tag = entry.tag
			sfntEntry.checkSum = entry.checkSum
			sfntEntry.offset = entry.origOffset
			sfntEntry.length = entry.origLength
			directory = directory + sfntEntry.toString()

		directory_end = sfntDirectorySize + len(self.tables) * sfntDirectoryEntrySize
		assert directory_end == len(directory)

		checksums.append(calcChecksum(directory))
		checksum = sum(checksums) & 0xffffffff
		# BiboAfba!
		checksumadjustment = (0xB1B0AFBA - checksum) & 0xffffffff
		return checksumadjustment

	def writeMasterChecksum(self):
		"""Write checkSumAdjustment to the transformBuffer."""
		checksumadjustment = self._calcMasterChecksum()
		self.transformBuffer.seek(self.tables['head'].offset + 8)
		self.transformBuffer.write(struct.pack(">L", checksumadjustment))

	def _calcTotalSize(self):
		"""Calculate total size of WOFF2 font, including any meta- and/or private data."""
		offset = self.directorySize
		for entry in self.tables.values():
			offset += len(entry.toString())
		offset += self.totalCompressedSize
		offset = (offset + 3) & ~3
		offset = self._calcFlavorDataOffsetsAndSize(offset)
		return offset

	def _calcFlavorDataOffsetsAndSize(self, start):
		"""Calculate offsets and lengths for any meta- and/or private data."""
		offset = start
		data = self.flavorData
		if data.metaData:
			self.metaOrigLength = len(data.metaData)
			self.metaOffset = offset
			self.compressedMetaData = brotli.compress(
				data.metaData, mode=brotli.MODE_TEXT)
			self.metaLength = len(self.compressedMetaData)
			offset += self.metaLength
		else:
			self.metaOffset = self.metaLength = self.metaOrigLength = 0
			self.compressedMetaData = b""
		if data.privData:
			# make sure private data is padded to 4-byte boundary
			offset = (offset + 3) & ~3
			self.privOffset = offset
			self.privLength = len(data.privData)
			offset += self.privLength
		else:
			self.privOffset = self.privLength = 0
		return offset

	def _getVersion(self):
		"""Return the WOFF2 font's (majorVersion, minorVersion) tuple."""
		data = self.flavorData
		if data.majorVersion is not None and data.minorVersion is not None:
			return data.majorVersion, data.minorVersion
		else:
			# if None, return 'fontRevision' from 'head' table
			if 'head' in self.tables:
				return struct.unpack(">HH", self.tables['head'].data[4:8])
			else:
				return 0, 0

	def _packTableDirectory(self):
		"""Return WOFF2 table directory data."""
		directory = sstruct.pack(self.directoryFormat, self)
		for entry in self.tables.values():
			directory = directory + entry.toString()
		return directory

	def _writeFlavorData(self):
		"""Write metadata and/or private data using appropiate padding."""
		compressedMetaData = self.compressedMetaData
		privData = self.flavorData.privData
		if compressedMetaData and privData:
			compressedMetaData = pad(compressedMetaData, size=4)
		if compressedMetaData:
			self.file.seek(self.metaOffset)
			assert self.file.tell() == self.metaOffset
			self.file.write(compressedMetaData)
		if privData:
			self.file.seek(self.privOffset)
			assert self.file.tell() == self.privOffset
			self.file.write(privData)

	def reordersTables(self):
		return True
예제 #34
0
 def create_font(celf):
     font = TTFont()
     font.setGlyphOrder(celf.GLYPH_ORDER)
     return font
예제 #35
0
    fonts.append(TTFont(sys.argv[index], lazy=False))
    if fonts[0].getGlyphOrder() != fonts[-1].getGlyphOrder():
        glyphs1 = fonts[0].getGlyphOrder()
        glyphs2 = fonts[-1].getGlyphOrder()

        missing = [glyph for glyph in glyphs1 if glyph not in glyphs2]
        extra = [glyph for glyph in glyphs2 if glyph not in glyphs1]
        print("all fonts must have the same glyph order!")
        print("missing from {}:".format(sys.argv[index]))
        print(missing)
        print("extra from {}:".format(sys.argv[index]))
        print(extra)
        sys.exit(1)

out_font = TTFont()
out_font.setGlyphOrder(fonts[0].getGlyphOrder())

# Most tables are just copied from the first input file
# TODO: em values and PPEM will vary, does this matter?
for tag in ("head", "hhea", "maxp", "OS/2", "hmtx", "cmap", "name", "post"):
    out_font[tag] = fonts[0][tag]

out_font["EBLC"] = eblc = table_E_B_L_C_()
out_font["EBDT"] = ebdt = table_E_B_D_T_()

eblc.version = fonts[0]["EBLC"].version
ebdt.version = fonts[0]["EBDT"].version

eblc.strikes = []
ebdt.strikeData = []
예제 #36
0
def test_max_ctx_calc_no_features():
    font = TTFont()
    assert maxCtxFont(font) == 0
    font.setGlyphOrder(['.notdef'])
    addOpenTypeFeaturesFromString(font, '')
    assert maxCtxFont(font) == 0
예제 #37
0
 def create_font(celf):
     font = TTFont()
     font.setGlyphOrder(celf.GLYPH_ORDER)
     return font
예제 #38
0
def makeLookup1():
    # make a variation of the shell TTX data
    f = open(shellSourcePath)
    ttxData = f.read()
    f.close()
    ttxData = ttxData.replace("__familyName__", "gsubtest-lookup1")
    tempShellSourcePath = shellSourcePath + ".temp"
    f = open(tempShellSourcePath, "wb")
    f.write(ttxData)
    f.close()

    # compile the shell
    shell = TTFont(sfntVersion="OTTO")
    shell.importXML(tempShellSourcePath)
    shell.save(shellTempPath)
    os.remove(tempShellSourcePath)

    # load the shell
    shell = TTFont(shellTempPath)

    # grab the PASS and FAIL data
    hmtx = shell["hmtx"]
    glyphSet = shell.getGlyphSet()

    failGlyph = glyphSet["F"]
    failGlyph.decompile()
    failGlyphProgram = list(failGlyph.program)
    failGlyphMetrics = hmtx["F"]

    passGlyph = glyphSet["P"]
    passGlyph.decompile()
    passGlyphProgram = list(passGlyph.program)
    passGlyphMetrics = hmtx["P"]

    # grab some tables
    hmtx = shell["hmtx"]
    cmap = shell["cmap"]

    # start the glyph order
    existingGlyphs = [".notdef", "space", "F", "P"]
    glyphOrder = list(existingGlyphs)

    # start the CFF
    cff = shell["CFF "].cff
    globalSubrs = cff.GlobalSubrs
    topDict = cff.topDictIndex[0]
    topDict.charset = existingGlyphs
    private = topDict.Private
    charStrings = topDict.CharStrings
    charStringsIndex = charStrings.charStringsIndex

    features = sorted(mapping)

    # build the outline, hmtx and cmap data
    cp = baseCodepoint
    for index, tag in enumerate(features):

        # tag.pass
        glyphName = "%s.pass" % tag
        glyphOrder.append(glyphName)
        addGlyphToCFF(
            glyphName=glyphName,
            program=passGlyphProgram,
            private=private,
            globalSubrs=globalSubrs,
            charStringsIndex=charStringsIndex,
            topDict=topDict,
            charStrings=charStrings,
        )
        hmtx[glyphName] = passGlyphMetrics

        for table in cmap.tables:
            if table.format == 4:
                table.cmap[cp] = glyphName
            else:
                raise NotImplementedError("Unsupported cmap table format: %d" %
                                          table.format)
        cp += 1

        # tag.fail
        glyphName = "%s.fail" % tag
        glyphOrder.append(glyphName)
        addGlyphToCFF(
            glyphName=glyphName,
            program=failGlyphProgram,
            private=private,
            globalSubrs=globalSubrs,
            charStringsIndex=charStringsIndex,
            topDict=topDict,
            charStrings=charStrings,
        )
        hmtx[glyphName] = failGlyphMetrics

        for table in cmap.tables:
            if table.format == 4:
                table.cmap[cp] = glyphName
            else:
                raise NotImplementedError("Unsupported cmap table format: %d" %
                                          table.format)

                # bump this up so that the sequence is the same as the lookup 3 font
        cp += 3

    # set the glyph order
    shell.setGlyphOrder(glyphOrder)

    # start the GSUB
    shell["GSUB"] = newTable("GSUB")
    gsub = shell["GSUB"].table = GSUB()
    gsub.Version = 1.0

    # make a list of all the features we will make
    featureCount = len(features)

    # set up the script list
    scriptList = gsub.ScriptList = ScriptList()
    scriptList.ScriptCount = 1
    scriptList.ScriptRecord = []
    scriptRecord = ScriptRecord()
    scriptList.ScriptRecord.append(scriptRecord)
    scriptRecord.ScriptTag = "DFLT"
    script = scriptRecord.Script = Script()
    defaultLangSys = script.DefaultLangSys = DefaultLangSys()
    defaultLangSys.FeatureCount = featureCount
    defaultLangSys.FeatureIndex = range(defaultLangSys.FeatureCount)
    defaultLangSys.ReqFeatureIndex = 65535
    defaultLangSys.LookupOrder = None
    script.LangSysCount = 0
    script.LangSysRecord = []

    # set up the feature list
    featureList = gsub.FeatureList = FeatureList()
    featureList.FeatureCount = featureCount
    featureList.FeatureRecord = []
    for index, tag in enumerate(features):
        # feature record
        featureRecord = FeatureRecord()
        featureRecord.FeatureTag = tag
        feature = featureRecord.Feature = Feature()
        featureList.FeatureRecord.append(featureRecord)
        # feature
        feature.FeatureParams = None
        feature.LookupCount = 1
        feature.LookupListIndex = [index]

    # write the lookups
    lookupList = gsub.LookupList = LookupList()
    lookupList.LookupCount = featureCount
    lookupList.Lookup = []
    for tag in features:
        # lookup
        lookup = Lookup()
        lookup.LookupType = 1
        lookup.LookupFlag = 0
        lookup.SubTableCount = 1
        lookup.SubTable = []
        lookupList.Lookup.append(lookup)
        # subtable
        subtable = SingleSubst()
        subtable.Format = 2
        subtable.LookupType = 1
        subtable.mapping = {
            "%s.pass" % tag: "%s.fail" % tag,
            "%s.fail" % tag: "%s.pass" % tag,
        }
        lookup.SubTable.append(subtable)

    path = outputPath % 1 + ".otf"
    if os.path.exists(path):
        os.remove(path)
    shell.save(path)

    # get rid of the shell
    if os.path.exists(shellTempPath):
        os.remove(shellTempPath)
 def __init__(self, ufo):
     self.ufo = ufo
     otf = TTFont()
     otf.setGlyphOrder(ufo.glyphOrder)
     super(CompositorUFOFont, self).__init__(otf)