Пример #1
0
 def get_glyph_id(self, glyph):
     ttf = TTFont(self.file_path)
     ttf.saveXML('../fonts/01.xml')
     # gly_list = ttf.getGlyphOrder()  # 获取 GlyphOrder 字段的值
     index = ttf.getGlyphID(glyph)
     # os.remove(self.file_path)
     return index
Пример #2
0
    def cmap_dump(self):
        font = TTFont(self.fontfile)
        #TODO(ahmetcelik) cmap in format 12 should be used if it exists
        cmapTable = font['cmap'].getcmap(3, 10)
        if not cmapTable:
            cmapTable = font['cmap'].getcmap(3, 1)
        assert cmapTable, 'Unicode cmap table required'
        cmap = cmapTable.cmap  # unicode table

        codepoints = []
        glyphs = []

        for code, name in cmap.iteritems():
            id = font.getGlyphID(name)
            glyphs.append(id)
            codepoints.append(code)
            if self.debug:
                print id, name, code
        font.close()

        cp_dumper = Dumper(self.folder + '/codepoints')
        cp_dumper.dump_array(codepoints, 'I', '>')
        cp_dumper.close()

        gid_dumper = Dumper(self.folder + '/gids')
        gid_dumper.dump_array(glyphs, 'H', '>')
        gid_dumper.close()
Пример #3
0
  def cmap_dump(self):
    font = TTFont(self.fontfile)
    #TODO(ahmetcelik) cmap in format 12 should be used if it exists
    cmapTable = font['cmap'].getcmap(3, 10)
    if not cmapTable:
      cmapTable = font['cmap'].getcmap(3, 1)
    assert cmapTable,'Unicode cmap table required'
    cmap = cmapTable.cmap  # unicode table


    codepoints = []
    glyphs = []

    for code, name in cmap.iteritems():
      id = font.getGlyphID(name)
      glyphs.append(id)
      codepoints.append(code)
      if self.debug:
        print id,name,code
    font.close()

    cp_dumper = Dumper(self.folder + '/codepoints')
    cp_dumper.dump_array(codepoints, 'I', '>')
    cp_dumper.close()

    gid_dumper = Dumper(self.folder + '/gids')
    gid_dumper.dump_array(glyphs, 'H', '>')
    gid_dumper.close()
Пример #4
0
class TTXLEFont(LEFontInstance):

    def __init__(self, fname, size=12):
        super(TTXLEFont, self).__init__()

        self.ttx = TTFont(fname)
        self.size = size
        self.upem = self.ttx['head'].unitsPerEm
        self.cmap = self.ttx['cmap'].getcmap(3, 1).cmap

    def getFontTable(self, table):
        return self.ttx.getTableData(table)

    def getAscent(self):
        self.ttx['hhea'].ascent * self.size * 1. / self.upem

    def getDescent(self):
        self.ttx['hhea'].descent * self.size * 1. / self.upem

    def getLeading(self):
        self.ttx['hhea'].lineGap * self.size * 1. / self.upem

    def getUnitsPerEm(self):
        return self.upem

    def mapCharToGlyph(self, code):
        return self.ttx.getGlyphID(self.cmap[code])

    def getGlyphAdvance(self, glyph):

        if glyph >= self.ttx['maxp'].numGlyphs:
            return (0., 0.)

        name = self.ttx.getGlyphName(glyph)
        x = self.ttx['hmtx'][name][0] * self.size * 1. / self.upem

        if 'vmtx' in self.ttx:
            y = self.ttx['vmtx'][name][0] * self.size * 1. / self.upem
        else:
            y = 0.

        return (x, y)

    def getGlyphPoint(self, glyph, point):
        return (0., 0.)

    def getXPixelsPerEm(self):
        return self.size

    def getYPixelsPerEm(self):
        return self.size

    def getScaleFactorX(self):
        return 1.

    def getScaleFactorY(self):
        return 1.
Пример #5
0
class TTXLEFont(LEFontInstance):
    def __init__(self, fname, size=12):
        super(TTXLEFont, self).__init__()

        self.ttx = TTFont(fname)
        self.size = size
        self.upem = self.ttx['head'].unitsPerEm
        self.cmap = self.ttx['cmap'].getcmap(3, 1).cmap

    def getFontTable(self, table):
        return self.ttx.getTableData(table)

    def getAscent(self):
        self.ttx['hhea'].ascent * self.size * 1. / self.upem

    def getDescent(self):
        self.ttx['hhea'].descent * self.size * 1. / self.upem

    def getLeading(self):
        self.ttx['hhea'].lineGap * self.size * 1. / self.upem

    def getUnitsPerEm(self):
        return self.upem

    def mapCharToGlyph(self, code):
        return self.ttx.getGlyphID(self.cmap[code])

    def getGlyphAdvance(self, glyph):

        if glyph >= self.ttx['maxp'].numGlyphs:
            return (0., 0.)

        name = self.ttx.getGlyphName(glyph)
        x = self.ttx['hmtx'][name][0] * self.size * 1. / self.upem

        if 'vmtx' in self.ttx:
            y = self.ttx['vmtx'][name][0] * self.size * 1. / self.upem
        else:
            y = 0.

        return (x, y)

    def getGlyphPoint(self, glyph, point):
        return (0., 0.)

    def getXPixelsPerEm(self):
        return self.size

    def getYPixelsPerEm(self):
        return self.size

    def getScaleFactorX(self):
        return 1.

    def getScaleFactorY(self):
        return 1.
Пример #6
0
    def tran(self, text, html):
        url = re.findall("url\('(.*?.woff)'", html)[0]
        with open('人人车01.ttf', 'wb') as f:
            f.write(requests.get(url=url).content)
        font1 = TTFont('人人车.ttf')
        obj_list1 = font1.getGlyphNames()[1:]  # 获取所有字符的对象,去除第一个和最后一个
        uni_list1 = font1.getGlyphOrder()[1:]
        font2 = TTFont('人人车01.ttf')
        obj_list2 = font2.getGlyphNames()[1:]  # 获取所有字符的对象,去除第一个和最后一个
        uni_list2 = font2.getGlyphOrder()[1:]
        dict = {
            'zero': '0',
            'one': '1',
            'two': '2',
            'three': '3',
            'four': '4',
            'five': '5',
            'six': '6',
            'seven': '7',
            'eight': '8',
            'nine': '9'
        }
        dict1 = {
            'zero': '0',
            'one': '1',
            'two': '2',
            'four': '3',
            'three': '4',
            'five': '5',
            'seven': '6',
            'nine': '7',
            'six': '8',
            'eight': '9'
        }
        ''' 遍历加密的内容text,在新的ttf文件中查找每一个text的元素。如果找到,则替换'''
        for a in text:
            for uni2 in uni_list2:
                # print(uni2)
                try:
                    id = dict[str(uni2)]  # 找到unit2未加密对应的数字
                except:
                    continue
                id_1 = font2.getGlyphID(str(uni2))  # Z找到unit2在ttf文件中的id
                obj2 = font2['glyf'][uni2]
                # str(id) != str(id_1):  # 若未加密的数字id和ttf中对应的id_1不相等,说明a加密了
                if str(id) == str(a):
                    for uni1 in uni_list1:

                        obj1 = font1['glyf'][uni1]
                        if obj1 == obj2:
                            text = text.replace(a, dict1[uni1])
        return text
