[ACCEPTED]-How to get width of a truetype font character in 1200ths of an inch with Python?-truetype

Accepted answer
Score: 10

Raw text widths are usually calculated in 38 typographer's points, but since the point for the purpose of 37 font definitions is defined as 1/72 of an 36 inch, you can easily convert it into any 35 other unit.

To get the design width of a character (expressed 34 in em units), you need access to the low-level 33 data of the font. The easiest way is to 32 pip install fonttools, which has everything to work at the lowest 31 possible level of font definitions.

With 30 fontTools installed, you can:

  1. load the font 29 data – this requires the path to the actual 28 font file;

  2. character widths are stored as 27 glyph widths, meaning you must retrieve a 'character-to-glyph' mapping; this 26 is in the cmap table of a font:

    a. load the cmap for 25 your font. The most useful is the Unicode 24 map – a font may contain others. b. load 23 the glyph set for your font. This is a list of names for 22 the glyphs in that font.

  3. Then, for each Unicode 21 character, first look up its name and then 20 use the name to retrieve its width in design 19 units.

  4. Don't forget that the 'design units' is 18 based on the overall 'design width' of a 17 font. This can be a standard value of 1000 (typical 16 for Type 1 fonts), 2048 (typical for TrueType 15 fonts), or any other value.

That leads to 14 this function:

from fontTools.ttLib import TTFont
from fontTools.ttLib.tables._c_m_a_p import CmapSubtable

font = TTFont('/Library/Fonts/Arial.ttf')
cmap = font['cmap']
t = cmap.getcmap(3,1).cmap
s = font.getGlyphSet()
units_per_em = font['head'].unitsPerEm

def getTextWidth(text,pointSize):
    total = 0
    for c in text:
        if ord(c) in t and t[ord(c)] in s:
            total += s[t[ord(c)]].width
            total += s['.notdef'].width
    total = total*float(pointSize)/units_per_em;
    return total

text = 'This is a test'

width = getTextWidth(text,12)

print ('Text: "%s"' % text)
print ('Width in points: %f' % width)
print ('Width in inches: %f' % (width/72))
print ('Width in cm: %f' % (width*2.54/72))
print ('Width in WP Units: %f' % (width*1200/72))

The result is:

Text: "This is a test"
Width in points: 67.353516
Width in inches: 0.935465
Width in cm: 2.376082
Width in WP Units: 1122.558594

and is correct 13 when comparing to what Adobe InDesign reports. (Note 12 that per-character kerning is not applied here! That 11 would require a lot more code.)

Characters 10 that are not defined in the font are silently 9 ignored and, as usually is done, the width 8 for the .notdef glyph gets used. If you want this 7 reported as an error, remove the if test in 6 the function.

The cast to float in the function 5 getTextWidth is so this works under both Python 2.7 4 and 3.5, but note that if you use Python 3 2.7 and larger value Unicode characters 2 (not plain ASCII), you need to rewrite the 1 function to correctly use UTF8 characters.

Score: 0

This worked better for me:

def pixel_width(unicode_text): 

    im  =  Image.new ( "RGB", (width,height), back_ground_color ) 
    draw  =  ImageDraw.Draw (im) 
    unicode_font = ImageFont.truetype("./usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf", font_size) 
    draw.text ( (0,0), unicode_text, font=unicode_font, fill=font_color ) 
    box = Image.open("/dev/shm/text.png").getbbox() 
    return box[2] - box[0] 


More Related questions