Sunday, September 1, 2013

Starting to explore the vector glyph information

I started exploring the freetype-py library and the vector information available for glyphs. As part of the exploration I found this great unicode characters table. In addition to the character code and the writing system it belongs to, this table also shows the areas of the world where the writing system is used. This can be very useful to the third layer of the visualization regarding the history / geography of writing systems.

I started by looking at the glyph-vector example provided with the library. Note that this example uses matplotlib to plot the glyph. The glyph information is initially divided into closed contours - closed lines, for example the letter O has two contours, the outer circle and the inner circle while the letter S has only one contour. The closed contours are in turn divided into segments that can be either a straight line or a quadratic Bezier curve (or curves). A clear and concise description of the outlines and how the control points define the line in TrueType can be found here.

Below are a few examples of characters from different writing systems with their control lines (dashed). These are of course just single characters and therefore do not represent the entire writing system to which they belong. This is only meant to show the type of information available, the great variance between the writing systems and how these parameters can be used to asses similarity. 




These are characters from the Chinese character set, Latin, Hebrew and Tibetan (from left to right respectively). The statistics for each character is as follows:


Writing System
Chinese
English
Hebrew
Tibetan
Closed contours
7 2 1 9
Total segments
57 17 12 58
Straight lines
47 11 8 9
Curves
9 6 4 49




The code is below. Note that it relies on having matplotlib and the path to the ttf file.

# coding=UTF8

# -----------------------------------------------------------------------------
#
#  FreeType high-level python API - Copyright 2011 Nicolas P. Rougier
#  Distributed under the terms of the new BSD license.
#
# -----------------------------------------------------------------------------
'''
Show how to access glyph outline description.
'''
from freetype import *

if __name__ == '__main__':
    import numpy
    import matplotlib.pyplot as plt
    from matplotlib.path import Path
    import matplotlib.patches as patches

    face = Face('path/to/file/Arial Unicode.ttf')
    face.set_char_size( 48*64 )
    face.load_char(u'\u0F03')
    slot = face.glyph

    outline = slot.outline
    points = numpy.array(outline.points, dtype=[('x',float), ('y',float)])
    x, y = points['x'], points['y']

    figure = plt.figure(figsize=(8,10))
    axis = figure.add_subplot(111)
    #axis.scatter(points['x'], points['y'], alpha=.25)
    start, end = 0, 0
    lines, curves1, curves2 = 0, 0, 0

    VERTS, CODES = [], []
    # Iterate over each contour
    for i in range(len(outline.contours)):
        end    = outline.contours[i]
        points = outline.points[start:end+1] 
        points.append(points[0])
        tags   = outline.tags[start:end+1]
        tags.append(tags[0])

        segments = [ [points[0],], ]

        for j in range(1, len(points) ):
            segments[-1].append(points[j])
            if tags[j] & (1 << 0) and j < (len(points)-1):
                segments.append( [points[j],] )
        verts = [points[0], ]
        codes = [Path.MOVETO,]
        for segment in segments:
            if len(segment) == 2:
                verts.extend(segment[1:])
                codes.extend([Path.LINETO])
                lines+=1
            elif len(segment) == 3:
                verts.extend(segment[1:])
                codes.extend([Path.CURVE3, Path.CURVE3])
                curves1+=1
            else:
                verts.append(segment[1])
                codes.append(Path.CURVE3)
                for i in range(1,len(segment)-2):
                    A,B = segment[i], segment[i+1]
                    C = ((A[0]+B[0])/2.0, (A[1]+B[1])/2.0)
                    verts.extend([ C, B ])
                    codes.extend([ Path.CURVE3, Path.CURVE3])
                verts.append(segment[-1])
                codes.append(Path.CURVE3)
                curves2+=1
        VERTS.extend(verts)
        CODES.extend(codes)
        start = end+1
    
    print 'segments: ', len(outline.contours)
    print 'lines: ', lines
    print 'curves: ', curves1+curves2

    # Draw glyph lines
    path = Path(VERTS, CODES)
    glyph = patches.PathPatch(path, facecolor='.75', fill=False, lw=1)

    # Draw "control" lines
    for i, code in enumerate(CODES):
        if code == Path.CURVE3:
            CODES[i] = Path.LINETO
    path = Path(VERTS, CODES)
    patch = patches.PathPatch(path, ec='.5', fill=False, ls='dashed', lw=1 )

    axis.add_patch(patch)
    axis.add_patch(glyph)

    axis.set_xlim(x.min()-100, x.max()+100)
    plt.xticks([])
    axis.set_ylim(y.min()-100, y.max()+100)
    plt.yticks([])
    plt.show()


No comments:

Post a Comment