Пример #7
0
 def parse(self, response, **kwargs):
     page_num = response.meta['page_num']
     num_li = []
     font = self.find_b64_font.search(response.xpath('//style/text()').get()).group(1)
     font = TTFont(BytesIO(base64.b64decode(font)))
     best_cmap = font.getBestCmap()
     tmp_num_li = response.xpath('//div[@class="row"]/div[@class="col-md-1"]/text()').getall()
     for tmp_num in tmp_num_li:
         tmp_li = []
         tmp_num = tmp_num.strip()
         for num in tmp_num:
             glyph_id = best_cmap.get(ast.literal_eval(num.encode('unicode_escape').decode().replace('\\u', '0x')))
             real_num = str(font.getGlyphID(glyph_id) - 1)
             tmp_li.append(real_num)
         num_li.append(int(''.join(tmp_li)))
     self.num_count += sum(num_li)
     self.logger.info(f'[ page: {page_num} ] => {num_li}')
Пример #8
0
 def parse(self, response, **kwargs):
     page_num = response.meta['page_num']
     num_li = []
     font = self.find_b64_font.search(
         response.xpath('//style/text()').get()).group(1)
     font = TTFont(BytesIO(base64.b64decode(font)))
     best_cmap = font.getBestCmap()
     current_num_map = dict()
     for i in best_cmap.values():
         real_num = str(font.getGlyphID(i) - 1)
         current_num_map[i] = real_num
     tmp_num_li = response.xpath(
         '//div[@class="row"]/div[@class="col-md-1"]/text()').getall()
     for tmp_num in tmp_num_li:
         tmp_li = []
         tmp_num = tmp_num.strip()
         for num in tmp_num:
             tmp_li.append(current_num_map[self.num_map[num]])
         num_li.append(int(''.join(tmp_li)))
     self.num_count += sum(num_li)
     self.logger.info(f'[ page: {page_num} ] => {num_li}')
Пример #9
0
def fontchange(str1):
    font = TTFont('tyc-num5-22.woff')  # 打开文件
    # font.saveXML('./tyc-num.xml')
    mappings = {}
    for k, v in font.getBestCmap().items():
        if v.startswith('uni'):
            # 形如 <map code="0xe040" name="uni45"/>  可直接转换得到结果
            mappings['{:x}'.format(k)] = unichr(int(v[3:], 16))
        else:
            mappings['{:x}'.format(k)] = v
    num_dict = {
        '5': '0',
        '9': '1',
        '7': '2',
        '4': '3',
        '1': '4',
        '6': '5',
        '2': '6',
        '0': '7',
        '8': '8',
        '3': '9'
    }
    list1 = []
    for i in str1:
        try:
            if i.isdigit():
                # new_str = new_str.join(num_dict[i])
                list1.append(''.join(num_dict[i]))
            else:
                key = re.search('.*u([0-9a-f]{4}).*',
                                str(i.encode('unicode_escape'))).group(1)
                value = mappings[key]
                # 得到另一个映射
                id = font.getGlyphID(value)
                list1.append(''.join(ocr[id]))
        except Exception:
            list1.append(i)
    return ''.join(str(x) for x in list1)
Пример #10
0
  def cmap_dump(self):
    font = TTFont(self.fontfile)
    cmap = font['cmap'].getcmap(3, 1).cmap  # unicode table
    assert cmap, 'Unicode cmap table required'

    codepoints = []
    glyphs = []

    for code, name in cmap.iteritems():
      id = font.getGlyphID(name)
      glyphs.append(id)
      codepoints.append(code)
      if self.debug:
        print id,name,code
    font.close()

    cp_dumper = Dumper(self.folder + '/codepoints')
    cp_dumper.dump_array(codepoints, 'H', '>')
    cp_dumper.close()

    gid_dumper = Dumper(self.folder + '/gids')
    gid_dumper.dump_array(glyphs, 'H', '>')
    gid_dumper.close()
