font.getGlyphID gives KeyError on unicode

104 Views Asked by At

I'm trying to render SVG out of an unicode using Python's fonttools:

from fontTools.ttLib import TTFont
from fontTools.pens.svgPathPen import SVGPathPen

def unicode_to_svg(unicode_char, font_path, output_svg_path):
    # Load the TTF font using fonttools
    font = TTFont(font_path)

    # Get the glyph index for the Unicode character
    glyph_index = font.getGlyphID(unicode_char)

    # Convert the glyph to an SVG path
    glyph_set = font.getGlyphSet()
    glyph_name = font.getGlyphName(glyph_index)
    glyph = glyph_set[glyph_name]
    
    pen = SVGPathPen(glyph_set)
    glyph.draw(pen)
    svg_path_data = pen.getCommands()

    # Create the SVG content
    svg_content = f'''
    <svg width="3000" height="3000" xmlns="http://www.w3.org/2000/svg">
        <path d="{svg_path_data}" fill="red" />
    </svg>
    '''
    
    # Save the SVG content to a file
    with open(output_svg_path, 'w') as f:
        f.write(svg_content)

# Example usage
font_path = './Nikosh.ttf'
# unicode_char = 'A'
unicode_char = 'ম'
output_svg_path = 'output.svg'
unicode_to_svg(unicode_char, font_path, output_svg_path)

But the issue is, it works fine for English alphabets. But when I try to render unicode (such as Bengali) it gives me:

KeyError: 'ম'

On this line: glyph_index = font.getGlyphID(unicode_char)

I simply wanted to take a deeper dive into SVG rendering of different languages, fonts, texts, etc.

How can I resolve this issue? And make it SVG render-able for any unicode character?

2

There are 2 best solutions below

2
JosefZ On BEST ANSWER
help(font.getGlyphID)

Help on method getGlyphID in module fontTools.ttLib.ttFont:

getGlyphID(glyphName) method of fontTools.ttLib.ttFont.TTFont instance.
Returns the ID of the glyph with the given name.

The function char_in_font comes from my another project, included here unmodified… Check its output before passing to getGlyphID.

from fontTools.ttLib import TTFont
from fontTools.pens.svgPathPen import SVGPathPen

def char_in_font(unicode_char, font):
    '''check if a char is in font, return its glyphName'''
    for cmap in font['cmap'].tables:
        if cmap.isUnicode() or cmap.getEncoding() == 'utf_16_be':
            if ord(unicode_char) in cmap.cmap:
                # print(type(cmap))
                auxcn = cmap.cmap[ord(unicode_char)]
                # print(auxcn, type(auxcn))
                return auxcn if auxcn != '' else '<nil>'
    return ''


def unicode_to_svg(unicode_char, font_path, output_svg_path):
    # Load the TTF font using fonttools
    font = TTFont(font_path)

    # Get the glyph index for the Unicode character
    glyph_index = font.getGlyphID(char_in_font(unicode_char, font))

    # Convert the glyph to an SVG path
    glyph_set = font.getGlyphSet()
    glyph_name = font.getGlyphName(glyph_index)
    glyph = glyph_set[glyph_name]
    
    pen = SVGPathPen(glyph_set)
    glyph.draw(pen)
    svg_path_data = pen.getCommands()

    # Create the SVG content
    svg_content = f'''
    <svg width="3000" height="3000" xmlns="http://www.w3.org/2000/svg">
        <path d="{svg_path_data}" fill="red" />
    </svg>
    '''
    
    # Save the SVG content to a file
    with open(output_svg_path, 'w') as f:
        f.write(svg_content)


# Example usage
font_path = r'D:\Downloads\Fonty\Nikosh\Nikosh.ttf'
# unicode_char = 'A'
unicode_char = 'ম'
output_svg_path = f'output{unicode_char}.svg'
unicode_to_svg(unicode_char, font_path, output_svg_path)

Output:

.\SO\77072040.py
start ""  outputম.svg

enter image description here

dir output*.svg
09/09/2023  15:55               247 outputA.svg
09/09/2023  16:18               751 outputম.svg
start "" outputA.svg

enter image description here

0
Andj On

The FontTools approach works well for individual glyphs, if you need to create an SVG image of a single glyph.

If you need to render a string, instead, in a script like Bengali, then FontTools can't do what you need. You need to access a font rendering system, not just the glyphs in the font.

The primary opensource font rendering library is Harfbuzz. To use it, there are the uharfbuzz and vharfbuzz packages.

I'll use vharfbuzz:

  1. import vharfbuzz
  2. Create an instance of the Vharfbuzz class
  3. Create a Harfbuzz buffer, shaping the specified text
  4. Convert buffer to SVG and write file
from vharfbuzz import Vharfbuzz
vhb = Vharfbuzz('./NotoSerifBengali-vf.ttf')
buf = vhb.shape('বাংলা')
with open('test.svg', 'w') as f:
    f.write(vhb.buf_to_svg(buf))