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

后端 未结 2 2022
广开言路
广开言路 2021-02-06 14:01

I can get the height and width of a character in pixels with PIL (see below), but (unless I\'m mistaken) pixel size depends on the screen\'s DPI, which can vary. Instead what I\

相关标签:
2条回答
  • 2021-02-06 14:18

    This worked better for me:

    def pixel_width(unicode_text): 
        width=len(unicode_text)*50 
        height=100 
        back_ground_color=(0,0,0) 
        font_size=64 
        font_color=(255,255,255) 
    
        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 ) 
        im.save("/dev/shm/text.png") 
        box = Image.open("/dev/shm/text.png").getbbox() 
        return box[2] - box[0] 
    
    0 讨论(0)
  • 2021-02-06 14:26

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

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

    With fontTools installed, you can:

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

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

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

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

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

    That leads to 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
            else:
                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 when comparing to what Adobe InDesign reports. (Note that per-character kerning is not applied here! That would require a lot more code.)

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

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

    0 讨论(0)
提交回复
热议问题