Пример #11
0
class Builder(object):
    def __init__(self, conf=None):
        self.conf = conf
        self.uids_for_glyph_names = None

        if self.conf['verbose']:
            logging.getLogger().setLevel(logging.DEBUG)

    def run(self):
        logger.info("Creating a new font")
        ff_font = fforge.create_font(self.conf)

        # Find and add regular glyphs
        svg_filepaths = util.get_svg_filepaths(self.conf['glyph_svg_dir'])
        # TODO: Validate regular SVGs
        logger.info("Adding glyphs and ligatures")
        fforge.add_glyphs(ff_font, svg_filepaths, self.conf)

        tmp_dir = tempfile.mkdtemp()
        tmp_file = os.path.join(tmp_dir, "tmp.ttf")
        logger.debug("Using temp file: %s", tmp_file)

        # TODO: Validate ligature tables to avoid warning during generate
        # "Lookup subtable contains unused glyph NAME making the whole subtable invalid"
        logger.info("Generating intermediate font file")
        ff_font.generate(tmp_file)
        del ff_font

        logger.info("Reading intermediate font file")
        self.font = TTFont(tmp_file)
        logger.info("Adding SVGinOT SVG files")
        # TODO: Validate color SVGs
        self.add_color_svg()
        self.add_name_table()
        logger.info("Saving output file: %s", self.conf['output_file'])
        self.font.save(self.conf['output_file'])

        # Cleaning Up
        os.remove(tmp_file)
        os.rmdir(tmp_dir)

        logger.info("Done!")
        # 0 for success
        return 0

    def add_color_svg(self):
        svg_files = util.get_svg_filepaths(self.conf['color_svg_dir'])
        svg_list = []

        # Set default namespace (avoids "ns0:svg")
        ET.register_namespace("", "http://www.w3.org/2000/svg")

        for filepath in svg_files:
            glyph_id = self.get_glyph_id(filepath)

            svg_tree = ET.parse(filepath)
            svg_root = svg_tree.getroot()
            # Add Glyph ID as SVG root id, required by SVGinOT spec.
            svg_root.set('id', "glyph{}".format(glyph_id))

            # Remove the viewBox/height/width attributes since they are
            # processed inconsistently by Gecko and Edge renderers.
            try:
                del svg_root.attrib['viewBox']
            except KeyError:
                pass

            try:
                del svg_root.attrib['height']
            except KeyError:
                pass

            try:
                del svg_root.attrib['width']
            except KeyError:
                pass

            # Add the transform to size the SVG to the FONT_EM
            svg_transform = self.create_color_transform(filepath)
            logger.debug("Set SVG transform: {}".format(svg_transform))

            svg_transform_attrib = {"transform": svg_transform}
            # Create a new group tag to apply the transform to
            new_svg_group = ET.Element('g', svg_transform_attrib)
            # Copy all SVG root children to the new group
            for child in svg_root:
                new_svg_group.append(child)

            # Backup the root attribs, clear the children, and apply attribs
            svg_root_attrib = svg_root.items()
            svg_root.clear()
            for name, value in svg_root_attrib:
                svg_root.set(name, value)

            # Append the new group.
            svg_root.append(new_svg_group)

            data = ET.tostring(svg_root, encoding='UTF-8')
            logger.debug("Glyph ID: %d Adding SVG: %s", glyph_id, filepath)
            svg_list.append([data, glyph_id, glyph_id])

        svg_table = table_S_V_G_()
        # The SVG table must be sorted by glyph_id
        svg_table.docList = sorted(svg_list, key=lambda table: table[1])
        svg_table.colorPalettes = None
        self.font['SVG '] = svg_table

    def get_glyph_id(self, filepath):
        """
        Find a Glyph ID for the filename in filepath
        """
        if self.uids_for_glyph_names is None:
            self.uids_for_glyph_names = self.get_uids_for_glyph_names()

        (codepoint, filename) = util.codepoint_from_filepath(filepath)

        # Check for a regular glyph first
        try:
            glyph_name = self.uids_for_glyph_names[codepoint]
        except KeyError:
            # If that doesn't work check for a Ligature Glyph
            glyph_id = self.font.getGlyphID(filename)

            if glyph_id is -1:
                logger.warning(
                    "No Glyph ID found for: %s (Note: A regular "
                    "glyph is required for each color glyph)", filepath)

            logger.debug("Found Ligature Glyph: %s", filename)
            return glyph_id

        logger.debug("Found regular Glyph: %s", glyph_name)
        return self.font.getGlyphID(glyph_name)

    def get_uids_for_glyph_names(self):
        """
        Get a dict of glyph names in the font indexed by unicode IDs
        """
        codepoints = {}
        for subtable in self.font['cmap'].tables:
            if subtable.isUnicode():
                for codepoint, name in subtable.cmap.items():
                    # NOTE: May overwrite previous values
                    codepoints[codepoint] = name
        if len(codepoints) is 0:
            raise NoCodePointsException(
                'No Unicode IDs/CodePoints found in font.')

        return codepoints

    def create_color_transform(self, filepath):
        """
        Generate the transform for the color SVG.
        """
        svg_transform = ""
        if 'color_transform' in self.conf:
            svg_transform = "{} ".format(self.conf['color_transform'])
        svg_height, _ = util.get_dimensions(filepath)

        # Find the scale multiplier based on current height verses intended
        # height (aka font EM). Whatever the SVG is, it needs to be scaled to
        # fit within the FONT_EM.
        scale = FONT_EM / svg_height

        # No need to adjust, but called out for clarity.
        translate_x = 0
        # SVG(y Down) vs Glyph/TTF(y Up) y-coordinate differences.
        # Simple answer is -FONT_EM but that does not account for descent.
        # Y=0 is on the baseline without a descent adjustment.
        # Default font descent: -2/10*2048 = -409.6
        translate_y = -(FONT_EM - FONT_EM * .2)

        svg_transform += "translate({},{}) scale({})".format(
            translate_x, translate_y, scale)

        return svg_transform

    def add_name_table(self):
        # FontForge doesn't support all font fields, so we use FontTools.
        self.name_table = table__n_a_m_e()
        self.name_table.names = []

        tn = self.conf['table_name']

        # Set the values that will always exist.
        self.add_name_records(tn['family'], NR.FAMILY)
        self.add_name_records(tn['subfamily'], NR.SUBFAMILY)

        fullname = ''
        if 'full_name' in tn:
            fullname = tn['full_name']
        else:
            fullname = "{} {}".format(tn['family'], tn['subfamily'])
        self.add_name_records(fullname, NR.FULL_NAME)

        # Add the build date to the version
        now = time.strftime('%Y%m%d')
        version = "{} {}".format(tn['version'], now)
        self.add_name_records(version, NR.VERSION)

        # Add the build date to the unique id
        unique_id = ''
        if 'unique_id' in tn:
            unique_id = "{} {}".format(tn['unique_id'], now)
        else:
            unique_id = now
        self.add_name_records(unique_id, NR.UNIQUE_ID)

        # Set the values that don't always exist
        for key, name_id in (('copyright', NR.COPYRIGHT), ('postscript_name',
                                                           NR.PS_NAME),
                             ('trademark', NR.TRADEMARK), ('manufacturer',
                                                           NR.MANUFACTURER),
                             ('designer', NR.DESIGNER), ('description',
                                                         NR.DESCRIPTION),
                             ('url_vendor', NR.URL_VENDOR), ('url_designer',
                                                             NR.URL_DESIGNER),
                             ('license', NR.LICENSE), ('url_license',
                                                       NR.URL_LICENSE)):
            if key in tn:
                self.add_name_records(tn[key], name_id)

        self.font['name'] = self.name_table

    def add_name_records(self, text, name_id):
        # <namerecord nameID="0" platformID="0" platEncID="0" langID="0x0">
        self._add_name_record(text, name_id, NR.PLATFORM_UNICODE,
                              NR.UC_ENC_UNICODE1, NR.UC_LANG_NA)

        # <namerecord nameID="0" platformID="1" platEncID="0" langID="0x0" unicode="True">
        self._add_name_record(text, name_id, NR.PLATFORM_MAC, NR.MAC_ENC_ROMAN,
                              NR.MAC_LANG_EN)

        # <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
        self._add_name_record(text, name_id, NR.PLATFORM_WIN,
                              NR.WIN_ENC_UNICODE, NR.WIN_LANG_EN)

    def _add_name_record(self, text, name_id, platform_id, plat_enc_id,
                         lang_id):
        # TODO: The installed version of fontTools doesn't have
        # table__n_a_m_e.setName().
        record = NameRecord()
        # PyYAML creates strings, force to Unicode
        record.string = unicode(text)
        record.nameID = name_id
        record.platformID = platform_id
        record.platEncID = plat_enc_id
        record.langID = lang_id
        self.name_table.names.append(record)
Пример #12
0
# Aligned by design. Curr offset will point to the start of the first font
curr_offset = header_size + len(args.pt) * size_info_size

# This will be filled with the font data
font_data = bytearray()

# Iterate though all sizes
for pt in range(len(args.pt)):

    # Get the font size
    font_size = args.pt[pt]

    # Load the font
    font = ImageFont.truetype(args.ttf, font_size)
    font_info = TTFont(args.ttf)
    start_id = font_info.getGlyphID('space')
    scale = font_info['head'].unitsPerEm

    if args.prev:
        preview = ""
        for i in range(num_chars):
            preview += str(chr(i + start_char))
        image = Image.new("RGB", (1000, 1000))
        draw = ImageDraw.Draw(image)
        draw.text((0, 0), preview, font=font)
        image = image.crop(image.getbbox())
        image.save(args.tf + "_prev" + str(args.pt[pt]) + ".png")
        pixels = image.load()

    # Find the max and min values
    ymax = 0
Пример #13
0
    def _export_svg(self, otfpath, palette=0, parent_window=None):
        font = TTFont(otfpath)
        if font.has_key("SVG "):
            print("    WARNING: Replacing existing SVG table in %s" % otfpath)
        # font.getReverseGlyphMap(rebuild=1)

        svg = table_S_V_G_("SVG ")
        svg.version = 0
        svg.docList = []
        svg.colorPalettes = None

        if parent_window is not None:
            progress = ProgressWindow("Rendering SVG ...", tickCount=len(self.keys()), parentWindow=parent_window)

        _palette = self.palettes[palette]
        _svg_palette = []
        _docList = []

        reindex = {0xffff: 0xffff}
        count = 0

        for i in sorted(_palette.keys(), key=lambda k: int(k)):
            red   = int(_palette[i][1:3], 16)
            green = int(_palette[i][3:5], 16)
            blue  = int(_palette[i][5:7], 16)
            if len(_palette[i]) >= 9:
                alpha  = int(_palette[i][7:9], 16)
            else:
                alpha = 0xff
            reindex[int(i)] = count
            count += 1
            _svg_palette.append((red, green, blue, alpha))
        # print("Palette:", len(_svg_palette), _svg_palette)

        _pen = SVGpen(self.rfont, optimize_output=True)

        for glyphname in self.keys():  # ["A", "P"]: #self.keys():

            # look up glyph id
            try:
                gid = font.getGlyphID(glyphname)
            except:
                assert 0, "SVG table contains a glyph name not in font.getGlyphNames(): " + str(glyphname)

            # update progress bar
            if parent_window is not None:
                progress.update("Rendering SVG for /%s ..." % glyphname)

            # build svg glyph
            _svg_transfrom_group = """<g transform="scale(1 -1)">%s</g>"""

            contents = u""
            for i in range(len(self[glyphname].layers)):
                _color_index = reindex[self[glyphname].colors[i]]
                # print("    Layer %i, color %i" % (i, _color_index))
                rglyph = self.rfont[self[glyphname].layers[i]]
                if _color_index == 0xffff:
                    r, g, b, a = (0, 0, 0, 0xff)
                else:
                    r, g, b, a = _svg_palette[_color_index]
                _pen.reset()
                rglyph.draw(_pen)
                if _pen.d:
                    contents += u'<g fill="#%02x%02x%02x"><path d="%s"/></g>' % (r, g, b, _pen.d)
            if contents:
                contents = _svg_transfrom_group % contents
            _svg_doc = u"""<svg enable-background="new 0 0 64 64" id="glyph%i" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">%s</svg>""" % (gid, contents)
            _docList.append((_svg_doc, gid, gid))

        svg.docList = sorted(_docList, key=itemgetter(1))

        if parent_window is not None:
            progress.close()

        # save
        font["SVG "] = svg
        font.save(otfpath[:-4] + "_svg" + otfpath[-4:])
        font.close()
Пример #14
0
class Builder(object):

    def __init__(self, conf=None):
        self.conf = conf
        self.uids_for_glyph_names = None

        if self.conf['verbose']:
            logging.getLogger().setLevel(logging.DEBUG)

    def run(self):
        logger.info("Creating a new font")
        ff_font = fforge.create_font(self.conf)

        # Find and add regular glyphs
        svg_filepaths = util.get_svg_filepaths(self.conf['glyph_svg_dir'])
        # TODO: Validate regular SVGs
        logger.info("Adding glyphs and ligatures")
        fforge.add_glyphs(ff_font, svg_filepaths, self.conf)

        tmp_dir = tempfile.mkdtemp()
        tmp_file = os.path.join(tmp_dir, "tmp.ttf")
        logger.debug("Using temp file: %s", tmp_file)

        # TODO: Validate ligature tables to avoid warning during generate
        # "Lookup subtable contains unused glyph NAME making the whole subtable invalid"
        logger.info("Generating intermediate font file")
        ff_font.generate(tmp_file)
        del ff_font

        logger.info("Reading intermediate font file")
        self.font = TTFont(tmp_file)
        logger.info("Adding SVGinOT SVG files")
        # TODO: Validate color SVGs
        self.add_color_svg()
        self.add_name_table()
        logger.info("Saving output file: %s", self.conf['output_file'])
        self.font.save(self.conf['output_file'])

        # Cleaning Up
        os.remove(tmp_file)
        os.rmdir(tmp_dir)

        logger.info("Done!")
        # 0 for success
        return 0

    def add_color_svg(self):
        svg_files = util.get_svg_filepaths(self.conf['color_svg_dir'])
        svg_list = []

        # Set default namespace (avoids "ns0:svg")
        ET.register_namespace("", "http://www.w3.org/2000/svg")

        for filepath in svg_files:
            glyph_id = self.get_glyph_id(filepath)

            svg_tree = ET.parse(filepath)
            svg_root = svg_tree.getroot()
            # Add Glyph ID as SVG root id, required by SVGinOT spec.
            svg_root.set('id', "glyph{}".format(glyph_id))

            # Remove the viewBox/height/width attributes since they are
            # processed inconsistently by Gecko and Edge renderers.
            try:
                del svg_root.attrib['viewBox']
            except KeyError:
                pass

            try:
                del svg_root.attrib['height']
            except KeyError:
                pass

            try:
                del svg_root.attrib['width']
            except KeyError:
                pass

            # Add the transform to size the SVG to the FONT_EM
            svg_transform = self.create_color_transform(filepath)
            logger.debug("Set SVG transform: {}".format(svg_transform))

            svg_transform_attrib = {"transform": svg_transform}
            # Create a new group tag to apply the transform to
            new_svg_group = ET.Element('g', svg_transform_attrib)
            # Copy all SVG root children to the new group
            for child in svg_root:
                new_svg_group.append(child)

            # Backup the root attribs, clear the children, and apply attribs
            svg_root_attrib = svg_root.items()
            svg_root.clear()
            for name, value in svg_root_attrib:
                svg_root.set(name, value)

            # Append the new group.
            svg_root.append(new_svg_group)

            data = ET.tostring(svg_root, encoding='UTF-8')
            logger.debug("Glyph ID: %d Adding SVG: %s", glyph_id, filepath)
            svg_list.append([data, glyph_id, glyph_id])

        svg_table = table_S_V_G_()
        # The SVG table must be sorted by glyph_id
        svg_table.docList = sorted(svg_list, key=lambda table: table[1])
        svg_table.colorPalettes = None
        self.font['SVG '] = svg_table

    def get_glyph_id(self, filepath):
        """
        Find a Glyph ID for the filename in filepath
        """
        if self.uids_for_glyph_names is None:
            self.uids_for_glyph_names = self.get_uids_for_glyph_names()

        (codepoint, filename) = util.codepoint_from_filepath(filepath)

        # Check for a regular glyph first
        try:
            glyph_name = self.uids_for_glyph_names[codepoint]
        except KeyError:
            # If that doesn't work check for a Ligature Glyph
            glyph_id = self.font.getGlyphID(filename)

            if glyph_id is -1:
                logger.warning("No Glyph ID found for: %s (Note: A regular "
                               "glyph is required for each color glyph)", filepath)

            logger.debug("Found Ligature Glyph: %s", filename)
            return glyph_id

        logger.debug("Found regular Glyph: %s", glyph_name)
        return self.font.getGlyphID(glyph_name)

    def get_uids_for_glyph_names(self):
        """
        Get a dict of glyph names in the font indexed by unicode IDs
        """
        codepoints = {}
        for subtable in self.font['cmap'].tables:
            if subtable.isUnicode():
                for codepoint, name in subtable.cmap.items():
                    # NOTE: May overwrite previous values
                    codepoints[codepoint] = name
        if len(codepoints) is 0:
            raise NoCodePointsException(
                'No Unicode IDs/CodePoints found in font.')

        return codepoints

    def create_color_transform(self, filepath):
        """
        Generate the transform for the color SVG.
        """
        svg_transform = ""
        if 'color_transform' in self.conf:
            svg_transform = "{} ".format(self.conf['color_transform'])
        svg_height, _ = util.get_dimensions(filepath)

        # Find the scale multiplier based on current height verses intended
        # height (aka font EM). Whatever the SVG is, it needs to be scaled to
        # fit within the FONT_EM.
        scale = FONT_EM / svg_height

        # No need to adjust, but called out for clarity.
        translate_x = 0
        # SVG(y Down) vs Glyph/TTF(y Up) y-coordinate differences.
        # Simple answer is -FONT_EM but that does not account for descent.
        # Y=0 is on the baseline without a descent adjustment.
        # Default font descent: -2/10*2048 = -409.6
        translate_y = - (FONT_EM - FONT_EM * .2)

        svg_transform += "translate({},{}) scale({})".format(translate_x,
                                                             translate_y,
                                                             scale)

        return svg_transform

    def add_name_table(self):
        # FontForge doesn't support all font fields, so we use FontTools.
        self.name_table = table__n_a_m_e()
        self.name_table.names = []

        tn = self.conf['table_name']

        # Set the values that will always exist.
        self.add_name_records(tn['family'], NR.FAMILY)
        self.add_name_records(tn['subfamily'], NR.SUBFAMILY)

        fullname = ''
        if 'full_name' in tn:
            fullname = tn['full_name']
        else:
            fullname = "{} {}".format(tn['family'], tn['subfamily'])
        self.add_name_records(fullname, NR.FULL_NAME)

        # Add the build date to the version
        now = time.strftime('%Y%m%d')
        version = "{} {}".format(tn['version'], now)
        self.add_name_records(version, NR.VERSION)

        # Add the build date to the unique id
        unique_id = ''
        if 'unique_id' in tn:
            unique_id = "{} {}".format(tn['unique_id'], now)
        else:
            unique_id = now
        self.add_name_records(unique_id, NR.UNIQUE_ID)

        # Set the values that don't always exist
        for key, name_id in (
                ('copyright', NR.COPYRIGHT),
                ('postscript_name', NR.PS_NAME),
                ('trademark', NR.TRADEMARK),
                ('manufacturer', NR.MANUFACTURER),
                ('designer', NR.DESIGNER),
                ('description', NR.DESCRIPTION),
                ('url_vendor', NR.URL_VENDOR),
                ('url_designer', NR.URL_DESIGNER),
                ('license', NR.LICENSE),
                ('url_license', NR.URL_LICENSE)):
            if key in tn:
                self.add_name_records(tn[key], name_id)

        self.font['name'] = self.name_table

    def add_name_records(self, text, name_id):
        # <namerecord nameID="0" platformID="0" platEncID="0" langID="0x0">
        self._add_name_record(text, name_id,
                              NR.PLATFORM_UNICODE,
                              NR.UC_ENC_UNICODE1,
                              NR.UC_LANG_NA)

        # <namerecord nameID="0" platformID="1" platEncID="0" langID="0x0" unicode="True">
        self._add_name_record(text, name_id,
                              NR.PLATFORM_MAC,
                              NR.MAC_ENC_ROMAN,
                              NR.MAC_LANG_EN)

        # <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
        self._add_name_record(text, name_id,
                              NR.PLATFORM_WIN,
                              NR.WIN_ENC_UNICODE,
                              NR.WIN_LANG_EN)

    def _add_name_record(self, text, name_id, platform_id, plat_enc_id, lang_id):
        # TODO: The installed version of fontTools doesn't have
        # table__n_a_m_e.setName().
        record = NameRecord()
        # PyYAML creates strings, force to Unicode
        record.string = unicode(text)
        record.nameID = name_id
        record.platformID = platform_id
        record.platEncID = plat_enc_id
        record.langID = lang_id
        self.name_table.names.append(record)
Пример #15
0
    def get_number(self, company_name):
        res = requests.get(
            "https://ss.cods.org.cn/latest/searchR?q={}&currentPage=1&t=common&searchToken="
            .format(company_name),
            headers=self.headers)
        woff_name = re.findall(r"\/(\d+\.woff2)", res.text)[0]
        # print(woff_name)
        # 获得动态字体地址并请求获得字体对象
        woff_url = "https://ss.cods.org.cn/css/woff/{}".format(woff_name)
        woff_res = requests.get(woff_url, headers=self.headers)
        font = TTFont(BytesIO(woff_res.content))
        cmap = font.getBestCmap()

        fmap = {
            0: None,
            1: None,
            2: None,
            3: '0',
            4: '1',
            5: '2',
            6: '3',
            7: '4',
            8: '5',
            9: '6',
            10: '7',
            11: '8',
            12: '9',
            13: 'A',
            14: 'B',
            15: 'C',
            16: 'D',
            17: 'E',
            18: 'F',
            19: 'G',
            20: 'H',
            21: 'I',
            22: 'J',
            23: 'K',
            24: 'L',
            25: 'M',
            26: 'N',
            27: 'O',
            28: 'P',
            29: 'Q',
            30: 'R',
            31: 'S',
            32: 'T',
            33: 'U',
            34: 'V',
            35: 'W',
            36: 'X',
            37: 'Y',
            38: 'Z',
            39: '0',
            40: '1',
            41: '2',
            42: '3',
            43: '4',
            44: '5',
            45: '6',
            46: '7',
            47: '8',
            48: '9',
            49: 'A',
            50: 'B',
            51: 'C',
            52: 'D',
            53: 'E',
            54: 'F',
            55: 'G',
            56: 'H',
            57: 'I',
            58: 'J',
            59: 'K',
            60: 'L',
            61: 'M',
            62: 'N',
            63: 'O',
            64: 'P',
            65: 'Q',
            66: 'R',
            67: 'S',
            68: 'T',
            69: 'U',
            70: 'V',
            71: 'W',
            72: 'X',
            73: 'Y',
            74: 'Z'
        }
        html = etree.HTML(res.text)
        number = html.xpath(
            "//div[@class='result result-2']//div[@class='info']/h6[text()='统一社会信用代码:']/following-sibling::p[1]/text()"
        )[0]
        # print(number)
        result = ""
        for n in number:
            n = ord(n)

            _id = font.getGlyphID(cmap[n])
            # 根据索引id获得最终结果
            r_n = fmap[_id]
            result += r_n
        return result
Пример #16
0
    def _export_svg(self, otfpath, palette=0, parent_window=None):
        font = TTFont(otfpath)
        if font.has_key("SVG "):
            print("    WARNING: Replacing existing SVG table in %s" % otfpath)
        # font.getReverseGlyphMap(rebuild=1)

        svg = table_S_V_G_("SVG ")
        svg.version = 0
        svg.docList = []
        svg.colorPalettes = None

        if parent_window is not None:
            progress = ProgressWindow("Rendering SVG ...",
                                      tickCount=len(self.keys()),
                                      parentWindow=parent_window)

        _palette = self.palettes[palette]
        _svg_palette = []
        _docList = []

        reindex = {0xffff: 0xffff}
        count = 0

        for i in sorted(_palette.keys(), key=lambda k: int(k)):
            red = int(_palette[i][1:3], 16)
            green = int(_palette[i][3:5], 16)
            blue = int(_palette[i][5:7], 16)
            if len(_palette[i]) >= 9:
                alpha = int(_palette[i][7:9], 16)
            else:
                alpha = 0xff
            reindex[int(i)] = count
            count += 1
            _svg_palette.append((red, green, blue, alpha))
        # print("Palette:", len(_svg_palette), _svg_palette)

        _pen = SVGpen(self.rfont, optimize_output=True)

        for glyphname in self.keys():  # ["A", "P"]: #self.keys():

            # look up glyph id
            try:
                gid = font.getGlyphID(glyphname)
            except:
                assert 0, "SVG table contains a glyph name not in font.getGlyphNames(): " + str(
                    glyphname)

            # update progress bar
            if parent_window is not None:
                progress.update("Rendering SVG for /%s ..." % glyphname)

            # build svg glyph
            _svg_transfrom_group = """<g transform="scale(1 -1)">%s</g>"""

            contents = u""
            for i in range(len(self[glyphname].layers)):
                _color_index = reindex[self[glyphname].colors[i]]
                # print("    Layer %i, color %i" % (i, _color_index))
                rglyph = self.rfont[self[glyphname].layers[i]]
                if _color_index == 0xffff:
                    r, g, b, a = (0, 0, 0, 0xff)
                else:
                    r, g, b, a = _svg_palette[_color_index]
                _pen.reset()
                rglyph.draw(_pen)
                if _pen.d:
                    contents += u'<g fill="#%02x%02x%02x"><path d="%s"/></g>' % (
                        r, g, b, _pen.d)
            if contents:
                contents = _svg_transfrom_group % contents
            _svg_doc = u"""<svg enable-background="new 0 0 64 64" id="glyph%i" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">%s</svg>""" % (
                gid, contents)
            _docList.append((_svg_doc, gid, gid))

        svg.docList = sorted(_docList, key=itemgetter(1))

        if parent_window is not None:
            progress.close()

        # save
        font["SVG "] = svg
        font.save(otfpath[:-4] + "_svg" + otfpath[-4:])
        font.close()
Пример #17
0
def add_svg_table(font_path, file_paths, compress_table=False):
    gnames_dict = {}  # key: glyph name; value: SVG file path
    for fpath in file_paths:
        gname = os.path.splitext(os.path.basename(fpath))[0]  # trim extension
        # validate glyph name
        if not glyph_name_is_valid(gname, fpath):
            continue
        # skip any duplicates and 'space'
        if gname in gnames_dict or gname == 'space':
            log.warning("Skipped file '{}'. The glyph name derived from it "
                        "is either a duplicate or 'space'".format(fpath))
            continue
        # limit the length of glyph name to 31 chars
        if len(gname) > 31:
            num = 0
            trimmed_gname = get_trimmed_glyph_name(gname, num)
            while trimmed_gname in gnames_dict:
                num += 1
                trimmed_gname = get_trimmed_glyph_name(trimmed_gname, num)
            gnames_dict[trimmed_gname] = fpath
            log.warning("Glyph name '{}' was trimmed to 31 characters: "
                        "'{}'".format(gname, trimmed_gname))
        else:
            gnames_dict[gname] = fpath

    font = TTFont(font_path)
    svg_docs_dict = {}
    for gname, svg_file_path in gnames_dict.items():
        try:
            gid = font.getGlyphID(gname)
        except KeyError:
            log.warning('Could not find a glyph named {} in the font'
                        ''.format(gname))
            continue

        with io.open(svg_file_path, encoding='utf-8') as fp:
            svg_item_data = fp.read()

        # Set id value
        svg_item_data = set_svg_id(svg_item_data, gid)

        # Scale and shift the artwork, by adjusting its viewBox
        svg_item_data = adjust_viewbox(svg_item_data, svg_file_path,
                                       VIEWBOX_SCALE)

        # Clean SVG document
        svg_item_data = clean_svg_doc(svg_item_data)

        svg_docs_dict[gid] = (svg_item_data.strip(), gid, gid)

    # Don't modify the input font if there's no SVG data
    if not svg_docs_dict:
        log.warning('None of the SVG files found could be added to the font')
        font.close()
        return

    # Make a list of the SVG documents sorted by GID
    svg_docs_list = sorted(svg_docs_dict.values(), key=lambda doc: doc[1])

    svg_table = newTable('SVG ')
    svg_table.compressed = compress_table
    svg_table.docList = svg_docs_list
    svg_table.colorPalettes = None
    font['SVG '] = svg_table

    ext = '.ttf' if 'glyf' in font else '.otf'
    svg_font_filename = '{}{}'.format(PS_NAME, ext)
    svg_font_path = os.path.join(os.path.dirname(font_path), svg_font_filename)
    font.save(svg_font_path)
    font.close()
    log.info("Wrote '{}' containing {} SVG glyphs".format(
        os.path.basename(svg_font_path), len(svg_docs_list)))
    return svg_font_path
Пример #18
0
class TTVarCFont:
    def __init__(self, path, ttFont=None, hbFont=None):
        if ttFont is not None:
            assert hbFont is not None
            assert path is None
            self.ttFont = ttFont
        else:
            assert hbFont is None
            self.ttFont = TTFont(path, lazy=True)
        self.axes = {
            axis.axisTag: (axis.minValue, axis.defaultValue, axis.maxValue)
            for axis in self.ttFont["fvar"].axes
        }
        if hbFont is not None:
            self.hbFont = hbFont
        else:
            with open(path, "rb") as f:
                face = hb.Face(f.read())
            self.hbFont = hb.Font(face)

    def keys(self):
        return self.ttFont.getGlyphNames()

    def __contains__(self, glyphName):
        return glyphName in self.ttFont.getReverseGlyphMap()

    def drawGlyph(self, pen, glyphName, location):
        normLocation = normalizeLocation(location, self.axes)
        fvarTable = self.ttFont["fvar"]
        glyfTable = self.ttFont["glyf"]
        varcTable = self.ttFont.get("VarC")
        if varcTable is not None:
            glyphData = varcTable.GlyphData
        else:
            glyphData = {}

        g = glyfTable[glyphName]
        varComponents = glyphData.get(glyphName)
        if g.isComposite():
            componentOffsets = instantiateComponentOffsets(
                self.ttFont, glyphName, normLocation
            )
            if varComponents is not None:
                assert len(g.components) == len(varComponents)
                varcInstancer = VarStoreInstancer(
                    varcTable.VarStore, fvarTable.axes, normLocation
                )
                for (x, y), gc, vc in zip(
                    componentOffsets, g.components, varComponents
                ):
                    componentLocation = unpackComponentLocation(vc.coord, varcInstancer)
                    transform = unpackComponentTransform(
                        vc.transform, varcInstancer, vc.numIntBitsForScale
                    )
                    tPen = TransformPen(pen, _makeTransform(x, y, transform))
                    self.drawGlyph(tPen, gc.glyphName, componentLocation)
            else:
                for (x, y), gc in zip(componentOffsets, g.components):
                    tPen = TransformPen(pen, (1, 0, 0, 1, x, y))
                    self.drawGlyph(tPen, gc.glyphName, {})
        else:
            glyphID = self.ttFont.getGlyphID(glyphName)
            self.hbFont.set_variations(location)
            self.hbFont.draw_glyph_with_pen(glyphID, pen)
Пример #19
0
class Vharfbuzz:
    """A user-friendlier way to use Harfbuzz in Python.

    Args:
        filename (str): A path to a TrueType font file.
    """
    def __init__(self, filename):
        self.filename = filename
        with open(self.filename, "rb") as fontfile:
            self.fontdata = fontfile.read()
        self.ttfont = TTFont(filename)
        self.glyphOrder = self.ttfont.getGlyphOrder()
        self.prepare_shaper()
        self.shapers = None
        self.drawfuncs = None

    def prepare_shaper(self):
        face = hb.Face(self.fontdata)
        font = hb.Font(face)
        upem = face.upem
        font.scale = (upem, upem)
        hb.ot_font_set_funcs(font)
        self.hbfont = font

    def make_message_handling_function(self, buf, onchange):
        self.history = {"GSUB": [], "GPOS": []}
        self.lastLookupID = None

        def handle_message(msg, buf2):
            m = re.match("start lookup (\\d+)", msg)
            if m:
                lookupid = int(m[1])
                self.history[self.stage].append(self.serialize_buf(buf2))

            m = re.match("end lookup (\\d+)", msg)
            if m:
                lookupid = int(m[1])
                if self.serialize_buf(buf2) != self.history[self.stage][-1]:
                    onchange(self, self.stage, lookupid, self._copy_buf(buf2))
                self.history[self.stage].pop()
            if msg.startswith("start GPOS stage"):
                self.stage = "GPOS"

        return handle_message

    def shape(self, text, parameters=None, onchange=None):
        """Shapes a text

    This shapes a piece of text.

    Args:
        text (str): A string of text
        parameters: A dictionary containing parameters to pass to Harfbuzz.
            Relevant keys include ``script``, ``direction``, ``language``
            (these three are normally guessed from the string contents),
            ``features``, ``variations`` and ``shaper``.
        onchange: An optional function with three parameters. See below.

    Additionally, if an `onchange` function is provided, this will be called
    every time the buffer changes *during* shaping, with the following arguments:

    - ``self``: the vharfbuzz object.
    - ``stage``: either "GSUB" or "GPOS"
    - ``lookupid``: the current lookup ID
    - ``buffer``: a copy of the buffer as a list of lists (glyphname, cluster, position)

    Returns:
        A uharfbuzz ``hb.Buffer`` object
    """
        if not parameters:
            parameters = {}
        self.prepare_shaper()
        buf = hb.Buffer()
        buf.add_str(text)
        buf.guess_segment_properties()
        if "script" in parameters and parameters["script"]:
            buf.script = parameters["script"]
        if "direction" in parameters and parameters["direction"]:
            buf.direction = parameters["direction"]
        if "language" in parameters and parameters["language"]:
            buf.language = parameters["language"]
        shapers = self.shapers
        if "shaper" in parameters and parameters["shaper"]:
            shapers = [parameters["shaper"]]

        features = parameters.get("features")
        if "variations" in parameters:
            self.hbfont.set_variations(parameters["variations"])
        self.stage = "GSUB"
        if onchange:
            f = self.make_message_handling_function(buf, onchange)
            buf.set_message_func(f)
        hb.shape(self.hbfont, buf, features, shapers=shapers)
        self.stage = "GPOS"
        return buf

    def _copy_buf(self, buf):
        # Or at least the bits we care about
        outs = []
        for info, pos in zip(buf.glyph_infos, buf.glyph_positions):
            l = [self.glyphOrder[info.codepoint], info.cluster]
            if self.stage == "GPOS":
                l.append(pos.position)
            else:
                l.append(None)
            outs.append(l)
        return outs

    def serialize_buf(self, buf, glyphsonly=False):
        """Serializes a buffer to a string

        Returns the contents of the given buffer in a string format similar to
        that used by ``hb-shape``.

        Args:
            buf: The ``hb.Buffer`` object.

        Returns: A serialized string.

       """
        outs = []
        for info, pos in zip(buf.glyph_infos, buf.glyph_positions):
            glyphname = self.glyphOrder[info.codepoint]
            if glyphsonly:
                outs.append(glyphname)
                continue
            outs.append("%s=%i" % (glyphname, info.cluster))
            if self.stage == "GPOS" and (pos.position[0] != 0
                                         or pos.position[1] != 0):
                outs[-1] = outs[-1] + "@%i,%i" % (pos.position[0],
                                                  pos.position[1])
            if self.stage == "GPOS":
                outs[-1] = outs[-1] + "+%i" % (pos.position[2])
        return "|".join(outs)

    def buf_from_string(self, s):
        """Deserializes a string.

        This attempts to perform the inverse operation to :py:meth:`serialize_buf`,
        turning a serialized buffer back into an object. The object is not a
        ``hb.Buffer``, but has a similar structure (``glyph_infos`` and ``glyph_positions``)
        so can be passed to code which expects a ``hb.Buffer``, such as
        :py:meth:`buf_to_svg` below.

        Args:
            s (str): A string produced by :py:meth:`serialize_buf`

        Returns a ``FakeBuffer`` object.
        """
        buf = FakeBuffer()
        buf.glyph_infos = []
        buf.glyph_positions = []
        for item in s.split("|"):
            m = re.match(r"^(.*)=(\d+)(@(-?\d+),(-?\d+))?(\+(-?\d+))?$", item)
            if not m:
                raise ValueError("Couldn't parse glyph %s in %s" % (item, s))
            groups = m.groups()
            info = FakeItem()
            info.codepoint = self.ttfont.getGlyphID(groups[0])
            info.cluster = int(groups[1])
            buf.glyph_infos.append(info)
            pos = FakeItem()
            pos.position = [
                int(x or 0) for x in (groups[3], groups[4], groups[6], 0)
            ]  # Sorry, vertical scripts
            buf.glyph_positions.append(pos)
        return buf

    def setup_svg_draw_funcs(self, container):
        if self.drawfuncs:
            return

        def move_to(x, y, c):
            c["output_string"] = c["output_string"] + f"M{x},{y}"

        def line_to(x, y, c):
            c["output_string"] = c["output_string"] + f"L{x},{y}"

        def cubic_to(c1x, c1y, c2x, c2y, x, y, c):
            c["output_string"] = (c["output_string"] +
                                  f"C{c1x},{c1y} {c2x},{c2y} {x},{y}")

        def quadratic_to(c1x, c1y, x, y, c):
            c["output_string"] = c["output_string"] + f"Q{c1x},{c1y} {x},{y}"

        def close_path(c):
            c["output_string"] = c["output_string"] + "Z"

        self.drawfuncs = hb.DrawFuncs()
        self.drawfuncs.set_move_to_func(move_to, container)
        self.drawfuncs.set_line_to_func(line_to, container)
        self.drawfuncs.set_cubic_to_func(cubic_to, container)
        self.drawfuncs.set_quadratic_to_func(quadratic_to, container)
        self.drawfuncs.set_close_path_func(close_path, container)

    def glyph_to_svg_path(self, gid):
        """Converts a glyph to SVG

        Args:
            gid (int): Glyph ID to render

        Returns: An SVG string containing a path to represent the glyph.
        """
        if not hasattr(hb, "DrawFuncs"):
            raise ValueError(
                "glyph_to_svg_path requires uharfbuzz with draw function support"
            )

        container = {"output_string": ""}
        self.setup_svg_draw_funcs(container)
        self.drawfuncs.get_glyph_shape(self.hbfont, gid)
        return container["output_string"]

    def buf_to_svg(self, buf):
        """Converts a buffer to SVG

        Args:
            buf (hb.Buffer): uharfbuzz ``hb.Buffer``

        Returns: An SVG string containing a rendering of the buffer
        """
        x_cursor = 0
        paths = []
        svg = ""
        if "hhea" in self.ttfont:
            ascender = self.ttfont["hhea"].ascender + 500
            descender = self.ttfont["hhea"].descender - 500
            fullheight = ascender - descender
        elif "OS/2":
            ascender = self.ttfont["OS/2"].sTypoAscender + 500
            descender = self.ttfont["OS/2"].sTypoDescender - 500
            fullheight = ascender - descender
        else:
            fullheight = 1500
            descender = 500
        y_cursor = -descender

        for info, pos in zip(buf.glyph_infos, buf.glyph_positions):
            glyph_path = self.glyph_to_svg_path(info.codepoint)
            dx, dy = pos.position[0], pos.position[1]
            p = (f'<path d="{glyph_path}" ' +
                 f' transform="translate({x_cursor+dx}, {y_cursor+dy})"/>\n')
            svg += p
            x_cursor += pos.position[2]
            y_cursor += pos.position[3]

        svg = ((
            f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {x_cursor} {fullheight}"'
            + ' transform="matrix(1 0 0 -1 0 0)">\n') + svg + "</svg>\n")
        return svg